v0.9.4: short on-PATH commands + live 3-level tab-completion + durable fixture

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>
This commit is contained in:
Clover 2026-06-08 11:58:25 -07:00
parent ec9a57d6aa
commit df1810cba7
45 changed files with 979 additions and 6 deletions

View File

@ -4,6 +4,42 @@ All notable changes to `cloverleaf-larry` / `larry-anywhere` are recorded here.
Versioning is loose-semver; bumps trigger the in-process self-update on every Versioning is loose-semver; bumps trigger the in-process self-update on every
running client via `LARRY_BASE_URL` + `MANIFEST`. running client via `LARRY_BASE_URL` + `MANIFEST`.
## v0.9.4 — 2026-06-08
**Short, directly-invokable commands on PATH + dynamic 3-level tab-completion +
a durable checked-in fixture. The toolkit is now usable by hand without the
`larry tools <name>` prefix.** (Clover)
- **`bin/` of thin wrappers, symlinked onto PATH by the installer.** `tbn adt`,
`tbp 7001`, `tbh 172.31`, `tbpr route_epic`, `where <thread>`, `paths`,
`route_test`, and a full-name passthrough per operator tool (`nc-find`,
`nc-parse`, `nc-paths`, …) now run DIRECTLY — no `larry tools` / `nc-larry`
prefix. Each wrapper resolves `lib/` via `bin/_nc_common.sh` (honours
`LARRY_LIB_DIR` → repo `../lib``$LARRY_HOME/lib`) and execs the matching
`nc-*`/`hl7-*` tool with args mapped to its flags. The short v1-family names
(`tbn`/`tbp`/`tbh`/`tbpr`) map onto `nc-find --name/--port/--host/--process`.
- **`-h`/`--help` on every wrapper** (prints its own leading comment block; the
full-name passthroughs defer to the underlying tool's `--help`).
- **Dynamic bash tab-completion (`bin/nc-completion.bash`), enumerated LIVE from
the NetConfig site tree under `$HCIROOT`** — never a static list. Three levels:
(1) command names (PATH default), (2) **site names** (`tbn <TAB>`→ adt …),
(3) **thread names** (`paths <TAB>`, `where <TAB>`, `route_test
--source-thread <TAB>`). Sites/threads are read through the SAME parser the
tools use (`lib/nc-parse.sh`), cached per (HCIROOT, newest-NetConfig-mtime) so
repeated TABs are instant and the cache self-invalidates on a config change.
The installer appends a guarded `source` line to the user's bash rc.
- **Durable checked-in demo fixture** (`fixtures/integrator/`): a 3-site
synthetic integrator (epic → ancout → codamx) exercising cross-site route
fan-out AND a cross-site fan-in (codamx/IB_ADT_cmx fed by both epic direct and
ancout relay), plus a multi-route inbound (IB_ADT_epic → two delivery legs).
PHI-free. **Resolves the v0.9.3 fixture-design conflict:** a destination block
must never share its NAME with a local protocol in the same site — such a
collision makes `nc-paths` `_xsite_down_targets` (lib/nc-paths.sh:378) treat
the DEST as an intra-site hop and SUPPRESS the cross-site link. Every
cross-site destination block here is `XS_`-prefixed (`XS_to_ancout`,
`XS_to_codamx`, `XS_anc_to_codamx`) so no collision is possible.
- DEFERRED: `fetch-token.sh` broker wiring (broker contract still finalizing).
## v0.9.1 — 2026-05-31 ## v0.9.1 — 2026-05-31
**Upgrade to broker-mode now WIPES the now-obsolete local Anthropic/OAuth **Upgrade to broker-mode now WIPES the now-obsolete local Anthropic/OAuth

View File

@ -23,17 +23,17 @@
# scripts/make-manifest.sh and bump VERSION. # scripts/make-manifest.sh and bump VERSION.
# Top-level scripts # Top-level scripts
larry.sh bd3bd27898afce693b44f75eb4fa3fab44b1a963d9b43f5559f5d9d0a5516e40 larry.sh 32c81f6ca2e677756c1efc851337d71cd553f8a0bbf96f2eaeb5e8d8ec68377d
larry-tunnel.sh 6b050e4eeab15669f4858eaf3b807f168f211ced07815db9521bc40a093f6aaa larry-tunnel.sh 6b050e4eeab15669f4858eaf3b807f168f211ced07815db9521bc40a093f6aaa
larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831 larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831
larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0 larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0
install-larry.sh 072a036ad5bbf80e866cfd2dd74de50f8defd69a3f835032579b0cb9d421ad5b install-larry.sh 0a7bc75e75ebfe75eb9c47bda259b31072b79003af42a8e7f40a7e48469a874f
uninstall-larry.sh c53ad2d8354c7adeb243b541f027f3f481e4a8661eecfd7af14d7ca53cfcaad9 uninstall-larry.sh c53ad2d8354c7adeb243b541f027f3f481e4a8661eecfd7af14d7ca53cfcaad9
# Metadata # Metadata
VERSION 38ecaa1e4c36c6691944c83df7671fca8b86f5cbf2d4e22c0012aa52df14b149 VERSION a61cc7a990558a2b76c4a814eb233267e219158dcf184b6b30ce458d27027674
MANUAL.md 5ff54d6d5fae826f8b3da1eb3be6476076bb15f9b1417a4de285e59ea37e1b1f MANUAL.md 5ff54d6d5fae826f8b3da1eb3be6476076bb15f9b1417a4de285e59ea37e1b1f
CHANGELOG.md 934007dc1b08b6c90120f009e3cc7870815e7b251fdf8f6629aa4c004c866017 CHANGELOG.md 8eacdea7711e4e22b1afb0f79f7deb970881a133ca6277732ba4ba16ac1b194f
# Agent personas (system-prompt overlays) # Agent personas (system-prompt overlays)
agents/larry.md 0a1ef737e7fc133ab35be09f79c3a4df33de814e0404b69b950932d0c8a01be1 agents/larry.md 0a1ef737e7fc133ab35be09f79c3a4df33de814e0404b69b950932d0c8a01be1
@ -116,3 +116,56 @@ lib/nc-diff-interface.sh c922d10323f06346efa53ada68b44d32d9568ff0bd848c59af34041
lib/nc-find.sh 2264877c56100378a1b780d640dcaa806aa5501ddd204c6b6a8eb5d3e07bf966 lib/nc-find.sh 2264877c56100378a1b780d640dcaa806aa5501ddd204c6b6a8eb5d3e07bf966
lib/nc-insert-protocol.sh ad1fa0bafbf4fdfb12bad20f9c22c3eed519f8846774331e26aa9becd6f8898a lib/nc-insert-protocol.sh ad1fa0bafbf4fdfb12bad20f9c22c3eed519f8846774331e26aa9becd6f8898a
lib/nc-regression.sh 70999a60608439f7bf1a3abb9f5e9854b5ea03025ef29ddbca683896346d1bce lib/nc-regression.sh 70999a60608439f7bf1a3abb9f5e9854b5ea03025ef29ddbca683896346d1bce
# v0.9.4: short, directly-invokable command wrappers on PATH (bin/) + dynamic
# bash tab-completion. Each wrapper is a thin shim that resolves lib/ and execs
# the matching nc-*/hl7-* tool — so `tbn adt`, `paths`, `route_test`, `nc-find`
# … run directly (no `larry tools` prefix). _nc_common.sh is the shared
# resolver (sourced, not a command); nc-completion.bash is the 3-level
# (command/site/thread) completion, enumerated LIVE from the NetConfig tree.
bin/_nc_common.sh 591b60d091e9c8a5e1068844dffe2e19f44d262fcf1132dc47e425d4673495e1
bin/nc-completion.bash 3a5b1b29f34f2382556d9f8620882ab7edbb2d27dfa35177793889b289d22a9a
bin/tbn 60e69288a502f1280e6bf4bc6dc19568858b34024ba26c34f8ff7ed52457d02c
bin/tbp 8cdc82de0bbbee8da97d4ab958f7b5ead5aed8c7658c3d73ff9106df6383bf29
bin/tbh 79556fe72d58393c2a00e744b892c5a1a69bae3be6fcdd0ba46dd108ee67c092
bin/tbpr dca260c16e10944eaedbcb3c59f43e4631586d521742a2f124128ec4090bf117
bin/where 4a69061b9ab5b09d4d1ca6b48e1cf597d8c6c8ac88092e8e5a1d523a506de907
bin/paths 3abe6c6ff548f8df6a5df83d48da77bb4da4e39df1d2c3b04cd9960c5fde20a5
bin/route_test 75e0ee675dba3dec8f36d374ed62d7b4c006fc1516fbcc8364d22e842cea31f1
bin/nc-parse c50ae884fdee50e9b089f0f92e3fd79d968b53f85c452a6d8a89a230bff12d29
bin/nc-paths b6fb67cb97530f54c6efb6b2dadc41700b1f9c7b5599b9e6d5d166fd68789a72
bin/nc-find b8298c8289bbb476f55ae142e41ae0432f6d2e5719ba1d95177ccf9299b3e6cf
bin/nc-inbound af62c41b1448fbad39135ffb8d3b0035ef17871249fa6ae99701c83c47c67c89
bin/nc-status 73c4ae9dc92d4d86b660df3edc9c9207013188ab8bda18965559b7221cac607b
bin/nc-engine e774ab31685b7b9ff900605f6695c5b4430764903ea2a4ffc6bd60714f8f47fb
bin/nc-xlate 0336cc1fb3ef0985db91c8cc418b99ca03010b497d8d54b2b651367530ae86fa
bin/nc-table f069b95f6ca563d833f2c2b9ec77995918242a6bb501dfd52ad1aad1147a6305
bin/nc-create-thread 81091802664449122cd30c7a1862220b7f8227cf26e54c300d29a449e242be88
bin/nc-set-field 8254a6bcb205b0a23c211845413d0f954f160675b4b94a9443412a1747c18ac3
bin/nc-insert-protocol 64052ef5ab2ea222866f4d4276d933e6467305520858ff6503a408bd7551e011
bin/nc-make-jump e88b977c34fa326f0c32f434dafc61cf9782bf951b2904b02d6287523e4a0406
bin/nc-provision-jumps f7b556099298c0b343b9f0698a9f5533533692095b62ffb0b145f00d1f7c720f
bin/nc-tclgen 789731c5509d7060ebc4b8a769d82fa620771af06be206e441be23929a66f947
bin/nc-document 66f386d38ccca924d97a2935d8fc9fbcbf0aef6333f5193d340684667468d241
bin/nc-revisions 0e2432045e722aa46380f3fbb55d8115668f9d6bf3eb00ba2ccb9e8513af5a44
bin/nc-diff-interface ef07e61bc5a9c8f3c46b023a09d7ec2c7c43538e391a99dbc8cbabace6c9f850
bin/nc-smat-diff 4e878b096c282a7f3cd36da73d05405d7cd82c198fd10c17b362e23743b75f43
bin/nc-regression 070cf2e9f0f0339e7c387b60fcea0ba8dc58f0b7431c59272a3deb16e4876b7f
bin/nc-msgs 173e89bfc9fb1b86b1c27fe634b7560b25597621f47e5c38f35e7e3dbdec8a6e
bin/hl7-field cb56984dfc9342563e05cdf0e3514a64df2099d24adeaa1d5b22cc6ce298eb73
bin/hl7-diff 2d965567ea4f737d97ea92313dfacd84b745cea74313d1be565a3234240b76f3
bin/len2nl d73136e353d7964f097b4a50993ba2a3364550e13b7ae5a1b43af00ed8eddbe3
bin/hl7-sanitize 7e2d883b4e49501eccf3db0ef27f4866dc664c3bde51fd6ad2a7804ca7de8e4e
bin/hl7-desanitize 78c5bda12e7374c056fac940ac1889ceca87f19a1e711dc658cc46b63b020925
bin/each-site 435ed2ec2f6a48e7d15c604cebd7bb49d4744a964770682498bf12174a0e2dea
bin/csv-to-table 9625f29bb874e096f0df4e19f9aa04a5beb61334d6b57f0c8b041d0e09ebf8bf
bin/table-to-csv 961e45971ef3284985aedfebd5bfe601adec5273ca8ec78c9077fae83d7278ac
# v0.9.4: durable checked-in demo fixture — a 3-site synthetic integrator
# (epic→ancout→codamx) that exercises cross-site route fan-out + fan-in. PHI-
# free. 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 — the v0.9.3 fixture-design bug, resolved here).
fixtures/integrator/epic/NetConfig 3a2c18cbff90f2e4d6962933de1e4b036eacf3621fc64d00f1d04c419aae36cc
fixtures/integrator/ancout/NetConfig e14504868e9d853f307c4018ecdb5f601fd1cf6724a3ebca5d2b6edf49187a0f
fixtures/integrator/codamx/NetConfig 850e1c8b4a1cc87ce6cb1c12c70f4526090a895d4150aa16bc0efbd1338966a6

View File

@ -1 +1 @@
0.9.3 0.9.4

72
bin/_nc_common.sh Executable file
View File

@ -0,0 +1,72 @@
#!/usr/bin/env bash
# _nc_common.sh — shared resolver sourced by every bin/ short-command wrapper
# AND by the bash-completion script (bin/nc-completion.bash).
#
# It is NOT a standalone tool — it only defines helpers. The wrappers are thin:
# they resolve the lib/ toolkit dir, then exec the right lib/nc-*.sh with the
# user's args mapped to that tool's flags. Putting the wrappers on PATH is what
# makes `tbn adt` work directly (no `larry tools` / `nc-larry` prefix).
#
# Resolution order for the lib/ dir (first hit wins):
# 1. $LARRY_LIB_DIR if set and valid
# 2. <dir-of-this-bin>/../lib (repo layout: bin/.. /lib)
# 3. $LARRY_HOME/lib (installed layout)
# 4. <dir-of-this-bin>/../../lib (defensive: bin symlinked one level deeper)
#
# Site/thread enumeration is LIVE off the NetConfig tree under $HCIROOT, reusing
# the SAME parser the tools use (lib/nc-parse.sh) so completion can never drift
# from what the tools actually see.
# --- lib/ dir resolution -----------------------------------------------------
_nc_resolve_lib() {
if [ -n "${LARRY_LIB_DIR:-}" ] && [ -f "$LARRY_LIB_DIR/nc-parse.sh" ]; then
printf '%s' "$LARRY_LIB_DIR"; return 0
fi
# dir of the *real* file backing this source (follow one symlink level)
local src="${BASH_SOURCE[0]}"
if [ -L "$src" ]; then src="$(readlink "$src")"; fi
local bindir
bindir="$(cd "$(dirname "$src")" 2>/dev/null && pwd)"
local c
for c in "$bindir/../lib" "${LARRY_HOME:-$HOME/.larry}/lib" "$bindir/../../lib"; do
if [ -d "$c" ] && [ -f "$c/nc-parse.sh" ]; then
( cd "$c" && pwd ); return 0
fi
done
return 1
}
# --- HCIROOT resolution (env, then a couple of sane fallbacks) ----------------
_nc_hciroot() {
if [ -n "${HCIROOT:-}" ] && [ -d "$HCIROOT" ]; then printf '%s' "$HCIROOT"; return 0; fi
printf '%s' "${HCIROOT:-}"; return 0
}
# --- live site enumeration ----------------------------------------------------
# A "site" = an immediate subdir of $HCIROOT that contains a NetConfig file.
# This is what nc-find / nc-paths actually walk, so completion stays truthful.
_nc_sites() {
local root; root="$(_nc_hciroot)"
[ -n "$root" ] && [ -d "$root" ] || return 0
local d
for d in "$root"/*/; do
[ -f "${d}NetConfig" ] && basename "${d%/}"
done 2>/dev/null
}
# --- live thread enumeration --------------------------------------------------
# Every protocol (thread) name across every site's NetConfig, deduped. Uses the
# library parser so it matches the tools' view exactly. Optional arg = restrict
# to one site.
_nc_threads() {
local only_site="${1:-}"
local root lib; root="$(_nc_hciroot)"; lib="$(_nc_resolve_lib)" || return 0
[ -n "$root" ] && [ -d "$root" ] || return 0
local nc site
for nc in "$root"/*/NetConfig; do
[ -f "$nc" ] || continue
site="$(basename "$(dirname "$nc")")"
[ -n "$only_site" ] && [ "$site" != "$only_site" ] && continue
bash "$lib/nc-parse.sh" list-protocols "$nc" 2>/dev/null
done | sort -u
}

9
bin/csv-to-table Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# csv-to-table — direct, on-PATH wrapper for lib/csv-to-table.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "csv-to-table: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/csv-to-table.sh" --help; fi
exec bash "$lib/csv-to-table.sh" "$@"

9
bin/each-site Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# each-site — direct, on-PATH wrapper for lib/each-site.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "each-site: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/each-site.sh" --help; fi
exec bash "$lib/each-site.sh" "$@"

9
bin/hl7-desanitize Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# hl7-desanitize — direct, on-PATH wrapper for lib/hl7-desanitize.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "hl7-desanitize: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/hl7-desanitize.sh" --help; fi
exec bash "$lib/hl7-desanitize.sh" "$@"

9
bin/hl7-diff Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# hl7-diff — direct, on-PATH wrapper for lib/hl7-diff.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "hl7-diff: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/hl7-diff.sh" --help; fi
exec bash "$lib/hl7-diff.sh" "$@"

9
bin/hl7-field Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# hl7-field — direct, on-PATH wrapper for lib/hl7-field.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "hl7-field: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/hl7-field.sh" --help; fi
exec bash "$lib/hl7-field.sh" "$@"

9
bin/hl7-sanitize Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# hl7-sanitize — direct, on-PATH wrapper for lib/hl7-sanitize.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "hl7-sanitize: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/hl7-sanitize.sh" --help; fi
exec bash "$lib/hl7-sanitize.sh" "$@"

9
bin/len2nl Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# len2nl — direct, on-PATH wrapper for lib/len2nl.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "len2nl: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/len2nl.sh" --help; fi
exec bash "$lib/len2nl.sh" "$@"

162
bin/nc-completion.bash Normal file
View File

@ -0,0 +1,162 @@
# nc-completion.bash — dynamic bash tab-completion for the Cloverleaf-Larry
# short commands. Source this file (the installer adds it to your shell rc):
#
# source /path/to/cloverleaf-larry/bin/nc-completion.bash
#
# THREE LEVELS, all enumerated LIVE off the NetConfig site tree under $HCIROOT
# (never a static list — it reflects exactly what the tools see right now):
#
# 1. COMMAND NAMES — `tb<TAB>` → tbn tbp tbh tbpr ; `nc-<TAB>` → nc-find …
# 2. SITE NAMES — `tbn <TAB>` → adt ancout codamx epic …
# 3. THREAD NAMES — `paths <TAB>`, `route_test --source-thread <TAB>`,
# `where <TAB>` → IB_ADT_epic MUX_ADT_ancout …
#
# Sites/threads are read through the SAME parser the tools use (lib/nc-parse.sh
# via bin/_nc_common.sh), so completion can never drift from reality. Results
# are cached per (HCIROOT, mtime-of-tree) for the life of the shell so repeated
# TABs are instant; the cache invalidates automatically when a NetConfig under
# $HCIROOT changes.
# Locate _nc_common.sh relative to THIS completion file (works for both the repo
# layout and an installed copy). Sourced once at load.
if [ -z "${_NC_COMPLETION_COMMON:-}" ]; then
_ncc_self="${BASH_SOURCE[0]}"
_NC_COMPLETION_COMMON="$(cd "$(dirname "$_ncc_self")" 2>/dev/null && pwd)/_nc_common.sh"
unset _ncc_self
fi
# --- cache key = HCIROOT + newest NetConfig mtime (cheap staleness check) ----
_nc_comp_cachekey() {
local root="${HCIROOT:-}"
[ -n "$root" ] && [ -d "$root" ] || { printf 'none'; return; }
local newest
newest="$(find "$root" -maxdepth 2 -name NetConfig -printf '%T@\n' 2>/dev/null | sort -rn | head -1)"
printf '%s@%s' "$root" "${newest:-0}"
}
# --- live site list (cached) -------------------------------------------------
_NC_COMP_SITES_KEY=""; _NC_COMP_SITES=""
_nc_comp_sites() {
local key; key="$(_nc_comp_cachekey)"
if [ "$key" != "$_NC_COMP_SITES_KEY" ]; then
[ -f "$_NC_COMPLETION_COMMON" ] && . "$_NC_COMPLETION_COMMON"
_NC_COMP_SITES="$(_nc_sites 2>/dev/null | tr '\n' ' ')"
_NC_COMP_SITES_KEY="$key"
fi
printf '%s' "$_NC_COMP_SITES"
}
# --- live thread list (cached) -----------------------------------------------
_NC_COMP_THREADS_KEY=""; _NC_COMP_THREADS=""
_nc_comp_threads() {
local key; key="$(_nc_comp_cachekey)"
if [ "$key" != "$_NC_COMP_THREADS_KEY" ]; then
[ -f "$_NC_COMPLETION_COMMON" ] && . "$_NC_COMPLETION_COMMON"
_NC_COMP_THREADS="$(_nc_threads 2>/dev/null | tr '\n' ' ')"
_NC_COMP_THREADS_KEY="$key"
fi
printf '%s' "$_NC_COMP_THREADS"
}
# --- generic completer: offer sites + threads (union) for a name argument ----
# Most short commands take "a site OR a thread"; offering both satisfies every
# documented level (tbn→sites, paths/where→threads) without over-fitting.
_nc_complete_names() {
local cur="${COMP_WORDS[COMP_CWORD]}"
local pool="$(_nc_comp_sites) $(_nc_comp_threads)"
COMPREPLY=( $(compgen -W "$pool" -- "$cur") )
}
# --- threads-only completer (route_test --source-thread, paths, where) -------
_nc_complete_threads_only() {
local cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=( $(compgen -W "$(_nc_comp_threads)" -- "$cur") )
}
# --- sites-only completer ----------------------------------------------------
_nc_complete_sites_only() {
local cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=( $(compgen -W "$(_nc_comp_sites)" -- "$cur") )
}
# --- per-command completers ---------------------------------------------------
# tbn: thread-name search, but a site name is the natural first thing operators
# type (tbn adt) — so offer the union (sites + threads). Same for tbh/tbpr.
_nc_complete_tbn() { _nc_complete_names; }
_nc_complete_tbh() { _nc_complete_names; }
_nc_complete_tbpr() {
# process-name search → no live process index; fall back to threads+sites.
_nc_complete_names
}
# tbp: v1 "thread by port". The task wants this to surface thread names on TAB,
# so we complete against threads (the operator can still type a bare port).
_nc_complete_tbp() { _nc_complete_threads_only; }
# where: positional <thread>.
_nc_complete_where() { _nc_complete_threads_only; }
# paths: first arg <thread>, optional 2nd arg <site>, then flags.
_nc_complete_paths() {
local cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}"
case "$cur" in
-*) COMPREPLY=( $(compgen -W "--up --down --site-only --all --site --format --hciroot" -- "$cur") ); return ;;
esac
case "$prev" in
--site) _nc_complete_sites_only; return ;;
--format) COMPREPLY=( $(compgen -W "v1 table tsv jsonl nodes" -- "$cur") ); return ;;
esac
# 1st positional → thread; 2nd positional → site. Count prior non-flag words.
local i nonflag=0
for ((i=1; i<COMP_CWORD; i++)); do
case "${COMP_WORDS[i]}" in -*) ;; *) ((nonflag++)) ;; esac
done
if [ "$nonflag" -eq 0 ]; then _nc_complete_threads_only
elif [ "$nonflag" -eq 1 ]; then _nc_complete_sites_only
else _nc_complete_names; fi
}
# route_test: <thread> <file>, or --source-thread <thread> [--file <file>].
_nc_complete_route_test() {
local cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}"
case "$prev" in
--source-thread|--thread) _nc_complete_threads_only; return ;;
--file) COMPREPLY=( $(compgen -f -- "$cur") ); return ;;
esac
case "$cur" in
-*) COMPREPLY=( $(compgen -W "--source-thread --file --dry-run" -- "$cur") ); return ;;
esac
# bare first positional → thread; subsequent → file path.
local i nonflag=0
for ((i=1; i<COMP_CWORD; i++)); do
case "${COMP_WORDS[i]}" in --source-thread|--thread|--file) ((i++)) ;; -*) ;; *) ((nonflag++)) ;; esac
done
if [ "$nonflag" -eq 0 ]; then _nc_complete_threads_only
else COMPREPLY=( $(compgen -f -- "$cur") ); fi
}
# nc-find: mode-aware (after --name/--where/etc, offer names; after --port, nothing).
_nc_complete_nc_find() {
local cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}"
case "$prev" in
--name|--where) _nc_complete_names; return ;;
--process) _nc_complete_threads_only; return ;;
--format) COMPREPLY=( $(compgen -W "table tsv jsonl" -- "$cur") ); return ;;
--hciroot) COMPREPLY=( $(compgen -d -- "$cur") ); return ;;
esac
COMPREPLY=( $(compgen -W "--name --port --host --process --where --xlate --tclproc --format --hciroot --case-sensitive" -- "$cur") )
}
# nc-paths shares the paths completer.
_nc_complete_nc_paths() { _nc_complete_paths; }
# Register. Short commands first (the headline ergonomics), then full names.
complete -F _nc_complete_tbn tbn
complete -F _nc_complete_tbp tbp
complete -F _nc_complete_tbh tbh
complete -F _nc_complete_tbpr tbpr
complete -F _nc_complete_where where
complete -F _nc_complete_paths paths
complete -F _nc_complete_route_test route_test
complete -F _nc_complete_nc_find nc-find
complete -F _nc_complete_nc_paths nc-paths

