cloverleaf-larry/lib/ssh-helper.sh
Bryan Johnson 1709655a9c v0.6.8: cross-env Cloverleaf workflows over SSH ControlMaster
Closes the gap between v0.6.7's ssh_exec/ssh_status primitives and the local
nc_* tools, so Bryan's two motivating workflows compose cleanly:

  1. "Compare the ADT site NetConfig on qa to dev"
  2. "Grab smat files from dev and bring to qa for regression testing"

ssh_pull, ssh_push (lib/ssh-helper.sh + larry.sh):
  scp via the existing ControlMaster socket — no second auth, no second TCP
  handshake. Master-not-open and missing-remote-file paths fail with explicit
  messages ("open the master with /ssh-setup <alias> first"). Pull caches to
  /tmp/larry-pulls/<alias>.<basename>.<hash-of-remote-path> when local_path is
  omitted, so repeat pulls of the same remote file are idempotent. Validates
  byte counts post-transfer to catch partial transfers.

ssh_pull_smat (lib/ssh-helper.sh + larry.sh):
  Cloverleaf-aware smatdb pull. Full mode scp's the entire .smatdb;
  sampled mode (days_back=N) runs sqlite3 server-side via ssh_exec to extract
  up to 1000 recent messages as TSV with base64-encoded MessageContent blobs
  (verified end-to-end with a synthetic smatdb fixture matching nc-msgs.sh's
  smat_msgs schema). Avoids transferring multi-GB archives when only N
  samples are needed.

nc_diff_interface tool (newly wired):
  Promotes lib/nc-diff-interface.sh into the LLM-callable tool surface. Used
  by the new /nc-diff-env slash command for workflow #1.

nc_regression cross-env (lib/nc-regression.sh + larry.sh):
  source_ssh_alias / target_ssh_alias args. Phase 1 (discovery) and Phase 2
  (sample) run via ssh_exec + ssh_pull / ssh_pull_smat against the source
  alias. Phase 3/4 (route_test) push inputs over and pull outputs back via
  ssh_push / ssh_pull. Phases 5/6 (diff + summary) stay local. Reports
  reference the SSH alias names rather than raw user@host strings.

/nc-diff-env and /nc-regression-env slash commands (larry.sh):
  Templated prompts to Larry-the-LLM that explicitly cite the motivating
  workflows, call out ssh_status / ssh_pull / nc_diff_interface and the
  nc_regression cross-env fields. Registered in _LARRY_SLASH_CMDS +
  _LARRY_SLASH_CMDS_DESC + /help per v0.6.7 patterns.

Bug fix unearthed during cross-env work:
  lib/nc-regression.sh phase_5 / phase_6 used printf 'FORMAT' where FORMAT
  begins with '- '. bash 3.2 (macOS default) reads the leading '-' as a bad
  option and emits nothing — silently dropping the entire "Configuration"
  section of regression-summary.md. Switched the affected lines to
  printf -- 'FORMAT' so the format string is unambiguous.

Tool/slash surface deltas vs v0.6.7:
  Tools: 31 → 35 (+ssh_pull, +ssh_push, +ssh_pull_smat, +nc_diff_interface)
  Slash commands: 34 → 36 (+/nc-diff-env, +/nc-regression-env)

Updated tool descriptions for read_file, grep_files, nc_msgs to point at
ssh_pull / ssh_pull_smat as the cross-env pre-step so Larry-the-LLM picks
the right chain on the first attempt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 15:52:58 -07:00

