cloverleaf-larry/MANUAL.md
Bryan Johnson 61f1500492 v0.3.1: OAuth subscription auth + offline manual cheat sheet
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>
2026-05-26 09:57:44 -07:00

18 KiB

Larry-Anywhere — Manual Tool Cheat Sheet (no-Larry / offline mode)

Every Larry-Anywhere capability is also a standalone bash script under lib/. When the internet is down, you're on a plane, or the Anthropic API is unreachable, you can drive these tools directly from the shell. Larry just sequences them — the tools work without Larry.

This page documents every command with copy-paste examples. Print it.


Conventions

  • $LARRY_HOME defaults to ~/.larry/. Lib scripts live at $LARRY_HOME/lib/.
  • For brevity, examples below use lib/<tool>.sh. From $LARRY_HOME/, that's ./lib/<tool>.sh. Or add $LARRY_HOME/lib to your PATH.
  • $HCIROOT = Cloverleaf install root (e.g. /opt/cloverleaf/cis2025/integrator).
  • $HCISITE = current site name (e.g. adt).
  • $HCISITEDIR = $HCIROOT/$HCISITE.

Set these before running anything site-specific:

export HCIROOT=/opt/cloverleaf/cis2025/integrator
export HCISITE=adt
export HCISITEDIR="$HCIROOT/$HCISITE"

Authentication (larry-auth.sh, lib/oauth.sh)

Only needed if you're running the Larry REPL (larry.sh). The lib/ tools themselves never call Anthropic — they're pure local bash.

larry-auth.sh login              # OAuth via Claude.ai subscription
larry-auth.sh status             # show current auth state + expiry
larry-auth.sh refresh            # force-refresh the access token
larry-auth.sh logout             # delete tokens (revert to API key)

Falling back to API key: edit $LARRY_HOME/.env with ANTHROPIC_API_KEY=sk-ant-..., chmod 600. Larry uses API key whenever $LARRY_HOME/.oauth.json is absent.


NetConfig parsing — read (lib/nc-parse.sh)

The foundational reader. Every other NetConfig tool calls this.

# List every protocol (thread) in a NetConfig file
lib/nc-parse.sh list-protocols "$HCISITEDIR/NetConfig"

# List every process
lib/nc-parse.sh list-processes "$HCISITEDIR/NetConfig"

# Find the line where a thread is declared
lib/nc-parse.sh protocol-line "$HCISITEDIR/NetConfig" ADTto_3m
#   → 488

# Get the FULL TCL block for one protocol
lib/nc-parse.sh protocol-block "$HCISITEDIR/NetConfig" IB_ADT_muxS

# Extract a top-level field value
lib/nc-parse.sh protocol-field "$HCISITEDIR/NetConfig" IB_ADT_muxS PROCESSNAME
#   → ADT
lib/nc-parse.sh protocol-field "$HCISITEDIR/NetConfig" IB_ADT_muxS OBWORKASIB
#   → 1

# Drill into nested blocks via dotted path — HOST/PORT/TYPE/ISSERVER live inside PROTOCOL{}
lib/nc-parse.sh protocol-nested "$HCISITEDIR/NetConfig" ADTto_3m PROTOCOL.PORT
#   → 51006
lib/nc-parse.sh protocol-nested "$HCISITEDIR/NetConfig" ADTto_3m PROTOCOL.HOST
#   → SHD360ENCINT02T
lib/nc-parse.sh protocol-nested "$HCISITEDIR/NetConfig" ORU_fr_OPACS PROTOCOL.ISSERVER
#   → 1 (it's a TCP listener)

# TSV summary of every protocol — direction, port, host, type at a glance
lib/nc-parse.sh protocol-summary "$HCISITEDIR/NetConfig"
lib/nc-parse.sh protocol-summary "$HCISITEDIR/NetConfig" --filter adt   # only ADT-ish names

# Routing destinations (what does X route to?)
lib/nc-parse.sh destinations "$HCISITEDIR/NetConfig" IB_ADT_muxS

# Routing sources (what routes INTO X?) — inverse
lib/nc-parse.sh sources "$HCISITEDIR/NetConfig" ADTto_CodaMetrix