9
bin/nc-create-thread Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-create-thread — direct, on-PATH wrapper for lib/nc-create-thread.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-create-thread: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-create-thread.sh" --help; fi
exec bash "$lib/nc-create-thread.sh" "$@"

9
bin/nc-diff-interface Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-diff-interface — direct, on-PATH wrapper for lib/nc-diff-interface.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-diff-interface: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-diff-interface.sh" --help; fi
exec bash "$lib/nc-diff-interface.sh" "$@"

9
bin/nc-document Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-document — direct, on-PATH wrapper for lib/nc-document.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-document: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-document.sh" --help; fi
exec bash "$lib/nc-document.sh" "$@"

9
bin/nc-engine Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-engine — direct, on-PATH wrapper for lib/nc-engine.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-engine: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-engine.sh" --help; fi
exec bash "$lib/nc-engine.sh" "$@"

9
bin/nc-find Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-find — direct, on-PATH wrapper for lib/nc-find.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-find: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-find.sh" --help; fi
exec bash "$lib/nc-find.sh" "$@"

9
bin/nc-inbound Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-inbound — direct, on-PATH wrapper for lib/nc-inbound.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-inbound: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-inbound.sh" --help; fi
exec bash "$lib/nc-inbound.sh" "$@"

9
bin/nc-insert-protocol Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-insert-protocol — direct, on-PATH wrapper for lib/nc-insert-protocol.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-insert-protocol: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-insert-protocol.sh" --help; fi
exec bash "$lib/nc-insert-protocol.sh" "$@"