457 lines
19 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# ssh-helper.sh — secure SSH command execution via ControlMaster.
#
# Architecture:
# • Hosts configured in $LARRY_HOME/.ssh-hosts.tsv as
# alias \t user@host \t port
# • Passwords stored at $LARRY_HOME/.ssh-creds/<alias>, mode 0600.
# The password file is the single point of truth — to rotate (daily-changing
# passwords) just overwrite the file with the new one and re-run 'setup'.
# • sshpass reads the password via -f (file), so it never lands in argv or
# environment where Larry the LLM (or other processes via /proc) could see it.
# • The first 'setup' call opens a long-lived SSH ControlMaster connection
# (default ControlPersist=8h). Subsequent 'exec' calls multiplex through
# the master socket and need no password.
# • Larry's tool layer only sees: alias, command, command_output.
# Never the password. Never the user@host (unless added to the alias list).
#
# Subcommands:
# hosts list configured hosts
# add <alias> <user@host[:port]> add a host to the alias list
# remove <alias> remove an alias (also clears cred + socket)
# pass <alias> set/update the password (hidden interactive)
# setup <alias> open ControlMaster (uses stored password ONCE)
# close <alias> close ControlMaster
# status [alias] show open masters / cred presence
# exec <alias> <command...> run command via master (returns output)
# pull <alias> <remote> [local] scp remote → local via existing master
# push <alias> <local> <remote> scp local → remote via existing master
# pull-smat <alias> <site> <thread> [days_back]
# pull a thread's smatdb (full) or sample
# recent messages from it (sampled, TSV b64)
# help print this help
set -u
set -o pipefail
LARRY_HOME="${LARRY_HOME:-$HOME/.larry}"
SSH_HOSTS_FILE="$LARRY_HOME/.ssh-hosts.tsv"
SSH_CREDS_DIR="$LARRY_HOME/.ssh-creds"
SSH_SOCKETS_DIR="$LARRY_HOME/.ssh-sockets"
SSH_CONTROL_PERSIST="${LARRY_SSH_CONTROL_PERSIST:-8h}"
die() { printf 'ssh-helper: %s\n' "$*" >&2; exit 1; }
warn() { printf 'ssh-helper: warn: %s\n' "$*" >&2; }
ok() { printf 'ssh-helper: %s\n' "$*"; }
ensure_layout() {
mkdir -p "$LARRY_HOME" "$SSH_CREDS_DIR" "$SSH_SOCKETS_DIR" 2>/dev/null
chmod 700 "$LARRY_HOME" "$SSH_CREDS_DIR" "$SSH_SOCKETS_DIR" 2>/dev/null || true
if [ ! -f "$SSH_HOSTS_FILE" ]; then
umask 077
printf 'alias\taddr\tport\n' > "$SSH_HOSTS_FILE"
chmod 600 "$SSH_HOSTS_FILE"
fi
}
# read_host_addr ALIAS → echoes "ADDR\tPORT" or empty
read_host_addr() {
local alias="$1"
[ -f "$SSH_HOSTS_FILE" ] || { printf ''; return 1; }
awk -F'\t' -v a="$alias" 'NR>1 && $1==a { print $2 "\t" $3; exit }' < "$SSH_HOSTS_FILE"
}
require_sshpass() {
command -v sshpass >/dev/null 2>&1 \
|| die "sshpass not on PATH — install it (apt install sshpass / brew install sshpass) and retry"
}
cmd_help() {
sed -n '4,30p' "$0"
}
cmd_hosts() {
ensure_layout
if [ "$(wc -l < "$SSH_HOSTS_FILE")" -le 1 ]; then
echo "no hosts configured. Add with: ssh-helper.sh add <alias> <user@host[:port]>"
return 0
fi
printf 'alias user@host port cred master\n'
printf '%s\n' '───── ───────── ──── ──── ──────'
awk -F'\t' 'NR>1' "$SSH_HOSTS_FILE" | while IFS=$'\t' read -r alias addr port; do
local cred_state=""
[ -f "$SSH_CREDS_DIR/$alias" ] && cred_state="✓"
local master_state=""
local sock="$SSH_SOCKETS_DIR/$alias.sock"
if [ -S "$sock" ] && ssh -S "$sock" -O check -p "$port" "$addr" 2>/dev/null; then
master_state="open"
fi
printf '%-20s%-52s%-6s%-6s%s\n' "$alias" "$addr" "${port:-22}" "$cred_state" "$master_state"
done
}
cmd_add() {
local alias="${1:-}" target="${2:-}"
[ -n "$alias" ] && [ -n "$target" ] || die "usage: add <alias> <user@host[:port]>"
[[ "$target" =~ ^[^@[:space:]]+@[^:[:space:]]+(:[0-9]+)?$ ]] \
|| die "target must look like user@host or user@host:port"
local addr port
if [[ "$target" == *:* ]]; then
addr="${target%:*}"
port="${target##*:}"
else
addr="$target"
port="22"
fi
ensure_layout
# Reject duplicates (use 'remove' first)
if awk -F'\t' -v a="$alias" 'NR>1 && $1==a { found=1; exit } END { exit !found }' "$SSH_HOSTS_FILE"; then
die "alias '$alias' already exists. Use 'remove $alias' first."
fi
umask 077
printf '%s\t%s\t%s\n' "$alias" "$addr" "$port" >> "$SSH_HOSTS_FILE"
chmod 600 "$SSH_HOSTS_FILE"
ok "added $alias$addr (port $port). Next: ssh-helper.sh pass $alias"
}
cmd_remove() {
local alias="${1:-}"
[ -n "$alias" ] || die "usage: remove <alias>"
ensure_layout
local tmp; tmp=$(mktemp)
awk -F'\t' -v a="$alias" 'NR==1 || $1!=a' "$SSH_HOSTS_FILE" > "$tmp" && mv "$tmp" "$SSH_HOSTS_FILE"
chmod 600 "$SSH_HOSTS_FILE"
# Close + clean master socket and cred
cmd_close "$alias" 2>/dev/null || true
rm -f "$SSH_CREDS_DIR/$alias" "$SSH_SOCKETS_DIR/$alias.sock" 2>/dev/null
ok "removed $alias (cred + socket cleared)"
}
cmd_pass() {
local alias="${1:-}"
[ -n "$alias" ] || die "usage: pass <alias>"
local addr_port; addr_port=$(read_host_addr "$alias")
[ -n "$addr_port" ] || die "no such alias: $alias (run 'add' first)"
ensure_layout
printf 'Password for %s (input is hidden; press Enter when done): ' "$alias" >&2
local pw=""
stty -echo 2>/dev/null
IFS= read -r pw </dev/tty || true
stty echo 2>/dev/null
echo "" >&2
[ -n "$pw" ] || die "no password entered"
umask 077
# NO trailing newline — sshpass -f expects raw password as full file content
printf '%s' "$pw" > "$SSH_CREDS_DIR/$alias"
chmod 600 "$SSH_CREDS_DIR/$alias"
ok "password saved to $SSH_CREDS_DIR/$alias (mode 0600). Next: ssh-helper.sh setup $alias"
}
cmd_setup() {
local alias="${1:-}"
[ -n "$alias" ] || die "usage: setup <alias>"
local addr_port; addr_port=$(read_host_addr "$alias")
[ -n "$addr_port" ] || die "no such alias: $alias"
local addr port
addr=$(printf '%s' "$addr_port" | cut -f1)
port=$(printf '%s' "$addr_port" | cut -f2)
ensure_layout
local sock="$SSH_SOCKETS_DIR/$alias.sock"
if [ -S "$sock" ] && ssh -S "$sock" -O check -p "$port" "$addr" 2>/dev/null; then
ok "master already open for $alias ($addr:$port)"
return 0
fi
local credfile="$SSH_CREDS_DIR/$alias"
[ -f "$credfile" ] || die "no password set for $alias — run 'pass $alias' first"
require_sshpass
ok "opening ssh master for $alias ($addr:$port) — ControlPersist=$SSH_CONTROL_PERSIST..."
if sshpass -f "$credfile" ssh \
-o "ControlMaster=yes" \
-o "ControlPath=$sock" \
-o "ControlPersist=$SSH_CONTROL_PERSIST" \
-o "StrictHostKeyChecking=accept-new" \
-o "ConnectTimeout=10" \
-p "$port" \
-N -f \
"$addr" 2>/tmp/larry-ssh-setup.err; then
if ssh -S "$sock" -O check -p "$port" "$addr" 2>/dev/null; then
ok "✓ master open: $alias$addr:$port (socket: $sock)"
rm -f /tmp/larry-ssh-setup.err
return 0
fi
fi
printf 'ssh-helper: setup failed. sshpass/ssh stderr:\n' >&2
cat /tmp/larry-ssh-setup.err >&2 2>/dev/null
rm -f /tmp/larry-ssh-setup.err
return 1
}
cmd_close() {
local alias="${1:-}"
[ -n "$alias" ] || die "usage: close <alias>"
local addr_port; addr_port=$(read_host_addr "$alias") || addr_port=""
local sock="$SSH_SOCKETS_DIR/$alias.sock"
if [ -S "$sock" ] && [ -n "$addr_port" ]; then
local addr port
addr=$(printf '%s' "$addr_port" | cut -f1)
port=$(printf '%s' "$addr_port" | cut -f2)
ssh -S "$sock" -O exit -p "$port" "$addr" 2>/dev/null || true
fi
rm -f "$sock"
ok "closed master for $alias"
}
cmd_status() {
ensure_layout
if [ -n "${1:-}" ]; then
local alias="$1"
local addr_port; addr_port=$(read_host_addr "$alias")
[ -n "$addr_port" ] || die "no such alias: $alias"
local addr port
addr=$(printf '%s' "$addr_port" | cut -f1)
port=$(printf '%s' "$addr_port" | cut -f2)
local sock="$SSH_SOCKETS_DIR/$alias.sock"
printf 'alias: %s\naddr: %s\nport: %s\ncred: %s\nsocket: %s\nstatus: ' \
"$alias" "$addr" "$port" \
"$([ -f "$SSH_CREDS_DIR/$alias" ] && echo present || echo missing)" \
"$sock"
if [ -S "$sock" ] && ssh -S "$sock" -O check -p "$port" "$addr" 2>/dev/null; then
echo "master OPEN"
else
echo "no master (run setup)"
fi
return 0
fi
cmd_hosts
}
cmd_exec() {
local alias="${1:-}"
[ -n "$alias" ] || die "usage: exec <alias> <command...>"
shift
local cmd="$*"
[ -n "$cmd" ] || die "no command given"
local addr_port; addr_port=$(read_host_addr "$alias")
[ -n "$addr_port" ] || die "no such alias: $alias"
local addr port
addr=$(printf '%s' "$addr_port" | cut -f1)
port=$(printf '%s' "$addr_port" | cut -f2)
local sock="$SSH_SOCKETS_DIR/$alias.sock"
if [ ! -S "$sock" ] || ! ssh -S "$sock" -O check -p "$port" "$addr" 2>/dev/null; then
die "no open master for $alias — run 'setup $alias' first"
fi
# Multiplexed; no password needed.
ssh -S "$sock" -p "$port" -o BatchMode=yes "$addr" "$cmd"
}
# ── v0.6.8: scp helpers that multiplex via the existing ControlMaster ────────
# We use ssh's ControlPath/ControlMaster=no for scp (scp reads ssh-style options
# via -o), so the file transfer rides the open master and needs no second auth.
# Resolve ADDR/PORT/SOCK for an alias; die if master not open. Sets globals:
# _RH_ADDR _RH_PORT _RH_SOCK
_resolve_open_master() {
local alias="$1"
local addr_port; addr_port=$(read_host_addr "$alias")
[ -n "$addr_port" ] || die "no such alias: $alias"
_RH_ADDR=$(printf '%s' "$addr_port" | cut -f1)
_RH_PORT=$(printf '%s' "$addr_port" | cut -f2)
_RH_SOCK="$SSH_SOCKETS_DIR/$alias.sock"
if [ ! -S "$_RH_SOCK" ] || ! ssh -S "$_RH_SOCK" -O check -p "$_RH_PORT" "$_RH_ADDR" 2>/dev/null; then
die "no open master for $alias — open it with /ssh-setup $alias first"
fi
}
# Deterministic local cache path for ssh_pull.
# /tmp/larry-pulls/<alias>.<basename>.<short-hash-of-remote-path>
_pull_cache_path() {
local alias="$1" remote="$2"
local base; base=$(basename -- "$remote" 2>/dev/null)
[ -z "$base" ] && base="file"
# 8-char hex hash of full remote path. We try the most common hashers in
# turn; on a stripped box without any, fall back to a length+checksum proxy
# so the path is still deterministic per <alias,remote_path>.
local hash=""
if command -v shasum >/dev/null 2>&1; then
hash=$(printf '%s' "$remote" | shasum -a 1 2>/dev/null | cut -c1-8)
elif command -v sha1sum >/dev/null 2>&1; then
hash=$(printf '%s' "$remote" | sha1sum 2>/dev/null | cut -c1-8)
elif command -v md5sum >/dev/null 2>&1; then
hash=$(printf '%s' "$remote" | md5sum 2>/dev/null | cut -c1-8)
else
hash=$(printf '%s' "$remote" | cksum 2>/dev/null | awk '{printf "%08x", $1}' | cut -c1-8)
fi
[ -z "$hash" ] && hash="00000000"
mkdir -p /tmp/larry-pulls 2>/dev/null
printf '/tmp/larry-pulls/%s.%s.%s' "$alias" "$base" "$hash"
}
cmd_pull() {
local alias="${1:-}" remote="${2:-}" local_path="${3:-}"
[ -n "$alias" ] && [ -n "$remote" ] || die "usage: pull <alias> <remote_path> [local_path]"
_resolve_open_master "$alias"
[ -z "$local_path" ] && local_path=$(_pull_cache_path "$alias" "$remote")
mkdir -p "$(dirname "$local_path")" 2>/dev/null
# Get remote file size up-front for a partial-transfer sanity check.
local remote_size=""
remote_size=$(ssh -S "$_RH_SOCK" -p "$_RH_PORT" -o BatchMode=yes "$_RH_ADDR" \
"wc -c < $(printf '%q' "$remote") 2>/dev/null" 2>/dev/null | tr -d ' ')
if [ -z "$remote_size" ] || ! [[ "$remote_size" =~ ^[0-9]+$ ]]; then
die "remote file not found or not readable: $remote"
fi
# scp via the existing master: -o ControlPath=... -o ControlMaster=no
local scp_err; scp_err=$(mktemp 2>/dev/null || echo "/tmp/larry-scp.err.$$")
if scp -q \
-o "ControlPath=$_RH_SOCK" \
-o "ControlMaster=no" \
-o "BatchMode=yes" \
-P "$_RH_PORT" \
"$_RH_ADDR:$remote" "$local_path" 2>"$scp_err"; then
local got; got=$(wc -c < "$local_path" 2>/dev/null | tr -d ' ')
if [ "$got" != "$remote_size" ]; then
rm -f "$scp_err"
die "partial transfer: remote=$remote_size bytes, local=$got bytes ($local_path)"
fi
rm -f "$scp_err"
ok "pulled $alias:$remote$local_path ($got bytes)"
# Print only the local path on the final line so callers (tool layer) can
# capture it deterministically with `tail -1` or similar.
printf '%s\n' "$local_path"
return 0
fi
local rc=$?
printf 'ssh-helper: scp pull failed (rc=%d):\n' "$rc" >&2
cat "$scp_err" >&2 2>/dev/null
rm -f "$scp_err"
return 1
}
cmd_push() {
local alias="${1:-}" local_path="${2:-}" remote="${3:-}"
[ -n "$alias" ] && [ -n "$local_path" ] && [ -n "$remote" ] \
|| die "usage: push <alias> <local_path> <remote_path>"
[ -f "$local_path" ] || die "local file not found: $local_path"
_resolve_open_master "$alias"
local local_size; local_size=$(wc -c < "$local_path" 2>/dev/null | tr -d ' ')
local scp_err; scp_err=$(mktemp 2>/dev/null || echo "/tmp/larry-scp.err.$$")
if scp -q \
-o "ControlPath=$_RH_SOCK" \
-o "ControlMaster=no" \
-o "BatchMode=yes" \
-P "$_RH_PORT" \
"$local_path" "$_RH_ADDR:$remote" 2>"$scp_err"; then
# Validate via remote wc -c.
local got
got=$(ssh -S "$_RH_SOCK" -p "$_RH_PORT" -o BatchMode=yes "$_RH_ADDR" \
"wc -c < $(printf '%q' "$remote") 2>/dev/null" 2>/dev/null | tr -d ' ')
if [ "$got" != "$local_size" ]; then
rm -f "$scp_err"
die "partial transfer: local=$local_size bytes, remote=$got bytes ($alias:$remote)"
fi
rm -f "$scp_err"
ok "pushed $local_path$alias:$remote ($got bytes)"
return 0
fi
local rc=$?
printf 'ssh-helper: scp push failed (rc=%d):\n' "$rc" >&2
cat "$scp_err" >&2 2>/dev/null
rm -f "$scp_err"
return 1
}
# pull-smat: smart pull for a Cloverleaf thread's .smatdb file.
# Two modes:
# Full pull: pull-smat <alias> <site> <thread>
# Locates $HCISITEDIR/exec/processes/*/<thread>.smatdb on the
# remote via find, then scp's the entire .smatdb file.
# Sampled: pull-smat <alias> <site> <thread> <days_back>
# Runs sqlite3 server-side, extracts up to 1000 most-recent
# messages from the last <days_back> days, encodes each
# MessageContent BLOB as base64, returns TSV:
# unix_ts<TAB>direction<TAB>type<TAB>source<TAB>dest<TAB>message_blob_b64
# The schema (table=smat_msgs, columns Time/Type/SourceConn/
# DestConn/MessageContent) is the same one nc-msgs.sh uses.
cmd_pull_smat() {
local alias="${1:-}" site="${2:-}" thread="${3:-}" days_back="${4:-}"
[ -n "$alias" ] && [ -n "$site" ] && [ -n "$thread" ] \
|| die "usage: pull-smat <alias> <site> <thread> [days_back]"
_resolve_open_master "$alias"
# Discover the remote .smatdb path. We rely on HCIROOT being exported in
# the remote shell rc (typical Cloverleaf user profile), else SITEDIR is
# taken as <HCIROOT>/<site> via ssh-resolved $HCIROOT. We do the find
# remotely to avoid hard-coding process directory names.
local find_cmd
find_cmd='set -e; SDIR="${HCISITEDIR:-${HCIROOT:-}/'"$site"'}"; '
find_cmd+='[ -d "$SDIR" ] || { echo "ERROR: sitedir not found on remote: $SDIR" >&2; exit 2; }; '
find_cmd+='F=$(find "$SDIR/exec/processes" -maxdepth 2 -type f -name "'"$thread"'.smatdb" 2>/dev/null | head -1); '
find_cmd+='[ -n "$F" ] || F=$(find "$SDIR" -type f -name "'"$thread"'.smatdb" 2>/dev/null | head -1); '
find_cmd+='[ -n "$F" ] || { echo "ERROR: no smatdb found for thread '"$thread"' under $SDIR" >&2; exit 3; }; '
find_cmd+='printf "%s\n" "$F"'
local remote_smatdb
remote_smatdb=$(ssh -S "$_RH_SOCK" -p "$_RH_PORT" -o BatchMode=yes "$_RH_ADDR" "$find_cmd" 2>&1 | tail -1)
case "$remote_smatdb" in
ERROR:*|'') die "remote smatdb lookup failed: $remote_smatdb" ;;
esac
if [ -z "$days_back" ]; then
# Full mode: scp the whole .smatdb file.
local local_path
local_path=$(_pull_cache_path "$alias" "$remote_smatdb")
cmd_pull "$alias" "$remote_smatdb" "$local_path"
return $?
fi
# Sampled mode: run sqlite3 on the remote, return TSV with b64-encoded blobs.
# base64 -w0 is GNU coreutils; on BSD use plain base64 (no -w). We accept
# whichever is present; the awk in the SQL pipeline strips internal newlines
# for sturdy TSV.
#
# Output line shape (each message):
# <unix_ts_s>\t<direction>\t<type>\t<source>\t<dest>\t<b64-of-MessageContent>
# `direction` is "in" when DestConn=thread, else "out" (best-effort heuristic).
local sample_cmd
sample_cmd='set -e; '
sample_cmd+='which sqlite3 >/dev/null 2>&1 || { echo "ERROR: sqlite3 not on remote PATH" >&2; exit 4; }; '
sample_cmd+='B64() { if base64 --help 2>&1 | grep -q -- " -w"; then base64 -w0; else base64 | tr -d "\n"; fi; }; '
# Note: sqlite3 ".mode tabs" prints rows tab-separated; we redirect blob via
# writefile() into temp files, then base64 each. That avoids any binary
# mangling in the sqlite3 -ascii path. Approach: select rowids, then for each
# rowid pull MessageContent into a per-row temp file, b64 it inline.
sample_cmd+='TMP=$(mktemp -d); trap "rm -rf $TMP" EXIT; '
sample_cmd+='CUTOFF_MS=$(( ( $(date +%s) - '"$days_back"' * 86400 ) * 1000 )); '
sample_cmd+='sqlite3 "'"$remote_smatdb"'" "SELECT rowid, Time, IFNULL(Type,\"\"), IFNULL(SourceConn,\"\"), IFNULL(DestConn,\"\") FROM smat_msgs WHERE Time >= $CUTOFF_MS ORDER BY Time DESC LIMIT 1000" '
sample_cmd+='| while IFS="|" read -r rid tm typ src dst; do '
sample_cmd+=' blobfile="$TMP/$rid.bin"; '
sample_cmd+=' sqlite3 "'"$remote_smatdb"'" "SELECT writefile(\"$blobfile\", MessageContent) FROM smat_msgs WHERE rowid=$rid" >/dev/null 2>&1; '
sample_cmd+=' if [ "$dst" = "'"$thread"'" ]; then dir="in"; else dir="out"; fi; '
sample_cmd+=' printf "%s\t%s\t%s\t%s\t%s\t" "$(( tm / 1000 ))" "$dir" "$typ" "$src" "$dst"; '
sample_cmd+=' B64 < "$blobfile"; '
sample_cmd+=' printf "\n"; '
sample_cmd+='done; '
sample_cmd+='TOTAL=$(sqlite3 "'"$remote_smatdb"'" "SELECT COUNT(*) FROM smat_msgs WHERE Time >= $CUTOFF_MS"); '
sample_cmd+='RETURNED=$(sqlite3 "'"$remote_smatdb"'" "SELECT MIN(1000, COUNT(*)) FROM smat_msgs WHERE Time >= $CUTOFF_MS"); '
sample_cmd+='echo "# smatdb=$(basename '"$remote_smatdb"') days_back='"$days_back"' total_in_window=$TOTAL returned=$RETURNED truncated=$([ "$TOTAL" -gt 1000 ] && echo yes || echo no)" >&2'
ssh -S "$_RH_SOCK" -p "$_RH_PORT" -o BatchMode=yes "$_RH_ADDR" "$sample_cmd"
}
case "${1:-help}" in
hosts|list) shift; cmd_hosts ;;
add) shift; cmd_add "$@" ;;
remove|rm) shift; cmd_remove "$@" ;;
pass|passwd) shift; cmd_pass "$@" ;;
setup|open) shift; cmd_setup "$@" ;;
close|exit) shift; cmd_close "$@" ;;
status) shift; cmd_status "$@" ;;
exec|run) shift; cmd_exec "$@" ;;
pull) shift; cmd_pull "$@" ;;
push) shift; cmd_push "$@" ;;
pull-smat) shift; cmd_pull_smat "$@" ;;
-h|--help|help) cmd_help ;;
*) die "unknown subcommand: ${1:-} (run with --help)" ;;
esac