# Xlate files referenced (one protocol, or all)
lib/nc-parse.sh xlate-refs "$HCISITEDIR/NetConfig" IB_ADT_muxS
lib/nc-parse.sh xlate-refs "$HCISITEDIR/NetConfig"        # all in file

# TCL procs referenced
lib/nc-parse.sh tclproc-refs "$HCISITEDIR/NetConfig" IB_ADT_muxS

# Get just the DATAXLATE routing block (the heart of routing config)
lib/nc-parse.sh route-block "$HCISITEDIR/NetConfig" IB_ADT_muxS

Inbound thread classifier (lib/nc-inbound.sh)

Identifies inbound threads — TCP listeners directly fed by upstream clients (Epic etc.), or ICL/file inbounds fed via Cloverleaf's internal link.

# Every inbound thread (both classes), table format
lib/nc-inbound.sh "$HCISITEDIR/NetConfig" --format table

# Just real TCP listeners (the "directly fed by upstream" subset)
lib/nc-inbound.sh "$HCISITEDIR/NetConfig" --mode tcp-listen --format table

# Just ICL/file inbounds
lib/nc-inbound.sh "$HCISITEDIR/NetConfig" --mode icl-or-file --format table

# JSONL for piping into other tools
lib/nc-inbound.sh "$HCISITEDIR/NetConfig" --mode tcp-listen --format jsonl

Cross-site finder (lib/nc-find.sh) — the v1 tbn/tbp/tbh/tbpr/where replacements

Walks every NetConfig under $HCIROOT (or a passed list) and returns matches.

# tbn equivalent: partial name match
lib/nc-find.sh --name adt --format table

# tbp equivalent: exact port
lib/nc-find.sh --port 51204 --format table

# tbh equivalent: substring on host
lib/nc-find.sh --host SHD360 --format table

# tbpr equivalent: substring on PROCESSNAME
lib/nc-find.sh --process codametrix --format table

# v1 `<thread> where` — locate a thread across all sites
lib/nc-find.sh --where IB_ADT_muxS --format table

# Threads referencing a specific xlate file
lib/nc-find.sh --xlate Epic_ADT_CodaMetrix --format table

# Threads referencing a specific TCL proc
lib/nc-find.sh --tclproc trxId_IB_ADT_muxS --format table

# Override HCIROOT or pass explicit netconfigs
lib/nc-find.sh --name adt --hciroot /other/install/integrator --format table
lib/nc-find.sh --name adt --netconfigs "/a/NetConfig:/b/NetConfig" --format jsonl

Message search — smat queries (lib/nc-msgs.sh)

Smat databases are SQLite 3. Reads via native sqlite3 -ascii — no Cloverleaf binary involved.

# Count messages in a thread's smat
lib/nc-msgs.sh ADTto_3m --format count

# Recent 5 messages (text format with metadata + parsed segments)
lib/nc-msgs.sh ADTto_3m --limit 5 --format text

# Time range — supports human expressions
lib/nc-msgs.sh ADTto_3m --after "3 days ago" --format count
lib/nc-msgs.sh ADTto_3m --after "2026-05-20" --before "2026-05-26 12:00:00"

# Filter by HL7 field — find messages where PID.3 equals an MRN
lib/nc-msgs.sh ADTto_3m --field PID.3=5720501458 --limit 20 --format text

# Account-number search at PID.18
lib/nc-msgs.sh ADTto_3m --field PID.18=623000286 --format text

# JSON output for piping
lib/nc-msgs.sh ADTto_3m --field PID.3=5720501458 --format json | jq

# Explicit smatdb path (skip auto-locate)
lib/nc-msgs.sh ADTto_3m --db "$HCISITEDIR/exec/processes/3M/ADTto_3m.smatdb" --format count

# Raw format (for piping into route-test inputs) — messages separated by 0x1c
lib/nc-msgs.sh ADTto_3m --limit 10 --format raw > inputs.msgs

HL7 field extraction (lib/hl7-field.sh)

Extract specific fields from a single HL7 message.

# Read message from file, extract MRN
lib/hl7-field.sh PID.3 /path/to/message.hl7
#   → 5720501458

# From stdin
cat msg.hl7 | lib/hl7-field.sh MSH.10
#   → 27175  (message control ID)

