From ba224477e381e597d6e1beeca27bb1595e026ec9 Mon Sep 17 00:00:00 2001 From: bj Date: Mon, 8 Jun 2026 15:24:02 -0700 Subject: [PATCH] v0.9.5: `cheat` on-PATH command (live, never-drifts reference) + verified no-uninstall config-preserving update - bin/cheat: one-screen reference for ALL short commands, generated live from the bin/ wrapper set + each tool's help block (description from the wrapper header / backing lib line 1; ONE real example from the wrapper's example block / lib Usage form). cheat + cheat -h. LC_ALL=C em-dash slicing. - Wired into install-larry.sh symlink loop + MANIFEST (auto-synced on update). - Proved the update path: simulated v0.9.0 install with populated config/auth/ site-data -> re-ran installer against v0.9.5 origin -> reached v0.9.5 with bin/cheat on PATH + completion wired + fixtures present, ALL 8 state files byte-identical (sha256), tbn adt + completion working after. Idempotent re-run (no duplicate rc line). NO uninstall needed for an update. - VERSION + LARRY_VERSION -> 0.9.5; MANIFEST regenerated (--check clean, 95 entries); bash -n clean. Deliverables: cheatsheet + update-procedure (myPKA). Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 29 +++++++ MANIFEST | 11 ++- VERSION | 2 +- bin/cheat | 197 +++++++++++++++++++++++++++++++++++++++++++++++ install-larry.sh | 2 +- larry.sh | 2 +- 6 files changed, 236 insertions(+), 7 deletions(-) create mode 100755 bin/cheat diff --git a/CHANGELOG.md b/CHANGELOG.md index eb8aa4a..6a11544 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,35 @@ All notable changes to `cloverleaf-larry` / `larry-anywhere` are recorded here. Versioning is loose-semver; bumps trigger the in-process self-update on every running client via `LARRY_BASE_URL` + `MANIFEST`. +## v0.9.5 — 2026-06-08 + +**`cheat` — a one-screen, never-drifts reference for every short command — plus a +verified config-preserving update procedure (NO uninstall).** (Clover) + +- **`bin/cheat` (on PATH, `-h`).** Prints ALL short commands with a one-line + description and ONE real example each. Generated LIVE from the installed `bin/` + wrapper set + each tool's own help block (the wrapper's `# — …` header, + falling back to the backing `lib/.sh` line 1 for thin pass-throughs; the + first indented `# …` example, falling back to the lib tool's first + `# Usage:` form), so it can never drift from what is actually on the box. + `cheat ` narrows by substring; `cheat -h` explains itself. Added to the + installer's wrapper-symlink loop and to the MANIFEST (auto-synced on update). + Em-dash header parsing runs under `LC_ALL=C` so the 3-byte em-dash is sliced by + byte, not char (a UTF-8-locale awk `substr` would over-skip 2 chars). +- **Verified update story (Bryan's #1 concern):** re-running `install-larry.sh` + (or relaunching `larry`) pulls v0.9.5 from the Gitea origin and PRESERVES all + state (`.api-key`, `.oauth.json`, `.deployment-id`, `.ssh-creds/`, `sessions/`, + `log/`, curated `inbound-systems.tsv`) — none of which is in the MANIFEST, so + the updater never touches them. Proven by simulating a v0.9.0 install with + populated config/auth/site-data, running the update, and confirming v0.9.5 + + `bin/cheat` on PATH + completion wired + fixtures present, with all 8 state + files byte-identical (sha256) and `tbn adt`/completion working after. The + installer's rc-wiring and curated-file handling are idempotent (2nd run = no + duplicate completion line, config still intact). No uninstall is needed for an + update; `uninstall-larry.sh` is decommission-only. Procedure deliverable: + `Deliverables/2026-06-08-cloverleaf-larry-update-procedure.md`; cheatsheet: + `Deliverables/2026-06-08-cloverleaf-larry-cheatsheet.md`. + ## v0.9.4 — 2026-06-08 **Short, directly-invokable commands on PATH + dynamic 3-level tab-completion + diff --git a/MANIFEST b/MANIFEST index b3f71df..163fd9c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -23,17 +23,17 @@ # scripts/make-manifest.sh and bump VERSION. # Top-level scripts -larry.sh 32c81f6ca2e677756c1efc851337d71cd553f8a0bbf96f2eaeb5e8d8ec68377d +larry.sh 93e72650cf031ea3ec6a3cce3a323d2a712bfd7a1cbaed15eac9e0da9cc8f824 larry-tunnel.sh 6b050e4eeab15669f4858eaf3b807f168f211ced07815db9521bc40a093f6aaa larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831 larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0 -install-larry.sh 0a7bc75e75ebfe75eb9c47bda259b31072b79003af42a8e7f40a7e48469a874f +install-larry.sh b7ca41ac5b6e3a687ba22d1246dc102fe70a3ed47d6d0a3b0fdedb44cb37debd uninstall-larry.sh c53ad2d8354c7adeb243b541f027f3f481e4a8661eecfd7af14d7ca53cfcaad9 # Metadata -VERSION a61cc7a990558a2b76c4a814eb233267e219158dcf184b6b30ce458d27027674 +VERSION 8780409dbc52c96276102c5a079a7d3e325510d7bf87af5257508ecc088a3730 MANUAL.md 5ff54d6d5fae826f8b3da1eb3be6476076bb15f9b1417a4de285e59ea37e1b1f -CHANGELOG.md 8eacdea7711e4e22b1afb0f79f7deb970881a133ca6277732ba4ba16ac1b194f +CHANGELOG.md 75e80341189204a3320eb60da1db7c190b5e34f94c3359895ef2744153107dcd # Agent personas (system-prompt overlays) agents/larry.md 0a1ef737e7fc133ab35be09f79c3a4df33de814e0404b69b950932d0c8a01be1 @@ -125,6 +125,9 @@ lib/nc-regression.sh 70999a60608439f7bf1a3abb9f5e9854b5ea03025ef29ddbca683896346 # (command/site/thread) completion, enumerated LIVE from the NetConfig tree. bin/_nc_common.sh 591b60d091e9c8a5e1068844dffe2e19f44d262fcf1132dc47e425d4673495e1 bin/nc-completion.bash 3a5b1b29f34f2382556d9f8620882ab7edbb2d27dfa35177793889b289d22a9a +# v0.9.5: `cheat` — one-screen reference generated LIVE from this bin/ set + +# each tool's help block (never drifts). On PATH; `cheat`, `cheat `, -h. +bin/cheat 94a164da2aebe15b68f52ed675ff75547235c35eaa25f96df73fbe1c695b93a2 bin/tbn 60e69288a502f1280e6bf4bc6dc19568858b34024ba26c34f8ff7ed52457d02c bin/tbp 8cdc82de0bbbee8da97d4ab958f7b5ead5aed8c7658c3d73ff9106df6383bf29 bin/tbh 79556fe72d58393c2a00e744b892c5a1a69bae3be6fcdd0ba46dd108ee67c092 diff --git a/VERSION b/VERSION index a602fc9..b0bb878 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.4 +0.9.5 diff --git a/bin/cheat b/bin/cheat new file mode 100755 index 0000000..ccbbf5c --- /dev/null +++ b/bin/cheat @@ -0,0 +1,197 @@ +#!/usr/bin/env bash +# cheat — one-screen reference for ALL Cloverleaf-Larry short commands. +# Generated LIVE from the bin/ wrapper set + each tool's help block, so it can +# NEVER drift from what is actually installed on PATH. No static list. +# +# cheat # the full table: command · what it does · one example +# cheat tbn # just the row(s) whose name contains 'tbn' +# cheat hl7 # filter — every command matching 'hl7' +# cheat -h # this help +# +# For each command shown, `cheat` derives: +# • the description — from the wrapper's own `# name — …` header line, or (for +# the thin nc-*/hl7-* pass-through wrappers) from line 1 of +# the underlying lib/.sh, whichever is more specific. +# • ONE real example — the first indented `# …` example in the wrapper's +# help block; else the first `# Usage:` form of the backing +# lib tool, rewritten to the bare on-PATH command; else +# ` -h`. +# Run ` -h` (or ` --help`) on any row for the full, authoritative help. +set -o pipefail + +# --- locate our own bin/ dir (follow one symlink level, like the wrappers) --- +_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")" +_BINDIR="$(cd "$(dirname "$_self")" && pwd)" +# Reuse the shared lib resolver so the example-from-lib path finds lib/.sh. +[ -f "$_BINDIR/_nc_common.sh" ] && . "$_BINDIR/_nc_common.sh" + +if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then + awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "${BASH_SOURCE[0]}" + exit 0 +fi + +_filter="${1:-}" + +# Resolve the lib/ dir once (best-effort; example-from-lib degrades gracefully). +_LIB="" +if command -v _nc_resolve_lib >/dev/null 2>&1; then + _LIB="$(_nc_resolve_lib 2>/dev/null || true)" +fi + +# --- header line of a wrapper: the `# ` comment (line 2) ------- +# Returns the text after the em-dash separator. The codebase convention is a +# literal em-dash (—, U+2014) between the command name and its description; we +# split ONLY on that so hyphenated command names (csv-to-table, hl7-field) stay +# intact. A wrapper whose first comment has no em-dash yields no desc here (the +# caller then falls back to the backing lib tool's description). +_wrapper_desc() { + # LC_ALL=C → byte-oriented index()/substr() so the 3-byte em-dash (—) is + # skipped exactly (under a UTF-8 locale substr counts chars and over-skips). + LC_ALL=C awk ' + NR==1 { next } # shebang + /^#/ { + line = $0 + sub(/^# ?/, "", line) + i = index(line, "\xe2\x80\x94") # UTF-8 bytes for — (em-dash) + if (i > 0) { + rest = substr(line, i + 3) + sub(/^[[:space:]]+/, "", rest) + print rest + exit + } + } + !/^#/ { exit } + ' "$1" +} + +# Is this wrapper one of the thin pass-through wrappers whose own description is +# the generic boilerplate? If so we prefer the backing lib tool's description. +_is_boilerplate_desc() { + case "$1" in + *"direct, on-PATH wrapper for lib/"*) return 0 ;; + *) return 1 ;; + esac +} + +# Description of the backing lib tool (line 1 after its `# tool.sh — `). +_lib_desc() { + local tool="$1" f="$_LIB/$1.sh" + [ -n "$_LIB" ] && [ -f "$f" ] || return 1 + LC_ALL=C awk ' + /^#/ { + line = $0; sub(/^# ?/, "", line) + i = index(line, "\xe2\x80\x94") # UTF-8 bytes for — (em-dash) + if (i > 0) { + rest = substr(line, i + 3) + sub(/^[[:space:]]+/, "", rest) + print rest; exit + } + } + !/^#/ { exit } + ' "$f" +} + +# First indented `# …` example inside a wrapper's help block. The header +# line ("# — …") is excluded by requiring (a) at least TWO spaces after the +# `#` (real example blocks are indented `# …`) and (b) no em-dash on the +# line (the header carries the em-dash; example lines never do). +_wrapper_example() { + local cmd="$1" + LC_ALL=C awk -v cmd="$cmd" ' + NR==1 { next } + /^#/ { + raw = $0 + body = raw; sub(/^#/, "", body) # keep indentation + if (body !~ ("^[[:space:]][[:space:]]+" cmd "([[:space:]]|$)")) { next } + if (index(body, "\xe2\x80\x94") > 0) next # skip the header (has —) + sub(/^[[:space:]]+/, "", body) + sub(/[[:space:]]+#.*$/, "", body) # drop trailing "# comment" + print body; exit + } + !/^#/ { exit } + ' "$2" +} + +# First `# Usage:` form of the backing lib tool, rewritten to the bare command. +# e.g. " nc-find.sh --name PATTERN # …" -> "nc-find --name PATTERN" +_lib_example() { + local cmd="$1" tool="$2" f="$_LIB/$2.sh" + [ -n "$_LIB" ] && [ -f "$f" ] || return 1 + LC_ALL=C awk -v tool="$tool" -v cmd="$cmd" ' + /^#[[:space:]]*Usage:/ { inu=1; next } + inu && /^#/ { + line = $0; sub(/^#/, "", line) + if (line ~ /[^[:space:]]/) { + sub(/^[[:space:]]+/, "", line) + sub(/[[:space:]]+#.*$/, "", line) # drop trailing comment + if (line ~ ("^" tool "\\.sh")) { + sub("^" tool "\\.sh", cmd, line) # nc-find.sh … -> nc-find … + print line; exit + } + } + next + } + inu && !/^#/ { exit } + ' "$f" +} + +# --- gather the command list (skip helpers / sourced files / fetched bins) --- +_cmds=() +for _p in "$_BINDIR"/*; do + [ -f "$_p" ] && [ -x "$_p" ] || continue + _b="$(basename "$_p")" + case "$_b" in + _nc_common.sh|nc-completion.bash|cheat|jq|jq.exe) continue ;; + *.bash) continue ;; + esac + _cmds+=("$_b") +done + +# Sort for stable output. +IFS=$'\n' _cmds=($(printf '%s\n' "${_cmds[@]}" | sort)); unset IFS + +# --- build rows ------------------------------------------------------------- +_rows="" +for _c in "${_cmds[@]}"; do + if [ -n "$_filter" ] && ! printf '%s' "$_c" | grep -qiF "$_filter"; then + continue + fi + _wf="$_BINDIR/$_c" + + # description + _desc="$(_wrapper_desc "$_wf")" + if [ -z "$_desc" ] || _is_boilerplate_desc "$_desc"; then + _ld="$(_lib_desc "$_c")" + [ -n "$_ld" ] && _desc="$_ld" + fi + [ -z "$_desc" ] && _desc="(no description)" + # collapse whitespace, trim to one clause for the table + _desc="$(printf '%s' "$_desc" | tr -s '[:space:]' ' ' | sed 's/^ *//; s/ *$//')" + + # example + _ex="$(_wrapper_example "$_c" "$_wf")" + if [ -z "$_ex" ]; then + _ex="$(_lib_example "$_c" "$_c")" + fi + [ -z "$_ex" ] && _ex="$_c -h" + _ex="$(printf '%s' "$_ex" | tr -s '[:space:]' ' ' | sed 's/^ *//; s/ *$//')" + + _rows="${_rows}${_c}"$'\t'"${_desc}"$'\t'"${_ex}"$'\n' +done + +if [ -z "$_rows" ]; then + echo "cheat: no commands match '${_filter}'" >&2 + exit 1 +fi + +# --- render: aligned three-column table ------------------------------------- +printf 'Cloverleaf-Larry short commands (run -h for full help)\n\n' +printf '%s' "$_rows" | awk -F'\t' ' + { c[NR]=$1; d[NR]=$2; e[NR]=$3; n=NR; if (length($1)>w) w=length($1) } + END { + for (i=1;i<=n;i++) { + printf " %-*s %s\n", w, c[i], d[i] + printf " %-*s e.g. %s\n", w, "", e[i] + } + } +' diff --git a/install-larry.sh b/install-larry.sh index 1e5d057..0abc619 100755 --- a/install-larry.sh +++ b/install-larry.sh @@ -261,7 +261,7 @@ fetch lib/journal.sh "$LARRY_HOME/lib/journal.sh" # completion available immediately, before the first `larry` launch. fetch bin/_nc_common.sh "$LARRY_HOME/bin/_nc_common.sh" fetch bin/nc-completion.bash "$LARRY_HOME/bin/nc-completion.bash" -for _w in tbn tbp tbh tbpr where paths route_test \ +for _w in cheat tbn tbp tbh tbpr where paths route_test \ nc-parse nc-paths nc-find nc-inbound nc-status nc-engine nc-xlate nc-table \ nc-create-thread nc-set-field nc-insert-protocol nc-make-jump nc-provision-jumps \ nc-tclgen nc-document nc-revisions nc-diff-interface nc-smat-diff nc-regression \ diff --git a/larry.sh b/larry.sh index 4830ef2..0263e94 100755 --- a/larry.sh +++ b/larry.sh @@ -99,7 +99,7 @@ set -o pipefail # ───────────────────────────────────────────────────────────────────────────── # Config # ───────────────────────────────────────────────────────────────────────────── -LARRY_VERSION="0.9.4" +LARRY_VERSION="0.9.5" LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" # ─────────────────────────────────────────────────────────────────────────────