9
bin/nc-make-jump Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-make-jump — direct, on-PATH wrapper for lib/nc-make-jump.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-make-jump: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-make-jump.sh" --help; fi
exec bash "$lib/nc-make-jump.sh" "$@"

9
bin/nc-msgs Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-msgs — direct, on-PATH wrapper for lib/nc-msgs.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-msgs: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-msgs.sh" --help; fi
exec bash "$lib/nc-msgs.sh" "$@"

9
bin/nc-parse Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-parse — direct, on-PATH wrapper for lib/nc-parse.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-parse: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-parse.sh" --help; fi
exec bash "$lib/nc-parse.sh" "$@"

9
bin/nc-paths Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-paths — direct, on-PATH wrapper for lib/nc-paths.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-paths: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-paths.sh" --help; fi
exec bash "$lib/nc-paths.sh" "$@"

9
bin/nc-provision-jumps Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-provision-jumps — direct, on-PATH wrapper for lib/nc-provision-jumps.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-provision-jumps: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-provision-jumps.sh" --help; fi
exec bash "$lib/nc-provision-jumps.sh" "$@"

9
bin/nc-regression Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-regression — direct, on-PATH wrapper for lib/nc-regression.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-regression: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-regression.sh" --help; fi
exec bash "$lib/nc-regression.sh" "$@"