# Component extraction
lib/hl7-field.sh MSH.9 msg.hl7        # → ADT^A08
lib/hl7-field.sh MSH.9.1 msg.hl7      # → ADT
lib/hl7-field.sh MSH.9.2 msg.hl7      # → A08

# Patient name (whole field + components)
lib/hl7-field.sh PID.5 msg.hl7        # → MORRIS^SALLY^^^^^^LHS^^^^^LEH^M
lib/hl7-field.sh PID.5.1 msg.hl7      # → MORRIS  (family name)
lib/hl7-field.sh PID.5.2 msg.hl7      # → SALLY   (given name)

# Pipe smat dump → extract → sort | uniq
lib/nc-msgs.sh ADTto_3m --limit 100 --format raw \
  | awk -v RS=$'\x1c' '{print $0 > "/tmp/m"NR; system("lib/hl7-field.sh PID.3 /tmp/m"NR)}'

HL7-aware diff (lib/hl7-diff.sh)

Compare two HL7 files (or multi-message dumps) with field-level normalization.

# Default — ignores MSH.7 (timestamp); shows everything else
lib/hl7-diff.sh left.hl7 right.hl7

# Add more fields to ignore
lib/hl7-diff.sh --ignore "MSH.7,MSH.10,EVN.6" left.hl7 right.hl7

# Inverse: ONLY compare specific fields
lib/hl7-diff.sh --include-fields "PID.3,PID.18,MSH.9" left.hl7 right.hl7

# Just count the differences
lib/hl7-diff.sh --format count left.hl7 right.hl7
#   → 5

# TSV output for parsing
lib/hl7-diff.sh --format tsv left.hl7 right.hl7
# columns: msg_idx \t field_path \t left_value \t right_value

Jump thread generation (lib/nc-make-jump.sh)

Generates the 3-thread cross-environment data-replay pattern: linux_<tag>_out on OLD, windows_<tag>_in + windows_<tag>_out in NEW's server_jump site. Output is plain TCL text — no file writes.

# Generate for one inbound, target new linux host:port, output to stdout
lib/nc-make-jump.sh "$HCISITEDIR/NetConfig" \
  --inbound ORU_fr_OPACS \
  --new-host newlinux01.test \
  --jump-port 61204

# Write each artifact to separate files
lib/nc-make-jump.sh "$HCISITEDIR/NetConfig" \
  --inbound ORU_fr_OPACS \
  --new-host newlinux01.test \
  --jump-port 61204 \
  --out-prefix /tmp/oru_jump
# Produces:
#   /tmp/oru_jump.old_out.tcl       — paste into OLD env NetConfig
#   /tmp/oru_jump.new_in.tcl        — paste into NEW server_jump NetConfig
#   /tmp/oru_jump.new_out.tcl       — paste into NEW server_jump NetConfig
#   /tmp/oru_jump.route_add.tcl     — splice into OLD inbound's DATAXLATE

# Override defaults
lib/nc-make-jump.sh "$HCISITEDIR/NetConfig" \
  --inbound ORU_fr_OPACS --new-host newlinux01 --jump-port 61204 \
  --inbound-host 10.0.0.5            # NEW-side outbound dials this instead of 127.0.0.1
  --process-jump migration_jump      # different process on NEW than default "server_jump"
  --encoding UTF8                    # override if not ASCII

NetConfig modification — journaled writes (lib/nc-insert-protocol.sh)

Inserts new protocol blocks and splices route entries. Every write is journaled — backup, diff, atomic replace.

# Insert a new protocol at end of file
lib/nc-insert-protocol.sh insert "$HCISITEDIR/NetConfig" /tmp/oru_jump.old_out.tcl
# →  journal entry: 2026-05-26-09xxxx/001_NetConfig
#    rollback: larry-rollback.sh --entry 2026-05-26-09xxxx/001_NetConfig

# Insert before/after a named anchor protocol
lib/nc-insert-protocol.sh insert "$HCISITEDIR/NetConfig" /tmp/new_block.tcl --mode after --anchor IB_ADT_muxS
lib/nc-insert-protocol.sh insert "$HCISITEDIR/NetConfig" /tmp/new_block.tcl --mode before --anchor ADTto_3m

# Splice a route entry into an existing protocol's DATAXLATE block
lib/nc-insert-protocol.sh add-route "$HCISITEDIR/NetConfig" ORU_fr_OPACS /tmp/oru_jump.route_add.tcl

