@commissionsight/cli 0.1.1 → 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
@@ -51,9 +51,12 @@ onboarded immediately.)
51
51
  cs schema --json
52
52
  ```
53
53
 
54
- Returns the **entire** command/flag tree as JSON: every command, its positional
55
- args (with `required`/`variadic`), and every option (`type`, `short`, `desc`,
56
- `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:
57
60
 
58
61
  ```json
59
62
  {
@@ -92,8 +95,11 @@ safely `JSON.parse(stdout)` without stripping anything.
92
95
  ```
93
96
 
94
97
  `error.code` is a stable slug derived from the API's problem+json body
95
- (`period_exists`, `already_retracted`, …) or `null`. Branch on `error.code` /
96
- `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`.
97
103
 
98
104
  ### 3. Use exit codes for control flow
99
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 };
@@ -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
@@ -212,13 +212,9 @@ function finishError(io, err, json, quiet) {
212
212
  return code;
213
213
  }
214
214
  io.stderr(`error: ${env.error.message}\n`);
215
- // Actionable hints.
216
- if (env.error.status === 409 && env.error.code === 'period_exists') {
217
- io.stderr('hint: retry with --replace to correct this period atomically\n');
218
- }
219
- if (env.error.status === 401 || env.error.status === 403) {
220
- io.stderr("hint: check your token with 'cs auth status'\n");
221
- }
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`);
222
218
  if (err instanceof UsageError && err.candidates?.length) {
223
219
  io.stderr('candidates:\n');
224
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.1",
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",