@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 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: every command, its positional
53
- args (with `required`/`variadic`), and every option (`type`, `short`, `desc`,
54
- `choices`, `default`). Parse this instead of memorizing commands. Shape:
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`. Branch on `error.code` /
94
- `error.status`, not on `message` text.
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. */
@@ -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: deriveCode(err.body),
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 };
@@ -3,5 +3,14 @@
3
3
  */
4
4
  import type { Cmd } from '../types.js';
5
5
  import type { IO } from '../io.js';
6
- export declare function renderTopHelp(io: IO, table: Cmd[]): void;
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;
@@ -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
- export function renderTopHelp(io, table) {
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 wantsHelp = hasFlag(argv, 'help', 'h') || argv.length === 0 || words.length === 0;
100
- if (wantsHelp && (argv.length === 0 || hasFlag(argv, 'help', 'h'))) {
101
- renderTopHelp(io, table);
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
- // Actionable hints.
213
- if (env.error.status === 409 && env.error.code === 'period_exists') {
214
- io.stderr('hint: retry with --replace to correct this period atomically\n');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commissionsight/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Command-line interface for the CommissionSight API — auth, workspaces, and statement uploads for any carrier and period.",
5
5
  "keywords": [
6
6
  "commissionsight",