v0.8.33: uninstall command + --no-api deterministic-only mode

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>
This commit is contained in:
Bryan Johnson 2026-05-29 09:43:51 -07:00
parent 39f0e00c01
commit 7606a535c9
8 changed files with 508 additions and 14 deletions

View File

@ -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 <name>` 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

View File

@ -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

View File

@ -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 <name>` 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

View File

@ -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 <name>` 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

View File

@ -1 +1 @@
0.8.32
0.8.33

View File

@ -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 ""

196
larry.sh
View File

@ -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 <name> [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 <name>` 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 <name>` 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 <name> [args] run a tool by hand (no args → its --help)
larry tools <name> --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 <name>' 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 ) 2>/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 <thread> <site>" "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 <mode> <query>" "find threads by name/port/host/process/where/xlate" ;;
esac
case "$lc" in
*protocol*|*field*|*encoding*|*processname*|*"thread config"*|*inspect*)
_na_suggest "nc-parse <subcmd> <NetConfig>" "list/inspect protocols, fields, routes, xlate refs" ;;
esac
case "$lc" in
*inbound*|*listener*|*server\ thread*)
_na_suggest "nc-inbound <NetConfig>" "list the inbound (listener) threads" ;;
esac
case "$lc" in
*status*|*"not up"*|*notup*|*queued*|*connection*|*running*)
_na_suggest "nc-status <subcmd>" "engine runtime status (live engine box)" ;;
esac
case "$lc" in
*xlate*|*translat*|*.xlt*)
_na_suggest "nc-xlate <xlate-file>" "visualize/explore an xlate mapping tree" ;;
esac
case "$lc" in
*table*|*.tbl*|*lookup*)
_na_suggest "nc-table <subcmd> <.tbl>" "read/modify a Cloverleaf lookup table" ;;
esac
case "$lc" in
*smat*|*message*|*"msg "*|*archived*|*mrn*)
_na_suggest "nc-msgs <thread>" "query archived (smat) messages" ;;
esac
case "$lc" in
*hl7*|*pid*|*msh*|*pv1*|*segment*)
_na_suggest "hl7-field <PATH> <msg>" "extract an HL7 field (PID.3, MSH.10, …)"
_na_suggest "hl7-diff <a> <b>" "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 <S> …" "build jump sets for ALL inbound roots" ;;
esac
case "$lc" in
*regress*|*diff*|*compare*)
_na_suggest "nc-diff-interface <a> <b>" "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 <name> --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
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 <name>${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

206
uninstall-larry.sh Executable file
View File

@ -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/tty || ans=""; else read -r ans || ans=""; fi
case "$ans" in
y|Y|yes|YES) : ;;
*) say "aborted — nothing removed."; exit 0 ;;
esac
fi
# ─────────────────────────────────────────────────────────────────────────────
# Remove. Print every path as it goes. rm -rf is scoped strictly to the list we
# built above — we never expand globs or touch a parent dir.
# ─────────────────────────────────────────────────────────────────────────────
removed=0
for t in "${PROGRAM_TARGETS[@]}"; do
if rm -rf "$t" 2>/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."