9
bin/nc-revisions Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-revisions — direct, on-PATH wrapper for lib/nc-revisions.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-revisions: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-revisions.sh" --help; fi
exec bash "$lib/nc-revisions.sh" "$@"

9
bin/nc-set-field Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-set-field — direct, on-PATH wrapper for lib/nc-set-field.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-set-field: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-set-field.sh" --help; fi
exec bash "$lib/nc-set-field.sh" "$@"

9
bin/nc-smat-diff Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-smat-diff — direct, on-PATH wrapper for lib/nc-smat-diff.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-smat-diff: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-smat-diff.sh" --help; fi
exec bash "$lib/nc-smat-diff.sh" "$@"

9
bin/nc-status Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-status — direct, on-PATH wrapper for lib/nc-status.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-status: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-status.sh" --help; fi
exec bash "$lib/nc-status.sh" "$@"

9
bin/nc-table Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-table — direct, on-PATH wrapper for lib/nc-table.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-table: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-table.sh" --help; fi
exec bash "$lib/nc-table.sh" "$@"

9
bin/nc-tclgen Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-tclgen — direct, on-PATH wrapper for lib/nc-tclgen.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-tclgen: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-tclgen.sh" --help; fi
exec bash "$lib/nc-tclgen.sh" "$@"

9
bin/nc-xlate Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# nc-xlate — direct, on-PATH wrapper for lib/nc-xlate.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "nc-xlate: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/nc-xlate.sh" --help; fi
exec bash "$lib/nc-xlate.sh" "$@"

