- 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 <filter> + 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 <noreply@anthropic.com>
Make the toolkit usable BY HAND without the `larry tools <name>` prefix.
- bin/ of thin wrappers (tbn/tbp/tbh/tbpr/where/paths/route_test + a full-name
passthrough per operator tool). Installer symlinks them into LARRY_BIN_DIR so
`tbn adt` runs directly. Each resolves lib/ via bin/_nc_common.sh
(LARRY_LIB_DIR -> ../lib -> $LARRY_HOME/lib) and execs the matching tool.
- -h/--help on every wrapper.
- bin/nc-completion.bash: dynamic bash completion, 3 levels (command / SITE /
THREAD) enumerated LIVE from the NetConfig tree under $HCIROOT via the same
lib/nc-parse.sh the tools use; cached per (HCIROOT, newest-NetConfig-mtime).
Installer appends a guarded source line to the user's bash rc.
- fixtures/integrator: durable 3-site demo (epic->ancout->codamx) with cross-
site fan-out + fan-in and a multi-route inbound. RESOLVES the v0.9.3 fixture
conflict: cross-site destination blocks are XS_*-prefixed so they never
collide with a local protocol name (a collision makes nc-paths
_xsite_down_targets suppress the cross-site hop, lib/nc-paths.sh:378).
- DEFERRED: fetch-token.sh broker wiring (broker contract still finalizing).
VERSION+LARRY_VERSION -> 0.9.4; MANIFEST regenerated (--check clean); bash -n
clean; verified live on .135 (short commands off PATH + all 3 completion levels).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3 of the Larry remote kill-switch (Pax design; Mack's broker on .135 LAN
8181 / Tailscale 100.86.16.114:8181). Deployed Larry no longer holds a long-lived
sk-ant-… key: it holds a per-deployment enrollment secret, mints a short-lived
token from the broker, and routes every LLM call THROUGH the broker /v1/messages
(real key injected server-side). set-authorized <id> false => the deployment 401s
and dies, no box access required.
- LARRY_AUTH_MODE=broker is the DEFAULT (was apikey). Self-update flips existing
installs to broker-mode too, so upgrading Gundersen delivers the kill-switch.
Escape hatch (documented, not default): LARRY_AUTH_MODE=apikey (no kill-switch,
never for PHI boxes).
- New lib/broker.sh: enroll+mint, fail-closed heartbeat, best-effort PHI wipe
(reuses uninstall-larry.sh's shred/overwrite secure-delete + LARRY_HOME guard).
- Fail-closed preflight at launch + in-REPL heartbeat (default 60s, 3-miss budget):
disabled => refuse to run (+ PHI wipe for profile:phi); unreachable past budget
=> refuse to run (NO wipe on a network blip — only an explicit disable wipes).
- call_api / call_api_stream broker branch: Bearer short-lived token, no x-api-key,
token never on disk.
- install-larry.sh enrollment provisioning: LARRY_DEPLOYMENT_ID + LARRY_ENROLL_SECRET
(+ LARRY_PROFILE/LARRY_BROKER_URL) baked 0600 + into the shim; box shows up in the
dashboard ready to toggle.
- /auth reports broker state.
Reachability (flagged for Bryan): the broker is LAN + Tailscale only (no public
route). Egress-restricted boxes reach it over Tailscale (default URL = tailnet).
A box that can reach neither fail-closes = won't run (correct kill, useless work
state) — such a box MUST run Tailscale, or Bryan must stand up a hardened public
broker ingress.
Bug fixed in test: _broker_json_field jq `// empty` rendered literal false as
empty, mis-classifying a DISABLED deployment as an unreachable MISS (delaying
fail-close + skipping the PHI wipe). Fixed to `if has($k) then .[$k] else "" end`.
Verified end-to-end against the live broker: enroll -> mint -> proxied call ->
disable -> instant 401 + heartbeat fail-close + 5 PHI files shredded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two operator-requested features:
1. `larry uninstall` / uninstall-larry.sh — there was no uninstaller before.
Reverses install-larry.sh exactly: removes $LARRY_HOME (bundle + bin/jq +
optional phi-venv + all runtime artifacts incl. log/headers.log, sessions,
journal, lessons, creds) and the `larry` PATH shim. DRY-RUN by default;
--yes to delete, --keep-data to preserve user data. Removes ONLY what the
installer created (shim removed only if it carries our auto-gen header;
shell rc / Cloverleaf sites / $HCIROOT never touched). Stops running PHI
sidecar / tunnel via their own pidfiles. Shipped by the installer +
manifest-synced; dispatched early like `larry tools` so it works offline.
2. --no-api (env LARRY_NO_API=1) — deterministic-only mode making ZERO LLM API
calls (zero cost). REPL + all local/deterministic commands still work; a
free-text prompt is routed to the matching `larry tools <name>` instead of
the model. No API key required (first-run auth prompt skipped). call_api /
call_api_stream hard-refuse as defense in depth.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Hardens the installer + auto-updater against the Gitea private-repo trap
(Clover #5 diagnosis): an unauthenticated raw-file read of a sign-in-gated
Gitea returns the HTML Sign-In page at HTTP 200, which `curl -fsSL` treats as
success — so the old code parsed HTML as VERSION/MANIFEST/larry.sh content and
silently aborted (or overwrote real files with HTML). This stranded a work-box
at v0.7.3 until the REQUIRE_SIGNIN_VIEW=false flip.
- New lib/fetch-safe.sh: fetch_validate URL DEST KIND [MAX_TIME]. Detects the
HTML-login trap (DOCTYPE/<html/"Sign In - Gitea"/<title>Sign In markers, or
text/html Content-Type) and validates content shape per file type (semver
VERSION, path-list MANIFEST, shebang larry.sh, non-HTML .sh). On failure:
actionable error + non-zero, target file left untouched.
- install-larry.sh (curl|bash bootstrap) and larry.sh self_update() each carry
a byte-identical inline copy (both run before lib/ can be sourced).
- Every remote-content fetch routed through the validator: install fetch();
agent fetch; sync_from_manifest MANIFEST + per-file; _fetch_with_fallback.
- Optional LARRY_GITEA_TOKEN / GITEA_TOKEN env var adds Authorization: token
<PAT> for authenticated fetch against private repos. Never hardcoded/logged.
Documented in --help + MANUAL.md.
Co-Authored-By: Clover (Claude Opus 4.7) <noreply@anthropic.com>
The only path that closes V1 (free-text PHI gap — the dominant real-world
failure mode per Vera). Opt-in install; larry runs in v0.8.1 mode on hosts
without Presidio (MobaXterm/Cygwin per Bryan's accepted tradeoff).
New files:
- lib/phi-presidio-sidecar.py — FastAPI service on 127.0.0.1:$LARRY_PHI_PORT
(default 41189). Presidio AnalyzerEngine + AnonymizerEngine over spaCy
en_core_web_sm + 3 HL7-specific custom recognizers (HL7_MRN, HL7_CARET_NAME,
HL7_PHONE_BARE). POST /redact and GET /health.
- lib/phi-sidecar.sh — lifecycle (start/stop/status/health/ensure). ensure
is idempotent; called backgrounded from main_loop so it never blocks the
first prompt. Honors LARRY_PHI_VENV.
- lib/phi-client.sh — bash client (phi_client_available / phi_redact_text /
phi_redact_entities). CR-safe; 5s timeout bounds tier-5 stall.
larry.sh:
- auto_detect_phi gains tier-5: after tiers 1-4, before status summary,
source phi-client.sh, run Presidio on a token-masked copy of the input,
tokenize each entity through hl7-sanitize.sh tokenize-value (category
presidio_<TYPE>) so token IDs stay stable. Honors confirm + strict modes.
Removed the v0.7.3 early-return that skipped past tier-5 when tiers 1-4
found nothing — pure prose now always reaches tier-5.
- Token-safe substitution: existing [[...]] tokens are pulled to sentinels,
tier-5 value is replaced, sentinels restored — prevents the token-within-
token corruption that naive literal-replace caused on already-tokenized
text. Acronym guard drops HL7/clinical jargon (SSN/MRN/DOB/ADT) Presidio
over-tags as ORGANIZATION.
- Graceful degradation: sidecar unreachable → tier-5 no-ops with a one-time
stderr warning. /phi-sidecar slash command + completion table.
install-larry.sh:
- Probes python3 3.9+; offers to create $LARRY_HOME/phi-venv and install
presidio + fastapi + uvicorn + en_core_web_sm. Skips silently (with a
v0.8.1-mode note) on Cygwin/MobaXterm without python3, and on
non-interactive pipe installs. Sets LARRY_PHI_VENV in the larry shim.
MANIFEST: three new lib files added for auto-sync.
Prototype validation (Bryan's Mac, Apple Silicon, Python 3.14):
cold start (en_core_web_sm): ~9s (vs ~82s if Presidio auto-grabs _lg;
we pin _sm for the REPL budget)
warm analyzer latency: P50 20.6ms / P95 22.7ms
end-to-end HTTP round-trip: ~57ms warm; ~150ms first-post-startup
All comfortably under the 200ms-per-turn budget.
MobaXterm verdict: v0.8.2 is Mac/Linux-only. MobaXterm stays on v0.8.1 +
nudges, per Bryan's explicit acceptance. install-larry.sh enforces this
by platform detection; larry.sh tier-5 silently no-ops when the sidecar
is absent (which IS the MobaXterm path — no code is platform-gated).
Verification: bash -n clean on larry.sh + all 3 new lib scripts; python3
ast.parse clean on the sidecar; end-to-end tier-5 tested live against the
sidecar (pure prose, rule-pack+tier-5 combined with no token corruption,
!nophi bypass); strict-mode fail-closed abort tested; CR-taint, path-block,
and base64 round-trip batteries re-run green.
Co-Authored-By: Clover (Claude Opus 4.7) <noreply@anthropic.com>
The v0.7.2 GitHub fallback is now functionally broken: the GitHub mirror
is being made private, so anonymous raw fetches return 401/403. Rather
than ship a silent-failure path to a dead URL, remove the fallback
entirely.
Changes:
larry.sh
- LARRY_BASE_URL_FALLBACK / LARRY_ORIGIN_DEFAULT_GITHUB removed
- sync_from_manifest_with_fallback and _fetch_with_fallback retained
by name (call-site compat) but are now single-source wrappers; on
origin unreachable they warn "auto-update skipped this launch" and
proceed with locally cached files (no crash)
- status-line _origin_badge collapses to "" (default) or "custom"
(user-pinned HTTPS URL); legacy github / fallback badges gone
- /origin slash command simplified:
/origin show effective origin + pin file
/origin gitea pin to the default Gitea URL
/origin auto clear the pin
/origin <https-url> pin to an arbitrary HTTPS mirror
/origin github returns a clear error (mirror is private)
- /help text updated to reflect single-source model
- LARRY_VERSION 0.7.3 -> 0.7.4
install-larry.sh
- LARRY_BASE_URL_FALLBACK removed; single-origin install path
- fetch() dies with a clear error when origin unreachable:
"install failed: cannot reach LARRY_BASE_URL=... — verify the URL
or set LARRY_BASE_URL to a reachable mirror"
- post-install fallback-warning block removed
VERSION: 0.7.3 -> 0.7.4
Migration: stale $LARRY_HOME/.origin files containing the legacy keyword
"github" are treated as invalid — Larry warns once at startup and reverts
to the default Gitea origin. We deliberately do NOT auto-rewrite the
file (so the user can choose) and do NOT translate it to the GitHub raw
URL (which would just 401 on the next fetch).
Verification:
- bash -n larry.sh / install-larry.sh: pass
- larry --version: prints 0.7.4
- LARRY_BASE_URL=https://invalid.example.invalid + stale .last-sync-version:
logs "warn: ... unreachable, auto-update skipped this launch", no crash
- /origin auto clears pin file; /origin (no arg) shows current effective
origin and pin file; /origin <https-url> persisted; /origin github
returns clear error; /origin gitea re-pins to default
- stale .origin containing "github" -> startup warn + revert to default
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Switches the canonical $LARRY_BASE_URL default from raw.githubusercontent.com
to the self-hosted Gitea mirror at git.bjnoela.com. GitHub stays in the
loop as $LARRY_BASE_URL_FALLBACK and is used automatically when the primary
fails (DNS, timeout, HTTP error, private repo).
What's new
- Origin defaults split into LARRY_BASE_URL (Gitea) +
LARRY_BASE_URL_FALLBACK (GitHub). Env vars still override either side.
- Every network call in self_update tries primary first, then fallback.
Emits "warn: gitea unreachable, falling back to github" on switch and
"warn: self-update skipped (both origins unreachable)" if both fail.
- New /origin slash-command family:
/origin — show current primary/fallback + which served last
/origin gitea — pin to Gitea (default state)
/origin github — swap so GitHub is primary, Gitea fallback
/origin auto — clear pin, revert to defaults
/origin <https://...> — pin to an arbitrary HTTPS base URL
Pin is persisted to $LARRY_HOME/.origin and re-read on next launch.
- Status line picks up a light origin badge when state is non-default
("github" pinned, "custom" pinned, or "gitea→github" on failover).
- install-larry.sh mirrors the same primary→fallback fetch logic so
first-contact installs still work even if Gitea is unreachable.
ACTION REQUIRED — Bryan, before this commit's auto-update path becomes
live you must set git.bjnoela.com/bryan/cloverleaf-larry repo visibility
to PUBLIC. Gitea defaults to private; until you toggle it, every client
will silently fall back to GitHub. Verify by running, from any box:
curl -fsSI https://git.bjnoela.com/bryan/cloverleaf-larry/raw/branch/main/VERSION
A 200 with the published VERSION means clients hit Gitea; a 404/403 means
they still ride the GitHub fallback.
Don't break
- v0.7.1 status-line position (between turns)
- v0.7.0 HL7 completion, mouse mode
- v0.6.9 status line state tracking, header capture
- v0.6.7 streaming, @file, slash completion, persistent history
- v0.6.6 CR-strip + slash TAB
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two REPL enhancements:
1. HL7 v2.x inline tab completion. Type a segment ID or SEG.field or
SEG.field.component in any prompt and TAB completes against a built-in
schema (18 segments fully fielded: MSH, PID, PV1, PV2, EVN, MSA, ERR,
NK1, GT1, IN1, IN2, OBR, OBX, ORC, AL1, DG1, PR1, ROL; component
breakdowns for MSH.9, PID.3, PID.5, PID.11, PV1.3, NK1.4, OBX.3, IN1.4).
New slash commands /hl7 <SEG> and /hl7-fields <SEG.N> print schema
without typing. Z-segments get a "site-specific" hint instead of a
guess. Exact-match wins over prefix siblings (PID.3 completes over
PID.30; MSH completes over MSH+MSA).
2. Mouse mode. /mouse on|off and LARRY_NO_MOUSE env kill switch enable
bracketed-paste + SGR mouse reporting (mode 1006). Click-to-position
cursor in the input line is intentionally NOT implemented in this
pass — it requires per-terminal escape parsing inside bind -x which
is not reliable across iTerm2 / macOS Terminal / MobaXterm / Cygwin
in a single pass. Documented as terminal-dependent.
New file: lib/hl7-schema.sh (sourced; bash assoc arrays for the segment
+field+component tables, plus helpers hl7_segments / hl7_fields_for /
hl7_components_for / hl7_field_name).
MANIFEST + install-larry.sh updated to fetch the new lib file on
install/self-update.
Regression-safe: v0.6.9 status line, slash completion, @file completion,
streaming SSE, header capture, and all 37 prior slash commands are
unchanged. Added 3 new slash commands (/hl7, /hl7-fields, /mouse).
Verification: 15/15 automated checks on the three completion paths
(segment, field, component) — including mid-buffer completion and
exact-match preference.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
NEW lib/ssh-helper.sh implements the full SSH command surface:
hosts/list show configured remote hosts
add <alias> <user@host[:port]> register a new host
remove <alias> remove + clean cred + socket
pass <alias> set/update password (hidden interactive)
setup <alias> open long-lived ControlMaster
close <alias> close ControlMaster
status [alias] show open masters + cred presence
exec <alias> <command...> run command via master
Architecture:
• $LARRY_HOME/.ssh-hosts.tsv — alias \t user@host \t port (3-col)
• $LARRY_HOME/.ssh-creds/<alias> — raw password, mode 0600
• $LARRY_HOME/.ssh-sockets/<alias>.sock — ControlMaster socket
The password is read from disk by sshpass via -f (file argument), so it
never lands in argv or environment. It is used ONCE to open the master;
all subsequent execs multiplex through the socket with no auth. Daily-
rotating passwords: just overwrite the cred file and re-run setup.
SLASH COMMANDS wired in larry.sh REPL: /ssh-hosts /ssh-add /ssh-remove
/ssh-pass /ssh-setup /ssh-close /ssh-status /ssh <alias> <cmd>.
LARRY TOOLS exposed to the LLM:
ssh_status — list aliases + open-master state
ssh_exec — run command on remote via the master socket
Both tool descriptions explicitly tell Larry the password is unreachable
and to ask Bryan to run /ssh-setup if a master is closed. Tool inputs
and outputs never contain the password. Output capped at max_lines
(default 500) with a "[ssh_exec: exit rc=N]" footer.
Bundle updated: MANIFEST + install-larry.sh both now include
lib/ssh-helper.sh. Auto-update will pull it on next launch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Five small Unix-style loop & format helpers, fully offline:
lib/each.sh
- replaces v1 `each`
- run a CMD per item: args, stdin lines, or {}-placeholder substitution
- example: tbn adt | awk '{print $2}' | each.sh 'route_test {}'
lib/each-site.sh
- replaces v1 each_site / each_site_hdr / each_site_tcl patterns
- iterates every site under $HCIROOT with HCISITE/HCISITEDIR auto-exported
- --filter REGEX limits which sites; --hdr prints a header before each
lib/len2nl.sh
- replaces v1 `len2nl`
- strict superset: handles length-prefixed (digits before MSH),
MLLP (\x0b...\x1c\x0d), and segment CRs (→ LF)
- works as stdin filter or with file arg
lib/csv-to-table.sh
- 2-column CSV → Cloverleaf .tbl format
- emits proper prologue (who, date, bidir, type, version)
- --has-header --default VALUE --bidir 0|1 --in-delim CHAR --user NAME --out PATH
lib/table-to-csv.sh
- reverse: .tbl → CSV
- --with-header --delim CHAR --include-meta
- confirmed clean round-trip: CSV → table → CSV byte-identical for the data rows
All 5 are pipeable, have --help, zero external deps beyond bash+awk+sed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bryan's ask: use Larry on prod data without PHI ever leaving the client box.
Added:
lib/hl7-sanitize.sh — tokenize PHI fields in HL7 messages
lib/hl7-desanitize.sh — reverse op (local view-time unmask)
Tokenization model:
- Replace PHI fields with [[CATEGORY_NNNN]] tokens (MRN, NAME, DOB,
ADDR, PHONE, ACCT, SSN, PROV, VISIT, etc.)
- Same value → same token across messages (deterministic via local
lookup table; analysis can still correlate patients).
- Lookup table at $LARRY_HOME/sanitize/lookup.tsv mode 0600 — never
leaves the client.
- Default PHI rule set covers PID, PV1, NK1, GT1, IN1, OBR, OBX,
DG1, ORC; --rules-file to extend.
- --strict also tokenizes unknown Z segments wholesale.
Prompt-side preprocessing in larry.sh:
- {{phi:VALUE}} inline marker, auto-category lookup
- {{phi:CATEGORY:VALUE}} explicit category
- Replaced with the token BEFORE the user input enters conversation
history. The original never reaches the API.
- Local feedback "phi> {{phi:...}} → [[TOKEN]]" printed to terminal only.
New REPL slash commands:
/phi <value> tokenize a single value, print the token
/unmask <token> show original (local terminal only, never API)
/tokens show full PHI ↔ token lookup table
New tools in larry.sh schema:
hl7_sanitize agent can sanitize a file before reading PHI
tokenize-value / detokenize-value (subcommands of hl7-sanitize.sh)
Persona update (agents/larry.md):
- Documented PHI mode and rules for proactive sanitize-first behavior
MANUAL.md updated with the full PHI section including limitations.
Brings total native tools to 29.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bryan's pivot: until bjnoela.com is back online, transfer learnings via
local file capture on the client + manual paste-back to home-Larry. NO
credentials required on the client box.
Capture flow:
- lib/lessons.sh records lessons to $LARRY_HOME/lessons/<date>.md
- lesson_record tool in larry.sh lets the agent record proactively
- /lesson, /lessons, /export REPL commands
- agents/larry.md updated: capture corrections, conventions, quirks
silently when Bryan teaches them
Export flow:
- lessons.sh export | bundle | --gh-issue (uses gh CLI if available)
- Bryan pastes the bundle to home-Larry on his dev machine
- home-Larry commits the refinement into cloverleaf-larry/agents/
- next launch on any client pulls updated persona via self-update
Brings total native tools to 28.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two additions:
1. OAuth subscription auth (lib/oauth.sh + larry-auth.sh)
- PKCE-based out-of-band flow against Claude.ai (no localhost server
needed; works behind any firewall).
- Uses the same client_id Claude Code uses, so calls bill against your
Max/Pro subscription quota instead of pay-as-you-go API metering.
- Tokens stored at $LARRY_HOME/.oauth.json (mode 0600), auto-refresh.
- larry.sh now detects oauth file at startup and uses Bearer auth.
- First-run flow now offers OAuth or API key; /login, /logout, /auth
slash commands in the REPL.
- Transparent fallback to API key if OAuth flow fails.
2. MANUAL.md — offline tool cheat sheet
- Documents every lib/*.sh script with copy-paste examples.
- Bryan's backup plan: when Anthropic is unreachable (no internet, on
a plane, etc.), all the underlying tools work standalone from the
shell. Larry just sequences them; they do not need Larry to run.
- Quick-recipe table at the bottom for the common day-to-day asks.
Files added:
- lib/oauth.sh
- larry-auth.sh
- MANUAL.md
Files modified:
- larry.sh — auth-mode detection, /auth /login /logout commands
- install-larry.sh — fetch new files
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>