From 7606a535c950a0a7afe9bf91fe769d8dddc6a78e Mon Sep 17 00:00:00 2001 From: Bryan Johnson Date: Fri, 29 May 2026 09:43:51 -0700 Subject: [PATCH] v0.8.33: uninstall command + --no-api deterministic-only mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ` 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 --- CHANGELOG.md | 55 ++++++++++++ MANIFEST | 11 +-- MANUAL.md | 23 +++++ README.md | 18 ++++ VERSION | 2 +- install-larry.sh | 7 +- larry.sh | 200 +++++++++++++++++++++++++++++++++++++++++-- uninstall-larry.sh | 206 +++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 508 insertions(+), 14 deletions(-) create mode 100755 uninstall-larry.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bcd5a8..f89f9e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,61 @@ 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.8.33 — 2026-05-29 + +**Two operator-requested features: a real uninstaller, and a deterministic-only +(no-API) mode.** + +### 1. `larry uninstall` / `uninstall-larry.sh` — remove EVERYTHING the installer put down + +There was no uninstall command before this. Now there is one, and it reverses +`install-larry.sh` exactly. + +- **New script `uninstall-larry.sh`** (shipped by the installer into + `$LARRY_HOME`, manifest-synced) + a **`larry uninstall` subcommand** that + delegates to it. Runs BEFORE bootstrap/self-update/auth, so you can uninstall + even when the API/origin is unreachable (same early-dispatch pattern as + `larry tools`). +- **DRY-RUN BY DEFAULT.** `larry uninstall` (or `--dry-run`) only PREVIEWS the + full removal list and prints "would stop" for any running PHI sidecar / tunnel. + `larry uninstall --yes` actually deletes (with a `[y/N]` confirm unless `--yes`). + `--keep-data` removes program files but preserves user data + (sessions/journal/lessons/knowledge/sanitize/log + creds). +- **Footprint removed:** the whole `$LARRY_HOME` (shipped bundle + `bin/jq` + fallback + optional `phi-venv` + all runtime artifacts: sessions, journal, + lessons, knowledge, sanitize, log incl. `headers.log`, `.history`, `.origin`, + `.api-key`/`.env`/`.oauth.json`, `.ssh-creds`/`.ssh-sockets`/`.ssh-hosts.tsv`, + `known_hosts`, tunnel.*, every dotfile state marker) **and** the `larry` PATH + shim at `$LARRY_BIN_DIR/larry` (default `$HOME/bin/larry`). +- **SAFETY:** the shim is removed ONLY if it carries our auto-generated header + (`Auto-generated by install-larry.sh`) — an unrelated `larry` on PATH is left + alone. Background procs are stopped via THEIR OWN pidfiles only (never a + guessed PID). `rm -rf` is scoped strictly to the built target list — no globs, + no parent dirs. The installer NEVER edits your shell rc, so the uninstaller + doesn't either: it prints a one-line reminder to remove any PATH export you + added by hand. Cloverleaf sites / `$HCIROOT` are never touched. + +### 2. `--no-api` — deterministic-only mode (ZERO LLM API calls, zero cost) + +- **New flag `--no-api`** (env: `LARRY_NO_API=1`). In this mode larry makes + ZERO `/v1/messages` requests — no pay-as-you-go cost, ever. The interactive + REPL and ALL local/deterministic commands still work; a free-text prompt is + NOT sent to the model. Instead larry keyword-sniffs the prompt and points you + at the matching deterministic `larry tools ` command (the same + `lib/nc-*.sh` / `hl7-*.sh` tools the LLM would have called) — or at + `larry tools list` when nothing matches. +- **No key required:** the first-run auth prompt is skipped in `--no-api` mode + (no key is read because no key is used). The REPL prompt label shows + `you[no-api]>` instead of a model name. +- **Defense in depth:** `call_api` and `call_api_stream` HARD-REFUSE when + `LARRY_NO_API=1`, so even a stray codepath cannot silently bill a request. The + turn handler short-circuits upstream, so the guard normally never fires. +- **What's unavailable:** open-ended reasoning / free-form authoring (anything + that genuinely needs the model). Those surface a clear "unavailable in + --no-api mode" message rather than erroring obscurely or silently calling out. + Coverage reference: `Deliverables/2026-05-28-larry-deterministic-tool-coverage-plan.md` + — most of Bryan's NetConfig/HL7 operations already have deterministic tools. + ## v0.8.32 — 2026-05-28 **★ CAPSTONE TOOL: `nc_provision_jumps` — point at a SITE and build the diff --git a/MANIFEST b/MANIFEST index b1488fe..6cb4ef9 100644 --- a/MANIFEST +++ b/MANIFEST @@ -23,16 +23,17 @@ # scripts/make-manifest.sh and bump VERSION. # Top-level scripts -larry.sh e37681171c8d3b7de10b1ebaf4c6c6db4198b2d9c20b106c6b7d43ba48f5a90d +larry.sh e58a26763dc8035da95dee0d00f9270b8cde683341377bfe896388248a269a1d larry-tunnel.sh 6b050e4eeab15669f4858eaf3b807f168f211ced07815db9521bc40a093f6aaa larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831 larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0 -install-larry.sh fa36e23a39eacbd0d7ecedd3b42131902f816ee7e98241dfc6e28c6e4ba80423 +install-larry.sh 0779dd74c0cecef174edbe7782da275be9bf6d2ec7e6ae88c1de46c666adc8b2 +uninstall-larry.sh 5d0e2fe1366c818dc207f70c85e38279218b58f5eae10a8a6b0a277a02f2e4d0 # Metadata -VERSION 4bd9f5643b9ceafed59c1be74ee8fec03786be48797dfa118157e894ddbc3ed9 -MANUAL.md c64bd0251a51ad150508b4e1185355bc4826a64071d4de339f92ed550dbfacde -CHANGELOG.md d07489204cea869763e8f9a8091a5f5a2931b9aa893eaf92ca5113dc3981288c +VERSION d9d12c30d3607edd1bfd2ef0feb0738f917e113a4bc1570d5dbb2ea8426531a8 +MANUAL.md 5ff54d6d5fae826f8b3da1eb3be6476076bb15f9b1417a4de285e59ea37e1b1f +CHANGELOG.md 67ed1238990e96d481ffa804cbfcbf219958438d6285e6efe864a33b8df97a32 # Agent personas (system-prompt overlays) agents/larry.md 0a1ef737e7fc133ab35be09f79c3a4df33de814e0404b69b950932d0c8a01be1 diff --git a/MANUAL.md b/MANUAL.md index 3c10b2c..c3b2f8a 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -38,6 +38,29 @@ To use the AI brain: ask IT to allowlist api.anthropic.com, or run from a networ This is graceful degradation and honest guidance only. Larry will **not** try to bypass, mask, proxy around, or otherwise circumvent a corporate security control on a PHI box — that is off the table by design. The fix is to get the endpoint allowlisted by IT, or run from a permitted network. +### Deterministic-only mode (`--no-api`) + +To run the REPL with the model rail **deliberately OFF** — zero LLM API calls, zero pay-as-you-go cost — launch with `--no-api` (or `export LARRY_NO_API=1`): + +```bash +larry --no-api # interactive shell; free-text prompts are answered by + # pointing you at the matching `larry tools ` command +``` + +In this mode no `/v1/messages` request EVER fires (the API functions hard-refuse as a backstop), and **no API key is required** — the first-run auth prompt is skipped. Every deterministic command and slash command still works. Open-ended reasoning / free-form authoring are unavailable and say so. This differs from `larry tools …` only in that it keeps the interactive REPL + all local commands; use whichever fits. + +### Uninstall + +To remove everything the installer put down (dry-run first): + +```bash +larry uninstall # DRY-RUN — lists exactly what would be removed +larry uninstall --yes # actually delete (or: $LARRY_HOME/uninstall-larry.sh --yes) +larry uninstall --keep-data # remove program files, KEEP sessions/journal/lessons/creds +``` + +It removes the whole `$LARRY_HOME` (bundle + `bin/jq` + optional `phi-venv` + all runtime artifacts incl. `log/headers.log`, sessions, journal, lessons, creds) and the `larry` PATH shim at `$LARRY_BIN_DIR/larry` (default `~/bin/larry`), stopping any running PHI sidecar / tunnel first. It touches **nothing** outside that footprint — not your shell rc, not your Cloverleaf sites, not `$HCIROOT`. + --- ## Conventions diff --git a/README.md b/README.md index 1bca8f3..2ce79f4 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,24 @@ larry # prompts for ANTHROPIC_API_KEY once Every time you run `larry`, it self-updates from the canonical GitHub URL. To suppress for one launch: `larry --no-update`. To disable permanently: `export LARRY_NO_UPDATE=1`. +### Uninstall (remove everything the installer put down) + +```bash +larry uninstall # DRY-RUN: lists exactly what would be removed +larry uninstall --yes # actually delete (or run uninstall-larry.sh directly) +larry uninstall --keep-data # remove program files, keep sessions/journal/creds +``` + +Reverses `install-larry.sh` exactly: removes `$LARRY_HOME` (the whole install dir, the bundled `jq` fallback, the optional Presidio `phi-venv`, and all runtime artifacts incl. `log/headers.log`, sessions, journal, lessons, knowledge, sanitize, and the `.api-key`/`.env`/`.oauth.json`/`.ssh-*` credential files) **and** the `larry` shim at `$LARRY_BIN_DIR/larry` (default `~/bin/larry`). It stops a running PHI sidecar / tunnel first (via their own pidfiles). It removes **only** what the installer created — it never touches your shell rc, your Cloverleaf sites, or `$HCIROOT`. If you added a `PATH` export by hand, it reminds you to remove that one line. + +### Deterministic-only mode (`--no-api`, zero LLM cost) + +```bash +larry --no-api # interactive REPL with the model rail OFF (env: LARRY_NO_API=1) +``` + +Makes **zero** LLM API calls. The REPL and every deterministic command still work; a free-text prompt is answered by pointing you at the matching `larry tools ` command (the same `nc-*` / `hl7-*` tools the model would have invoked) instead of calling the model. No API key is required in this mode. Open-ended reasoning / free-form authoring are unavailable and say so clearly. (See also `larry tools list` for the standalone, no-REPL toolkit.) + ### Offline / scp install (when the client box can't reach github.com) ```bash diff --git a/VERSION b/VERSION index a3fcdfd..f901547 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.8.32 +0.8.33 diff --git a/install-larry.sh b/install-larry.sh index a5e2b3f..8bde20d 100755 --- a/install-larry.sh +++ b/install-larry.sh @@ -221,6 +221,7 @@ fetch agents/cloverleaf-cheatsheet.md "$LARRY_HOME/agents/cloverleaf-cheatsheet. fetch agents/regress.md "$LARRY_HOME/agents/regress.md" fetch larry-rollback.sh "$LARRY_HOME/larry-rollback.sh" fetch larry-auth.sh "$LARRY_HOME/larry-auth.sh" +fetch uninstall-larry.sh "$LARRY_HOME/uninstall-larry.sh" fetch lib/fetch-safe.sh "$LARRY_HOME/lib/fetch-safe.sh" fetch lib/cygwin-safe.sh "$LARRY_HOME/lib/cygwin-safe.sh" fetch lib/oauth.sh "$LARRY_HOME/lib/oauth.sh" @@ -257,7 +258,7 @@ fetch VERSION "$LARRY_HOME/VERSION" fetch MANUAL.md "$LARRY_HOME/MANUAL.md" # Bryan-curated inbound-systems lookup — seed only if absent (never clobber edits). fetch_if_missing inbound-systems.tsv "$LARRY_HOME/inbound-systems.tsv" -chmod +x "$LARRY_HOME/larry.sh" "$LARRY_HOME/larry-tunnel.sh" "$LARRY_HOME/larry-rollback.sh" "$LARRY_HOME/larry-auth.sh" "$LARRY_HOME/lib/"*.sh +chmod +x "$LARRY_HOME/larry.sh" "$LARRY_HOME/larry-tunnel.sh" "$LARRY_HOME/larry-rollback.sh" "$LARRY_HOME/larry-auth.sh" "$LARRY_HOME/uninstall-larry.sh" "$LARRY_HOME/lib/"*.sh # ───────────────────────────────────────────────────────────────────────────── # jq fallback — download static binary into $LARRY_HOME/bin/ if missing @@ -408,3 +409,7 @@ echo "Reverse SSH tunnel (optional, run in another shell or backgrounded):" echo " $LARRY_HOME/larry-tunnel.sh --serveo # zero-config" echo " $LARRY_HOME/larry-tunnel.sh --hop=user@bjnoela.com:22 # your hop" echo "" +echo "To remove EVERYTHING this installed (dry-run first, then --yes):" +echo " larry uninstall # or: $LARRY_HOME/uninstall-larry.sh" +echo " larry uninstall --yes # actually delete" +echo "" diff --git a/larry.sh b/larry.sh index 3135ba1..0fbb04c 100755 --- a/larry.sh +++ b/larry.sh @@ -7,6 +7,7 @@ # larry.sh /path/to/cloverleaf/root # interactive, cd into that path first # larry.sh tools list # list the manual Cloverleaf/HL7 tools # larry.sh tools [args] # run a tool by hand (no API/LLM needed) +# larry.sh --no-api # deterministic-only: ZERO LLM API calls # larry.sh --no-update # skip self-update # larry.sh --version # print version and exit # larry.sh --help # print help and exit @@ -32,6 +33,12 @@ # LARRY_MODEL Claude model (default: claude-sonnet-4-6) # LARRY_MAX_TOKENS max output tokens per turn (default: 8192) # LARRY_NO_UPDATE set to 1 to disable self-update +# LARRY_NO_API set to 1 (or pass --no-api) for deterministic-only +# mode: larry makes ZERO LLM API calls (zero cost). The +# REPL + all local/deterministic commands still work; +# a free-text model turn is replaced by a pointer to +# the equivalent `larry tools ` command. No API +# key is required in this mode. # LARRY_GITEA_TOKEN optional Gitea PAT (read scope) for authenticated # fetch against a PRIVATE repo (alias: GITEA_TOKEN). # When set, update/install fetches add an @@ -78,7 +85,7 @@ set -o pipefail # ───────────────────────────────────────────────────────────────────────────── # Config # ───────────────────────────────────────────────────────────────────────────── -LARRY_VERSION="0.8.32" +LARRY_VERSION="0.8.33" LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" # ───────────────────────────────────────────────────────────────────────────── @@ -144,6 +151,29 @@ LARRY_MAX_TOKENS="${LARRY_MAX_TOKENS:-8192}" LARRY_API_URL="${LARRY_API_URL:-https://api.anthropic.com/v1/messages}" LARRY_NO_UPDATE="${LARRY_NO_UPDATE:-0}" +# ───────────────────────────────────────────────────────────────────────────── +# v0.8.33: NO-API / deterministic-only mode (Bryan: "add the option to not use +# an api and only use the deterministic tools"). +# +# When on, larry makes ZERO LLM API calls (zero pay-as-you-go cost): no +# /v1/messages request EVER fires. The REPL still runs, but a free-text turn no +# longer hits the model — instead larry prints how to reach the same answer via +# the deterministic `larry tools ` toolkit (the lib/nc-*.sh + hl7-*.sh +# suite, which is exactly what the LLM would have called anyway). Slash commands +# that DON'T need the model (/env, /cost, /help, /hl7, /ssh-*, /headers-sync, +# /nc-diff-env, /nc-regression-env …) keep working unchanged. +# +# This is distinct from `larry tools …` (the standalone, no-REPL entry point): +# --no-api keeps the interactive shell + all local commands, just with the +# model rail hard-disabled. It also DEFANGS the first-run auth prompt: no key is +# needed because no key is ever used. +# +# Enable with the --no-api flag or LARRY_NO_API=1. call_api / call_api_stream +# additionally hard-refuse in this mode (defense in depth) so no codepath can +# silently bill a request. +# ───────────────────────────────────────────────────────────────────────────── +LARRY_NO_API="${LARRY_NO_API:-0}" + # v0.8.14: last-call diagnostics for API-block detection (graceful degradation # into manual-tools mode on locked-down boxes). Set by call_api after each # request; read by _diagnose_api_block. NOTHING here circumvents a block — it @@ -451,6 +481,29 @@ if [ "${1:-}" = "tools" ]; then exit $? fi +# ───────────────────────────────────────────────────────────────────────────── +# `larry uninstall` — remove everything install-larry.sh put down (v0.8.33). +# +# Delegates to uninstall-larry.sh (the canonical implementation), found next to +# larry.sh or in $LARRY_HOME. Defaults to a DRY-RUN preview; pass --yes to +# delete. Runs HERE, before bootstrap / self-update / auth, so you can uninstall +# even when the API/origin is unreachable. See uninstall-larry.sh --help. +# ───────────────────────────────────────────────────────────────────────────── +if [ "${1:-}" = "uninstall" ]; then + shift + _uninst="" + for _cand in "$(cd "$(dirname "$0")" 2>/dev/null && pwd)/uninstall-larry.sh" \ + "$LARRY_HOME/uninstall-larry.sh"; do + [ -f "$_cand" ] && { _uninst="$_cand"; break; } + done + if [ -z "$_uninst" ]; then + printf 'error: uninstall-larry.sh not found next to larry.sh or in %s.\n' "$LARRY_HOME" >&2 + printf ' Re-run install-larry.sh (it now ships the uninstaller), or fetch it from the repo.\n' >&2 + exit 1 + fi + exec bash "$_uninst" "$@" +fi + # ───────────────────────────────────────────────────────────────────────────── # CLI args # ───────────────────────────────────────────────────────────────────────────── @@ -458,8 +511,9 @@ ARG_DIR="" for arg in "$@"; do case "$arg" in --version|-V) echo "larry-anywhere $LARRY_VERSION"; exit 0 ;; - --help|-h) sed -n '2,40p' "$0"; exit 0 ;; + --help|-h) sed -n '2,48p' "$0"; exit 0 ;; --no-update) LARRY_NO_UPDATE=1 ;; + --no-api) LARRY_NO_API=1 ;; -*) err "unknown flag: $arg"; exit 2 ;; *) ARG_DIR="$arg" ;; esac @@ -1173,7 +1227,13 @@ self_update # Now that self_update has had a chance to refresh lib/oauth.sh, gate on # credentials. On a fresh box (no .oauth.json, no API key) this is the first # interactive prompt the user sees. -if [ -z "$LARRY_AUTH_MODE" ]; then +# +# v0.8.33: in --no-api / deterministic-only mode we make ZERO model calls, so an +# API key is never needed. Skip the first-run auth prompt entirely rather than +# nagging Bryan for a credential he won't use. +if [ "$LARRY_NO_API" = "1" ]; then + LARRY_AUTH_MODE="none" +elif [ -z "$LARRY_AUTH_MODE" ]; then prompt_first_run_auth fi @@ -4568,6 +4628,13 @@ _curl_config_apikey() { call_api() { local payload_file="$1" + # v0.8.33: deterministic-only mode — hard-refuse before any request can fire. + # This is the last line of defense; agent_turn already short-circuits upstream + # in --no-api mode, so reaching here means a codepath tried to bill a call. + if [ "$LARRY_NO_API" = "1" ]; then + err "call_api blocked: --no-api / deterministic-only mode makes ZERO LLM API calls." + return 1 + fi local auth_args=() if [ "$LARRY_AUTH_MODE" = "oauth" ]; then local oauth_script="$LARRY_LIB_DIR/oauth.sh" @@ -4703,6 +4770,12 @@ call_api() { # pure SSE either way. call_api_stream() { local payload_file="$1" + # v0.8.33: deterministic-only mode — hard-refuse before any request can fire + # (defense in depth; agent_turn short-circuits upstream in --no-api mode). + if [ "$LARRY_NO_API" = "1" ]; then + err "call_api_stream blocked: --no-api / deterministic-only mode makes ZERO LLM API calls." + return 1 + fi local auth_args=() if [ "$LARRY_AUTH_MODE" = "oauth" ]; then local oauth_script="$LARRY_LIB_DIR/oauth.sh" @@ -5805,6 +5878,12 @@ Manual tools (work even when the API is blocked — run from the shell, not here larry tools [args] run a tool by hand (no args → its --help) larry tools --help usage, flags, expected input/output + an example +Deterministic-only mode (zero LLM API calls, zero cost): + larry --no-api run this REPL with the model rail OFF. Free-text + prompts are answered by pointing you at the + matching 'larry tools ' command instead of + calling the model. (env: LARRY_NO_API=1) + Slash commands: /quit /exit /q exit /clear clear the terminal screen (distinct from /reset) @@ -6811,8 +6890,95 @@ _readline_ok() { ( IFS= read -e -r -t 0 _x /dev/null } +# ───────────────────────────────────────────────────────────────────────────── +# v0.8.33: deterministic-only turn handler. In --no-api mode a free-text prompt +# does NOT hit the model — there is no model. Instead we tell the operator how to +# get the same answer from the deterministic toolkit, the same tools the LLM +# would otherwise have invoked. We do a light keyword sniff to suggest the most +# relevant tool(s); when nothing matches we point at the full catalog. We never +# guess at arguments or run anything — this prints guidance only. +# ───────────────────────────────────────────────────────────────────────────── +_no_api_turn() { + local input="$1" + local lc; lc=$(printf '%s' "$input" | tr 'A-Z' 'a-z') + printf '%s%s--no-api mode%s — no LLM call was made (zero cost). ' "$C_YELLOW" "$C_BOLD" "$C_RESET" >&2 + printf 'Run the matching deterministic tool by hand:\n\n' >&2 + + local hit=0 + _na_suggest() { printf ' %slarry tools %s%s %s\n' "$C_CYAN" "$1" "$C_RESET" "$2" >&2; hit=1; } + + case "$lc" in + *path*|*chain*|*route*|*trace*|*downstream*|*upstream*) + _na_suggest "nc-paths " "route-chain tracer (root→leaf paths)" ;; + esac + case "$lc" in + *" port "*|*"by port"*|*" host "*|*"by host"*|*"by name"*|*" where "*|*find*|*search*|*locate*|*"which thread"*) + _na_suggest "nc-find " "find threads by name/port/host/process/where/xlate" ;; + esac + case "$lc" in + *protocol*|*field*|*encoding*|*processname*|*"thread config"*|*inspect*) + _na_suggest "nc-parse " "list/inspect protocols, fields, routes, xlate refs" ;; + esac + case "$lc" in + *inbound*|*listener*|*server\ thread*) + _na_suggest "nc-inbound " "list the inbound (listener) threads" ;; + esac + case "$lc" in + *status*|*"not up"*|*notup*|*queued*|*connection*|*running*) + _na_suggest "nc-status " "engine runtime status (live engine box)" ;; + esac + case "$lc" in + *xlate*|*translat*|*.xlt*) + _na_suggest "nc-xlate " "visualize/explore an xlate mapping tree" ;; + esac + case "$lc" in + *table*|*.tbl*|*lookup*) + _na_suggest "nc-table <.tbl>" "read/modify a Cloverleaf lookup table" ;; + esac + case "$lc" in + *smat*|*message*|*"msg "*|*archived*|*mrn*) + _na_suggest "nc-msgs " "query archived (smat) messages" ;; + esac + case "$lc" in + *hl7*|*pid*|*msh*|*pv1*|*segment*) + _na_suggest "hl7-field " "extract an HL7 field (PID.3, MSH.10, …)" + _na_suggest "hl7-diff " "HL7-aware field-level diff" ;; + esac + case "$lc" in + *jump*|*provision*|*"server_jump"*|*"cross-env"*|*"cross environment"*) + _na_suggest "nc-make-jump …" "generate the 3-thread cross-env jump set" + _na_suggest "nc-provision-jumps --site …" "build jump sets for ALL inbound roots" ;; + esac + case "$lc" in + *regress*|*diff*|*compare*) + _na_suggest "nc-diff-interface " "diff one interface across two envs" + _na_suggest "nc-regression …" "end-to-end regression between two envs" ;; + esac + case "$lc" in + *document*|*"write up"*|*knowledge*) + _na_suggest "nc-document …" "generate a markdown knowledge entry" ;; + esac + + if [ "$hit" = "0" ]; then + printf ' %slarry tools list%s see every deterministic tool + a one-line desc\n' "$C_CYAN" "$C_RESET" >&2 + else + printf '\n %sFull catalog:%s %slarry tools list%s • %slarry tools --help%s for usage.\n' \ + "$C_DIM" "$C_RESET" "$C_CYAN" "$C_RESET" "$C_CYAN" "$C_RESET" >&2 + fi + printf '\n %sActions that genuinely need the model (open-ended reasoning, free-form\n' "$C_DIM" >&2 + printf ' authoring) are unavailable in --no-api mode. Relaunch without --no-api\n' >&2 + printf ' (and with an API key) to use them.%s\n' "$C_RESET" >&2 +} + main_loop() { - local system_prompt; system_prompt=$(build_system_prompt) + local system_prompt + if [ "$LARRY_NO_API" = "1" ]; then + # No model => no system prompt to build (and build_system_prompt is cheap + # but pointless here). Keep it empty; nothing in --no-api mode consumes it. + system_prompt="" + else + system_prompt=$(build_system_prompt) + fi # ── Persistent command history (v0.6.7) ──────────────────────────────────── # HISTFILE persists across `larry` invocations; HISTSIZE caps in-memory size. @@ -6859,8 +7025,13 @@ main_loop() { # trap installed in _install_mouse_mode tears this down on exit. _install_mouse_mode - larry_say "${C_BOLD}Larry-Anywhere v$LARRY_VERSION${C_RESET} ready. Model: $LARRY_MODEL." - larry_say "Type your message and press Enter. Use '<<' alone on a line to start multi-line (end with 'EOF'). /help for commands." + if [ "$LARRY_NO_API" = "1" ]; then + larry_say "${C_BOLD}Larry-Anywhere v$LARRY_VERSION${C_RESET} ready. ${C_YELLOW}--no-api / deterministic-only mode${C_RESET} (ZERO LLM API calls, zero cost)." + larry_say "Free-text prompts are answered by pointing you at the matching ${C_CYAN}larry tools ${C_RESET} command. Run ${C_CYAN}larry tools list${C_RESET} to see them. /help for commands." + else + larry_say "${C_BOLD}Larry-Anywhere v$LARRY_VERSION${C_RESET} ready. Model: $LARRY_MODEL." + larry_say "Type your message and press Enter. Use '<<' alone on a line to start multi-line (end with 'EOF'). /help for commands." + fi # v0.8.2: best-effort PHI Presidio sidecar start. Backgrounded so larry # is interactive immediately; tier-5 silently no-ops until the sidecar @@ -6877,7 +7048,12 @@ main_loop() { echo "" while true; do - local _short; _short=$(model_short_name) + local _short + if [ "$LARRY_NO_API" = "1" ]; then + _short="no-api" # v0.8.33: no model is in play; don't show a model name. + else + _short=$(model_short_name) + fi # v0.7.1: status line moved from above-prompt to between-turn # (see render_status_line and the post-input call below). printf '%syou[%s]>%s ' "$C_GREEN" "$_short" "$C_RESET" @@ -7617,6 +7793,16 @@ EOF fi log_section "user"; log_append "$input" + + # v0.8.33: deterministic-only mode — a free-text prompt never reaches the + # model. We log it (above), point the operator at the matching `larry tools` + # command, and loop. No payload is built, no API call fires, no key is read. + if [ "$LARRY_NO_API" = "1" ]; then + _no_api_turn "$input" + echo "" + continue + fi + # v0.7.1: render the persistent status line BETWEEN turns — after the # user has submitted real (non-slash, non-empty) input and after all # input preprocessing (@file, PHI) is done, but before agent_turn diff --git a/uninstall-larry.sh b/uninstall-larry.sh new file mode 100755 index 0000000..fdbc379 --- /dev/null +++ b/uninstall-larry.sh @@ -0,0 +1,206 @@ +#!/usr/bin/env bash +# uninstall-larry.sh — cleanly remove everything install-larry.sh put down. +# +# Reverses the installer EXACTLY: it removes only what Larry-Anywhere created +# (its install dir, the `larry` PATH shim, the optional PHI venv, the jq +# fallback binary, and all runtime artifacts under $LARRY_HOME) and NOTHING the +# installer didn't create. It does NOT touch your shell rc, your Cloverleaf +# sites, your $HCIROOT, or any file outside the install footprint. +# +# Default is a DRY-RUN preview. You must pass --yes (or confirm at the prompt) +# to actually delete. Every path removed is printed. +# +# Usage: +# uninstall-larry.sh # dry-run: list exactly what WOULD be removed +# uninstall-larry.sh --dry-run # (explicit) same as above +# uninstall-larry.sh --yes # remove without the interactive prompt +# uninstall-larry.sh --keep-data # remove program files but KEEP user data +# # (sessions/, journal/, lessons/, +# # knowledge/, sanitize/, log/, creds) +# uninstall-larry.sh --help +# +# Also reachable as a subcommand: larry uninstall [--dry-run|--yes|--keep-data] +# +# Env vars (mirror the installer so it targets the SAME footprint): +# LARRY_HOME install location (default: $HOME/.larry) +# LARRY_BIN_DIR where the `larry` shim was symlinked (default: $HOME/bin) +set -eu + +LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" +LARRY_BIN_DIR="${LARRY_BIN_DIR:-$HOME/bin}" + +C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_GREEN=$'\033[32m' +C_YELLOW=$'\033[33m'; C_RED=$'\033[31m'; C_CYAN=$'\033[36m'; C_DIM=$'\033[2m' + +say() { printf '%s%suninstall-larry>%s %s\n' "$C_CYAN" "$C_BOLD" "$C_RESET" "$*"; } +ok() { printf ' %s✓%s %s\n' "$C_GREEN" "$C_RESET" "$*"; } +warn() { printf ' %s!%s %s\n' "$C_YELLOW" "$C_RESET" "$*"; } +die() { printf '%serror:%s %s\n' "$C_RED" "$C_RESET" "$*" >&2; exit 1; } + +# ───────────────────────────────────────────────────────────────────────────── +# Args +# ───────────────────────────────────────────────────────────────────────────── +DRY_RUN=1 # default: preview only +ASSUME_YES=0 +KEEP_DATA=0 +for arg in "${@:-}"; do + case "$arg" in + --yes|-y) DRY_RUN=0; ASSUME_YES=1 ;; + --dry-run|-n) DRY_RUN=1 ;; + --keep-data) KEEP_DATA=1 ;; + --help|-h) sed -n '2,38p' "$0"; exit 0 ;; + "") : ;; + *) die "unknown flag: $arg (try --help)" ;; + esac +done + +# ───────────────────────────────────────────────────────────────────────────── +# Stop background processes Larry may have started, BEFORE deleting their files. +# Both PID files live under $LARRY_HOME. Best-effort: a stale/dead PID is fine. +# We only kill a PID we can read from Larry's own pidfile — never a guessed one. +# ───────────────────────────────────────────────────────────────────────────── +stop_bg_proc() { + # $1 = human label, $2 = pidfile path + local label="$1" pidfile="$2" pid="" + [ -f "$pidfile" ] || return 0 + pid="$(tr -d '[:space:]' < "$pidfile" 2>/dev/null || echo "")" + case "$pid" in + ''|*[!0-9]*) return 0 ;; # empty or non-numeric → nothing safe to do + esac + if kill -0 "$pid" 2>/dev/null; then + if [ "$DRY_RUN" = "1" ]; then + printf ' %s•%s would stop %s (PID %s)\n' "$C_YELLOW" "$C_RESET" "$label" "$pid" + else + kill "$pid" 2>/dev/null && ok "stopped $label (PID $pid)" || warn "could not stop $label (PID $pid) — kill it manually" + fi + fi +} + +# ───────────────────────────────────────────────────────────────────────────── +# Build the removal list. We separate PROGRAM artifacts (always removed) from +# USER DATA (removed unless --keep-data). Everything here was created by the +# installer or by Larry at runtime — we never list anything else. +# ───────────────────────────────────────────────────────────────────────────── +PROGRAM_TARGETS=() # files/dirs that ARE the install footprint +DATA_TARGETS=() # user-generated data under $LARRY_HOME + +# The shim the installer drops onto PATH. We only remove it if it is OUR shim +# (auto-generated header), so we never delete an unrelated `larry` someone else +# put there. +SHIM="$LARRY_BIN_DIR/larry" +if [ -f "$SHIM" ] && grep -q 'Auto-generated by install-larry.sh' "$SHIM" 2>/dev/null; then + PROGRAM_TARGETS+=("$SHIM") +elif [ -e "$SHIM" ]; then + warn "$SHIM exists but is NOT our auto-generated shim — leaving it untouched." +fi + +# The entire install dir is ours. Rather than enumerate every dotfile, we list +# $LARRY_HOME itself for the full uninstall. With --keep-data we instead remove +# the program files inside it and preserve the user-data subtrees. +if [ -d "$LARRY_HOME" ]; then + if [ "$KEEP_DATA" = "0" ]; then + PROGRAM_TARGETS+=("$LARRY_HOME") + else + # Program files (shipped bundle + caches + state markers + the jq fallback + # + the optional PHI venv). Anything not in this list under $LARRY_HOME is + # treated as user data and preserved. + for p in \ + larry.sh larry-tunnel.sh larry-rollback.sh larry-auth.sh uninstall-larry.sh \ + install-larry.sh VERSION MANUAL.md MANIFEST CHANGELOG.md README.md \ + inbound-systems.tsv agents lib bin phi-venv \ + .last-sync-version .origin .oauth-optin-warned \ + .phi-notice-shown .b64-py3-notice-shown \ + .last-curl-rc .last-curl-stderr .last-curl-headers \ + .last-stream-headers .last-stream-curlerr \ + .phi-sidecar.pid tunnel.pid tunnel.url tunnel.log .history; do + [ -e "$LARRY_HOME/$p" ] && PROGRAM_TARGETS+=("$LARRY_HOME/$p") + done + # User data preserved under --keep-data (listed for transparency). + for d in sessions journal lessons knowledge sanitize regression log \ + .api-key .env .oauth.json .ssh-creds .ssh-sockets .ssh-hosts.tsv \ + known_hosts .headers-sync-offset .headers-sync-state; do + [ -e "$LARRY_HOME/$d" ] && DATA_TARGETS+=("$LARRY_HOME/$d") + done + fi +fi + +if [ "${#PROGRAM_TARGETS[@]}" -eq 0 ]; then + say "nothing to remove — no Larry-Anywhere install footprint found." + say " (looked for LARRY_HOME=$LARRY_HOME and shim $SHIM)" + exit 0 +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Preview +# ───────────────────────────────────────────────────────────────────────────── +say "Larry-Anywhere uninstall — install footprint to remove:" +echo "" +printf '%sProgram files (always removed):%s\n' "$C_BOLD" "$C_RESET" +for t in "${PROGRAM_TARGETS[@]}"; do + if [ -d "$t" ]; then printf ' %s(dir) %s%s\n' "$C_DIM" "$t" "$C_RESET" + else printf ' %s\n' "$t"; fi +done +if [ "$KEEP_DATA" = "1" ]; then + echo "" + printf '%sUser data PRESERVED (--keep-data):%s\n' "$C_BOLD" "$C_RESET" + if [ "${#DATA_TARGETS[@]}" -eq 0 ]; then + printf ' %s(none found)%s\n' "$C_DIM" "$C_RESET" + else + for d in "${DATA_TARGETS[@]}"; do printf ' %skept%s %s\n' "$C_GREEN" "$C_RESET" "$d"; done + fi +fi +echo "" +say "NOT touched: your shell rc (any PATH export you added stays — remove it by hand)," +say " your Cloverleaf sites / \$HCIROOT, and anything outside the list above." +echo "" + +# Stop background procs (preview prints "would stop …"; real run kills them). +stop_bg_proc "PHI Presidio sidecar" "$LARRY_HOME/.phi-sidecar.pid" +stop_bg_proc "reverse SSH tunnel" "$LARRY_HOME/tunnel.pid" +echo "" + +if [ "$DRY_RUN" = "1" ]; then + say "${C_BOLD}DRY-RUN${C_RESET} — nothing was removed. Re-run with ${C_CYAN}--yes${C_RESET} to delete the above." + exit 0 +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Confirm (skipped with --yes... but --yes already set ASSUME_YES; this is the +# belt-and-suspenders path if someone sets DRY_RUN=0 by other means). +# ───────────────────────────────────────────────────────────────────────────── +if [ "$ASSUME_YES" != "1" ]; then + printf '%sProceed with removal? This cannot be undone. [y/N]: %s' "$C_YELLOW" "$C_RESET" + ans="" + if [ -t 0 ]; then read -r ans /dev/null; then + ok "removed $t" + removed=$((removed+1)) + else + warn "could not remove $t (check permissions; remove by hand)" + fi +done + +echo "" +say "removed $removed item(s)." +if [ "$KEEP_DATA" = "1" ] && [ "${#DATA_TARGETS[@]}" -gt 0 ]; then + say "your data is preserved under $LARRY_HOME — delete it manually if you want a full wipe." +fi +# Reminder: the installer never EDITS your shell rc; it only WARNS you to add a +# PATH export. If you added one, it's still there — this is the one manual step. +case ":$PATH:" in + *":$LARRY_BIN_DIR:"*) + say "reminder: you may have added 'export PATH=\"$LARRY_BIN_DIR:\$PATH\"' to your shell rc. Remove that line by hand if $LARRY_BIN_DIR is now empty." ;; +esac +say "uninstall complete."