22
bin/paths Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env bash
# paths — route-chain PATH tracer. Short, directly-invokable wrapper.
# Maps to: nc-paths.sh (full root-to-leaf chains, intra `-->` / cross-site `==>`).
#
# paths IB_ADT_epic epic # downstream+upstream chains for a thread
# paths IB_ADT_epic epic --down # downstream only
# paths --all # every chain across every site
# paths IB_ADT_cmx codamx --up # who feeds this thread (cross-site fan-in)
#
# All flags/args pass straight through to nc-paths.sh.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "${BASH_SOURCE[0]}"
exit 0
fi
[ $# -ge 1 ] || { echo "paths: usage: paths <thread> [site] [--up|--down|--all] | paths --all" >&2; exit 2; }
lib="$(_nc_resolve_lib)" || { echo "paths: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
exec bash "$lib/nc-paths.sh" "$@"

40
bin/route_test Executable file
View File

@ -0,0 +1,40 @@
#!/usr/bin/env bash
# route_test — run Cloverleaf route_test for a thread against a message file.
# Short, directly-invokable wrapper over nc-engine.sh route-test.
# NEEDS A LIVE ENGINE (hciroutetest). On a static fixture it dry-prints the cmd.
#
# route_test <thread> <msgfile> # positional (v1 form)
# route_test --source-thread <thread> <msgfile># named form (completes thread)
# route_test --source-thread <thread> --file <msgfile>
#
# Any extra flags (e.g. --dry-run, confirm=yes) pass through to nc-engine.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "${BASH_SOURCE[0]}"
exit 0
fi
lib="$(_nc_resolve_lib)" || { echo "route_test: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
# Accept either positional <thread> <file> or the named --source-thread/--file
# forms (the named form is what tab-completion targets). Unrecognized flags are
# collected and passed through to nc-engine route-test.
thread=""; file=""; pass=()
while [ $# -gt 0 ]; do
case "$1" in
--source-thread|--thread) shift; thread="$1" ;;
--file) shift; file="$1" ;;
-*) pass+=("$1") ;;
*) if [ -z "$thread" ]; then thread="$1"
elif [ -z "$file" ]; then file="$1"
else pass+=("$1"); fi ;;
esac
shift
done
[ -n "$thread" ] || { echo "route_test: missing thread (route_test <thread> <msgfile>)" >&2; exit 2; }
[ -n "$file" ] || { echo "route_test: missing message file (route_test <thread> <msgfile>)" >&2; exit 2; }
exec bash "$lib/nc-engine.sh" route-test "$thread" "$file" "${pass[@]}"

