cloverleaf-larry/bin/nc-completion.bash
Clover df1810cba7 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>
2026-06-08 11:58:25 -07:00

163 lines
6.8 KiB
Bash

# 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