CLI Contracts Specification
Technical specification for pair-cli command contracts, TypeScript types, validation rules, and error handling.
Technical specification for pair-cli command contracts including TypeScript types, validation rules, error handling, and exit codes.
CLI-Wide Rules
These apply to every command below, not to a single one.
- Excess positionals are rejected. Commander runs with
allowExcessArguments(false)for all registered commands, so a stray positional fails witherror: too many arguments for '<command>'and exit code 1 instead of being silently dropped. This makes each command's positional-arity validation reachable, and catches the classic unquoted option value (--name Acme KBused to bindnametoAcmeand discardKB).
This is a behavior change, earlier releases ignored the extra argument, so a wrapper or CI invocation carrying one (e.g. pair-cli install ./a ./b) now fails; drop the extra argument or quote the value. GitHub Releases names the release that introduced it.
Command Option Types
Base Types
kb is read by the KB pre-flight (bootstrapEnvironment), which runs before install and
update: false skips the pre-flight fetch, and false together with a url is rejected by
validateCliOptions. Both were dead through 0.5.1: the pre-flight was unreachable, so
pair-cli install --no-kb installed a KB anyway and the conflict never fired. See the
--no-kb note in the CLI reference.
The conflict is checked in the CLI hook (runKbPreflight), above the pre-flight's own
skip, and bootstrapEnvironment takes no url: a named source (--source, --git, --url,
local or remote) skips the pre-flight entirely, because the command fetches that source itself
into its own cache slot. A pre-flight that also warmed it downloaded the same archive twice.
Install Command
Update Command
Update-Link Command
Package Command
Scaffold-KB Command
Validation: --host must be github or generic; --name must be non-empty, free of newlines/control characters and of ${{ (GitHub Actions would evaluate it as a live expression in the generated workflow), and at most 100 characters (it is embedded in generated YAML and bash); at most one positional argument (extra positionals are rejected, see CLI-Wide Rules, which applies to every command).
Refused targets: a path that exists and is not a directory, or a configured pair project (.pair/adoption/ present), not bypassable with --force. Re-scaffold policy: seed files (KB content) are never overwritten; scaffold-owned files are regenerated only when identical-check fails AND (--force OR interactive confirmation), otherwise reported as skipped.
The generated scripts/release.sh is written with mode 0755.
KB Verify Command
Validation Error Catalog
Error Categories
| Category | Exit Code | Description |
|---|---|---|
| Option Validation | 1 | Invalid option combination or missing required option |
| File System | 1 | Path not found, permission denied, not writable |
| Network | 2 | Download failure, connection timeout, DNS error |
| Checksum | 2 | SHA256 validation failure |
| Configuration | 1 | Invalid config.json structure or values |
| KB Structure | 1 | Invalid KB directory structure |
Option Validation Errors
| Code | Error | Trigger | Resolution |
|---|---|---|---|
| V001 | Conflicting options | --list-targets with target args | Use separately |
| V002 | Offline requires source | --offline without --source | Provide --source |
| V003 | Offline cannot use URL | --offline with HTTP URL | Use local path |
| V004 | Invalid config file | --config points to missing file | Fix path |
| V005 | Malformed JSON | Invalid JSON in config | Fix syntax |
File System Errors
| Code | Error | Trigger | Resolution |
|---|---|---|---|
| FS001 | Path not found | Target path missing | Create dir or fix path |
| FS002 | Permission denied | Insufficient permissions | Fix permissions |
| FS003 | Not writable | Read-only target | Fix write permissions |
| FS004 | Disk full | Insufficient space | Free space |
Package Verification Errors
| Code | Error | Trigger | Resolution |
|---|---|---|---|
| VER001 | Package not found | File missing | Check path |
| VER002 | Invalid format | Not valid ZIP | Use valid package |
| VER003 | Checksum mismatch | Content tampered | Re-download |
| VER004 | Missing manifest | No manifest.json | Use valid package |
| VER005 | Missing registry | Structure mismatch | Rebuild package |
Validation Logic
Install/Update Option Validation
Exit Code Contract
| Code | Meaning | Examples |
|---|---|---|
0 | Success | Command completed successfully |
1 | Validation Error, or an install/update run that did not fully apply | Invalid options, config errors, KB structure errors; a shipped registry that failed to copy; a run that installed/updated nothing |
2 | Download/Network Error | Network timeout, checksum mismatch, packaging error |
3 | File System Error | Disk full, permission denied during operation |
install / update: the code follows the tally
Both commands classify every registry as ok, skipped or failed and derive the status
code from that one tally: the same tally the printed summary is built from, so the text
and the code cannot disagree:
| Tally | Summary | Code |
|---|---|---|
any failed | … finished with errors (…) | 1 |
no failed, no ok (everything skipped) | Nothing to install/update (…) | 1 |
at least one ok, no failed | Installation/Update complete (…) | 0 |
A registry the source does not ship is skipped, never failed, and any number of skips
still exits 0 as long as something was installed. Two consequences for automation: 1
from these two commands is not necessarily a validation error (it may be a per-registry
copy failure or a no-op), and update returns a non-zero code at all: it previously
exited 0 unless it threw.
kb-verify / kb-info / kb-cache: their non-zero code now reaches the shell
Not new behaviour these commands report, new behaviour a caller observes. All three
already returned 1 from their handlers (an unreadable or unextractable KB, a checksum
mismatch, an unknown cache slot), but the entry point force-exited with an explicit
process.exit(0), and in Node an explicit code OVERRIDES process.exitCode. Forwarding
the reported code (the fix AC5 required for install) un-swallows theirs too:
| Command | Printed before and after | Exit code before | Exit code now |
|---|---|---|---|
pair-cli kb-verify <kb> | the checksum/structure failure | 0 | 1 |
pair-cli kb-info <kb> | the KB it could not read | 0 | 1 |
pair-cli kb-cache <op> | the cache operation that failed | 0 | 1 |
A CI step that ran pair-cli kb-verify ./kb and passed while printing a checksum mismatch now
fails that step. That is the contract these commands always described, but a pipeline
relying on the old code turning green is the one place the change is felt.
Test Matrices
Install/Update Option Validation Matrix
--source | --offline | Monorepo | Expected Result | Exit Code |
|---|---|---|---|---|
| - | - | Yes | Use monorepo dataset | 0 |
| - | - | No | Auto-download | 0 or 2 |
https://... | - | - | Download from URL | 0 or 2 |
./local | - | - | Use local path | 0 or 1 |
./local | Yes | - | Use local (offline) | 0 or 1 |
| - | Yes | - | ERROR | 1 |
https://... | Yes | - | ERROR | 1 |
The codes above are the OPTION-validation outcome only. A run that gets past validation
still exits per the tally above, 1 when a shipped registry fails or when nothing was
installed, whatever the source.
Related
- CLI Commands Reference: User-facing command documentation
- KB Source Resolution Spec: KB source resolution algorithm
- CLI Help Examples: Practical usage examples