v0.8.18: readable terminal output (vertical entity lists + verbatim-fenced aligned tables) + cmd_push direct-mode branch + _direct_ssh_opts dedup

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Bryan Johnson 2026-05-28 09:57:36 -07:00
parent d55e222341
commit 65807308d8
6 changed files with 178 additions and 30 deletions

View File

@ -4,6 +4,64 @@ All notable changes to `cloverleaf-larry` / `larry-anywhere` are recorded here.
Versioning is loose-semver; bumps trigger the in-process self-update on every Versioning is loose-semver; bumps trigger the in-process self-update on every
running client via `LARRY_BASE_URL` + `MANIFEST`. running client via `LARRY_BASE_URL` + `MANIFEST`.
## v0.8.18 — 2026-05-28
Readable terminal output + two DIRECT-mode follow-ups from Vera's v0.8.17 gate
(Clover). larry runs in a plain monospace terminal that does NOT render
markdown — this release makes tabular results actually line up and site lists
read vertically, and closes the deferred `ssh_push` direct-mode gap plus the
duplicated direct-ssh options.
**1. Readable output in a monospace terminal (Bryan's primary ask).**
The on-server LLM was re-rendering tool results as markdown tables
(`| col | col |`, `|---|`) — which print raw and never align — and collapsing
the site list into a comma-joined inline sentence. The tools already emit clean
data (nc-find/nc-inbound pre-align columns with `%-*s`; `list_sites` prints one
site per line with a `sites: N (excluded: …)` headline). The fix steers the
model to PRESERVE that, and reinforces it at the tool boundary so it can't drift:
- `agents/larry.md` — new **TERMINAL OUTPUT CONTRACT** section (5 rules): never
emit a markdown table; reproduce a tool's pre-aligned/fenced table VERBATIM in
a ```text fence; never hand-align columns (the tools do it deterministically);
render entity lists ONE PER LINE vertically (never comma-joined inline), and
keep `list_sites`'s count headline + one-per-line list as returned.
- `larry.sh` — new `_fence_aligned_table` helper wraps an already-aligned tool
table in a ```text fence with an explicit "reproduce VERBATIM; do NOT convert
to a markdown table" marker. `tool_nc_find` and `tool_nc_find_inbound` route
their `--format table` output through it (tsv/jsonl data formats pass through
unfenced). Empty / error / usage output passes through UNCHANGED (the operator
must still see errors plainly); the table bytes are never altered — only the
fence + marker lines are added.
**2. `cmd_push` DIRECT-mode branch (closes Vera's deferred MAJOR).**
`cmd_push` called `_resolve_open_master` unconditionally, so a DIRECT-mode alias
(no ControlMaster) died "no open master" — breaking `ssh_push` (exposed tool;
used by `nc_regression` phase 4 to push cross-env input bundles, central to the
`epic_adt_in` cross-env goal). Added the symmetric direct branch mirroring
`cmd_pull`: `_direct_scp <alias> <local> <addr>:<remote>` for the transfer, then
post-transfer size verification via `_dispatch_remote` (fresh per-command
`_run_direct`). `lib/ssh-helper.sh`.
**3. De-duplicated the direct-ssh options (closes Vera's MINOR).**
The shared direct-mode `-o` flags (the five security-critical ones —
`PreferredAuthentications=password`, `PubkeyAuthentication=no`,
`StrictHostKeyChecking=accept-new`, `ControlMaster=no`, `ControlPath=none`
plus `NumberOfPasswordPrompts=1` and `ConnectTimeout`) were copied in three
places (`cmd_setup` probe, `_run_direct`, `_direct_scp`). Extracted into one
`_direct_ssh_opts` helper so a security-option change can't drift across copies;
all three sites now splat `$(_direct_ssh_opts)`. ssh `-o` ordering is immaterial
(no conflicting duplicate keys) so this is byte-equivalent in BEHAVIOR — verified
the reconstructed argv matches the prior inline copies token-for-token.
`lib/ssh-helper.sh`.
**No traffic bypass (unchanged, absolute).** DIRECT mode is legitimate
forced-password auth only — no proxy, no tunnel, no masking, and host-key
checking STAYS ON (`StrictHostKeyChecking=accept-new`). The dedup helper keeps
that posture in a single source of truth.
`bash -n` clean on every changed file. `/sites` slash path drives clean under
`set -u`. VERSION + larry.sh:81 bumped to 0.8.18; MANIFEST regenerated.
## v0.8.17 — 2026-05-28 ## v0.8.17 — 2026-05-28
Per-alias DIRECT (no-multiplex) SSH mode (Clover). The real unblock for Per-alias DIRECT (no-multiplex) SSH mode (Clover). The real unblock for

View File

@ -23,19 +23,19 @@
# scripts/make-manifest.sh and bump VERSION. # scripts/make-manifest.sh and bump VERSION.
# Top-level scripts # Top-level scripts
larry.sh 7bdbe0743d7aec58ccedadfebd766e8bbfd828d47e33e51b2bac75a4d0706f5b larry.sh 087cc26634aa330049d46940ff6370dad2b84b267a8d4ce87b528eb8bd333d5d
larry-tunnel.sh 6b050e4eeab15669f4858eaf3b807f168f211ced07815db9521bc40a093f6aaa larry-tunnel.sh 6b050e4eeab15669f4858eaf3b807f168f211ced07815db9521bc40a093f6aaa
larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831 larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831
larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0 larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0
install-larry.sh e97da4e12a0d8863ca18d79b12f6c4294c72fa6d4b11dffeab66504236bb4eb1 install-larry.sh e97da4e12a0d8863ca18d79b12f6c4294c72fa6d4b11dffeab66504236bb4eb1
# Metadata # Metadata
VERSION a8c64c5df539331e33b8b4b5c1534d12f6238dbbbd313e7cebbf1cff1df0fe87 VERSION 1d14fd69d4f2d2b8118fa821e3c9a3d88f0a45cb6b262645ff643b4ae101d2b2
MANUAL.md 666128a086b59ff3c31a574aec0c5dd681666d66319da9f078451bf9013ca5e1 MANUAL.md 666128a086b59ff3c31a574aec0c5dd681666d66319da9f078451bf9013ca5e1
CHANGELOG.md a329fda8be2e5caa33f1dfec7a5c68adc7d7d19b8449032cbfc9542a766292b6 CHANGELOG.md 41763bdd066ed12d25a0f212378102fac4b5cfd91895a330f34e0859ae697d91
# Agent personas (system-prompt overlays) # Agent personas (system-prompt overlays)
agents/larry.md 11ea905fa7cac6fa7baeb11b2d62af07b15a666ce90cfe36491bcbc555244397 agents/larry.md 0a1ef737e7fc133ab35be09f79c3a4df33de814e0404b69b950932d0c8a01be1
agents/clover.md d1bbfd6cc4642c2bff6e15dcbdf051d71b063b3fe29e0be97d17b3180d3c7ac5 agents/clover.md d1bbfd6cc4642c2bff6e15dcbdf051d71b063b3fe29e0be97d17b3180d3c7ac5
agents/cloverleaf-cheatsheet.md c0a2aab91f1ddf092bce312def02cc6f3f62a1f653ca5af67a9430c3fcef4c3f agents/cloverleaf-cheatsheet.md c0a2aab91f1ddf092bce312def02cc6f3f62a1f653ca5af67a9430c3fcef4c3f
agents/regress.md bb05ed1439b1e35d6e9799e32d683bfab166472c72115c1f02757e227c74e42f agents/regress.md bb05ed1439b1e35d6e9799e32d683bfab166472c72115c1f02757e227c74e42f
@ -52,7 +52,7 @@ lib/fetch-safe.sh abecf0045b9856f63ffa346119443c11de56547344be32bddaed9fbae6b021
lib/oauth.sh 04a93376f88fe53cc1c86a5dbe577735c60375dadd4f2fda55b921ef3cddf22b lib/oauth.sh 04a93376f88fe53cc1c86a5dbe577735c60375dadd4f2fda55b921ef3cddf22b
# Secure SSH with ControlMaster (password hidden from Larry-the-LLM) # Secure SSH with ControlMaster (password hidden from Larry-the-LLM)
lib/ssh-helper.sh e9e2f33bb893d951e668d81dfe88057d235013b60cdba0e3441d1400d877a6d4 lib/ssh-helper.sh bd205aa87bc9e53821cac45888faa9434c1e182bee2bf16d6d838dcb79bfac3e
# v0.8.6: work-box → Mac headers.log sync (tsk-2026-05-27-023). Incremental, # v0.8.6: work-box → Mac headers.log sync (tsk-2026-05-27-023). Incremental,
# offset-tracked push of $LARRY_HOME/log/headers.log to a daemon-watched path # offset-tracked push of $LARRY_HOME/log/headers.log to a daemon-watched path

View File

@ -1 +1 @@
0.8.17 0.8.18

View File

@ -40,6 +40,18 @@ You have access to a small but sharp tool set:
You do **not** have subagent dispatch in portable mode. You are Larry + Clover (and any other specialist you need to channel) in one head. Be honest about that limitation when it matters. You do **not** have subagent dispatch in portable mode. You are Larry + Clover (and any other specialist you need to channel) in one head. Be honest about that limitation when it matters.
## TERMINAL OUTPUT CONTRACT (mandatory — you render to a plain monospace terminal)
Your output is read in a **plain monospace terminal that does NOT render markdown.** Markdown tables, bold, and headings print as raw literal characters. Follow these rules every time:
1. **NEVER emit a markdown table.** Do not write `| col | col |` rows or `|---|---|` separator lines — they print raw and the columns do NOT line up. If a tool already returned an aligned, fenced table block (the table tools do — see below), reproduce that block VERBATIM. Do not re-flow it, re-pad it, re-sort it, or convert it back into a markdown table.
2. **Tool output that is already a table → present it verbatim in a fenced code block.** Tools like `nc_find`, `nc_find_inbound`, and the table tools return columns that are ALREADY aligned with spaces by the tool, wrapped in a ```text … ``` fence. Echo that fenced block exactly as received. The alignment is deterministic and done by the tool — you must not "improve" it. Adding or removing a single space breaks the alignment in the terminal.
3. **Do not align columns yourself.** You are bad at counting monospace width; the tools do it deterministically. Your job is to pass the pre-aligned block through unchanged, then add a one-line takeaway ABOVE or BELOW the block (never inside it).
4. **Entity lists render ONE PER LINE, vertically — never comma-joined inline.** When you list sites, threads, files, processes, or any set of named things, put each on its own line (e.g. ` - ancout`). Do NOT write `ancout, ancout2, ancout3, …` on one line. The `list_sites` tool already prints sites one per line and prints a `sites: N (excluded: …)` headline — keep the count headline and the one-per-line list exactly as returned; do not collapse the list into a sentence.
5. **Keep prose minimal around data.** Lead with the answer (the count, the match), show the verbatim block, then stop. No restating every row in prose.
These rules override any instinct to "format nicely with markdown." In this environment, plain pre-aligned text IS the nice format.
## Working style ## Working style
- **Read before you write.** When pointed at a Cloverleaf root, start with `list_dir` and a targeted `grep_files` to map the lay of the land before proposing changes. - **Read before you write.** When pointed at a Cloverleaf root, start with `list_dir` and a targeted `grep_files` to map the lay of the land before proposing changes.

View File

@ -78,7 +78,7 @@ set -o pipefail
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Config # Config
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
LARRY_VERSION="0.8.17" LARRY_VERSION="0.8.18"
LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" LARRY_HOME="${LARRY_HOME:-$HOME/.larry}"
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
@ -1604,6 +1604,31 @@ _lib_err_if_missing() {
return 1 return 1
} }
# v0.8.18: _fence_aligned_table — wrap an already-space-aligned tool table in a
# ```text fence so the on-server LLM reproduces it VERBATIM in the monospace
# terminal instead of re-rendering it as a (mis-aligned) markdown table. Reads
# the tool's stdout/stderr on STDIN; the table tools (nc-find, nc-inbound) emit
# columns padded with %-*s, so the bytes are ALREADY aligned — we only need to
# fence them and tell the model not to touch them.
#
# Pass-through guarantee: if the captured text is empty, or looks like an error /
# usage line (so there is no real table to protect), we emit it UNCHANGED — the
# model must still see error text plainly. We never alter the table bytes; the
# fence and the two marker lines are the only additions.
_fence_aligned_table() {
local body; body=$(cat)
# Empty or obvious error/diagnostic → pass through untouched.
if [ -z "$body" ] || printf '%s' "$body" \
| grep -qiE '^(ERROR|nc-[a-z]+:|usage:|\[)' ; then
printf '%s\n' "$body"
return 0
fi
printf '%s\n' "TABLE (monospace, pre-aligned by the tool — reproduce VERBATIM in a code block; do NOT convert to a markdown table):"
printf '%s\n' '```text'
printf '%s\n' "$body"
printf '%s\n' '```'
}
tool_nc_list_protocols() { tool_nc_list_protocols() {
local nc="$1" local nc="$1"
_lib_err_if_missing || return _lib_err_if_missing || return
@ -1651,7 +1676,13 @@ tool_nc_xlate_refs() {
tool_nc_find_inbound() { tool_nc_find_inbound() {
local nc="$1" mode="${2:-all}" fmt="${3:-tsv}" local nc="$1" mode="${2:-all}" fmt="${3:-tsv}"
_lib_err_if_missing || return _lib_err_if_missing || return
# v0.8.18: fence the table format so the model reproduces it verbatim in the
# monospace terminal. tsv/jsonl are data formats — passed through unfenced.
if [ "$fmt" = "table" ]; then
"$LARRY_LIB_DIR/nc-inbound.sh" "$nc" --mode "$mode" --format "$fmt" 2>&1 | _fence_aligned_table
else
"$LARRY_LIB_DIR/nc-inbound.sh" "$nc" --mode "$mode" --format "$fmt" 2>&1 "$LARRY_LIB_DIR/nc-inbound.sh" "$nc" --mode "$mode" --format "$fmt" 2>&1
fi
} }
tool_nc_make_jump() { tool_nc_make_jump() {
local nc="$1" inbound="$2" new_host="$3" jump_port="$4" local nc="$1" inbound="$2" new_host="$3" jump_port="$4"
@ -1708,7 +1739,13 @@ tool_nc_find() {
name|port|host|process|where|xlate|tclproc) args+=(--"$mode" "$query") ;; name|port|host|process|where|xlate|tclproc) args+=(--"$mode" "$query") ;;
*) echo "ERROR: unknown nc_find mode: $mode"; return 1 ;; *) echo "ERROR: unknown nc_find mode: $mode"; return 1 ;;
esac esac
# v0.8.18: fence the table format so the model reproduces it verbatim in the
# monospace terminal. tsv/jsonl are data formats — passed through unfenced.
if [ "$format" = "table" ]; then
"$LARRY_LIB_DIR/nc-find.sh" "${args[@]}" 2>&1 | _fence_aligned_table
else
"$LARRY_LIB_DIR/nc-find.sh" "${args[@]}" 2>&1 "$LARRY_LIB_DIR/nc-find.sh" "${args[@]}" 2>&1
fi
} }
tool_nc_insert_protocol() { tool_nc_insert_protocol() {

View File

@ -331,14 +331,9 @@ cmd_setup() {
local errfile; errfile=$(mktemp 2>/dev/null || echo "/tmp/larry-ssh-direct-setup.err.$$") local errfile; errfile=$(mktemp 2>/dev/null || echo "/tmp/larry-ssh-direct-setup.err.$$")
# A trivial, side-effect-free probe. Forced password auth, host-key checked, # A trivial, side-effect-free probe. Forced password auth, host-key checked,
# no master. STDERR (banner/sudo) is captured for failure diagnosis only. # no master. STDERR (banner/sudo) is captured for failure diagnosis only.
# v0.8.18: shared DIRECT options via _direct_ssh_opts (was an inline copy).
sshpass -f "$credfile" ssh \ sshpass -f "$credfile" ssh \
-o "PreferredAuthentications=password" \ $(_direct_ssh_opts) \
-o "PubkeyAuthentication=no" \
-o "NumberOfPasswordPrompts=1" \
-o "StrictHostKeyChecking=accept-new" \
-o "ControlMaster=no" \
-o "ControlPath=none" \
-o "ConnectTimeout=$_DIRECT_CONNECT_TIMEOUT" \
-p "$port" \ -p "$port" \
"$addr" 'true' 2>"$errfile" "$addr" 'true' 2>"$errfile"
local vrc=$? local vrc=$?
@ -580,6 +575,37 @@ _remote_cmd_for() {
# _DIRECT_CONNECT_TIMEOUT — seconds for ssh ConnectTimeout (env-overridable). # _DIRECT_CONNECT_TIMEOUT — seconds for ssh ConnectTimeout (env-overridable).
_DIRECT_CONNECT_TIMEOUT="${LARRY_SSH_DIRECT_TIMEOUT:-10}" _DIRECT_CONNECT_TIMEOUT="${LARRY_SSH_DIRECT_TIMEOUT:-10}"
# _direct_ssh_opts → emit the shared ssh/scp `-o` option tokens for every DIRECT
# (no-ControlMaster) transport, one token per word, on STDOUT. v0.8.18: extracted
# so the security posture lives in ONE place and a change can't drift across the
# three call sites (cmd_setup probe, _run_direct, _direct_scp). The five
# security-critical flags are:
# PreferredAuthentications=password — force the password method so sshpass
# feeds the password cleanly past a banner
# PubkeyAuthentication=no — never silently fall back to a key
# StrictHostKeyChecking=accept-new — host-key checking STAYS ON (TOFU). This
# is the no-traffic-bypass guarantee: we
# never disable host verification.
# ControlMaster=no / ControlPath=none — never multiplex (the box rejects it)
# Plus two shared connection knobs identical across all three callers:
# NumberOfPasswordPrompts=1 — a stale password fails fast (one prompt)
# ConnectTimeout=$_DIRECT_CONNECT_TIMEOUT
# ssh `-o` ordering is immaterial (no conflicting duplicate keys), so emitting
# these as one contiguous block is byte-equivalent in BEHAVIOR to the prior
# inline copies. Callers splat the words unquoted: `ssh $(_direct_ssh_opts) ...`.
# Every token here is a single shell word (no spaces inside any -o value), so the
# unquoted expansion is safe.
_direct_ssh_opts() {
printf '%s\n' \
-o "PreferredAuthentications=password" \
-o "PubkeyAuthentication=no" \
-o "NumberOfPasswordPrompts=1" \
-o "StrictHostKeyChecking=accept-new" \
-o "ControlMaster=no" \
-o "ControlPath=none" \
-o "ConnectTimeout=$_DIRECT_CONNECT_TIMEOUT"
}
# _direct_creds ALIAS → echoes the credfile path (the file /ssh-pass writes), # _direct_creds ALIAS → echoes the credfile path (the file /ssh-pass writes),
# or empty (and warns) if absent. Same file the ControlMaster path uses. # or empty (and warns) if absent. Same file the ControlMaster path uses.
_direct_creds() { _direct_creds() {
@ -629,14 +655,9 @@ _run_direct() {
# path's v0.8.15 PreferredAuthentications=password hardening). BatchMode is # path's v0.8.15 PreferredAuthentications=password hardening). BatchMode is
# NOT set — sshpass supplies the password non-interactively via the askpass # NOT set — sshpass supplies the password non-interactively via the askpass
# file descriptor; BatchMode would suppress that path on some builds. # file descriptor; BatchMode would suppress that path on some builds.
# v0.8.18: shared DIRECT options via _direct_ssh_opts (was an inline copy).
sshpass -f "$credfile" ssh \ sshpass -f "$credfile" ssh \
-o "PreferredAuthentications=password" \ $(_direct_ssh_opts) \
-o "PubkeyAuthentication=no" \
-o "NumberOfPasswordPrompts=1" \
-o "StrictHostKeyChecking=accept-new" \
-o "ControlMaster=no" \
-o "ControlPath=none" \
-o "ConnectTimeout=$_DIRECT_CONNECT_TIMEOUT" \
-p "$port" \ -p "$port" \
"$addr" "$remote_cmd" 2>"$errfile" "$addr" "$remote_cmd" 2>"$errfile"
local rc=$? local rc=$?
@ -671,14 +692,10 @@ _direct_scp() {
local credfile; credfile=$(_direct_creds "$alias") \ local credfile; credfile=$(_direct_creds "$alias") \
|| die "no password set for $alias — run 'pass $alias' first" || die "no password set for $alias — run 'pass $alias' first"
local errfile; errfile=$(mktemp 2>/dev/null || echo "/tmp/larry-scp-direct.err.$$") local errfile; errfile=$(mktemp 2>/dev/null || echo "/tmp/larry-scp-direct.err.$$")
# v0.8.18: shared DIRECT options via _direct_ssh_opts (was an inline copy).
# scp reads the same ssh-style -o options; only the port flag differs (-P).
sshpass -f "$credfile" scp -q \ sshpass -f "$credfile" scp -q \
-o "PreferredAuthentications=password" \ $(_direct_ssh_opts) \
-o "PubkeyAuthentication=no" \
-o "NumberOfPasswordPrompts=1" \
-o "StrictHostKeyChecking=accept-new" \
-o "ControlMaster=no" \
-o "ControlPath=none" \
-o "ConnectTimeout=$_DIRECT_CONNECT_TIMEOUT" \
-P "$port" \ -P "$port" \
"$src" "$dst" 2>"$errfile" "$src" "$dst" 2>"$errfile"
local rc=$? local rc=$?
@ -959,6 +976,30 @@ cmd_push() {
[ -n "$alias" ] && [ -n "$local_path" ] && [ -n "$remote" ] \ [ -n "$alias" ] && [ -n "$local_path" ] && [ -n "$remote" ] \
|| die "usage: push <alias> <local_path> <remote_path>" || die "usage: push <alias> <local_path> <remote_path>"
[ -f "$local_path" ] || die "local file not found: $local_path" [ -f "$local_path" ] || die "local file not found: $local_path"
# v0.8.18: DIRECT mode — symmetric with cmd_pull's direct branch. No
# ControlMaster (the host rejects multiplexing); the transfer uses _direct_scp
# (fresh per-command sshpass), and the post-transfer size verification rides a
# fresh per-command connection via _dispatch_remote → _run_direct. Without this
# branch, ssh_push (an exposed tool, used by nc_regression phase 4 to push
# cross-env input bundles) died "no open master" for any DIRECT-mode alias.
if _alias_is_direct "$alias"; then
local addr_port; addr_port=$(read_host_addr "$alias")
[ -n "$addr_port" ] || die "no such alias: $alias"
local _d_addr; _d_addr=$(printf '%s' "$addr_port" | cut -f1)
local local_size; local_size=$(coerce_int "$(wc -c < "$local_path" 2>/dev/null)" 0)
if _direct_scp "$alias" "$local_path" "$_d_addr:$remote"; then
local got
got=$(coerce_int "$(_dispatch_remote "$alias" "wc -c < $(printf '%q' "$remote") 2>/dev/null" 2>/dev/null)" 0)
if [ "$got" != "$local_size" ]; then
die "partial transfer: local=$local_size bytes, remote=$got bytes ($alias:$remote)"
fi
ok "pushed $local_path$alias:$remote ($got bytes, direct)"
return 0
fi
return 1
fi
_resolve_open_master "$alias" _resolve_open_master "$alias"
# v0.7.5: coerce_int on wc output — Cygwin wc.exe CR-taint defense. # v0.7.5: coerce_int on wc output — Cygwin wc.exe CR-taint defense.