diff --git a/CHANGELOG.md b/CHANGELOG.md index c2e684f..eb8aa4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 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 ` prefix.** (Clover) + +- **`bin/` of thin wrappers, symlinked onto PATH by the installer.** `tbn adt`, + `tbp 7001`, `tbh 172.31`, `tbpr route_epic`, `where `, `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 `→ adt …), + (3) **thread names** (`paths `, `where `, `route_test + --source-thread `). 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 **Upgrade to broker-mode now WIPES the now-obsolete local Anthropic/OAuth diff --git a/MANIFEST b/MANIFEST index ab34df6..b3f71df 100644 --- a/MANIFEST +++ b/MANIFEST @@ -23,17 +23,17 @@ # scripts/make-manifest.sh and bump VERSION. # Top-level scripts -larry.sh bd3bd27898afce693b44f75eb4fa3fab44b1a963d9b43f5559f5d9d0a5516e40 +larry.sh 32c81f6ca2e677756c1efc851337d71cd553f8a0bbf96f2eaeb5e8d8ec68377d larry-tunnel.sh 6b050e4eeab15669f4858eaf3b807f168f211ced07815db9521bc40a093f6aaa larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831 larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0 -install-larry.sh 072a036ad5bbf80e866cfd2dd74de50f8defd69a3f835032579b0cb9d421ad5b +install-larry.sh 0a7bc75e75ebfe75eb9c47bda259b31072b79003af42a8e7f40a7e48469a874f uninstall-larry.sh c53ad2d8354c7adeb243b541f027f3f481e4a8661eecfd7af14d7ca53cfcaad9 # Metadata -VERSION 38ecaa1e4c36c6691944c83df7671fca8b86f5cbf2d4e22c0012aa52df14b149 +VERSION a61cc7a990558a2b76c4a814eb233267e219158dcf184b6b30ce458d27027674 MANUAL.md 5ff54d6d5fae826f8b3da1eb3be6476076bb15f9b1417a4de285e59ea37e1b1f -CHANGELOG.md 934007dc1b08b6c90120f009e3cc7870815e7b251fdf8f6629aa4c004c866017 +CHANGELOG.md 8eacdea7711e4e22b1afb0f79f7deb970881a133ca6277732ba4ba16ac1b194f # Agent personas (system-prompt overlays) agents/larry.md 0a1ef737e7fc133ab35be09f79c3a4df33de814e0404b69b950932d0c8a01be1 @@ -116,3 +116,56 @@ lib/nc-diff-interface.sh c922d10323f06346efa53ada68b44d32d9568ff0bd848c59af34041 lib/nc-find.sh 2264877c56100378a1b780d640dcaa806aa5501ddd204c6b6a8eb5d3e07bf966 lib/nc-insert-protocol.sh ad1fa0bafbf4fdfb12bad20f9c22c3eed519f8846774331e26aa9becd6f8898a 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 diff --git a/VERSION b/VERSION index 965065d..a602fc9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.3 +0.9.4 diff --git a/bin/_nc_common.sh b/bin/_nc_common.sh new file mode 100755 index 0000000..44ad679 --- /dev/null +++ b/bin/_nc_common.sh @@ -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. /../lib (repo layout: bin/.. /lib) +# 3. $LARRY_HOME/lib (installed layout) +# 4. /../../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 +} diff --git a/bin/csv-to-table b/bin/csv-to-table new file mode 100755 index 0000000..83509d6 --- /dev/null +++ b/bin/csv-to-table @@ -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" "$@" diff --git a/bin/each-site b/bin/each-site new file mode 100755 index 0000000..431a869 --- /dev/null +++ b/bin/each-site @@ -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" "$@" diff --git a/bin/hl7-desanitize b/bin/hl7-desanitize new file mode 100755 index 0000000..1405325 --- /dev/null +++ b/bin/hl7-desanitize @@ -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" "$@" diff --git a/bin/hl7-diff b/bin/hl7-diff new file mode 100755 index 0000000..e6c8ae1 --- /dev/null +++ b/bin/hl7-diff @@ -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" "$@" diff --git a/bin/hl7-field b/bin/hl7-field new file mode 100755 index 0000000..6d8f6be --- /dev/null +++ b/bin/hl7-field @@ -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" "$@" diff --git a/bin/hl7-sanitize b/bin/hl7-sanitize new file mode 100755 index 0000000..d2aa67d --- /dev/null +++ b/bin/hl7-sanitize @@ -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" "$@" diff --git a/bin/len2nl b/bin/len2nl new file mode 100755 index 0000000..bb8f4b6 --- /dev/null +++ b/bin/len2nl @@ -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" "$@" diff --git a/bin/nc-completion.bash b/bin/nc-completion.bash new file mode 100644 index 0000000..603564e --- /dev/null +++ b/bin/nc-completion.bash @@ -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` → tbn tbp tbh tbpr ; `nc-` → nc-find … +# 2. SITE NAMES — `tbn ` → adt ancout codamx epic … +# 3. THREAD NAMES — `paths `, `route_test --source-thread `, +# `where ` → 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 . +_nc_complete_where() { _nc_complete_threads_only; } + +# paths: first arg , optional 2nd arg , 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 , or --source-thread [--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&2; exit 1; } +if [ $# -eq 0 ]; then exec bash "$lib/nc-create-thread.sh" --help; fi +exec bash "$lib/nc-create-thread.sh" "$@" diff --git a/bin/nc-diff-interface b/bin/nc-diff-interface new file mode 100755 index 0000000..8ef49a3 --- /dev/null +++ b/bin/nc-diff-interface @@ -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" "$@" diff --git a/bin/nc-document b/bin/nc-document new file mode 100755 index 0000000..0bea22f --- /dev/null +++ b/bin/nc-document @@ -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" "$@" diff --git a/bin/nc-engine b/bin/nc-engine new file mode 100755 index 0000000..1f3e473 --- /dev/null +++ b/bin/nc-engine @@ -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" "$@" diff --git a/bin/nc-find b/bin/nc-find new file mode 100755 index 0000000..f0f2c5a --- /dev/null +++ b/bin/nc-find @@ -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" "$@" diff --git a/bin/nc-inbound b/bin/nc-inbound new file mode 100755 index 0000000..024bdc8 --- /dev/null +++ b/bin/nc-inbound @@ -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" "$@" diff --git a/bin/nc-insert-protocol b/bin/nc-insert-protocol new file mode 100755 index 0000000..7e754bb --- /dev/null +++ b/bin/nc-insert-protocol @@ -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" "$@" diff --git a/bin/nc-make-jump b/bin/nc-make-jump new file mode 100755 index 0000000..d0e324f --- /dev/null +++ b/bin/nc-make-jump @@ -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" "$@" diff --git a/bin/nc-msgs b/bin/nc-msgs new file mode 100755 index 0000000..fd6e6f0 --- /dev/null +++ b/bin/nc-msgs @@ -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" "$@" diff --git a/bin/nc-parse b/bin/nc-parse new file mode 100755 index 0000000..7ea92cf --- /dev/null +++ b/bin/nc-parse @@ -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" "$@" diff --git a/bin/nc-paths b/bin/nc-paths new file mode 100755 index 0000000..e29b832 --- /dev/null +++ b/bin/nc-paths @@ -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" "$@" diff --git a/bin/nc-provision-jumps b/bin/nc-provision-jumps new file mode 100755 index 0000000..a4a8e9e --- /dev/null +++ b/bin/nc-provision-jumps @@ -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" "$@" diff --git a/bin/nc-regression b/bin/nc-regression new file mode 100755 index 0000000..86b73be --- /dev/null +++ b/bin/nc-regression @@ -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" "$@" diff --git a/bin/nc-revisions b/bin/nc-revisions new file mode 100755 index 0000000..34900c2 --- /dev/null +++ b/bin/nc-revisions @@ -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" "$@" diff --git a/bin/nc-set-field b/bin/nc-set-field new file mode 100755 index 0000000..64c1568 --- /dev/null +++ b/bin/nc-set-field @@ -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" "$@" diff --git a/bin/nc-smat-diff b/bin/nc-smat-diff new file mode 100755 index 0000000..a37b9fd --- /dev/null +++ b/bin/nc-smat-diff @@ -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" "$@" diff --git a/bin/nc-status b/bin/nc-status new file mode 100755 index 0000000..b4a6a18 --- /dev/null +++ b/bin/nc-status @@ -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" "$@" diff --git a/bin/nc-table b/bin/nc-table new file mode 100755 index 0000000..363e0b3 --- /dev/null +++ b/bin/nc-table @@ -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" "$@" diff --git a/bin/nc-tclgen b/bin/nc-tclgen new file mode 100755 index 0000000..0b975d0 --- /dev/null +++ b/bin/nc-tclgen @@ -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" "$@" diff --git a/bin/nc-xlate b/bin/nc-xlate new file mode 100755 index 0000000..9b807b8 --- /dev/null +++ b/bin/nc-xlate @@ -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" "$@" diff --git a/bin/paths b/bin/paths new file mode 100755 index 0000000..640d41a --- /dev/null +++ b/bin/paths @@ -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 [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" "$@" diff --git a/bin/route_test b/bin/route_test new file mode 100755 index 0000000..addc0ae --- /dev/null +++ b/bin/route_test @@ -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 # positional (v1 form) +# route_test --source-thread # named form (completes thread) +# route_test --source-thread --file +# +# 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 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 )" >&2; exit 2; } +[ -n "$file" ] || { echo "route_test: missing message file (route_test )" >&2; exit 2; } +exec bash "$lib/nc-engine.sh" route-test "$thread" "$file" "${pass[@]}" diff --git a/bin/table-to-csv b/bin/table-to-csv new file mode 100755 index 0000000..298b7bc --- /dev/null +++ b/bin/table-to-csv @@ -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" "$@" diff --git a/bin/tbh b/bin/tbh new file mode 100755 index 0000000..d4ff285 --- /dev/null +++ b/bin/tbh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# tbh — "thread by host". Short, directly-invokable wrapper: tbh +# Cross-site substring match on PROTOCOL.HOST (v1 `tbh`). Maps to: +# nc-find.sh --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 [--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" "$@" diff --git a/bin/tbn b/bin/tbn new file mode 100755 index 0000000..ef010c7 --- /dev/null +++ b/bin/tbn @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# tbn — "thread by name". Short, directly-invokable wrapper: tbn +# Cross-site case-insensitive substring search on thread name (v1 `tbn`). +# Maps to: nc-find.sh --name [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 [--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" "$@" diff --git a/bin/tbp b/bin/tbp new file mode 100755 index 0000000..e25ab40 --- /dev/null +++ b/bin/tbp @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# tbp — "thread by port". Short, directly-invokable wrapper: tbp +# Cross-site exact port match (v1 `tbp`). Maps to: nc-find.sh --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 [--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" "$@" diff --git a/bin/tbpr b/bin/tbpr new file mode 100755 index 0000000..670601f --- /dev/null +++ b/bin/tbpr @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# tbpr — "thread by process". Short, directly-invokable wrapper: tbpr +# Cross-site substring match on PROCESSNAME (v1 `tbpr`). Maps to: +# nc-find.sh --process . +# +# 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 [--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" "$@" diff --git a/bin/where b/bin/where new file mode 100755 index 0000000..424d1ab --- /dev/null +++ b/bin/where @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# where — locate a thread's declaration. Short, directly-invokable wrapper: +# where # site / file / line of the thread's protocol block +# Maps to: nc-find.sh --where (v1 ` 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 " >&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" "$@" diff --git a/fixtures/integrator/ancout/NetConfig b/fixtures/integrator/ancout/NetConfig new file mode 100644 index 0000000..7a2ac70 --- /dev/null +++ b/fixtures/integrator/ancout/NetConfig @@ -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 } +} diff --git a/fixtures/integrator/codamx/NetConfig b/fixtures/integrator/codamx/NetConfig new file mode 100644 index 0000000..e5ed5b2 --- /dev/null +++ b/fixtures/integrator/codamx/NetConfig @@ -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 } + } } +} diff --git a/fixtures/integrator/epic/NetConfig b/fixtures/integrator/epic/NetConfig new file mode 100644 index 0000000..b60c6fa --- /dev/null +++ b/fixtures/integrator/epic/NetConfig @@ -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 } } }). +# +# 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 } +} diff --git a/install-larry.sh b/install-larry.sh index 9fa3231..1e5d057 100755 --- a/install-larry.sh +++ b/install-larry.sh @@ -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/nc-regression.sh "$LARRY_HOME/lib/nc-regression.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 MANUAL.md "$LARRY_HOME/MANUAL.md" # 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" 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). # Closes V1 from Vera's PHI-leak audit. Opt-in install; larry runs in diff --git a/larry.sh b/larry.sh index 4521c04..4830ef2 100755 --- a/larry.sh +++ b/larry.sh @@ -99,7 +99,7 @@ set -o pipefail # ───────────────────────────────────────────────────────────────────────────── # Config # ───────────────────────────────────────────────────────────────────────────── -LARRY_VERSION="0.9.2" +LARRY_VERSION="0.9.4" LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" # ─────────────────────────────────────────────────────────────────────────────