@davidorex/pi-context-cli
v0.31.0
Published
Auto-tracking command-line interface over the pi-context op-registry — every substrate op surfaces as a CLI command by reflection, no hardcoded command list
Readme
@davidorex/pi-context-cli
A command-line interface over the @davidorex/pi-context substrate operations.
The command set is auto-tracking: every operation in pi-context's op-registry whose surface is "use" surfaces as a CLI command by reflection. There is no hardcoded command list — adding an op to pi-context makes a new CLI command appear with zero changes to this package. Operations that depend on a pi runtime handle (e.g. list-tools) carry surface: "process" and are not surfaced here.
Install
npm i -g @davidorex/pi-context-cliThis provides a pi-context binary.
Usage
pi-context --help # grouped op index (by command class) + global flags
pi-context --version # print the package version (alias -v)
pi-context <op> --help # per-op help: synopsis + flags + examples + related commands
pi-context <op> --help --format json # the same help as a machine-readable JSON model
pi-context <op> [flags] # run an opEach op's flags derive from its parameter schema:
- scalar fields take
--field value(string/number);booleanfields are presence flags (--flag, or--flag true|false) - object / array / typeless fields take a JSON argument:
--field '<inline json>'or--field @path/to.json
Example:
pi-context read-block --block tasks
pi-context append-block-item --block issues --arrayKey issues --item @new-issue.json --autoId
pi-context read-block-page --block framework-gaps --offset 0 --limit 50
pi-context update --dryRunInput affordances
On top of the schema-derived flags, the CLI accepts a set of input conveniences. Each is additive — the schema-exact form (camelCase flags, explicit --arrayKey, a JSON --writer, separate --field/--op/--value) keeps working unchanged.
- kebab-case flags are accepted alongside the camelCase op-schema keys.
--dry-runresolves to--dryRun; any conventional kebab form resolves when its camelCase key exists. An unrecognized flag (kebab or otherwise) is still rejected. --idaliases the op's single id-param. When an op declares exactly one id-shaped parameter (itemId/parentId/taskId/unitId/ …),--id <value>resolves to it. An op that already declares a literalidparameter takes--iddirectly. An op with two id-params (e.g.complete-task,rename-canonical-id) rejects--idas ambiguous — name the explicit flag.--arrayKeyis derived from config for the block-mutation ops (append-block-item,update-block-item,upsert-block-item,remove-block-item, and the nested variants). Pass only--block <name>; the array key is read from that block'sconfig.block_kinds[].array_key. An explicit--arrayKeyoverrides the derivation.--writer kind:idshorthand.--writer human:[email protected]expands to{"kind":"human","user":"[email protected]"};--writer agent:claudeto{"kind":"agent","agent_id":"claude"}(monitor:→monitor_name,workflow:→workflow_step_id). The first colon delimits the kind; the identifier may itself contain colons.--where field:op:valueshorthand for the filter predicate:--where status:eq:donesets--field status --op eq --value done. Split on the first two colons only, so the value may contain colons.- CSV
--op in. When the comparison operator isin, a comma-separated--value a,b,cis split into the array["a","b","c"]. Order-independent —--op in --value a,b,cand--value a,b,c --op inare equivalent.
pi-context append-block-item --block framework-gaps --item @gap.json # arrayKey derived from config
pi-context find-references --id TASK-1 # --id → itemId
pi-context append-block-item --block tasks --item @t.json --writer human:[email protected]
pi-context filter-block-items --block tasks --where status:eq:done # field/op/value from one token
pi-context filter-block-items --block tasks --field tag --op in --value a,b,cContract preview + dry-run
For the block-mutation ops the CLI offers two pre-write affordances:
--show-schemapreviews a block op's contract and exits before any write — the array key, the required-field set, every field with its type (and enum values when declared), and the id pattern. Pass it with the op and--block <name>; no--itemis needed.append-block-item --dry-run(or--dryRun) validates the prospective whole file —{...existing, <arrayKey>: [...items, newItem]}against the block schema, exactly what a real append validates — and writes nothing. With--autoIdit reports the id that would be allocated. The output is[dry-run] PASS(or[dry-run] PASS — would append <id>); a schema-invalid item surfaces the field-named validation error.
pi-context append-block-item --block framework-gaps --show-schema # contract preview, no write
pi-context append-block-item --block tasks --item @t.json --dry-run # validate the prospective file, no write
pi-context append-block-item --block tasks --item @t.json --dry-run --autoId # also reports the allocated idOutput rendering
--format text|json|table selects how an op's result is rendered. --json is the exact alias of --format json.
text(default) — each op's human render: prose, a JSON.stringify'd value, or a read body with its paging footer.json— the{ ok, op, output }envelope ({ ok: false, op, error }on failure).outputis the un-stringified value (single-parse), read-capped at 50KB.table— a compact markdown table of a renderable row array (a read whose body is a collection, or a data op that returns an array). The projection is best-effort terse:idfirst when present, then up to three more fields (≤4 columns), cells one-lined and capped at 80 chars. A result that is not a complete tabular collection (prose, a non-array value, or an over-cap read) renders astextinstead — a degenerate table is never substituted for the real output.
pi-context read-block --block tasks --format table # markdown table of the tasks
pi-context filter-block-items --block tasks --where status:eq:open --format table
pi-context read-block --block tasks --json # ≡ --format jsonSchema-validation failures surface field-named guidance — which field and what constraint (e.g. `/gaps/0`: missing required field `description`) — rather than the raw validator phrasing, on both the text and --json surfaces.
Addressed reads return the whole addressed subtree (50KB-capped): read-schema --schemaName <name> --path <dotted.path> and read-config --registry <name> [--id <id>] return the complete node at that address — including all of its children — not a paged slice of one of its arrays.
pi-context pi-bound — constrained pi session
pi-context pi-bound [--grant <id>]... [...pi-args]pi-bound is a CLI process mode (not a substrate op): it launches a pi coding-agent session restricted to the composed pi-extension tool surface. On every launch it:
- runs
pi install -l <@davidorex/pi-project-workflows root>to register the extensions into the target dir's.pi/ - derives the static tool allowlist from the installed packages' generated
skills/*/SKILL.md(@davidorex/pi-context+@davidorex/pi-project-workflows) - always adds the built-in read-only tools
read,ls,grep,find - appends the bounded composites declared in the active substrate's
config.tool_operations[] - launches
pi --tools <union> ...pi-args
It runs from the process cwd and reads that dir's .pi-context.json for composites (warns, non-fatally, if absent).
Flags:
--grant <id>(repeatable) — scope the bounded composites to only the named ids. Default: all declared composites.- any other token — passed through verbatim to
pi(e.g.--continue/-cto resume a session).
pi-context pi-bound # launch with the full composed tool surface
pi-context pi-bound --grant grep-paths # restrict composites to a single named op
pi-context pi-bound -c # pass -c through to pi to resumeThis process mode replaces the former scripts/launch-constrained-pi.sh launch script.
pi-context update — drift-aware model update + conflict surfacing
pi-context update [--dryRun]update brings the installed schema model current with the packaged catalog. Per installed schema it consults the drift classification and routes by state: an in-sync schema is a no-op; a catalog-ahead schema re-syncs through the migration-aware path; a locally-modified / both-diverged schema is reconciled by a deterministic 3-way merge of base (the as-installed schema body in the object store, keyed by the recorded baseline content_hash) × ours (the installed schema) × theirs (the catalog schema). Disjoint edits auto-merge so both the user's and the catalog's changes survive (required / enum / array-valued type nodes merge as sets).
A schema whose per-path edits cannot be reconciled is left unmodified, and the conflict is SURFACED to the calling agent — update does not spawn a subordinate resolver. The conflict set is returned in the op output (the conflicts array, printed under --json) alongside a readable per-schema report on the default text surface. The report ends with a guidance line; the calling agent reconciles each conflicting schema and commits the reconciliation with pi-context resolve-conflict:
pi-context read-schema --schemaName <name> # inspect the current installed body
# resolve the conflicting paths into a reconciled draft-07 schema, then commit it:
pi-context resolve-conflict --schemaName <name> --schema '<reconciled-json>'resolve-conflict writes the reconciled body AND advances the merge base for that schema to the catalog, so the next update sees the schema as locally-modified and its deterministic merge takes the reconciled body (base === theirs → ours) — converging with zero conflicts and preserving the resolution. A bare write-schema does not advance the base, so update would re-report the same conflict on every run. Omit --schema to treat the current on-disk body as already reconciled and only advance the base.
update also additively propagates catalog-new config-registry entries (relation_types / invariants / block_kinds / lenses) absent from the substrate config, preserving every user-authored entry and any locally-diverged body of an existing entry (additive-only — present entries are never overwritten; the added ids are reported under registryAdditions).
A catalog-ahead schema whose resync is refused (blocked) carries its diagnostic under blockedDetail (one entry per blocked schema): the refusal reason — no-migration-chain (no shipped chain reaches the catalog version) vs validation-failed (the forward-migrated items fail the catalog schema) — the installed→catalog version pair, and for a validation failure the per-item failures naming the failing item id, field, and constraint. Under --json the blockedDetail array rides in the op-result envelope; on the default text surface the CLI renders a readable per-schema report below the op output (blocked: <name> (<from> -> <to>) then the no-chain line or the per-item failure lines), ending with a guidance line. A live blocked update also persists a pending-blocked record pinning the target catalog schema, and — for a validation-failed block — writes git-style failure markers INTO the block file at the offending items (full-line <<<<<<< BLOCKED … / >>>>>>> target: … sentinels), pinning the pre-marker bytes (the schema and migrations.json stay byte-unchanged). The resolution loop is: open the block file, fix the items between the markers (or widen the local schema) → pi-context resolve-blocked --schemaName <name>, which strips the markers, re-validates the corrected block against the pinned target and, on pass, writes the target schema + advances the merge base so a subsequent update converges (in-sync) instead of re-blocking. The standalone pi-context validate-block-items --block <name> runs the same per-item check on demand (read-only, returns { block, from?, to?, valid, failures[] }).
--dryRun predicts the precise per-schema outcome (resync / migrate / block / merge / conflict) by running the forward-migration and re-validation in memory, alongside the per-blocked-schema diagnostic detail and the config-registry entries that would be added, and writes nothing.
pi-context update --dryRun # predict the precise per-schema outcome (in-memory migrate + re-validate) + config-registry additions
pi-context update # apply: resync + auto-merge; conflicts → surfaced (op output + report) for the caller to reconcile + commit via resolve-conflictGlobal flags
--cwd <dir>— substrate root (default: current working directory; relative paths resolve against it)--json— emit a{ ok, op, output }envelope on success ({ ok: false, op, error }on failure) instead of raw output (≡--format json)--format <text|json|table>— select the output render (defaulttext, orjsonwith--json); see Output rendering--yes,--force— pre-authorize an auth-gated op in a non-interactive context--writer <json>— override the auto-resolved writer identity--show-schema— preview a block op's contract (array key / required fields / field types / id pattern) and exit; see Contract preview + dry-run--dry-run,--dryRun— forappend-block-item, validate the prospective file and write nothing; see Contract preview + dry-run--version,-v— print the package version and exit--help,-h— grouped top-level help (ops by command class + a Process modes section), or per-op help after an op name
Writer identity
Ops that declare a writer parameter (e.g. promote-item, write-schema-migration) get it injected automatically when not passed via --writer. The identity is resolved by cascade: git config user.email, then $USER, then the literal "operator". Pass --writer '{"kind":"human","user":"[email protected]"}' to override.
An explicit --writer is validated as a well-formed WriterIdentity: it must carry a kind of human / agent / monitor / workflow and that kind's identifier field as a non-empty string (user for human, agent_id for agent, monitor_name for monitor, workflow_step_id for workflow). A malformed --writer is an error — the CLI no longer silently stamps a garbage identity such as human/undefined.
The CLI builds a DispatchContext from the resolved (or explicit, validated) identity and threads it to every op, so writes stamp created_by / created_at. Filing through the CLI no longer fails on schemas that require author fields. The context builder is exported as buildCliDispatchContext.
Auth-gated ops
Ops marked authGated (writes that mutate config / schemas / migrations) require authorization, mirroring the in-pi dispatch gate:
--yes/--forceproceeds immediately- on an interactive terminal you are prompted (
Authorize <op>? [y/N]) - in a non-interactive context without
--yesthe op refuses
Exit codes
| Code | Meaning |
| --- | --- |
| 0 | success |
| 1 | op/runtime error, or declined authorization |
| 2 | usage error (unknown op, unknown flag, missing required field) |
| 3 | schema absent (the block's schema is not installed) |
| 4 | id-allocation failure (--autoId could not allocate the next id) |
| 5 | schema validation failure |
