cloverleaf-larry/lib/nc-inbound.sh
Bryan Johnson e08f030df5 v0.3.0: initial release of Larry-Anywhere
Portable AI agent for Cloverleaf integration work. Pure bash + curl + jq.
Zero dependency on v1 wrapper scripts or v2 cloverleaf-tools.pyz.

27 native Anthropic tools:

NetConfig parsing (read)
  nc_list_protocols, nc_list_processes, nc_protocol_block,
  nc_protocol_field, nc_protocol_nested, nc_protocol_summary,
  nc_destinations, nc_sources, nc_xlate_refs, nc_tclproc_refs

NetConfig modification (journal-backed writes with rollback)
  nc_insert_protocol, nc_add_route, larry_rollback_list

Workflows
  nc_find_inbound, nc_make_jump (3-thread jump pattern), nc_find
  (tbn/tbp/tbh/tbpr/where replacements), nc_document, nc_diff_interface,
  nc_regression

Messages
  hl7_field, nc_msgs (smat is SQLite!), hl7_diff (with --ignore MSH.7)

File system
  read_file, list_dir, grep_files, glob_files, write_file, bash_exec

Validated against a 22-site real Cloverleaf test install. Five worked
examples end-to-end: jump-thread generation, smat MRN search, system
documentation, interface+connected diff, HL7-aware regression diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 09:46:20 -07:00

112 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# nc-inbound.sh — identify inbound threads in a Cloverleaf NetConfig.
#
# Three classes of inbound thread:
# 1. tcp-listen — PROTOCOL.ISSERVER=1, listens for upstream client TCP connections.
# This is "directly fed by upstream client systems" in the strict sense.
# 2. icl-or-file — OBWORKASIB=1, TYPE=file. Fed via Cloverleaf's inter-cloverleaf
# link (ICL) or file-drop from another internal source.
# 3. file-edge — OBWORKASIB=1 + TYPE=file + no ICLSERVERPORT, suggests file-drop
# from an external system (less common).
#
# Usage:
# nc-inbound.sh <netconfig> [--mode tcp-listen|icl-or-file|all] [--format tsv|jsonl|table]
#
# Defaults: --mode all --format tsv
#
# TSV columns: name, process, class, port, host, type
set -u
set -o pipefail
NC_SELF="$0"
LIB_DIR="$(cd "$(dirname "$NC_SELF")" && pwd)"
NCP="$LIB_DIR/nc-parse.sh"
die() { printf 'nc-inbound: %s\n' "$*" >&2; exit 1; }
NC=""
MODE="all"
FMT="tsv"
while [ $# -gt 0 ]; do
case "$1" in
--mode) shift; MODE="$1" ;;
--format) shift; FMT="$1" ;;
-h|--help) sed -n '2,18p' "$NC_SELF"; exit 0 ;;
-*) die "unknown flag: $1" ;;
*) [ -z "$NC" ] && NC="$1" || die "extra arg: $1" ;;
esac
shift
done
[ -n "$NC" ] || die "usage: $NC_SELF <netconfig> [--mode ...] [--format ...]"
[ -f "$NC" ] || die "not a file: $NC"
case "$MODE" in tcp-listen|icl-or-file|all) ;; *) die "bad --mode: $MODE" ;; esac
case "$FMT" in tsv|jsonl|table) ;; *) die "bad --format: $FMT" ;; esac
# Build records: TSV stream emit
collect() {
local n
"$NCP" list-protocols "$NC" | while IFS= read -r n; do
local pname obib outonly ptype phost pport isserver klass
pname=$("$NCP" protocol-field "$NC" "$n" PROCESSNAME | head -1)
obib=$("$NCP" protocol-field "$NC" "$n" OBWORKASIB | head -1)
outonly=$("$NCP" protocol-field "$NC" "$n" OUTBOUNDONLY | head -1)
ptype=$("$NCP" protocol-nested "$NC" "$n" PROTOCOL.TYPE 2>/dev/null | head -1)
phost=$("$NCP" protocol-nested "$NC" "$n" PROTOCOL.HOST 2>/dev/null | head -1)
pport=$("$NCP" protocol-nested "$NC" "$n" PROTOCOL.PORT 2>/dev/null | head -1)
isserver=$("$NCP" protocol-nested "$NC" "$n" PROTOCOL.ISSERVER 2>/dev/null | head -1)
# Clean empty-brace markers
phost=$(printf '%s' "$phost" | sed 's/^{}$//')
pport=$(printf '%s' "$pport" | sed 's/^{}$//')
if [ "$isserver" = "1" ]; then
klass="tcp-listen"
elif [ "$obib" = "1" ]; then
klass="icl-or-file"
else
continue # not inbound — skip
fi
# Mode filter
case "$MODE" in
tcp-listen) [ "$klass" = "tcp-listen" ] || continue ;;
icl-or-file) [ "$klass" = "icl-or-file" ] || continue ;;
esac
printf "%s\t%s\t%s\t%s\t%s\t%s\n" \
"$n" "${pname:-}" "$klass" "${pport:-}" "${phost:-}" "${ptype:-}"
done
}
case "$FMT" in
tsv)
printf "name\tprocess\tclass\tport\thost\ttype\n"
collect
;;
jsonl)
collect | awk -F'\t' '
function esc(s) { gsub(/\\/, "\\\\", s); gsub(/"/, "\\\"", s); return s }
{
printf "{\"name\":\"%s\",\"process\":\"%s\",\"class\":\"%s\",\"port\":\"%s\",\"host\":\"%s\",\"type\":\"%s\"}\n",
esc($1), esc($2), esc($3), esc($4), esc($5), esc($6)
}'
;;
table)
{
printf "name\tprocess\tclass\tport\thost\ttype\n"
collect
} | awk -F'\t' '
{ for (i=1;i<=NF;i++){ if (length($i)>w[i]) w[i]=length($i); cell[NR,i]=$i }; rows=NR; cols=NF }
END {
for (r=1; r<=rows; r++) {
for (c=1; c<=cols; c++) printf "%-*s ", w[c], cell[r,c]
printf "\n"
if (r==1) { for (c=1; c<=cols; c++) for (k=0;k<w[c];k++) printf "-"; for (c=1; c<=cols; c++) printf " "; printf "\n" }
}
}'
;;
esac