@commissionsight/cli 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -6
- package/dist/output/envelope.d.ts +12 -0
- package/dist/output/envelope.js +48 -3
- package/dist/output/help.d.ts +10 -1
- package/dist/output/help.js +30 -1
- package/dist/output/schema-tree.d.ts +18 -0
- package/dist/output/schema-tree.js +23 -0
- package/dist/router.js +9 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,7 +41,9 @@ cs upload batch ./statements --workspace "Main Book" --wait --report ./ingest.js
|
|
|
41
41
|
## Using this CLI from an AI agent (Claude Code, etc.)
|
|
42
42
|
|
|
43
43
|
This section is the contract an autonomous agent should rely on. Everything here
|
|
44
|
-
is stable and machine-checkable.
|
|
44
|
+
is stable and machine-checkable. (Running `cs` with **no arguments** prints a
|
|
45
|
+
condensed version of this guidance, so a harness that discovers the binary is
|
|
46
|
+
onboarded immediately.)
|
|
45
47
|
|
|
46
48
|
### 1. Discover the surface programmatically — don't guess
|
|
47
49
|
|
|
@@ -49,9 +51,12 @@ is stable and machine-checkable.
|
|
|
49
51
|
cs schema --json
|
|
50
52
|
```
|
|
51
53
|
|
|
52
|
-
Returns the **entire** command/flag tree as JSON
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
Returns the **entire** command/flag tree as JSON **plus a `conventions` block**
|
|
55
|
+
(the envelope shape, exit-code table, error fields, auth, idempotency, and
|
|
56
|
+
pagination rules) — so a single call fully onboards an agent. It contains every
|
|
57
|
+
command, its positional args (with `required`/`variadic`), and every option
|
|
58
|
+
(`type`, `short`, `desc`, `choices`, `default`). Parse this instead of memorizing
|
|
59
|
+
commands. Shape:
|
|
55
60
|
|
|
56
61
|
```json
|
|
57
62
|
{
|
|
@@ -90,8 +95,11 @@ safely `JSON.parse(stdout)` without stripping anything.
|
|
|
90
95
|
```
|
|
91
96
|
|
|
92
97
|
`error.code` is a stable slug derived from the API's problem+json body
|
|
93
|
-
(`period_exists`, `already_retracted`, …) or `null`.
|
|
94
|
-
|
|
98
|
+
(`period_exists`, `already_retracted`, …) or `null`. The error object also carries
|
|
99
|
+
a machine-readable **`hint`** (the recovery action, e.g. "re-run with --replace")
|
|
100
|
+
and **`retriable`** (true for rate-limits/timeouts/5xx) — so an agent can recover
|
|
101
|
+
without parsing `message` text. Branch on `error.code` / `error.status` /
|
|
102
|
+
`error.retriable`.
|
|
95
103
|
|
|
96
104
|
### 3. Use exit codes for control flow
|
|
97
105
|
|
|
@@ -13,6 +13,10 @@ export interface ErrEnvelope {
|
|
|
13
13
|
status: number;
|
|
14
14
|
code: string | null;
|
|
15
15
|
message: string;
|
|
16
|
+
/** Actionable next step an agent can act on without parsing `message`. */
|
|
17
|
+
hint?: string;
|
|
18
|
+
/** True when retrying the same call may succeed (rate limit, timeout, 5xx). */
|
|
19
|
+
retriable: boolean;
|
|
16
20
|
body?: unknown;
|
|
17
21
|
};
|
|
18
22
|
}
|
|
@@ -23,6 +27,14 @@ export declare function ok(data: unknown): OkEnvelope;
|
|
|
23
27
|
* `type` URI slug, then `title`/`code`. Returns null when nothing usable.
|
|
24
28
|
*/
|
|
25
29
|
export declare function deriveCode(body: unknown): string | null;
|
|
30
|
+
/**
|
|
31
|
+
* Compute an actionable hint + retriability from an API status (+ derived code),
|
|
32
|
+
* so an agent can recover without parsing the human message.
|
|
33
|
+
*/
|
|
34
|
+
export declare function errorMeta(status: number, code: string | null): {
|
|
35
|
+
hint?: string;
|
|
36
|
+
retriable: boolean;
|
|
37
|
+
};
|
|
26
38
|
/** Shape any thrown value into the error envelope. */
|
|
27
39
|
export declare function errEnvelope(err: unknown): ErrEnvelope;
|
|
28
40
|
/** Convenience: the exit code that pairs with a thrown value. */
|
package/dist/output/envelope.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The stable JSON envelope (plan §5.1) and the error-shaping helpers.
|
|
3
3
|
* Success → { ok: true, data }. Failure → { ok: false, error }.
|
|
4
4
|
*/
|
|
5
|
-
import { ApiError, UsageError, exitCodeFor } from '../errors.js';
|
|
5
|
+
import { ApiError, UsageError, TimeoutError, exitCodeFor } from '../errors.js';
|
|
6
6
|
export function ok(data) {
|
|
7
7
|
return { ok: true, data };
|
|
8
8
|
}
|
|
@@ -32,19 +32,62 @@ function slug(s) {
|
|
|
32
32
|
.replace(/[^a-z0-9]+/g, '_')
|
|
33
33
|
.replace(/^_+|_+$/g, '');
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Compute an actionable hint + retriability from an API status (+ derived code),
|
|
37
|
+
* so an agent can recover without parsing the human message.
|
|
38
|
+
*/
|
|
39
|
+
export function errorMeta(status, code) {
|
|
40
|
+
if (status === 409 && code === 'period_exists') {
|
|
41
|
+
return { hint: 're-run with --replace to overwrite this carrier+period', retriable: false };
|
|
42
|
+
}
|
|
43
|
+
if (status === 409 && code === 'already_retracted') {
|
|
44
|
+
return { hint: 'this carrier+period was already retracted/replaced; no action needed', retriable: false };
|
|
45
|
+
}
|
|
46
|
+
if (status === 409)
|
|
47
|
+
return { hint: 'resource conflict; inspect error.body', retriable: false };
|
|
48
|
+
if (status === 401 || status === 403) {
|
|
49
|
+
return { hint: 'authenticate: set COMMISSIONSIGHT_TOKEN or run `cs auth login --token-stdin`', retriable: false };
|
|
50
|
+
}
|
|
51
|
+
if (status === 404)
|
|
52
|
+
return { hint: 'not found; verify the id/ref exists', retriable: false };
|
|
53
|
+
if (status === 400 || status === 422) {
|
|
54
|
+
return { hint: 'inputs rejected; read error.body for field-level errors', retriable: false };
|
|
55
|
+
}
|
|
56
|
+
if (status === 429)
|
|
57
|
+
return { hint: 'rate limited; back off and retry', retriable: true };
|
|
58
|
+
if (status >= 500)
|
|
59
|
+
return { hint: 'server error; retry shortly', retriable: true };
|
|
60
|
+
return { retriable: false };
|
|
61
|
+
}
|
|
35
62
|
/** Shape any thrown value into the error envelope. */
|
|
36
63
|
export function errEnvelope(err) {
|
|
37
64
|
if (err instanceof ApiError) {
|
|
65
|
+
const code = deriveCode(err.body);
|
|
66
|
+
const { hint, retriable } = errorMeta(err.status, code);
|
|
38
67
|
return {
|
|
39
68
|
ok: false,
|
|
40
69
|
error: {
|
|
41
70
|
status: err.status,
|
|
42
|
-
code
|
|
71
|
+
code,
|
|
43
72
|
message: err.message,
|
|
73
|
+
...(hint ? { hint } : {}),
|
|
74
|
+
retriable,
|
|
44
75
|
body: err.body,
|
|
45
76
|
},
|
|
46
77
|
};
|
|
47
78
|
}
|
|
79
|
+
if (err instanceof TimeoutError) {
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
error: {
|
|
83
|
+
status: 0,
|
|
84
|
+
code: 'timeout',
|
|
85
|
+
message: err.message,
|
|
86
|
+
hint: 'raise --timeout, or poll again with `cs job wait <jobId>`',
|
|
87
|
+
retriable: true,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
48
91
|
if (err instanceof UsageError) {
|
|
49
92
|
return {
|
|
50
93
|
ok: false,
|
|
@@ -52,6 +95,8 @@ export function errEnvelope(err) {
|
|
|
52
95
|
status: 0,
|
|
53
96
|
code: 'usage_error',
|
|
54
97
|
message: err.message,
|
|
98
|
+
hint: 'fix the invocation; run `cs <command> --help` or `cs schema --json`',
|
|
99
|
+
retriable: false,
|
|
55
100
|
...(err.candidates ? { body: { candidates: err.candidates } } : {}),
|
|
56
101
|
},
|
|
57
102
|
};
|
|
@@ -60,7 +105,7 @@ export function errEnvelope(err) {
|
|
|
60
105
|
const code = err instanceof Error && err.name && err.name !== 'Error'
|
|
61
106
|
? slug(err.name)
|
|
62
107
|
: null;
|
|
63
|
-
return { ok: false, error: { status: 0, code, message } };
|
|
108
|
+
return { ok: false, error: { status: 0, code, message, retriable: false } };
|
|
64
109
|
}
|
|
65
110
|
/** Convenience: the exit code that pairs with a thrown value. */
|
|
66
111
|
export { exitCodeFor };
|
package/dist/output/help.d.ts
CHANGED
|
@@ -3,5 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { Cmd } from '../types.js';
|
|
5
5
|
import type { IO } from '../io.js';
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The agent prompt shown when `cs` is run with no args — the first thing an AI
|
|
8
|
+
* harness (Claude Code, Codex, Copilot, …) sees. Tells it exactly how to drive
|
|
9
|
+
* the CLI: discover via `cs schema --json`, then parse the --json envelope and
|
|
10
|
+
* branch on exit codes. Kept plain-text so it is safe to capture/parse.
|
|
11
|
+
*/
|
|
12
|
+
export declare function renderAgentBanner(io: IO): void;
|
|
13
|
+
export declare function renderTopHelp(io: IO, table: Cmd[], opts?: {
|
|
14
|
+
agent?: boolean;
|
|
15
|
+
}): void;
|
|
7
16
|
export declare function renderCommandHelp(io: IO, cmd: Cmd, table: Cmd[]): void;
|
package/dist/output/help.js
CHANGED
|
@@ -7,8 +7,37 @@ function optLine(name, spec) {
|
|
|
7
7
|
const choices = spec.choices ? ` (${spec.choices.join('|')})` : '';
|
|
8
8
|
return ` ${flag.padEnd(30)}${spec.desc}${choices}`;
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The agent prompt shown when `cs` is run with no args — the first thing an AI
|
|
12
|
+
* harness (Claude Code, Codex, Copilot, …) sees. Tells it exactly how to drive
|
|
13
|
+
* the CLI: discover via `cs schema --json`, then parse the --json envelope and
|
|
14
|
+
* branch on exit codes. Kept plain-text so it is safe to capture/parse.
|
|
15
|
+
*/
|
|
16
|
+
export function renderAgentBanner(io) {
|
|
17
|
+
io.stdout([
|
|
18
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
|
19
|
+
'AI agents (Claude Code, Codex, Copilot, Cursor): drive this CLI like so.',
|
|
20
|
+
'',
|
|
21
|
+
'1. Discover everything — run: cs schema --json',
|
|
22
|
+
' → the full command/flag tree (every command, arg, option) as JSON.',
|
|
23
|
+
'2. Add --json to every command. stdout is exactly ONE envelope:',
|
|
24
|
+
' success → {"ok":true,"data":…}',
|
|
25
|
+
' failure → {"ok":false,"error":{"status","code","message"}}',
|
|
26
|
+
' All logs/progress go to stderr, so parse stdout directly.',
|
|
27
|
+
'3. Branch on exit codes — they are stable:',
|
|
28
|
+
' 0 ok · 2 usage · 3 auth · 4 not-found · 5 conflict (retry --replace)',
|
|
29
|
+
' 6 validation · 7 rate-limited · 124 timeout',
|
|
30
|
+
'4. Auth non-interactively: export COMMISSIONSIGHT_TOKEN, or',
|
|
31
|
+
' printf %s "$TOKEN" | cs auth login --token-stdin',
|
|
32
|
+
'5. Destructive commands need --yes. Uploads are idempotent — safe to retry.',
|
|
33
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
|
34
|
+
'',
|
|
35
|
+
].join('\n'));
|
|
36
|
+
}
|
|
37
|
+
export function renderTopHelp(io, table, opts = {}) {
|
|
11
38
|
io.stdout(`cs ${cliVersion()} — CommissionSight CLI\n\n`);
|
|
39
|
+
if (opts.agent)
|
|
40
|
+
renderAgentBanner(io);
|
|
12
41
|
io.stdout('Usage: cs <command> [subcommand] [args] [flags]\n\n');
|
|
13
42
|
// Group by top-level segment.
|
|
14
43
|
const groups = new Map();
|
|
@@ -23,10 +23,28 @@ export interface SchemaCommand {
|
|
|
23
23
|
}[];
|
|
24
24
|
options: SchemaOption[];
|
|
25
25
|
}
|
|
26
|
+
export interface SchemaConventions {
|
|
27
|
+
/** The stable output envelope (plan §5.1). */
|
|
28
|
+
envelope: {
|
|
29
|
+
success: string;
|
|
30
|
+
error: string;
|
|
31
|
+
stdout: string;
|
|
32
|
+
};
|
|
33
|
+
/** ApiError.status → exit-code semantics (plan §5.2). */
|
|
34
|
+
exitCodes: Record<string, string>;
|
|
35
|
+
/** Fields present on a failure envelope's `error` object. */
|
|
36
|
+
errorFields: string[];
|
|
37
|
+
auth: string;
|
|
38
|
+
idempotency: string;
|
|
39
|
+
pagination: string;
|
|
40
|
+
}
|
|
26
41
|
export interface SchemaDoc {
|
|
27
42
|
name: string;
|
|
28
43
|
version: string;
|
|
44
|
+
/** Machine-readable usage contract so one `cs schema --json` call is enough. */
|
|
45
|
+
conventions: SchemaConventions;
|
|
29
46
|
globalOptions: SchemaOption[];
|
|
30
47
|
commands: SchemaCommand[];
|
|
31
48
|
}
|
|
49
|
+
export declare const CONVENTIONS: SchemaConventions;
|
|
32
50
|
export declare function buildSchema(table: Cmd[], version: string): SchemaDoc;
|
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
import { GLOBAL_OPTIONS } from '../globals.js';
|
|
2
|
+
export const CONVENTIONS = {
|
|
3
|
+
envelope: {
|
|
4
|
+
success: '{"ok":true,"data":<result>}',
|
|
5
|
+
error: '{"ok":false,"error":{"status":<int>,"code":<string|null>,"message":<string>,"hint":<string?>,"retriable":<bool>,"body":<any?>}}',
|
|
6
|
+
stdout: 'With --json, stdout carries exactly one envelope; all logs/progress go to stderr.',
|
|
7
|
+
},
|
|
8
|
+
exitCodes: {
|
|
9
|
+
'0': 'success',
|
|
10
|
+
'1': 'unexpected/internal error (includes a job that finished failed)',
|
|
11
|
+
'2': 'usage error (bad flags/args)',
|
|
12
|
+
'3': 'auth error (401/403)',
|
|
13
|
+
'4': 'not found (404)',
|
|
14
|
+
'5': 'conflict (409) — e.g. period_exists; retry with --replace',
|
|
15
|
+
'6': 'validation/unprocessable (400/422)',
|
|
16
|
+
'7': 'rate limited (429) — retriable',
|
|
17
|
+
'124': 'timeout (--wait exceeded --timeout) — retriable',
|
|
18
|
+
},
|
|
19
|
+
errorFields: ['status', 'code', 'message', 'hint', 'retriable', 'body'],
|
|
20
|
+
auth: 'Set COMMISSIONSIGHT_TOKEN, or pipe a token via `cs auth login --token-stdin`. --token-file/--token also work; --token is discouraged (shell history).',
|
|
21
|
+
idempotency: 'cs upload / cs upload batch derive a stable idempotency key per file; re-running is safe and resumable (existing carrier+periods come back as skipped).',
|
|
22
|
+
pagination: 'List commands accept --limit and --all (auto-paginate); offset endpoints also take --offset, listFiles takes --cursor.',
|
|
23
|
+
};
|
|
2
24
|
function optionToSchema(flag, spec) {
|
|
3
25
|
return {
|
|
4
26
|
flag,
|
|
@@ -14,6 +36,7 @@ export function buildSchema(table, version) {
|
|
|
14
36
|
return {
|
|
15
37
|
name: 'cs',
|
|
16
38
|
version,
|
|
39
|
+
conventions: CONVENTIONS,
|
|
17
40
|
globalOptions: Object.entries(GLOBAL_OPTIONS).map(([f, s]) => optionToSchema(f, s)),
|
|
18
41
|
commands: table
|
|
19
42
|
.slice()
|
package/dist/router.js
CHANGED
|
@@ -96,9 +96,12 @@ export async function run(argv, opts = {}) {
|
|
|
96
96
|
}
|
|
97
97
|
// No command resolved.
|
|
98
98
|
if (!match) {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
const noArgs = argv.length === 0;
|
|
100
|
+
const wantsHelp = hasFlag(argv, 'help', 'h') || noArgs || words.length === 0;
|
|
101
|
+
if (wantsHelp && (noArgs || hasFlag(argv, 'help', 'h'))) {
|
|
102
|
+
// No-args invocation leads with the AI-agent prompt (the first touchpoint
|
|
103
|
+
// for a harness discovering the `cs` binary).
|
|
104
|
+
renderTopHelp(io, table, { agent: noArgs });
|
|
102
105
|
return EXIT.OK;
|
|
103
106
|
}
|
|
104
107
|
// Unknown command word(s).
|
|
@@ -209,13 +212,9 @@ function finishError(io, err, json, quiet) {
|
|
|
209
212
|
return code;
|
|
210
213
|
}
|
|
211
214
|
io.stderr(`error: ${env.error.message}\n`);
|
|
212
|
-
//
|
|
213
|
-
if (env.error.
|
|
214
|
-
io.stderr(
|
|
215
|
-
}
|
|
216
|
-
if (env.error.status === 401 || env.error.status === 403) {
|
|
217
|
-
io.stderr("hint: check your token with 'cs auth status'\n");
|
|
218
|
-
}
|
|
215
|
+
// Single source of truth for the actionable hint (also in the JSON envelope).
|
|
216
|
+
if (env.error.hint)
|
|
217
|
+
io.stderr(`hint: ${env.error.hint}\n`);
|
|
219
218
|
if (err instanceof UsageError && err.candidates?.length) {
|
|
220
219
|
io.stderr('candidates:\n');
|
|
221
220
|
for (const c of err.candidates)
|
package/package.json
CHANGED