9
bin/table-to-csv Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# table-to-csv — direct, on-PATH wrapper for lib/table-to-csv.sh (no `larry tools` prefix needed).
# All args pass straight through; -h/--help is handled by the underlying tool.
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
lib="$(_nc_resolve_lib)" || { echo "table-to-csv: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
if [ $# -eq 0 ]; then exec bash "$lib/table-to-csv.sh" --help; fi
exec bash "$lib/table-to-csv.sh" "$@"

20
bin/tbh Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# tbh — "thread by host". Short, directly-invokable wrapper: tbh <host-substr>
# Cross-site substring match on PROTOCOL.HOST (v1 `tbh`). Maps to:
# nc-find.sh --host <host>.
#
# tbh 172.31 # every thread whose host contains '172.31'
# tbh codamx --format tsv
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "${BASH_SOURCE[0]}"
exit 0
fi
[ $# -ge 1 ] || { echo "tbh: usage: tbh <host-substr> [--format tsv|table|jsonl]" >&2; exit 2; }
lib="$(_nc_resolve_lib)" || { echo "tbh: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
host="$1"; shift
exec bash "$lib/nc-find.sh" --host "$host" "$@"

20
bin/tbn Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# tbn — "thread by name". Short, directly-invokable wrapper: tbn <pattern>
# Cross-site case-insensitive substring search on thread name (v1 `tbn`).
# Maps to: nc-find.sh --name <pattern> [extra flags pass through].
#
# tbn adt # every thread whose name contains 'adt', all sites
# tbn IB_ADT --format tsv # pass-through flags go straight to nc-find
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "${BASH_SOURCE[0]}"
exit 0
fi
[ $# -ge 1 ] || { echo "tbn: usage: tbn <name-pattern> [--format tsv|table|jsonl]" >&2; exit 2; }
lib="$(_nc_resolve_lib)" || { echo "tbn: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
pat="$1"; shift
exec bash "$lib/nc-find.sh" --name "$pat" "$@"

19
bin/tbp Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
# tbp — "thread by port". Short, directly-invokable wrapper: tbp <port>
# Cross-site exact port match (v1 `tbp`). Maps to: nc-find.sh --port <port>.
#
# tbp 7001 # every thread listening/sending on port 7001
# tbp 62043 --format tsv
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "${BASH_SOURCE[0]}"
exit 0
fi
[ $# -ge 1 ] || { echo "tbp: usage: tbp <port> [--format tsv|table|jsonl]" >&2; exit 2; }
lib="$(_nc_resolve_lib)" || { echo "tbp: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
port="$1"; shift
exec bash "$lib/nc-find.sh" --port "$port" "$@"

20
bin/tbpr Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# tbpr — "thread by process". Short, directly-invokable wrapper: tbpr <proc>
# Cross-site substring match on PROCESSNAME (v1 `tbpr`). Maps to:
# nc-find.sh --process <proc>.
#
# tbpr route_epic # every thread whose process contains 'route_epic'
# tbpr recv --format tsv
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "${BASH_SOURCE[0]}"
exit 0
fi
[ $# -ge 1 ] || { echo "tbpr: usage: tbpr <process-substr> [--format tsv|table|jsonl]" >&2; exit 2; }
lib="$(_nc_resolve_lib)" || { echo "tbpr: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
proc="$1"; shift
exec bash "$lib/nc-find.sh" --process "$proc" "$@"

17
bin/where Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env bash
# where — locate a thread's declaration. Short, directly-invokable wrapper:
# where <thread> # site / file / line of the thread's protocol block
# Maps to: nc-find.sh --where <thread> (v1 `<thread> where`).
set -o pipefail
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
. "$(cd "$(dirname "$_self")" && pwd)/_nc_common.sh"
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "${BASH_SOURCE[0]}"
exit 0
fi
[ $# -ge 1 ] || { echo "where: usage: where <thread>" >&2; exit 2; }
lib="$(_nc_resolve_lib)" || { echo "where: lib/ toolkit not found (set LARRY_LIB_DIR or LARRY_HOME)" >&2; exit 1; }
thr="$1"; shift
exec bash "$lib/nc-find.sh" --where "$thr" "$@"

View File

@ -0,0 +1,65 @@
# NetConfig — fixture site `ancout` (mid-chain relay).
#
# Receives the epic ADT feed on IB_ADT_muxS, routes intra-site to a local
# delivery thread, and ALSO re-hops cross-site into codamx — so the walker
# exercises a >2-site chain. Cross-site destination block is `XS_anc_to_codamx`
# (XS_ prefix, no protocol shares it).
#
# Chain (downstream from IB_ADT_muxS):
# IB_ADT_muxS --> OB_ADT_deliver (intra, local delivery leaf)
# IB_ADT_muxS --> RLY_ADT_codamx ==> codamx/IB_ADT_cmx (cross via XS_anc_to_codamx)
process recv_anc {
}
process route_anc {
}
protocol IB_ADT_muxS {
{ PROCESSNAME recv_anc }
{ PROTOCOL {
{ TYPE tcpip }
{ ISSERVER 1 }
{ PORT 62043 }
} }
{ DATAXLATE {
{ ROUTE {
{ ROUTE_DETAILS {
{ DEST OB_ADT_deliver }
} }
{ ROUTE_DETAILS {
{ DEST RLY_ADT_codamx }
} }
} }
} }
}
protocol OB_ADT_deliver {
{ PROCESSNAME route_anc }
{ PROTOCOL {
{ TYPE tcpip }
{ ISSERVER 0 }
{ PORT 9100 }
} }
}
protocol RLY_ADT_codamx {
{ PROCESSNAME route_anc }
{ PROTOCOL {
{ TYPE tcpip }
{ ISSERVER 0 }
{ PORT 62044 }
} }
{ DATAXLATE {
{ ROUTE {
{ ROUTE_DETAILS {
{ DEST XS_anc_to_codamx }
} }
} }
} }
}
destination XS_anc_to_codamx {
{ SITE codamx }
{ THREAD IB_ADT_cmx }
{ PORT 62044 }
}

View File

@ -0,0 +1,39 @@
# NetConfig — fixture site `codamx` (terminal delivery site).
#
# Receives from BOTH epic (direct, via XS_to_codamx) and ancout (relay, via
# XS_anc_to_codamx) on IB_ADT_cmx, then delivers to a single outbound leaf.
# This makes IB_ADT_cmx a cross-site FAN-IN point (two upstream feeders) and
# gives the walker a clean terminal leaf.
#
# Chain (downstream from IB_ADT_cmx):
# IB_ADT_cmx --> OB_ADT_cmx_deliver (intra, leaf)
process recv_cmx {
}
process route_cmx {
}
protocol IB_ADT_cmx {
{ PROCESSNAME recv_cmx }
{ PROTOCOL {
{ TYPE tcpip }
{ ISSERVER 1 }
{ PORT 62044 }
} }
{ DATAXLATE {
{ ROUTE {
{ ROUTE_DETAILS {
{ DEST OB_ADT_cmx_deliver }
} }
} }
} }
}
protocol OB_ADT_cmx_deliver {
{ PROCESSNAME route_cmx }
{ PROTOCOL {
{ TYPE tcpip }
{ ISSERVER 0 }
{ PORT 39500 }
} }
}

View File

@ -0,0 +1,87 @@
# NetConfig — fixture site `epic` (entry / fan-out site).
#
# DURABLE CHECKED-IN FIXTURE for nc-paths cross-site walking + route fan-out.
# Synthetic, PHI-free, ~deliberately small. Grammar matches lib/nc-parse.sh
# (protocol/process/destination top-level blocks; { PROTOCOL { PORT/ISSERVER } };
# DATAXLATE { ROUTE { ROUTE_DETAILS { DEST <name> } } }).
#
# CONFLICT RESOLVED (the v0.9.3 fixture-design bug): a destination BLOCK must
# never share a NAME with a LOCAL protocol in the SAME site. nc-paths.sh
# _xsite_down_targets (lib/nc-paths.sh:378) skips any DEST whose name is also a
# local protocol — so a colliding dest-block name SUPPRESSES the cross-site hop.
# Here every cross-site destination block is prefixed `XS_` (XS = cross-site)
# and no protocol uses that prefix. Local intra-site DEST targets are bare
# protocol names; cross-site DEST targets are the `XS_*` block names.
#
# Topology:
# IB_ADT_epic (server :7001, multi-route inbound)
# --> MUX_ADT_ancout (intra) ==> ancout/IB_ADT_muxS (cross via XS_to_ancout)
# --> MUX_ADT_codamx (intra) ==> codamx/IB_ADT_cmx (cross via XS_to_codamx)
process recv_epic {
}
process route_epic {
}
protocol IB_ADT_epic {
{ PROCESSNAME recv_epic }
{ PROTOCOL {
{ TYPE tcpip }
{ ISSERVER 1 }
{ PORT 7001 }
} }
{ DATAXLATE {
{ ROUTE {
{ ROUTE_DETAILS {
{ DEST MUX_ADT_ancout }
} }
{ ROUTE_DETAILS {
{ DEST MUX_ADT_codamx }
} }
} }
} }
}
protocol MUX_ADT_ancout {
{ PROCESSNAME route_epic }
{ PROTOCOL {
{ TYPE tcpip }
{ ISSERVER 0 }
{ PORT 62043 }
} }
{ DATAXLATE {
{ ROUTE {
{ ROUTE_DETAILS {
{ DEST XS_to_ancout }
} }
} }
} }
}
protocol MUX_ADT_codamx {
{ PROCESSNAME route_epic }
{ PROTOCOL {
{ TYPE tcpip }
{ ISSERVER 0 }
{ PORT 62044 }
} }
{ DATAXLATE {
{ ROUTE {
{ ROUTE_DETAILS {
{ DEST XS_to_codamx }
} }
} }
} }
}
destination XS_to_ancout {
{ SITE ancout }
{ THREAD IB_ADT_muxS }
{ PORT 62043 }
}
destination XS_to_codamx {
{ SITE codamx }
{ THREAD IB_ADT_cmx }
{ PORT 62044 }
}

View File

@ -255,6 +255,21 @@ fetch lib/nc-insert-protocol.sh "$LARRY_HOME/lib/nc-insert-protocol.sh"
fetch lib/hl7-diff.sh "$LARRY_HOME/lib/hl7-diff.sh" fetch lib/hl7-diff.sh "$LARRY_HOME/lib/hl7-diff.sh"
fetch lib/nc-regression.sh "$LARRY_HOME/lib/nc-regression.sh" fetch lib/nc-regression.sh "$LARRY_HOME/lib/nc-regression.sh"
fetch lib/journal.sh "$LARRY_HOME/lib/journal.sh" fetch lib/journal.sh "$LARRY_HOME/lib/journal.sh"
# bin/ — short, directly-invokable command wrappers + dynamic tab-completion
# (v0.9.4). The full lib/ + bin/ set is reconciled by larry.sh self_update from
# MANIFEST on first launch; these explicit fetches make the short commands +
# completion available immediately, before the first `larry` launch.
fetch bin/_nc_common.sh "$LARRY_HOME/bin/_nc_common.sh"
fetch bin/nc-completion.bash "$LARRY_HOME/bin/nc-completion.bash"
for _w in tbn tbp tbh tbpr where paths route_test \
nc-parse nc-paths nc-find nc-inbound nc-status nc-engine nc-xlate nc-table \
nc-create-thread nc-set-field nc-insert-protocol nc-make-jump nc-provision-jumps \
nc-tclgen nc-document nc-revisions nc-diff-interface nc-smat-diff nc-regression \
nc-msgs hl7-field hl7-diff len2nl hl7-sanitize hl7-desanitize each-site \
csv-to-table table-to-csv; do
fetch "bin/$_w" "$LARRY_HOME/bin/$_w"
done
chmod +x "$LARRY_HOME/bin/"* 2>/dev/null || true
fetch VERSION "$LARRY_HOME/VERSION" fetch VERSION "$LARRY_HOME/VERSION"
fetch MANUAL.md "$LARRY_HOME/MANUAL.md" fetch MANUAL.md "$LARRY_HOME/MANUAL.md"
# Bryan-curated inbound-systems lookup — seed only if absent (never clobber edits). # Bryan-curated inbound-systems lookup — seed only if absent (never clobber edits).
@ -313,6 +328,40 @@ else
warn "cannot write to $LARRY_BIN_DIR — invoke larry directly as: $LARRY_HOME/larry.sh" warn "cannot write to $LARRY_BIN_DIR — invoke larry directly as: $LARRY_HOME/larry.sh"
fi fi
# ─────────────────────────────────────────────────────────────────────────────
# Short, directly-invokable commands onto PATH + dynamic tab-completion (v0.9.4)
#
# Symlink every wrapper in $LARRY_HOME/bin/ into $LARRY_BIN_DIR so `tbn adt`,
# `paths`, `route_test`, `nc-find` … work DIRECTLY (no `larry tools` prefix).
# Symlinks (not copies) so a self_update of $LARRY_HOME/bin stays authoritative.
# _nc_common.sh + nc-completion.bash are NOT linked (sourced helpers, not cmds).
# ─────────────────────────────────────────────────────────────────────────────
if [ -d "$LARRY_BIN_DIR" ] && [ -w "$LARRY_BIN_DIR" ] && [ -d "$LARRY_HOME/bin" ]; then
_linked=0
for _t in "$LARRY_HOME/bin/"*; do
_b="$(basename "$_t")"
case "$_b" in _nc_common.sh|nc-completion.bash|jq|jq.exe) continue ;; esac
ln -sf "$_t" "$LARRY_BIN_DIR/$_b" 2>/dev/null && _linked=$((_linked+1))
done
ok "linked $_linked short commands onto PATH ($LARRY_BIN_DIR) — e.g. 'tbn adt', 'paths', 'route_test'"
fi
# Tab-completion: append a guarded source line to the user's bash rc (idempotent).
_comp="$LARRY_HOME/bin/nc-completion.bash"
if [ -f "$_comp" ]; then
_rc=""
for _cand in "$HOME/.bashrc" "$HOME/.bash_profile"; do
[ -f "$_cand" ] && { _rc="$_cand"; break; }
done
[ -z "$_rc" ] && _rc="$HOME/.bashrc"
_line="[ -f \"$_comp\" ] && source \"$_comp\" # cloverleaf-larry tab-completion"
if ! grep -qF "$_comp" "$_rc" 2>/dev/null; then
printf '\n%s\n' "$_line" >> "$_rc" && ok "tab-completion enabled in $_rc (open a new shell or: source $_comp)"
else
ok "tab-completion already wired in $_rc"
fi
fi
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# v0.8.2 — optional PHI Presidio sidecar (free-text NER). # v0.8.2 — optional PHI Presidio sidecar (free-text NER).
# Closes V1 from Vera's PHI-leak audit. Opt-in install; larry runs in # Closes V1 from Vera's PHI-leak audit. Opt-in install; larry runs in

View File

@ -99,7 +99,7 @@ set -o pipefail
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Config # Config
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
LARRY_VERSION="0.9.2" LARRY_VERSION="0.9.4"
LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" LARRY_HOME="${LARRY_HOME:-$HOME/.larry}"
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────