diff --git a/scripts/tri/README.md b/scripts/tri/README.md new file mode 100644 index 0000000..0599806 --- /dev/null +++ b/scripts/tri/README.md @@ -0,0 +1,77 @@ +# tri — Cryptographic Triangles CLI + +A friendly bash wrapper around `trianglesd` RPC for humans and agents. + +## Install + +```bash +# System-wide +sudo cp tri /usr/local/bin/tri +sudo chmod +x /usr/local/bin/tri +sudo mkdir -p /etc/tri +sudo cp nodes.conf.example /etc/tri/nodes.conf +# Edit /etc/tri/nodes.conf with your node's RPC credentials + +# Bash completion +sudo cp tri-completion.bash /etc/bash_completion.d/ + +# Zsh completion +sudo cp _tri_zsh_completion /usr/local/share/zsh/site-functions/_tri +``` + +## Config + +Edit `/etc/tri/nodes.conf`: + +```bash +TRI_SSH_HOST="100.81.59.99" # Node IP (or remove for local) +TRI_SSH_USER="root" +TRI_RPC_PORT="19112" +TRI_RPC_USER="your-rpc-user" +TRI_RPC_PASS="your-rpc-password" +# TRI_WALLET_PASSPHRASE="wallet-passphrase" # If wallet is encrypted +``` + +## Commands + +### Info +- `tri` — Status overview +- `tri status` — Detailed node status +- `tri balance` — Wallet balance + UTXO count +- `tri peers` — Connected peers +- `tri stake` — Staking info + +### Wallet +- `tri address new` — New address +- `tri address list` — List addresses +- `tri address balance` — Per-address balances +- `tri send [memo]` — Send TRI +- `tri tx [N]` — Recent transactions +- `tri tx ` — Transaction details + +### Secure Messaging +- `tri msg inbox` — Read messages +- `tri msg outbox` — Sent messages +- `tri msg send ` — Send encrypted message +- `tri msg anon ` — Anonymous message +- `tri msg keys` — Messaging keys +- `tri msg enable` — Enable secure messaging +- `tri msg pubkey ` — Get public key + +### Advanced +- `tri raw [params...]` — Raw RPC passthrough + +## Agent Integration (Hermes, Krystie) + +Both agents on DNS2 share the same `/etc/tri/nodes.conf` and can execute all commands. +For inter-agent messaging via TRI's encrypted P2P network: + +1. Each agent needs a TRI address: `tri address new` +2. Enable messaging: `tri msg enable` +3. Register key: `tri raw smsglocalkeys recv +
` +4. Exchange addresses between agents +5. Send: `tri msg send "message"` +6. Read: `tri msg inbox` + +Messages are encrypted (ECDH), routed through the Tor P2P network, +stored for 48 hours, max 4096 bytes each. diff --git a/scripts/tri/_tri_zsh_completion b/scripts/tri/_tri_zsh_completion new file mode 100644 index 0000000..0b9f99a --- /dev/null +++ b/scripts/tri/_tri_zsh_completion @@ -0,0 +1,39 @@ +#compdef tri + +_tri() { + local -a commands + commands=( + 'status:Detailed node status' + 'balance:Wallet balance' + 'peers:Connected peers' + 'stake:Staking info' + 'address:Address management' + 'send:Send TRI' + 'tx:Transactions' + 'msg:Secure messaging' + 'raw:Raw RPC passthrough' + 'help:Show help' + ) + + _arguments -C \ + "1:command:->command" \ + "*::arg:->args" + + case "$state" in + command) + _describe 'tri command' commands + ;; + args) + case ${words[1]} in + address|addr) + _values 'subcommand' 'new' 'list' 'balance' + ;; + msg|message|messages) + _values 'subcommand' 'inbox' 'outbox' 'send' 'anon' 'keys' 'enable' 'pubkey' 'unlock' + ;; + esac + ;; + esac +} + +_tri "$@" diff --git a/scripts/tri/nodes.conf.example b/scripts/tri/nodes.conf.example new file mode 100644 index 0000000..fc4a68b --- /dev/null +++ b/scripts/tri/nodes.conf.example @@ -0,0 +1,32 @@ +# /etc/tri/nodes.conf — Triangles node configuration +# +# Shared by Hermes and Krystie. Both agents on DNS2 tunnel RPC +# to the trianglesd node on DNS3 via SSH. +# +# Node: DNS3 (100.81.59.99) + +# ─── Connection ────────────────────────────────────────────────────────────── + +# RPC is only accessible on localhost at the node, so we SSH-tunnel +TRI_SSH_HOST="your-node-ip-here" +TRI_SSH_USER="root" + +# RPC credentials (as set in triangles.conf on the node) +TRI_RPC_HOST="127.0.0.1" +TRI_RPC_PORT="19112" +TRI_RPC_USER="your-rpc-user-here" +TRI_RPC_PASS="your-rpc-password-here" + +# ─── Wallet ────────────────────────────────────────────────────────────────── + +# Wallet passphrase for unlocking (needed for messaging + sending) +# Leave empty if wallet is unencrypted or set via env var TRI_WALLET_PASSPHRASE +# TRI_WALLET_PASSPHRASE="" + +# Default sender address for messages (set after creating addresses) +# TRI_DEFAULT_FROM="" + +# ─── Agent Addresses ───────────────────────────────────────────────────────── +# When agents have their own TRI addresses, register them here: +# HERMES_TRI_ADDR="T..." +# KRYSTIE_TRI_ADDR="T..." diff --git a/scripts/tri/tri b/scripts/tri/tri new file mode 100755 index 0000000..ccef615 --- /dev/null +++ b/scripts/tri/tri @@ -0,0 +1,691 @@ +#!/usr/bin/env bash +# +# tri — Cryptographic Triangles command interface +# +# A friendly wrapper around trianglesd RPC for both human and agent use. +# Designed for Hermes, Krystie, and Sami to manage TRI wallets, monitor +# nodes, and communicate via the built-in secure messaging system. +# +# Config: /etc/tri/nodes.conf (or ~/.config/tri/nodes.conf) +# Completion: /etc/bash_completion.d/tri-completion.bash +# +# Usage: tri [subcommand] [args] +# tri Status overview +# tri help Full command list +# tri status Detailed node status +# tri balance Wallet balance +# tri peers Connected peers +# tri stake Staking info +# tri address new Generate new wallet address +# tri address list List wallet addresses +# tri address balance Per-address balances +# tri send [memo] Send TRI +# tri tx [N] Recent N transactions (default 10) +# tri tx Transaction details +# tri msg inbox Secure message inbox +# tri msg outbox Sent messages +# tri msg send Send encrypted message +# tri msg anon Send anonymous message +# tri msg keys List messaging keys +# tri msg enable Enable secure messaging +# tri msg pubkey Get public key for address +# tri msg unlock [secs] Unlock wallet for messaging (default 60s) +# tri raw [params...] Raw RPC passthrough +# + +set -euo pipefail + +# ─── Config ────────────────────────────────────────────────────────────────── + +TRI_CONFIG="/etc/tri/nodes.conf" +[[ -f "$HOME/.config/tri/nodes.conf" ]] && TRI_CONFIG="$HOME/.config/tri/nodes.conf" + +# Defaults (overridden by config file) +TRI_RPC_HOST="127.0.0.1" +TRI_RPC_PORT="19112" +TRI_RPC_USER="" +TRI_RPC_PASS="" +TRI_SSH_HOST="" # If set, RPC calls are tunneled via SSH to this host +TRI_SSH_USER="root" +TRI_WALLET_PASSPHRASE="" # For unlocking wallet when sending/messages +TRI_DEFAULT_FROM="" # Default sender address for messages + +# Load config +if [[ -f "$TRI_CONFIG" ]]; then + source "$TRI_CONFIG" +fi + +# Allow env overrides +[[ -n "${TRI_RPC_HOST_ENV:-}" ]] && TRI_RPC_HOST="$TRI_RPC_HOST_ENV" +[[ -n "${TRI_RPC_PORT_ENV:-}" ]] && TRI_RPC_PORT="$TRI_RPC_PORT_ENV" +[[ -n "${TRI_SSH_HOST_ENV:-}" ]] && TRI_SSH_HOST="$TRI_SSH_HOST_ENV" + +# ─── Colors ────────────────────────────────────────────────────────────────── + +if [[ -t 1 ]]; then + C_RESET="\033[0m" + C_BOLD="\033[1m" + C_DIM="\033[2m" + C_RED="\033[31m" + C_GREEN="\033[32m" + C_YELLOW="\033[33m" + C_BLUE="\033[34m" + C_CYAN="\033[36m" + C_MAGENTA="\033[35m" +else + C_RESET=""; C_BOLD=""; C_DIM=""; C_RED=""; C_GREEN=""; C_YELLOW="" + C_BLUE=""; C_CYAN=""; C_MAGENTA="" +fi + +# ─── Helpers ───────────────────────────────────────────────────────────────── + +# Core RPC call function. Executes JSON-RPC against the node. +# Usage: _tri_rpc [param1] [param2] ... +_tri_rpc() { + local method="$1"; shift + local params="[]" + + if [[ $# -gt 0 ]]; then + # Build JSON params array + local json_params=() + for p in "$@"; do + # Try to detect numbers and booleans + if [[ "$p" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then + json_params+=("$p") + elif [[ "$p" == "true" || "$p" == "false" || "$p" == "null" ]]; then + json_params+=("\"$p\"") + else + # Escape for JSON string + local escaped="${p//\\/\\\\}" + escaped="${escaped//\"/\\\"}" + json_params+=("\"$escaped\"") + fi + done + params="[$(IFS=,; echo "${json_params[*]}")]" + fi + + local payload="{\"jsonrpc\":\"1.0\",\"id\":\"tri\",\"method\":\"$method\",\"params\":$params}" + + if [[ -n "$TRI_SSH_HOST" ]]; then + # Tunnel via SSH + local auth="$TRI_RPC_USER:$TRI_RPC_PASS" + ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no \ + "${TRI_SSH_USER}@${TRI_SSH_HOST}" \ + "curl -s --connect-timeout 10 http://127.0.0.1:${TRI_RPC_PORT}/ \ + -u '${auth}' \ + -H 'Content-Type: application/json' \ + -d '${payload//\'/\'\\\'\'}'" 2>/dev/null + else + # Local connection + curl -s --connect-timeout 10 "http://${TRI_RPC_HOST}:${TRI_RPC_PORT}/" \ + -u "${TRI_RPC_USER}:${TRI_RPC_PASS}" \ + -H 'Content-Type: application/json' \ + -d "$payload" 2>/dev/null + fi +} + +# Pretty RPC call — extracts .result and pretty-prints JSON +# Usage: _tri_rpc_pretty [param1] [param2] ... +_tri_rpc_pretty() { + local raw + raw=$(_tri_rpc "$@") + + if [[ -z "$raw" ]]; then + echo -e "${C_RED}Error: No response from node${C_RESET}" >&2 + return 1 + fi + + # Check for error + local err + err=$(echo "$raw" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('error',{}).get('message','') if d.get('error') else '',end='')" 2>/dev/null || echo "") + if [[ -n "$err" ]]; then + echo -e "${C_RED}RPC Error: ${err}${C_RESET}" >&2 + return 1 + fi + + echo "$raw" | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin).get('result',''),indent=2))" 2>/dev/null +} + +# Raw RPC call — print full JSON response as-is +_tri_rpc_raw() { + _tri_rpc "$@" +} + +# Extract a single field from RPC result +# Usage: _tri_rpc_field [params...] +_tri_rpc_field() { + local method="$1"; shift + local field="$1"; shift + _tri_rpc "$method" "$@" | python3 -c " +import sys,json +d=json.load(sys.stdin) +r=d.get('result',{}) +if isinstance(r,dict): + print(r.get('$field','')) +else: + print(r) +" 2>/dev/null +} + +# Extract multiple fields +_tri_rpc_fields() { + local method="$1"; shift + _tri_rpc "$method" "$@" | python3 -c " +import sys,json +d=json.load(sys.stdin) +r=d.get('result',{}) +if isinstance(r, dict): + for k,v in r.items(): + if isinstance(v,(str,int,float,bool)) or v is None: + print(f'{k}: {v}') +" 2>/dev/null +} + +# Unlock wallet for messaging +_tri_unlock() { + local duration="${1:-60}" + if [[ -z "$TRI_WALLET_PASSPHRASE" ]]; then + echo -e "${C_YELLOW}Warning: TRI_WALLET_PASSPHRASE not set in config${C_RESET}" >&2 + return 1 + fi + _tri_rpc walletpassphrase "$TRI_WALLET_PASSPHRASE" "$duration" >/dev/null 2>&1 +} + +# ─── Commands: Info ────────────────────────────────────────────────────────── + +cmd_status() { + echo -e "${C_BOLD}${C_CYAN}Triangles Node Status${C_RESET}" + echo -e "${C_DIM}$(date -u '+%Y-%m-%d %H:%M:%S UTC')${C_RESET}" + echo "" + + local info + info=$(_tri_rpc getinfo 2>/dev/null) + + if [[ -z "$info" ]]; then + echo -e "${C_RED}Cannot connect to node${C_RESET}" + if [[ -n "$TRI_SSH_HOST" ]]; then + echo -e " Target: ${TRI_SSH_USER}@${TRI_SSH_HOST} → RPC ${TRI_RPC_PORT}" + else + echo -e " Target: ${TRI_RPC_HOST}:${TRI_RPC_PORT}" + fi + return 1 + fi + + echo "$info" | python3 -c " +import sys,json +d=json.load(sys.stdin)['result'] +print(f\" Version: {d.get('version','?')}\") +print(f\" Blocks: {d.get('blocks','?'):,}\") +print(f\" Connections: {d.get('connections','?')}\") +print(f\" Balance: {d.get('balance',0):.4f} TRI\") +print(f\" Stake: {d.get('stake',0):.4f} TRI\") +print(f\" Money Supply: {d.get('moneysupply',0):,.2f} TRI\") +print(f\" Difficulty: {d.get('difficulty','?')}\") +print(f\" Testnet: {d.get('testnet',False)}\") +" 2>/dev/null + + # Peer summary + local peer_count + peer_count=$(_tri_rpc_field getconnectioncount "result" 2>/dev/null || echo "?") + echo "" + echo -e " ${C_DIM}Node: ${TRI_SSH_HOST:-${TRI_RPC_HOST}}:${TRI_RPC_PORT}${C_RESET}" +} + +cmd_balance() { + local balance + balance=$(_tri_rpc_field getbalance "balance" 2>/dev/null || echo "error") + + if [[ "$balance" == "error" ]]; then + echo -e "${C_RED}Cannot connect to node${C_RESET}" >&2 + return 1 + fi + + local stake + stake=$(_tri_rpc_field getinfo "stake" 2>/dev/null || echo "0") + + echo -e "${C_BOLD}Wallet Balance${C_RESET}" + echo -e " Available: ${C_GREEN}${balance} TRI${C_RESET}" + echo -e " Staking: ${C_YELLOW}${stake} TRI${C_RESET}" + + # UTXO count + local utxo_count + utxo_count=$(_tri_rpc listunspent 2>/dev/null | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('result',[])))" 2>/dev/null || echo "?") + [[ "$utxo_count" != "?" ]] && echo -e " UTXOs: ${utxo_count}" +} + +cmd_peers() { + local raw + raw=$(_tri_rpc getpeerinfo 2>/dev/null) + + echo -e "${C_BOLD}Connected Peers${C_RESET}" + echo "$raw" | python3 -c " +import sys,json +d=json.load(sys.stdin) +peers=d.get('result',[]) +if not peers: + print(' (no peers connected)') +else: + for p in peers: + addr = p.get('addr','?') + subver = p.get('subver','?').replace('/','') + height = p.get('startingheight','?') + ping = p.get('pingtime',0) + if isinstance(ping,(int,float)) and ping > 0: + ping_ms = ping * 1000 + print(f' {addr:30s} {subver:25s} height={height} ping={ping_ms:.0f}ms') + else: + print(f' {addr:30s} {subver:25s} height={height}') + print(f'\n Total: {len(peers)} peer(s)') +" 2>/dev/null +} + +cmd_stake() { + echo -e "${C_BOLD}Staking Information${C_RESET}" + _tri_rpc_fields getstakinginfo 2>/dev/null | while read -r line; do + echo " $line" + done +} + +# ─── Commands: Wallet ──────────────────────────────────────────────────────── + +cmd_address() { + local sub="${1:-list}"; shift || true + + case "$sub" in + new) + local addr + addr=$(_tri_rpc_field getnewaddress "result" 2>/dev/null) + if [[ -n "$addr" ]]; then + echo "$addr" + else + echo -e "${C_RED}Failed to generate address${C_RESET}" >&2 + return 1 + fi + ;; + list) + echo -e "${C_BOLD}Wallet Addresses${C_RESET}" + _tri_rpc getaddressesbyaccount "" 2>/dev/null | python3 -c " +import sys,json +d=json.load(sys.stdin) +addrs=d.get('result',[]) +if not addrs: + print(' (no addresses)') +else: + for a in addrs: + print(f' {a}') + print(f'\n Total: {len(addrs)}') +" 2>/dev/null + ;; + balance) + echo -e "${C_BOLD}Address Balances${C_RESET}" + _tri_rpc listaddressgroupings 2>/dev/null | python3 -c " +import sys,json +d=json.load(sys.stdin) +groups=d.get('result',[]) +if not groups: + print(' (no address balances)') +else: + for group in groups: + for item in group: + addr=item[0] if isinstance(item,list) and len(item)>0 else '?' + amt=item[1] if isinstance(item,list) and len(item)>1 else '?' + print(f' {addr:40s} {amt} TRI') +" 2>/dev/null + ;; + *) + echo -e "${C_RED}Unknown subcommand: $sub${C_RESET}" >&2 + echo "Usage: tri address [new|list|balance]" >&2 + return 1 + ;; + esac +} + +cmd_send() { + if [[ $# -lt 2 ]]; then + echo -e "${C_RED}Usage: tri send
[memo]${C_RESET}" >&2 + return 1 + fi + + local addr="$1" + local amount="$2" + local memo="${3:-}" + + echo -e "${C_YELLOW}Sending ${amount} TRI to ${addr}...${C_RESET}" + + local result + if [[ -n "$memo" ]]; then + result=$(_tri_rpc sendtoaddress "$addr" "$amount" "$memo" 2>/dev/null) + else + result=$(_tri_rpc sendtoaddress "$addr" "$amount" 2>/dev/null) + fi + + local txid + txid=$(echo "$result" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('result','') if d.get('result') else d.get('error',{}).get('message','FAILED'),end='')" 2>/dev/null) + + if [[ "$txid" == "FAILED" ]] || [[ -z "$txid" ]]; then + echo -e "${C_RED}Send failed: $txid${C_RESET}" >&2 + return 1 + fi + + echo -e "${C_GREEN}Sent! TXID: ${txid}${C_RESET}" +} + +cmd_tx() { + if [[ $# -eq 0 ]]; then + # Recent transactions + echo -e "${C_BOLD}Recent Transactions${C_RESET}" + _tri_rpc listtransactions "*" 10 2>/dev/null | python3 -c " +import sys,json +d=json.load(sys.stdin) +txs=d.get('result',[]) +if not txs: + print(' (no transactions)') +else: + for t in reversed(txs): + category = t.get('category','?') + amount = t.get('amount',0) + addr = t.get('address','?') + confirmations = t.get('confirmations',0) + txid = t.get('txid','?') + time = t.get('time',0) + + from datetime import datetime + dt = datetime.fromtimestamp(time) if time else None + datestr = dt.strftime('%Y-%m-%d %H:%M') if dt else '???' + + # Color by category + if category == 'receive' or category == 'generate' or category == 'mint': + amt_str = f'+{amount} TRI' + else: + amt_str = f'-{amount} TRI' + + conf_str = f'{confirmations} conf' if confirmations > 0 else 'unconfirmed' + print(f' {datestr} {amt_str:>15s} {category:10s} {conf_str:>12s} {addr}') + print(f' {txid}') +" 2>/dev/null + else + # Transaction details + local txid="$1" + echo -e "${C_BOLD}Transaction: ${txid}${C_RESET}" + _tri_rpc_fields gettransaction "$txid" 2>/dev/null | while read -r line; do + echo " $line" + done + fi +} + +# ─── Commands: Secure Messaging ────────────────────────────────────────────── + +cmd_msg() { + local sub="${1:-inbox}"; shift || true + + case "$sub" in + inbox) + # Unlock wallet first if passphrase is configured + if [[ -n "$TRI_WALLET_PASSPHRASE" ]]; then + _tri_unlock 60 2>/dev/null || true + fi + + echo -e "${C_BOLD}${C_MAGENTA}Secure Message Inbox${C_RESET}" + _tri_rpc smsginbox "all" 2>/dev/null | python3 -c " +import sys,json +raw=json.load(sys.stdin) +d=raw.get('result',{}) +msg = d.get('message') +count_str = d.get('result','0 messages shown.') +# Extract count from result string like 'N messages shown.' +try: + count = int(count_str.split()[0]) +except: + count = 0 + +if count == 0 or msg is None: + print(' (inbox is empty)') +else: + # The daemon returns one message per RPC call (last one only). + # For full inbox dump, use: tri raw smsginbox all + frm = msg.get('from','?') + to = msg.get('to','?') + text = msg.get('text','(no text)') + sent = msg.get('sent','') + rcvd = msg.get('received','') + print(f' Latest message (of {count}):') + print(f' Sent: {sent}') + print(f' Received: {rcvd}') + print(f' From: {frm}') + print(f' To: {to}') + print(f' Text: {text[:200]}') + if count > 1: + print(f'') + print(f' ({count-1} more messages — use: tri raw smsginbox all)') +" 2>/dev/null + ;; + + outbox) + if [[ -n "$TRI_WALLET_PASSPHRASE" ]]; then + _tri_unlock 60 2>/dev/null || true + fi + + echo -e "${C_BOLD}${C_MAGENTA}Sent Messages${C_RESET}" + _tri_rpc smsgoutbox "all" 2>/dev/null | python3 -c " +import sys,json +raw=json.load(sys.stdin) +d=raw.get('result',{}) +msg = d.get('message') +count_str = d.get('result','0 sent messages shown.') +try: + count = int(count_str.split()[0]) +except: + count = 0 + +if count == 0 or msg is None: + print(' (outbox is empty)') +else: + to = msg.get('to','?') + frm = msg.get('from','?') + text = msg.get('text','(no text)') + sent = msg.get('sent','') + print(f' Latest sent (of {count}):') + print(f' Sent: {sent}') + print(f' From: {frm}') + print(f' To: {to}') + print(f' Text: {text[:200]}') + if count > 1: + print(f'') + print(f' ({count-1} more — use: tri raw smsgoutbox all)') +" 2>/dev/null + ;; + + send) + if [[ $# -lt 3 ]]; then + echo -e "${C_RED}Usage: tri msg send ${C_RESET}" >&2 + return 1 + fi + + local from_addr="$1" + local to_addr="$2" + shift 2 + local message="$*" + + # Unlock for send + if [[ -n "$TRI_WALLET_PASSPHRASE" ]]; then + _tri_unlock 60 2>/dev/null || true + fi + + echo -e "${C_YELLOW}Sending encrypted message...${C_RESET}" + local result + result=$(_tri_rpc smsgsend "$from_addr" "$to_addr" "$message" 2>/dev/null) + + local status + status=$(echo "$result" | python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('result',{}); print(r.get('result','') if isinstance(r,dict) else str(r),end='')" 2>/dev/null) + + if [[ "$status" == "Sent." ]]; then + echo -e "${C_GREEN}Message sent to ${to_addr}${C_RESET}" + else + local err + err=$(echo "$result" | python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('result',{}); print(r.get('error','unknown error') if isinstance(r,dict) else str(r),end='')" 2>/dev/null) + echo -e "${C_RED}Send failed: ${err}${C_RESET}" >&2 + return 1 + fi + ;; + + anon) + if [[ $# -lt 2 ]]; then + echo -e "${C_RED}Usage: tri msg anon ${C_RESET}" >&2 + return 1 + fi + + local to_addr="$1" + shift + local message="$*" + + echo -e "${C_YELLOW}Sending anonymous encrypted message...${C_RESET}" + local result + result=$(_tri_rpc smsgsendanon "$to_addr" "$message" 2>/dev/null) + + local status + status=$(echo "$result" | python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('result',{}); print(r.get('result','') if isinstance(r,dict) else str(r),end='')" 2>/dev/null) + + if [[ "$status" == "Sent." ]]; then + echo -e "${C_GREEN}Anonymous message sent to ${to_addr}${C_RESET}" + else + echo -e "${C_RED}Send failed${C_RESET}" >&2 + return 1 + fi + ;; + + keys) + echo -e "${C_BOLD}${C_MAGENTA}Messaging Keys${C_RESET}" + _tri_rpc smsglocalkeys "all" 2>/dev/null | python3 -c " +import sys,json +raw=json.load(sys.stdin) +d=raw.get('result',{}) +if isinstance(d, dict): + key_line = d.get('key','') + count_line = d.get('result','') + if key_line: + print(f' {key_line}') + if count_line: + print(f' {count_line}') +elif isinstance(d, str): + print(f' {d}') +else: + print(' (no keys registered)') +" 2>/dev/null + ;; + + enable) + echo -e "${C_YELLOW}Enabling secure messaging...${C_RESET}" + _tri_rpc_pretty smsgenable 2>/dev/null + ;; + + pubkey) + if [[ $# -lt 1 ]]; then + echo -e "${C_RED}Usage: tri msg pubkey
${C_RESET}" >&2 + return 1 + fi + _tri_rpc_pretty smsggetpubkey "$1" 2>/dev/null + ;; + + unlock) + local duration="${1:-60}" + if [[ -z "$TRI_WALLET_PASSPHRASE" ]]; then + echo -e "${C_RED}TRI_WALLET_PASSPHRASE not set in config${C_RESET}" >&2 + return 1 + fi + _tri_rpc walletpassphrase "$TRI_WALLET_PASSPHRASE" "$duration" >/dev/null 2>&1 + echo -e "${C_GREEN}Wallet unlocked for ${duration}s${C_RESET}" + ;; + + *) + echo -e "${C_RED}Unknown msg subcommand: $sub${C_RESET}" >&2 + echo "Usage: tri msg [inbox|outbox|send|anon|keys|enable|pubkey|unlock]" >&2 + return 1 + ;; + esac +} + +# ─── Commands: Raw RPC ─────────────────────────────────────────────────────── + +cmd_raw() { + if [[ $# -eq 0 ]]; then + echo -e "${C_RED}Usage: tri raw [params...]${C_RESET}" >&2 + echo "Example: tri raw getblockhash 2200000" >&2 + return 1 + fi + + _tri_rpc_pretty "$@" +} + +# ─── Help ──────────────────────────────────────────────────────────────────── + +cmd_help() { + cat << 'EOF' + + tri — Cryptographic Triangles Command Interface + + INFO + tri Status overview (blocks, connections, balance) + tri status Detailed node status + tri balance Wallet balance + UTXO count + tri peers Connected peers with ping times + tri stake Staking information + + WALLET + tri address new Generate new wallet address + tri address list List all wallet addresses + tri address balance Per-address balance breakdown + tri send [memo] Send TRI to address + tri tx [N] Recent N transactions (default 10) + tri tx Transaction details + + SECURE MESSAGING + tri msg inbox Read inbox messages (wallet auto-unlocks) + tri msg outbox Read sent messages + tri msg send Send encrypted message + tri msg anon Send anonymous message + tri msg keys List messaging keys + tri msg enable Enable secure messaging + tri msg pubkey Get public key for an address + tri msg unlock [secs] Unlock wallet for messaging (default 60s) + + ADVANCED + tri raw [params...] Raw RPC passthrough + tri help This help screen + + CONFIG + /etc/tri/nodes.conf System-wide config + ~/.config/tri/nodes.conf Per-user config override + + AGENTS (Hermes, Krystie) + Both agents use the same config and can execute all commands. + For messaging between agents, each needs its own TRI address + registered in the wallet. Use 'tri msg keys' to verify. + +EOF +} + +# ─── Main ──────────────────────────────────────────────────────────────────── + +main() { + local cmd="${1:-status}"; shift || true + + case "$cmd" in + status|info) cmd_status "$@" ;; + balance) cmd_balance "$@" ;; + peers) cmd_peers "$@" ;; + stake|staking) cmd_stake "$@" ;; + address|addr) cmd_address "$@" ;; + send) cmd_send "$@" ;; + tx|transactions) cmd_tx "$@" ;; + msg|message|messages) cmd_msg "$@" ;; + raw) cmd_raw "$@" ;; + help|-h|--help) cmd_help "$@" ;; + *) + echo -e "${C_RED}Unknown command: $cmd${C_RESET}" >&2 + echo "Run 'tri help' for available commands" >&2 + exit 1 + ;; + esac +} + +main "$@" diff --git a/scripts/tri/tri-completion.bash b/scripts/tri/tri-completion.bash new file mode 100644 index 0000000..41b049c --- /dev/null +++ b/scripts/tri/tri-completion.bash @@ -0,0 +1,40 @@ +# bash/zsh completion for tri command +# Install: source this file or place in /etc/bash_completion.d/ + +_tri_complete() { + local cur prev opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + # Top-level commands + local top_cmds="status balance peers stake address send tx msg raw help" + local addr_subcmds="new list balance" + local msg_subcmds="inbox outbox send anon keys enable pubkey unlock" + + if [[ ${COMP_CWORD} -eq 1 ]]; then + COMPREPLY=($(compgen -W "${top_cmds}" -- "${cur}")) + return 0 + fi + + # Subcommand completion + if [[ ${COMP_CWORD} -eq 2 ]]; then + case "${COMP_WORDS[1]}" in + address|addr) + COMPREPLY=($(compgen -W "${addr_subcmds}" -- "${cur}")) + return 0 + ;; + msg|message|messages) + COMPREPLY=($(compgen -W "${msg_subcmds}" -- "${cur}")) + return 0 + ;; + esac + fi + + # Address completion for send/msg send (would need wallet addresses in practice) + # For now, no further completion + + return 0 +} + +complete -F _tri_complete tri