After any write, see what changed:

larry-rollback.sh --list                                 # all journal entries
larry-rollback.sh --list --session <session-id>          # one session
cat "$LARRY_HOME/journal/<session>/manifest.md"           # human-readable summary
cat "$LARRY_HOME/journal/<session>/files/NNN_*.diff"      # the unified diff

Roll back:

larry-rollback.sh --target "$HCISITEDIR/NetConfig"       # all changes to this file, newest first
larry-rollback.sh --session 2026-05-26-09xxxx --yes      # whole session, no prompt
larry-rollback.sh --last 1                                # just the most recent write
larry-rollback.sh --entry 2026-05-26-09xxxx/001_NetConfig # one specific entry
larry-rollback.sh --dry-run --session ...                 # preview without changing anything

Pre-rollback copies land at <target>.larry-prerollback.<unix-ts> so you can redo.


System documentation (lib/nc-document.sh)

Walks every NetConfig under $HCIROOT, finds threads matching a pattern, composes a markdown knowledge entry.

# Auto-derived doc to stdout
lib/nc-document.sh --name codametrix

# Write to a file with context fields
lib/nc-document.sh --name codametrix \
  --out "$LARRY_HOME/knowledge/codametrix.md" \
  --title "CodaMetrix Coding System" \
  --status "production" \
  --poc-vendor "John Doe at CodaMetrix, jdoe@codametrix.com" \
  --poc-internal "Sarah Smith, Integration Team" \
  --escalation "Page #integration-oncall in Slack" \
  --open-items "- Renewal Q3 2026" \
  --notes "Lives in epic site mostly; 1 thread in ancout"

# Different scope sources
lib/nc-document.sh --name "3M" --hciroot /other/integrator --out /tmp/3m.md
lib/nc-document.sh --name epic_adt --netconfigs "$HCIROOT/epic/NetConfig:$HCIROOT/ancout/NetConfig"

Output: a complete markdown doc with cluster threads, sources, destinations, xlates, tclprocs, plus the placeholder context sections for the team.


Interface diff (lib/nc-diff-interface.sh)

Compares one interface (and connected threads) between two NetConfigs.

# Diff ADTto_3m + 1 hop of connected threads between test and prod
lib/nc-diff-interface.sh \
  --interface ADTto_3m \
  --left  /test/integrator/ancout/NetConfig \
  --right /prod/integrator/ancout/NetConfig \
  --left-label TEST --right-label PROD \
  --depth 1 \
  --out /tmp/adt_diff.md

# Walk further out
lib/nc-diff-interface.sh --interface ADTto_3m \
  --left  /test/integrator/ancout/NetConfig \
  --right /prod/integrator/ancout/NetConfig \
  --depth 3 \
  --out /tmp/adt_chain_diff.md

# Include table file diffs too (.tbl referenced by xlates/tclprocs)
lib/nc-diff-interface.sh --interface ADTto_3m ... --include-tables

Output: markdown report with cluster overview, per-thread protocol-block diff, per-xlate file diff, per-tclproc file diff.


Regression testing — end to end (lib/nc-regression.sh)

Full Example 6 orchestrator. Six phases: discover → sample → route-test A → route-test B → diff → summary.

# Phase-by-phase walk through (Bryan's house pattern)

# Phase 1+2: discover inbounds and sample messages from env-A only
lib/nc-regression.sh \
  --scope site \
  --env-a /opt/cloverleaf/test/integrator    --site-a adt \
  --env-b /opt/cloverleaf/prod/integrator    --site-b adt \
  --out   /tmp/reg-2026-05-26 \
  --count 10 \
  --phase 2

# Inspect what would be sampled (dry-run)
lib/nc-regression.sh --scope site --env-a /test --env-b /prod \
  --site-a adt --site-b adt --out /tmp/reg --count 10 --phase 2 --dry-run

# Full run — needs Cloverleaf route_test command supplied:
lib/nc-regression.sh \
  --scope site --site-a adt --site-b adt \
  --env-a /opt/cloverleaf/test/integrator \
  --env-b /opt/cloverleaf/prod/integrator \
  --out /tmp/reg-2026-05-26 \
  --count 10 \
  --route-test-cmd 'cd {HCIROOT}/{HCISITE} && . ./.profile && {THREAD} route_test {INPUT} && cp *.out.* {OUTPUT_DIR}/' \
  --phase all

