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>
112 lines
3.8 KiB
Bash
Executable File
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
|