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>
96 lines
7.1 KiB
Markdown
96 lines
7.1 KiB
Markdown
# Cloverleaf Operations — v3 Native Capability Reference
|
||
|
||
Larry-Anywhere v3 is **self-contained**. It does not invoke v1 bash scripts (`tbn`, `hlq`, `mr`, `mp`, `mg`, `awkcut`, `each_site`, etc.) and it does not invoke v2 `cloverleaf-tools.pyz`. Those layers can be present on a host; v3 ignores them.
|
||
|
||
Two kinds of capability:
|
||
|
||
1. **Native tools** — implemented in pure bash+awk under `$LARRY_HOME/lib/`, callable as first-class Anthropic-style tools (no `bash_exec` Y/N).
|
||
2. **Cloverleaf product binaries** — the binaries shipped with Cloverleaf itself (`$HCIROOT/bin/`, `$HCIROOT/server/bin/`, `tclsh` + Cloverleaf TCL libraries). These ARE part of Cloverleaf, not v1/v2. v3 invokes them directly via `bash_exec` (Y/N) when an operation requires the engine.
|
||
|
||
## What Larry has natively (Anthropic tools)
|
||
|
||
### NetConfig analysis — read
|
||
|
||
| tool | use for |
|
||
|---|---|
|
||
| `nc_list_protocols(netconfig)` | "what threads are in this site?" — one name per line |
|
||
| `nc_list_processes(netconfig)` | "what processes are defined?" |
|
||
| `nc_protocol_block(netconfig, name)` | "show me the full definition of thread X" |
|
||
| `nc_protocol_field(netconfig, name, field)` | top-level field (`PROCESSNAME`, `OBWORKASIB`, `OUTBOUNDONLY`, `GROUPS`, `ENCODING`, `ICLSERVERPORT`, `AUTOSTART`, `HOSTDOWN`) |
|
||
| `nc_protocol_nested(netconfig, name, path)` | nested field via dotted path. **Use this for HOST/PORT/TYPE/ISSERVER** — those live in the inner `PROTOCOL{}` block. e.g. path=`PROTOCOL.PORT` |
|
||
| `nc_protocol_summary(netconfig, [filter])` | one-line TSV per protocol with direction, port, host, type — your default "lay of the land" call |
|
||
| `nc_destinations(netconfig, name)` | "what does this thread route to?" — unique DEST list from DATAXLATE |
|
||
| `nc_xlate_refs(netconfig, [name])` | "what .xlt files are referenced?" — all or scoped to one protocol |
|
||
| `nc_find_inbound(netconfig, mode, format)` | "which threads are inbound?" — modes: `tcp-listen` (real upstream-client listeners, ISSERVER=1), `icl-or-file` (OBWORKASIB=1 internal mux/file inbounds), `all`. formats: tsv, jsonl, table |
|
||
|
||
### NetConfig modification — generate, then write via `write_file` (Y/N gated)
|
||
|
||
| tool | use for |
|
||
|---|---|
|
||
| `nc_make_jump(netconfig, inbound, new_host, jump_port, [process_old], [process_new], [encoding])` | Generate a jump-thread pair for cross-env data replay. Emits `to_<inbound>_server_jump` (OLD-side outbound tcpip-client), `fr_<inbound>_server_jump` (NEW-side server_jump inbound tcpip-server), AND the route-add snippet to splice into the OLD inbound's DATAXLATE. **Generation only — does not modify any file.** Larry uses `write_file` to actually persist, which goes through Y/N. |
|
||
|
||
When Larry needs to add the OLD-side jump block to an existing NetConfig, the pattern is:
|
||
1. `nc_make_jump(...)` → captures full text
|
||
2. `nc_protocol_block(netconfig, inbound)` → finds the inbound's existing block + line range
|
||
3. `read_file(netconfig)` → loads the whole file
|
||
4. Splice (in Larry's head): insert new block + route-add into the right spots
|
||
5. `write_file(netconfig, new_content)` → Y/N preview shows the unified diff
|
||
|
||
## What Larry invokes from Cloverleaf product binaries (via `bash_exec`, Y/N)
|
||
|
||
These are shipped with Cloverleaf. v3 invokes them directly — no v1 wrapper between.
|
||
|
||
| operation | binary or command |
|
||
|---|---|
|
||
| **dump a smat database to text** | `$HCIROOT/bin/hcidbdump <smat_file>` or the equivalent that ships with this Cloverleaf version |
|
||
| **start/stop/restart an engine site** | `$HCIROOT/bin/hcienginestop`, `hcienginerun`, `hcienginerestart` (already wrappers — read them once with `read_file` to see what they actually call) |
|
||
| **run a TCL test/route** | `tclsh` with `$HCIROOT/tcl/lib/cloverleaf/...` libraries on the auto-path. The `msi*` family (`msiAttach`, `msiGetStatSample`, etc.) is the Cloverleaf TCL API for smat/route access. |
|
||
| **engine connection status** | `hciconndump` or via TCL `msi*` calls |
|
||
| **check the netconfig is loadable** | Use `tclsh` to source it; engine will report parse errors |
|
||
|
||
When Larry doesn't know which binary fits an operation, the protocol is:
|
||
1. `list_dir("$HCIROOT/bin")` and `list_dir("$HCIROOT/server/bin")` to enumerate.
|
||
2. `read_file` on any wrapper script that looks relevant.
|
||
3. Propose the exact `bash_exec` command to Bryan with a `# why:` comment.
|
||
|
||
## NetConfig structural cheat-sheet
|
||
|
||
A site's `NetConfig` is TCL-style nested blocks. Top-level:
|
||
|
||
- `process <name> { ... }` — a process container (usually 5–15 per site).
|
||
- `protocol <name> { ... }` — a thread (the operational unit). Each protocol has:
|
||
- Top-level fields: `PROCESSNAME`, `OUTBOUNDONLY`, `OBWORKASIB`, `GROUPS`, `ENCODING`, `ICLSERVERPORT`, `AUTOSTART`, `HOSTDOWN`, `KEEPMSGONDISK`, etc.
|
||
- Inner `PROTOCOL { ... }` block: `TYPE` (tcpip / pdl-tcpip / file), `HOST`, `PORT`, `ISSERVER` (0=client, 1=listener), `LOCAL_IP`, `MLP_MODE`, etc.
|
||
- `DATAXLATE { ... }` block: the routing rules. Each route is `{ TRXID <regex> } { WILDCARD ON } { ROUTE_DETAILS { { DEST <thread> } { XLATE <.xlt> } { PROCS ... } { TYPE xlate|raw|generate } } }`.
|
||
- `RECVCONTROL`, `SAVECONTROL`, `TPS_INBOUND`, `TPS_OUTBOUND` — proc bindings.
|
||
|
||
## Direction inference (canonical)
|
||
|
||
Use `nc_find_inbound` rather than rolling this yourself, but for reference:
|
||
|
||
| if … | direction |
|
||
|---|---|
|
||
| `PROTOCOL.ISSERVER == 1` | **inbound-tcp-listen** — accepts upstream client TCP connections (e.g. listens for Epic). This is what Bryan means by "fed directly by upstream client systems." |
|
||
| `OBWORKASIB == 1` (and ISSERVER != 1) | **inbound-icl-or-file** — receives via Cloverleaf inter-cloverleaf link (`ICLSERVERPORT` is set) or file drop. Usually `TYPE=file`. |
|
||
| `OUTBOUNDONLY == 1` | **outbound** — TCP client pushing data to an external system. `HOST` and `PORT` give the target. |
|
||
| none of the above | **bidirectional / undefined** — rare; investigate. |
|
||
|
||
## The jump-thread pattern (Example 1)
|
||
|
||
For each inbound thread `T_in` on the OLD env, you want:
|
||
|
||
1. **On OLD** — modify NetConfig:
|
||
- Add a new outbound protocol `to_<T_in>_server_jump` (tcpip-client, points at new linux host:port).
|
||
- Add a route to `T_in`'s DATAXLATE block routing to that new outbound (TRXID `.*`, type raw, no xlate).
|
||
2. **On NEW** — modify the `server_jump` site's NetConfig:
|
||
- Add a new inbound protocol `fr_<T_in>_server_jump` (tcpip-server listening on same port, OBWORKASIB=1).
|
||
- Its DATAXLATE has one route: TRXID `.*` → DEST `<T_in>` (the existing inbound on NEW), type raw, no xlate.
|
||
|
||
Net result: data hitting `T_in` on OLD also flows to NEW via TCP, lands in `fr_<T_in>_server_jump`, gets injected into NEW's `T_in`, and follows NEW's normal downstream routing — letting Bryan validate the cloned environment with live OLD data.
|
||
|
||
Use `nc_make_jump` for the generation. Use `write_file` (Y/N) for the persistence.
|
||
|
||
## What this doc replaces
|
||
|
||
This replaces the earlier v0.2 cheat-sheet that listed v1 commands like `tbn`, `tbp`, `hlq`, `mr`, `mp`, `mg`, `hl`, etc. **v3 doesn't call those.** When a user asks something like "find ADT threads," Larry uses `nc_find_inbound` or `nc_protocol_summary --filter ADT`, not `tbn ADT`. The end-user value is the same; the implementation is owned by v3.
|