# Just diff existing outputs (you ran route_test manually before)
lib/nc-regression.sh --scope site --site-a adt --site-b adt \
  --env-a /opt/cloverleaf/test/integrator \
  --env-b /opt/cloverleaf/prod/integrator \
  --out /tmp/reg-2026-05-26 \
  --phase 5

# Other scopes
--scope thread:ADTto_3m                      # one thread
--scope threads:ADTto_3m,MFNto_3m,DFTto_3m   # specific list
--scope server                               # every inbound in every site under HCIROOT

Output tree:

/tmp/reg-2026-05-26/
├── inbounds.txt                       # the scope
├── inputs/<thread>.msgs               # sampled inputs (1 per inbound)
├── outputs/env-a/<thread>/<dest>...   # env-A route_test outputs
├── outputs/env-b/<thread>/<dest>...   # env-B route_test outputs (using same inputs)
├── diff/<thread>.<dest>.md            # per-pair hl7_diff report
├── diff/_index.md                     # diff summary table
└── regression-summary.md              # master report

Tip: if env-B is remote, pass --env-b-host <host> --env-b-user <user> and Phase 4 scp's inputs over before invoking route_test there. Or run on env-B separately and skip Phase 4 with --phase 5 for diff-only.


Reverse SSH tunnel (larry-tunnel.sh)

If you want a home Larry to SSH into the client box (when client → outbound SSH is allowed):

# Zero-config: serveo.net (third-party, NOT for sensitive sessions)
larry-tunnel.sh --serveo

# Your own hop (needs hop sshd configured with GatewayPorts)
LARRY_HOP_USER=larry-tunnel \
LARRY_HOP_HOST=bjnoela.com \
LARRY_HOP_KEY=~/.ssh/id_ed25519 \
  larry-tunnel.sh

# Inspect / stop
larry-tunnel.sh --status
larry-tunnel.sh --stop

Quick recipe: "I have to do X without internet"

Task Command
"what threads are here?" lib/nc-parse.sh list-protocols $HCISITEDIR/NetConfig
"find threads named 3m" lib/nc-find.sh --name 3m --format table
"where does d_foo live?" lib/nc-find.sh --where d_foo --format table
"what feeds d_foo?" lib/nc-parse.sh sources $HCISITEDIR/NetConfig d_foo
"what does d_foo route to?" lib/nc-parse.sh destinations $HCISITEDIR/NetConfig d_foo
"what xlates does d_foo use?" lib/nc-parse.sh xlate-refs $HCISITEDIR/NetConfig d_foo
"find messages for MRN X" lib/nc-msgs.sh d_foo --field PID.3=X --format text
"diff this interface across two envs" lib/nc-diff-interface.sh --interface NAME --left A/NC --right B/NC --depth 1 --out out.md
"generate jump threads for a migration" lib/nc-make-jump.sh ... --inbound X --new-host Y --jump-port Z --out-prefix /tmp/jump
"insert a new protocol with rollback" lib/nc-insert-protocol.sh insert NC /tmp/block.tcl then larry-rollback.sh --target NC if needed
"what changes did I make recently?" larry-rollback.sh --list
"undo my last change" larry-rollback.sh --last 1 --dry-run then --last 1 --yes
"document a system" lib/nc-document.sh --name PATTERN --out FILE
"full regression test between two envs" lib/nc-regression.sh --scope site --site-a SA --site-b SB --env-a EA --env-b EB --out DIR --count N --route-test-cmd '...' --phase all

Where to look when something breaks

  • ~/.larry/sessions/<id>.log.md — every Larry session is logged as markdown.
  • ~/.larry/journal/<session>/manifest.md — every journaled write in that session, with diffs.
  • ~/.larry/journal/index.tsv — flat index of every write across all sessions.
  • ~/.larry/.env — your API key (if using API-key auth).
  • ~/.larry/.oauth.json — OAuth tokens (if using subscription auth).
  • ~/.larry/agents/*.md — the personas loaded into Larry's system prompt. Editable; reloaded each launch.

All lib/ scripts accept --help (or -h) to print usage.