Add tri CLI wrapper: wallet + secure messaging for agents and humans
A bash command interface to trianglesd RPC designed for Hermes, Krystie, and Sami to manage TRI wallets and communicate via the built-in secure messaging system (smessage). Features: - Info: status, balance, peers, staking info - Wallet: addresses, send, transactions - Secure messaging: inbox, outbox, send (encrypted via ECDH over Tor P2P) - Raw RPC passthrough for any daemon command - Bash + zsh completion - SSH-tunneled RPC for remote node access - Config at /etc/tri/nodes.conf (shared between agents) Files: - scripts/tri/tri Main script - scripts/tri/nodes.conf.example Config template - scripts/tri/tri-completion.bash Bash completion - scripts/tri/_tri_zsh_completion Zsh completion - scripts/tri/README.md Documentation Tested against live DNS3 node (block 2,207,455, 4 peers). Secure messaging verified: send → inbox → outbox all working.
This commit is contained in:
@@ -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 <addr> <amt> [memo]` — Send TRI
|
||||||
|
- `tri tx [N]` — Recent transactions
|
||||||
|
- `tri tx <txid>` — Transaction details
|
||||||
|
|
||||||
|
### Secure Messaging
|
||||||
|
- `tri msg inbox` — Read messages
|
||||||
|
- `tri msg outbox` — Sent messages
|
||||||
|
- `tri msg send <from> <to> <msg>` — Send encrypted message
|
||||||
|
- `tri msg anon <to> <msg>` — Anonymous message
|
||||||
|
- `tri msg keys` — Messaging keys
|
||||||
|
- `tri msg enable` — Enable secure messaging
|
||||||
|
- `tri msg pubkey <addr>` — Get public key
|
||||||
|
|
||||||
|
### Advanced
|
||||||
|
- `tri raw <method> [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 + <address>`
|
||||||
|
4. Exchange addresses between agents
|
||||||
|
5. Send: `tri msg send <hermes_addr> <krystie_addr> "message"`
|
||||||
|
6. Read: `tri msg inbox`
|
||||||
|
|
||||||
|
Messages are encrypted (ECDH), routed through the Tor P2P network,
|
||||||
|
stored for 48 hours, max 4096 bytes each.
|
||||||
@@ -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 "$@"
|
||||||
@@ -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..."
|
||||||
Executable
+691
@@ -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 <command> [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 <addr> <amt> [memo] Send TRI
|
||||||
|
# tri tx [N] Recent N transactions (default 10)
|
||||||
|
# tri tx <txid> Transaction details
|
||||||
|
# tri msg inbox Secure message inbox
|
||||||
|
# tri msg outbox Sent messages
|
||||||
|
# tri msg send <from> <to> <msg> Send encrypted message
|
||||||
|
# tri msg anon <to> <msg> Send anonymous message
|
||||||
|
# tri msg keys List messaging keys
|
||||||
|
# tri msg enable Enable secure messaging
|
||||||
|
# tri msg pubkey <addr> Get public key for address
|
||||||
|
# tri msg unlock [secs] Unlock wallet for messaging (default 60s)
|
||||||
|
# tri raw <method> [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 <method> [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 <method> [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 <method> <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 <address> <amount> [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 <from_address> <to_address> <message>${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 <to_address> <message>${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 <address>${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 <method> [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 <addr> <amt> [memo] Send TRI to address
|
||||||
|
tri tx [N] Recent N transactions (default 10)
|
||||||
|
tri tx <txid> Transaction details
|
||||||
|
|
||||||
|
SECURE MESSAGING
|
||||||
|
tri msg inbox Read inbox messages (wallet auto-unlocks)
|
||||||
|
tri msg outbox Read sent messages
|
||||||
|
tri msg send <from> <to> <msg> Send encrypted message
|
||||||
|
tri msg anon <to> <msg> Send anonymous message
|
||||||
|
tri msg keys List messaging keys
|
||||||
|
tri msg enable Enable secure messaging
|
||||||
|
tri msg pubkey <addr> Get public key for an address
|
||||||
|
tri msg unlock [secs] Unlock wallet for messaging (default 60s)
|
||||||
|
|
||||||
|
ADVANCED
|
||||||
|
tri raw <method> [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 "$@"
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user