@ekanos/cli 0.1.0
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/LICENSE +21 -0
- package/README.md +326 -0
- package/dist/auth/credential-store.d.ts +96 -0
- package/dist/auth/credential-store.js +274 -0
- package/dist/auth/credential-store.js.map +1 -0
- package/dist/auth/device-flow.d.ts +57 -0
- package/dist/auth/device-flow.js +201 -0
- package/dist/auth/device-flow.js.map +1 -0
- package/dist/auth/fusion-api.d.ts +48 -0
- package/dist/auth/fusion-api.js +310 -0
- package/dist/auth/fusion-api.js.map +1 -0
- package/dist/auth/session.d.ts +57 -0
- package/dist/auth/session.js +114 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +20 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/dev.d.ts +36 -0
- package/dist/commands/dev.js +207 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/init.d.ts +30 -0
- package/dist/commands/init.js +178 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +20 -0
- package/dist/commands/login.js +92 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +20 -0
- package/dist/commands/logout.js +75 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/test.d.ts +16 -0
- package/dist/commands/test.js +54 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/validate.d.ts +14 -0
- package/dist/commands/validate.js +98 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/commands/whoami.d.ts +18 -0
- package/dist/commands/whoami.js +29 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/compatibility.d.ts +81 -0
- package/dist/compatibility.js +135 -0
- package/dist/compatibility.js.map +1 -0
- package/dist/context.d.ts +81 -0
- package/dist/context.js +125 -0
- package/dist/context.js.map +1 -0
- package/dist/errors.d.ts +41 -0
- package/dist/errors.js +111 -0
- package/dist/errors.js.map +1 -0
- package/dist/exit-codes.d.ts +52 -0
- package/dist/exit-codes.js +51 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/harness-scaffold.d.ts +212 -0
- package/dist/harness-scaffold.js +434 -0
- package/dist/harness-scaffold.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +262 -0
- package/dist/index.js.map +1 -0
- package/dist/load-definition.d.ts +20 -0
- package/dist/load-definition.js +126 -0
- package/dist/load-definition.js.map +1 -0
- package/dist/merge-manifest.d.ts +44 -0
- package/dist/merge-manifest.js +124 -0
- package/dist/merge-manifest.js.map +1 -0
- package/dist/package-manager.d.ts +25 -0
- package/dist/package-manager.js +57 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/parse-argv.d.ts +27 -0
- package/dist/parse-argv.js +74 -0
- package/dist/parse-argv.js.map +1 -0
- package/dist/project-checks.d.ts +18 -0
- package/dist/project-checks.js +77 -0
- package/dist/project-checks.js.map +1 -0
- package/dist/project.d.ts +140 -0
- package/dist/project.js +228 -0
- package/dist/project.js.map +1 -0
- package/dist/safe-fs.d.ts +55 -0
- package/dist/safe-fs.js +209 -0
- package/dist/safe-fs.js.map +1 -0
- package/dist/templates.d.ts +23 -0
- package/dist/templates.js +49 -0
- package/dist/templates.js.map +1 -0
- package/package.json +62 -0
- package/templates/ekanos.json.tmpl +4 -0
- package/templates/gitignore.tmpl +12 -0
- package/templates/harness/app/[slug]/activation/page.tsx.tmpl +1 -0
- package/templates/harness/app/[slug]/layout.tsx.tmpl +1 -0
- package/templates/harness/app/[slug]/tile/page.tsx.tmpl +1 -0
- package/templates/harness/app/[slug]/triggers/page.tsx.tmpl +1 -0
- package/templates/harness/app/[slug]/widgets/[widgetId]/page.tsx.tmpl +1 -0
- package/templates/harness/app/[slug]/widgets/page.tsx.tmpl +1 -0
- package/templates/harness/app/harness-shell.tsx.tmpl +9 -0
- package/templates/harness/app/layout.tsx.tmpl +4 -0
- package/templates/harness/app/page.tsx.tmpl +7 -0
- package/templates/harness/next.config.mjs.tmpl +46 -0
- package/templates/harness/package.json.tmpl +11 -0
- package/templates/harness/postcss.config.mjs.tmpl +6 -0
- package/templates/harness/styles/globals.css.tmpl +33 -0
- package/templates/harness/tsconfig.json.tmpl +31 -0
- package/templates/harness/types/team-accounts.d.ts.tmpl +16 -0
- package/templates/harness.config.entry.ts.tmpl +65 -0
- package/templates/harness.config.ts.tmpl +58 -0
- package/templates/package.json.tmpl +28 -0
- package/templates/src/integration.test.ts.tmpl +24 -0
- package/templates/src/integration.ts.tmpl +83 -0
- package/templates/tsconfig.json.tmpl +19 -0
- package/templates/vitest.config.ts.tmpl +32 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { CliError } from './errors.js';
|
|
2
|
+
import { type ExitCode } from './exit-codes.js';
|
|
3
|
+
/**
|
|
4
|
+
* A single writable sink. Real streams satisfy this; tests pass a capturing
|
|
5
|
+
* buffer so the stdout-purity invariant can be asserted byte-for-byte.
|
|
6
|
+
*/
|
|
7
|
+
export interface WriteStream {
|
|
8
|
+
write(chunk: string): void;
|
|
9
|
+
isTTY?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface ContextInput {
|
|
12
|
+
/**
|
|
13
|
+
* The user's EXPLICIT preference, already parsed: `true` for `--json`,
|
|
14
|
+
* `false` for `--no-json`, `undefined` when neither was passed. The
|
|
15
|
+
* undefined case is what allows the mode to be inferred instead.
|
|
16
|
+
*/
|
|
17
|
+
jsonFlag?: boolean;
|
|
18
|
+
stdout?: WriteStream;
|
|
19
|
+
stderr?: WriteStream;
|
|
20
|
+
/** Overrides process.env for CLAUDECODE detection (tests). */
|
|
21
|
+
env?: Record<string, string | undefined>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The agent-native output envelope, resolved ONCE at startup and threaded
|
|
25
|
+
* through every command.
|
|
26
|
+
*
|
|
27
|
+
* The load-bearing invariant: in JSON mode, stdout receives EXACTLY ONE JSON
|
|
28
|
+
* object for the whole process — nothing else, ever. Every progress line, warn,
|
|
29
|
+
* and scrap of human prose goes to stderr. This is the single most common way
|
|
30
|
+
* an agent's tool call breaks (a stray log line on stdout makes the JSON
|
|
31
|
+
* unparseable), so the context enforces it structurally: `succeed`/`fail` are
|
|
32
|
+
* the only writers of stdout, and a `settled` latch makes a second call throw
|
|
33
|
+
* rather than emit a second object.
|
|
34
|
+
*/
|
|
35
|
+
export declare class CliContext {
|
|
36
|
+
readonly jsonMode: boolean;
|
|
37
|
+
private readonly stdout;
|
|
38
|
+
private readonly stderr;
|
|
39
|
+
private settled;
|
|
40
|
+
constructor(input?: ContextInput);
|
|
41
|
+
/** Human/progress output — ALWAYS stderr, so stdout stays pure in JSON mode. */
|
|
42
|
+
log(message: string): void;
|
|
43
|
+
/** A warning — stderr, no ANSI. */
|
|
44
|
+
warn(message: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Emit the `claude-code-hint` protocol line to STDERR. Used on --help and on
|
|
47
|
+
* an unknown command so an agent reading stderr is told what to do next
|
|
48
|
+
* without the line ever touching stdout.
|
|
49
|
+
*/
|
|
50
|
+
emitClaudeCodeHint(payload: Record<string, unknown>): void;
|
|
51
|
+
/**
|
|
52
|
+
* Terminal success. Writes the single stdout object (JSON mode) or a concise
|
|
53
|
+
* human line (human mode). Returns the OK exit code so the caller can end the
|
|
54
|
+
* process with it. Idempotent-by-latch: a second settle throws.
|
|
55
|
+
*/
|
|
56
|
+
succeed(data: unknown, humanSummary?: string): ExitCode;
|
|
57
|
+
/**
|
|
58
|
+
* Terminal failure. Writes the single stdout error object (JSON mode) or the
|
|
59
|
+
* message + hint to stderr (human mode). Returns the error's exit code.
|
|
60
|
+
*
|
|
61
|
+
* `error.hint` is guaranteed non-empty by CliError's constructor, so the
|
|
62
|
+
* envelope's "every error carries a hint" invariant holds for every path
|
|
63
|
+
* that reaches here.
|
|
64
|
+
*/
|
|
65
|
+
fail(error: CliError, data?: unknown): ExitCode;
|
|
66
|
+
/**
|
|
67
|
+
* The low-level failure emitter. Used directly by `test`, whose exit code is
|
|
68
|
+
* a TRANSPARENT passthrough of the delegated runner's exit code (which is not
|
|
69
|
+
* one of the CLI's own frozen numeric conditions) and whose error `code` is
|
|
70
|
+
* an out-of-taxonomy string. Enforces the same non-empty-hint invariant as
|
|
71
|
+
* CliError so no failure path can emit an empty hint.
|
|
72
|
+
*/
|
|
73
|
+
failWith(params: {
|
|
74
|
+
code: string;
|
|
75
|
+
message: string;
|
|
76
|
+
hint: string;
|
|
77
|
+
exitCode: number;
|
|
78
|
+
data?: unknown;
|
|
79
|
+
}): number;
|
|
80
|
+
private settle;
|
|
81
|
+
}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { EXIT_CODES } from './exit-codes.js';
|
|
2
|
+
/**
|
|
3
|
+
* The agent-native output envelope, resolved ONCE at startup and threaded
|
|
4
|
+
* through every command.
|
|
5
|
+
*
|
|
6
|
+
* The load-bearing invariant: in JSON mode, stdout receives EXACTLY ONE JSON
|
|
7
|
+
* object for the whole process — nothing else, ever. Every progress line, warn,
|
|
8
|
+
* and scrap of human prose goes to stderr. This is the single most common way
|
|
9
|
+
* an agent's tool call breaks (a stray log line on stdout makes the JSON
|
|
10
|
+
* unparseable), so the context enforces it structurally: `succeed`/`fail` are
|
|
11
|
+
* the only writers of stdout, and a `settled` latch makes a second call throw
|
|
12
|
+
* rather than emit a second object.
|
|
13
|
+
*/
|
|
14
|
+
export class CliContext {
|
|
15
|
+
constructor(input = {}) {
|
|
16
|
+
var _a, _b, _c, _d;
|
|
17
|
+
this.settled = false;
|
|
18
|
+
const stdout = (_a = input.stdout) !== null && _a !== void 0 ? _a : defaultStream(process.stdout);
|
|
19
|
+
const stderr = (_b = input.stderr) !== null && _b !== void 0 ? _b : defaultStream(process.stderr);
|
|
20
|
+
const env = (_c = input.env) !== null && _c !== void 0 ? _c : process.env;
|
|
21
|
+
this.stdout = stdout;
|
|
22
|
+
this.stderr = stderr;
|
|
23
|
+
// Resolve JSON mode once. An EXPLICIT flag always wins: `--json` forces the
|
|
24
|
+
// envelope on even at a terminal, `--no-json` forces the human form even
|
|
25
|
+
// when piped. Only when the user expressed no preference is the mode
|
|
26
|
+
// inferred — from a non-TTY stdout (piped into another program or an agent)
|
|
27
|
+
// or the CLAUDECODE marker.
|
|
28
|
+
const nonTty = stdout.isTTY !== true;
|
|
29
|
+
this.jsonMode = (_d = input.jsonFlag) !== null && _d !== void 0 ? _d : (nonTty || env.CLAUDECODE === '1');
|
|
30
|
+
}
|
|
31
|
+
/** Human/progress output — ALWAYS stderr, so stdout stays pure in JSON mode. */
|
|
32
|
+
log(message) {
|
|
33
|
+
this.stderr.write(`${message}\n`);
|
|
34
|
+
}
|
|
35
|
+
/** A warning — stderr, no ANSI. */
|
|
36
|
+
warn(message) {
|
|
37
|
+
this.stderr.write(`${message}\n`);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Emit the `claude-code-hint` protocol line to STDERR. Used on --help and on
|
|
41
|
+
* an unknown command so an agent reading stderr is told what to do next
|
|
42
|
+
* without the line ever touching stdout.
|
|
43
|
+
*/
|
|
44
|
+
emitClaudeCodeHint(payload) {
|
|
45
|
+
this.stderr.write(`claude-code-hint: ${JSON.stringify(payload)}\n`);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Terminal success. Writes the single stdout object (JSON mode) or a concise
|
|
49
|
+
* human line (human mode). Returns the OK exit code so the caller can end the
|
|
50
|
+
* process with it. Idempotent-by-latch: a second settle throws.
|
|
51
|
+
*/
|
|
52
|
+
succeed(data, humanSummary) {
|
|
53
|
+
this.settle();
|
|
54
|
+
if (this.jsonMode) {
|
|
55
|
+
const envelope = { ok: true, data };
|
|
56
|
+
this.stdout.write(`${JSON.stringify(envelope)}\n`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
this.stdout.write(`${humanSummary !== null && humanSummary !== void 0 ? humanSummary : 'OK'}\n`);
|
|
60
|
+
}
|
|
61
|
+
return EXIT_CODES.OK;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Terminal failure. Writes the single stdout error object (JSON mode) or the
|
|
65
|
+
* message + hint to stderr (human mode). Returns the error's exit code.
|
|
66
|
+
*
|
|
67
|
+
* `error.hint` is guaranteed non-empty by CliError's constructor, so the
|
|
68
|
+
* envelope's "every error carries a hint" invariant holds for every path
|
|
69
|
+
* that reaches here.
|
|
70
|
+
*/
|
|
71
|
+
fail(error, data) {
|
|
72
|
+
this.failWith({
|
|
73
|
+
code: error.code,
|
|
74
|
+
message: error.message,
|
|
75
|
+
hint: error.hint,
|
|
76
|
+
exitCode: error.exitCode,
|
|
77
|
+
data,
|
|
78
|
+
});
|
|
79
|
+
return error.exitCode;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The low-level failure emitter. Used directly by `test`, whose exit code is
|
|
83
|
+
* a TRANSPARENT passthrough of the delegated runner's exit code (which is not
|
|
84
|
+
* one of the CLI's own frozen numeric conditions) and whose error `code` is
|
|
85
|
+
* an out-of-taxonomy string. Enforces the same non-empty-hint invariant as
|
|
86
|
+
* CliError so no failure path can emit an empty hint.
|
|
87
|
+
*/
|
|
88
|
+
failWith(params) {
|
|
89
|
+
this.settle();
|
|
90
|
+
if (!params.hint || params.hint.trim().length === 0) {
|
|
91
|
+
throw new Error(`failWith for "${params.message}" was given an empty hint. Every CLI ` +
|
|
92
|
+
'error must carry an imperative remediation.');
|
|
93
|
+
}
|
|
94
|
+
if (this.jsonMode) {
|
|
95
|
+
const envelope = Object.assign(Object.assign({ ok: false }, (params.data !== undefined ? { data: params.data } : {})), { error: {
|
|
96
|
+
code: params.code,
|
|
97
|
+
message: params.message,
|
|
98
|
+
hint: params.hint,
|
|
99
|
+
} });
|
|
100
|
+
this.stdout.write(`${JSON.stringify(envelope)}\n`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
this.stderr.write(`Error: ${params.message}\n`);
|
|
104
|
+
this.stderr.write(`Hint: ${params.hint}\n`);
|
|
105
|
+
}
|
|
106
|
+
return params.exitCode;
|
|
107
|
+
}
|
|
108
|
+
settle() {
|
|
109
|
+
if (this.settled) {
|
|
110
|
+
throw new Error('CliContext settled twice — a command tried to emit a second ' +
|
|
111
|
+
'terminal result. In JSON mode this would corrupt stdout with two ' +
|
|
112
|
+
'objects. This is a bug in the command.');
|
|
113
|
+
}
|
|
114
|
+
this.settled = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function defaultStream(stream) {
|
|
118
|
+
return {
|
|
119
|
+
write: (chunk) => {
|
|
120
|
+
stream.write(chunk);
|
|
121
|
+
},
|
|
122
|
+
isTTY: stream.isTTY,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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","sourcesContent":["import { CliError } from './errors';\nimport { EXIT_CODES, type ExitCode } from './exit-codes';\n\n/**\n * A single writable sink. Real streams satisfy this; tests pass a capturing\n * buffer so the stdout-purity invariant can be asserted byte-for-byte.\n */\nexport interface WriteStream {\n write(chunk: string): void;\n isTTY?: boolean;\n}\n\nexport interface ContextInput {\n /**\n * The user's EXPLICIT preference, already parsed: `true` for `--json`,\n * `false` for `--no-json`, `undefined` when neither was passed. The\n * undefined case is what allows the mode to be inferred instead.\n */\n jsonFlag?: boolean;\n stdout?: WriteStream;\n stderr?: WriteStream;\n /** Overrides process.env for CLAUDECODE detection (tests). */\n env?: Record<string, string | undefined>;\n}\n\n/** The success envelope. */\ninterface OkEnvelope {\n ok: true;\n data: unknown;\n}\n\n/** The error envelope. `data` is optional — validate carries its findings here. */\ninterface ErrEnvelope {\n ok: false;\n data?: unknown;\n error: { code: string; message: string; hint: string };\n}\n\n/**\n * The agent-native output envelope, resolved ONCE at startup and threaded\n * through every command.\n *\n * The load-bearing invariant: in JSON mode, stdout receives EXACTLY ONE JSON\n * object for the whole process — nothing else, ever. Every progress line, warn,\n * and scrap of human prose goes to stderr. This is the single most common way\n * an agent's tool call breaks (a stray log line on stdout makes the JSON\n * unparseable), so the context enforces it structurally: `succeed`/`fail` are\n * the only writers of stdout, and a `settled` latch makes a second call throw\n * rather than emit a second object.\n */\nexport class CliContext {\n readonly jsonMode: boolean;\n private readonly stdout: WriteStream;\n private readonly stderr: WriteStream;\n private settled = false;\n\n constructor(input: ContextInput = {}) {\n const stdout = input.stdout ?? defaultStream(process.stdout);\n const stderr = input.stderr ?? defaultStream(process.stderr);\n const env = input.env ?? process.env;\n\n this.stdout = stdout;\n this.stderr = stderr;\n\n // Resolve JSON mode once. An EXPLICIT flag always wins: `--json` forces the\n // envelope on even at a terminal, `--no-json` forces the human form even\n // when piped. Only when the user expressed no preference is the mode\n // inferred — from a non-TTY stdout (piped into another program or an agent)\n // or the CLAUDECODE marker.\n const nonTty = stdout.isTTY !== true;\n this.jsonMode = input.jsonFlag ?? (nonTty || env.CLAUDECODE === '1');\n }\n\n /** Human/progress output — ALWAYS stderr, so stdout stays pure in JSON mode. */\n log(message: string): void {\n this.stderr.write(`${message}\\n`);\n }\n\n /** A warning — stderr, no ANSI. */\n warn(message: string): void {\n this.stderr.write(`${message}\\n`);\n }\n\n /**\n * Emit the `claude-code-hint` protocol line to STDERR. Used on --help and on\n * an unknown command so an agent reading stderr is told what to do next\n * without the line ever touching stdout.\n */\n emitClaudeCodeHint(payload: Record<string, unknown>): void {\n this.stderr.write(`claude-code-hint: ${JSON.stringify(payload)}\\n`);\n }\n\n /**\n * Terminal success. Writes the single stdout object (JSON mode) or a concise\n * human line (human mode). Returns the OK exit code so the caller can end the\n * process with it. Idempotent-by-latch: a second settle throws.\n */\n succeed(data: unknown, humanSummary?: string): ExitCode {\n this.settle();\n if (this.jsonMode) {\n const envelope: OkEnvelope = { ok: true, data };\n this.stdout.write(`${JSON.stringify(envelope)}\\n`);\n } else {\n this.stdout.write(`${humanSummary ?? 'OK'}\\n`);\n }\n return EXIT_CODES.OK;\n }\n\n /**\n * Terminal failure. Writes the single stdout error object (JSON mode) or the\n * message + hint to stderr (human mode). Returns the error's exit code.\n *\n * `error.hint` is guaranteed non-empty by CliError's constructor, so the\n * envelope's \"every error carries a hint\" invariant holds for every path\n * that reaches here.\n */\n fail(error: CliError, data?: unknown): ExitCode {\n this.failWith({\n code: error.code,\n message: error.message,\n hint: error.hint,\n exitCode: error.exitCode,\n data,\n });\n return error.exitCode;\n }\n\n /**\n * The low-level failure emitter. Used directly by `test`, whose exit code is\n * a TRANSPARENT passthrough of the delegated runner's exit code (which is not\n * one of the CLI's own frozen numeric conditions) and whose error `code` is\n * an out-of-taxonomy string. Enforces the same non-empty-hint invariant as\n * CliError so no failure path can emit an empty hint.\n */\n failWith(params: {\n code: string;\n message: string;\n hint: string;\n exitCode: number;\n data?: unknown;\n }): number {\n this.settle();\n if (!params.hint || params.hint.trim().length === 0) {\n throw new Error(\n `failWith for \"${params.message}\" was given an empty hint. Every CLI ` +\n 'error must carry an imperative remediation.',\n );\n }\n if (this.jsonMode) {\n const envelope: ErrEnvelope = {\n ok: false,\n ...(params.data !== undefined ? { data: params.data } : {}),\n error: {\n code: params.code,\n message: params.message,\n hint: params.hint,\n },\n };\n this.stdout.write(`${JSON.stringify(envelope)}\\n`);\n } else {\n this.stderr.write(`Error: ${params.message}\\n`);\n this.stderr.write(`Hint: ${params.hint}\\n`);\n }\n return params.exitCode;\n }\n\n private settle(): void {\n if (this.settled) {\n throw new Error(\n 'CliContext settled twice — a command tried to emit a second ' +\n 'terminal result. In JSON mode this would corrupt stdout with two ' +\n 'objects. This is a bug in the command.',\n );\n }\n this.settled = true;\n }\n}\n\nfunction defaultStream(stream: NodeJS.WriteStream): WriteStream {\n return {\n write: (chunk: string) => {\n stream.write(chunk);\n },\n isTTY: stream.isTTY,\n };\n}\n"]}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type ErrorCode, type ExitCode } from './exit-codes.js';
|
|
2
|
+
/**
|
|
3
|
+
* The one error type every command path throws. It carries the three things
|
|
4
|
+
* the envelope needs — a stable string `code`, a human `message`, and a
|
|
5
|
+
* non-empty imperative `hint` — plus the numeric `exitCode` the process ends
|
|
6
|
+
* with. Constructing one with an empty hint is a programming error and throws
|
|
7
|
+
* immediately, which is what lets `context.fail()` guarantee the envelope's
|
|
8
|
+
* "every error has a hint" invariant without a runtime check at the edge.
|
|
9
|
+
*/
|
|
10
|
+
export declare class CliError extends Error {
|
|
11
|
+
readonly code: ErrorCode;
|
|
12
|
+
readonly exitCode: ExitCode;
|
|
13
|
+
readonly hint: string;
|
|
14
|
+
constructor(params: {
|
|
15
|
+
code: ErrorCode;
|
|
16
|
+
exitCode: ExitCode;
|
|
17
|
+
message: string;
|
|
18
|
+
hint: string;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/** A usage error (unknown flag/command, bad argument). Exit 2. */
|
|
22
|
+
export declare function usageError(message: string, hint: string): CliError;
|
|
23
|
+
/** Validation produced error-severity findings. Exit 3. */
|
|
24
|
+
export declare function validationError(message: string, hint: string): CliError;
|
|
25
|
+
/** A precondition failure (missing tool, config, or file). Exit 9. */
|
|
26
|
+
export declare function preconditionError(message: string, hint: string): CliError;
|
|
27
|
+
/** An invalid-state failure (idempotency / ordering). Exit 7. */
|
|
28
|
+
export declare function invalidStateError(message: string, hint: string): CliError;
|
|
29
|
+
/** A not-found failure. Exit 6. */
|
|
30
|
+
export declare function notFoundError(message: string, hint: string): CliError;
|
|
31
|
+
/** Authentication is required but absent or expired. Exit 4. */
|
|
32
|
+
export declare function authRequiredError(message: string, hint: string): CliError;
|
|
33
|
+
/** Authenticated, but the server refused. Exit 5. */
|
|
34
|
+
export declare function forbiddenError(message: string, hint: string): CliError;
|
|
35
|
+
/** A network operation failed (DNS, TLS, timeout, 5xx). Exit 8. */
|
|
36
|
+
export declare function networkError(message: string, hint: string): CliError;
|
|
37
|
+
/**
|
|
38
|
+
* Coerce anything thrown into a CliError. A thrown CliError passes through; a
|
|
39
|
+
* plain Error becomes an INTERNAL (exit 1) with a generic-but-non-empty hint.
|
|
40
|
+
*/
|
|
41
|
+
export declare function toCliError(error: unknown): CliError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { ERROR_CODES, EXIT_CODES, } from './exit-codes.js';
|
|
2
|
+
/**
|
|
3
|
+
* The one error type every command path throws. It carries the three things
|
|
4
|
+
* the envelope needs — a stable string `code`, a human `message`, and a
|
|
5
|
+
* non-empty imperative `hint` — plus the numeric `exitCode` the process ends
|
|
6
|
+
* with. Constructing one with an empty hint is a programming error and throws
|
|
7
|
+
* immediately, which is what lets `context.fail()` guarantee the envelope's
|
|
8
|
+
* "every error has a hint" invariant without a runtime check at the edge.
|
|
9
|
+
*/
|
|
10
|
+
export class CliError extends Error {
|
|
11
|
+
constructor(params) {
|
|
12
|
+
super(params.message);
|
|
13
|
+
this.name = 'CliError';
|
|
14
|
+
if (!params.hint || params.hint.trim().length === 0) {
|
|
15
|
+
throw new Error(`CliError for "${params.message}" was constructed with an empty hint. ` +
|
|
16
|
+
'Every CLI error must carry an imperative remediation.');
|
|
17
|
+
}
|
|
18
|
+
this.code = params.code;
|
|
19
|
+
this.exitCode = params.exitCode;
|
|
20
|
+
this.hint = params.hint;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** A usage error (unknown flag/command, bad argument). Exit 2. */
|
|
24
|
+
export function usageError(message, hint) {
|
|
25
|
+
return new CliError({
|
|
26
|
+
code: ERROR_CODES.USAGE,
|
|
27
|
+
exitCode: EXIT_CODES.USAGE,
|
|
28
|
+
message,
|
|
29
|
+
hint,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Validation produced error-severity findings. Exit 3. */
|
|
33
|
+
export function validationError(message, hint) {
|
|
34
|
+
return new CliError({
|
|
35
|
+
code: ERROR_CODES.VALIDATION,
|
|
36
|
+
exitCode: EXIT_CODES.VALIDATION,
|
|
37
|
+
message,
|
|
38
|
+
hint,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** A precondition failure (missing tool, config, or file). Exit 9. */
|
|
42
|
+
export function preconditionError(message, hint) {
|
|
43
|
+
return new CliError({
|
|
44
|
+
code: ERROR_CODES.PRECONDITION_FAILED,
|
|
45
|
+
exitCode: EXIT_CODES.PRECONDITION_FAILED,
|
|
46
|
+
message,
|
|
47
|
+
hint,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** An invalid-state failure (idempotency / ordering). Exit 7. */
|
|
51
|
+
export function invalidStateError(message, hint) {
|
|
52
|
+
return new CliError({
|
|
53
|
+
code: ERROR_CODES.INVALID_STATE,
|
|
54
|
+
exitCode: EXIT_CODES.INVALID_STATE,
|
|
55
|
+
message,
|
|
56
|
+
hint,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** A not-found failure. Exit 6. */
|
|
60
|
+
export function notFoundError(message, hint) {
|
|
61
|
+
return new CliError({
|
|
62
|
+
code: ERROR_CODES.NOT_FOUND,
|
|
63
|
+
exitCode: EXIT_CODES.NOT_FOUND,
|
|
64
|
+
message,
|
|
65
|
+
hint,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Authentication is required but absent or expired. Exit 4. */
|
|
69
|
+
export function authRequiredError(message, hint) {
|
|
70
|
+
return new CliError({
|
|
71
|
+
code: ERROR_CODES.AUTH_REQUIRED,
|
|
72
|
+
exitCode: EXIT_CODES.AUTH_REQUIRED,
|
|
73
|
+
message,
|
|
74
|
+
hint,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** Authenticated, but the server refused. Exit 5. */
|
|
78
|
+
export function forbiddenError(message, hint) {
|
|
79
|
+
return new CliError({
|
|
80
|
+
code: ERROR_CODES.FORBIDDEN,
|
|
81
|
+
exitCode: EXIT_CODES.FORBIDDEN,
|
|
82
|
+
message,
|
|
83
|
+
hint,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/** A network operation failed (DNS, TLS, timeout, 5xx). Exit 8. */
|
|
87
|
+
export function networkError(message, hint) {
|
|
88
|
+
return new CliError({
|
|
89
|
+
code: ERROR_CODES.NETWORK,
|
|
90
|
+
exitCode: EXIT_CODES.NETWORK,
|
|
91
|
+
message,
|
|
92
|
+
hint,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Coerce anything thrown into a CliError. A thrown CliError passes through; a
|
|
97
|
+
* plain Error becomes an INTERNAL (exit 1) with a generic-but-non-empty hint.
|
|
98
|
+
*/
|
|
99
|
+
export function toCliError(error) {
|
|
100
|
+
if (error instanceof CliError)
|
|
101
|
+
return error;
|
|
102
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
103
|
+
return new CliError({
|
|
104
|
+
code: ERROR_CODES.INTERNAL,
|
|
105
|
+
exitCode: EXIT_CODES.INTERNAL,
|
|
106
|
+
message,
|
|
107
|
+
hint: 'This is an internal CLI error. Re-run with --json to capture the full ' +
|
|
108
|
+
'envelope and report it at npm@govastly.com if it persists.',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ERROR_CODES,\n EXIT_CODES,\n type ErrorCode,\n type ExitCode,\n} from './exit-codes';\n\n/**\n * The one error type every command path throws. It carries the three things\n * the envelope needs — a stable string `code`, a human `message`, and a\n * non-empty imperative `hint` — plus the numeric `exitCode` the process ends\n * with. Constructing one with an empty hint is a programming error and throws\n * immediately, which is what lets `context.fail()` guarantee the envelope's\n * \"every error has a hint\" invariant without a runtime check at the edge.\n */\nexport class CliError extends Error {\n readonly code: ErrorCode;\n readonly exitCode: ExitCode;\n readonly hint: string;\n\n constructor(params: {\n code: ErrorCode;\n exitCode: ExitCode;\n message: string;\n hint: string;\n }) {\n super(params.message);\n this.name = 'CliError';\n\n if (!params.hint || params.hint.trim().length === 0) {\n throw new Error(\n `CliError for \"${params.message}\" was constructed with an empty hint. ` +\n 'Every CLI error must carry an imperative remediation.',\n );\n }\n\n this.code = params.code;\n this.exitCode = params.exitCode;\n this.hint = params.hint;\n }\n}\n\n/** A usage error (unknown flag/command, bad argument). Exit 2. */\nexport function usageError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.USAGE,\n exitCode: EXIT_CODES.USAGE,\n message,\n hint,\n });\n}\n\n/** Validation produced error-severity findings. Exit 3. */\nexport function validationError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.VALIDATION,\n exitCode: EXIT_CODES.VALIDATION,\n message,\n hint,\n });\n}\n\n/** A precondition failure (missing tool, config, or file). Exit 9. */\nexport function preconditionError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.PRECONDITION_FAILED,\n exitCode: EXIT_CODES.PRECONDITION_FAILED,\n message,\n hint,\n });\n}\n\n/** An invalid-state failure (idempotency / ordering). Exit 7. */\nexport function invalidStateError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.INVALID_STATE,\n exitCode: EXIT_CODES.INVALID_STATE,\n message,\n hint,\n });\n}\n\n/** A not-found failure. Exit 6. */\nexport function notFoundError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.NOT_FOUND,\n exitCode: EXIT_CODES.NOT_FOUND,\n message,\n hint,\n });\n}\n\n/** Authentication is required but absent or expired. Exit 4. */\nexport function authRequiredError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.AUTH_REQUIRED,\n exitCode: EXIT_CODES.AUTH_REQUIRED,\n message,\n hint,\n });\n}\n\n/** Authenticated, but the server refused. Exit 5. */\nexport function forbiddenError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.FORBIDDEN,\n exitCode: EXIT_CODES.FORBIDDEN,\n message,\n hint,\n });\n}\n\n/** A network operation failed (DNS, TLS, timeout, 5xx). Exit 8. */\nexport function networkError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.NETWORK,\n exitCode: EXIT_CODES.NETWORK,\n message,\n hint,\n });\n}\n\n/**\n * Coerce anything thrown into a CliError. A thrown CliError passes through; a\n * plain Error becomes an INTERNAL (exit 1) with a generic-but-non-empty hint.\n */\nexport function toCliError(error: unknown): CliError {\n if (error instanceof CliError) return error;\n\n const message = error instanceof Error ? error.message : String(error);\n return new CliError({\n code: ERROR_CODES.INTERNAL,\n exitCode: EXIT_CODES.INTERNAL,\n message,\n hint:\n 'This is an internal CLI error. Re-run with --json to capture the full ' +\n 'envelope and report it at npm@govastly.com if it persists.',\n });\n}\n"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The frozen exit-code taxonomy. This is a PUBLIC CONTRACT: agents and CI
|
|
3
|
+
* branch on these numbers, so a value never changes meaning once shipped. New
|
|
4
|
+
* conditions get new numbers; they are never renumbered.
|
|
5
|
+
*
|
|
6
|
+
* This cut (init / validate / test) only ever emits 0, 1, 2, 3, and 9 — but
|
|
7
|
+
* the whole taxonomy is fixed here up front so later cuts (auth, publish, the
|
|
8
|
+
* remote gate) slot in without renumbering.
|
|
9
|
+
*/
|
|
10
|
+
export declare const EXIT_CODES: {
|
|
11
|
+
/** Success. */
|
|
12
|
+
readonly OK: 0;
|
|
13
|
+
/** An unexpected internal error — a bug in the CLI, not the user's input. */
|
|
14
|
+
readonly INTERNAL: 1;
|
|
15
|
+
/** Usage error: an unknown flag/command, a missing required argument. */
|
|
16
|
+
readonly USAGE: 2;
|
|
17
|
+
/** Validation produced error-severity findings (the definition is invalid). */
|
|
18
|
+
readonly VALIDATION: 3;
|
|
19
|
+
/** Authentication is required but absent. */
|
|
20
|
+
readonly AUTH_REQUIRED: 4;
|
|
21
|
+
/** Authenticated, but not permitted. */
|
|
22
|
+
readonly FORBIDDEN: 5;
|
|
23
|
+
/** A named resource (project, file, definition) was not found. */
|
|
24
|
+
readonly NOT_FOUND: 6;
|
|
25
|
+
/** The command cannot run in the current state (idempotency / ordering). */
|
|
26
|
+
readonly INVALID_STATE: 7;
|
|
27
|
+
/** A network operation failed. */
|
|
28
|
+
readonly NETWORK: 8;
|
|
29
|
+
/** A precondition failed (a required tool, config, or file is missing). */
|
|
30
|
+
readonly PRECONDITION_FAILED: 9;
|
|
31
|
+
/** A remote/publish gate rejected the submission. */
|
|
32
|
+
readonly GATE_FAILED: 10;
|
|
33
|
+
};
|
|
34
|
+
export type ExitCode = (typeof EXIT_CODES)[keyof typeof EXIT_CODES];
|
|
35
|
+
/**
|
|
36
|
+
* Stable string codes carried in the JSON error envelope. Kept alongside the
|
|
37
|
+
* numeric exits so a machine reader can switch on a name without memorizing an
|
|
38
|
+
* integer. One code per numeric exit that this taxonomy can emit.
|
|
39
|
+
*/
|
|
40
|
+
export declare const ERROR_CODES: {
|
|
41
|
+
readonly INTERNAL: "internal";
|
|
42
|
+
readonly USAGE: "usage";
|
|
43
|
+
readonly VALIDATION: "validation";
|
|
44
|
+
readonly AUTH_REQUIRED: "auth_required";
|
|
45
|
+
readonly FORBIDDEN: "forbidden";
|
|
46
|
+
readonly NOT_FOUND: "not_found";
|
|
47
|
+
readonly INVALID_STATE: "invalid_state";
|
|
48
|
+
readonly NETWORK: "network";
|
|
49
|
+
readonly PRECONDITION_FAILED: "precondition_failed";
|
|
50
|
+
readonly GATE_FAILED: "gate_failed";
|
|
51
|
+
};
|
|
52
|
+
export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The frozen exit-code taxonomy. This is a PUBLIC CONTRACT: agents and CI
|
|
3
|
+
* branch on these numbers, so a value never changes meaning once shipped. New
|
|
4
|
+
* conditions get new numbers; they are never renumbered.
|
|
5
|
+
*
|
|
6
|
+
* This cut (init / validate / test) only ever emits 0, 1, 2, 3, and 9 — but
|
|
7
|
+
* the whole taxonomy is fixed here up front so later cuts (auth, publish, the
|
|
8
|
+
* remote gate) slot in without renumbering.
|
|
9
|
+
*/
|
|
10
|
+
export const EXIT_CODES = {
|
|
11
|
+
/** Success. */
|
|
12
|
+
OK: 0,
|
|
13
|
+
/** An unexpected internal error — a bug in the CLI, not the user's input. */
|
|
14
|
+
INTERNAL: 1,
|
|
15
|
+
/** Usage error: an unknown flag/command, a missing required argument. */
|
|
16
|
+
USAGE: 2,
|
|
17
|
+
/** Validation produced error-severity findings (the definition is invalid). */
|
|
18
|
+
VALIDATION: 3,
|
|
19
|
+
/** Authentication is required but absent. */
|
|
20
|
+
AUTH_REQUIRED: 4,
|
|
21
|
+
/** Authenticated, but not permitted. */
|
|
22
|
+
FORBIDDEN: 5,
|
|
23
|
+
/** A named resource (project, file, definition) was not found. */
|
|
24
|
+
NOT_FOUND: 6,
|
|
25
|
+
/** The command cannot run in the current state (idempotency / ordering). */
|
|
26
|
+
INVALID_STATE: 7,
|
|
27
|
+
/** A network operation failed. */
|
|
28
|
+
NETWORK: 8,
|
|
29
|
+
/** A precondition failed (a required tool, config, or file is missing). */
|
|
30
|
+
PRECONDITION_FAILED: 9,
|
|
31
|
+
/** A remote/publish gate rejected the submission. */
|
|
32
|
+
GATE_FAILED: 10,
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Stable string codes carried in the JSON error envelope. Kept alongside the
|
|
36
|
+
* numeric exits so a machine reader can switch on a name without memorizing an
|
|
37
|
+
* integer. One code per numeric exit that this taxonomy can emit.
|
|
38
|
+
*/
|
|
39
|
+
export const ERROR_CODES = {
|
|
40
|
+
INTERNAL: 'internal',
|
|
41
|
+
USAGE: 'usage',
|
|
42
|
+
VALIDATION: 'validation',
|
|
43
|
+
AUTH_REQUIRED: 'auth_required',
|
|
44
|
+
FORBIDDEN: 'forbidden',
|
|
45
|
+
NOT_FOUND: 'not_found',
|
|
46
|
+
INVALID_STATE: 'invalid_state',
|
|
47
|
+
NETWORK: 'network',
|
|
48
|
+
PRECONDITION_FAILED: 'precondition_failed',
|
|
49
|
+
GATE_FAILED: 'gate_failed',
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=exit-codes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exit-codes.js","sourceRoot":"","sources":["../src/exit-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,eAAe;IACf,EAAE,EAAE,CAAC;IACL,6EAA6E;IAC7E,QAAQ,EAAE,CAAC;IACX,yEAAyE;IACzE,KAAK,EAAE,CAAC;IACR,+EAA+E;IAC/E,UAAU,EAAE,CAAC;IACb,6CAA6C;IAC7C,aAAa,EAAE,CAAC;IAChB,wCAAwC;IACxC,SAAS,EAAE,CAAC;IACZ,kEAAkE;IAClE,SAAS,EAAE,CAAC;IACZ,4EAA4E;IAC5E,aAAa,EAAE,CAAC;IAChB,kCAAkC;IAClC,OAAO,EAAE,CAAC;IACV,2EAA2E;IAC3E,mBAAmB,EAAE,CAAC;IACtB,qDAAqD;IACrD,WAAW,EAAE,EAAE;CACP,CAAC;AAIX;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,QAAQ,EAAE,UAAU;IACpB,KAAK,EAAE,OAAO;IACd,UAAU,EAAE,YAAY;IACxB,aAAa,EAAE,eAAe;IAC9B,SAAS,EAAE,WAAW;IACtB,SAAS,EAAE,WAAW;IACtB,aAAa,EAAE,eAAe;IAC9B,OAAO,EAAE,SAAS;IAClB,mBAAmB,EAAE,qBAAqB;IAC1C,WAAW,EAAE,aAAa;CAClB,CAAC","sourcesContent":["/**\n * The frozen exit-code taxonomy. This is a PUBLIC CONTRACT: agents and CI\n * branch on these numbers, so a value never changes meaning once shipped. New\n * conditions get new numbers; they are never renumbered.\n *\n * This cut (init / validate / test) only ever emits 0, 1, 2, 3, and 9 — but\n * the whole taxonomy is fixed here up front so later cuts (auth, publish, the\n * remote gate) slot in without renumbering.\n */\nexport const EXIT_CODES = {\n /** Success. */\n OK: 0,\n /** An unexpected internal error — a bug in the CLI, not the user's input. */\n INTERNAL: 1,\n /** Usage error: an unknown flag/command, a missing required argument. */\n USAGE: 2,\n /** Validation produced error-severity findings (the definition is invalid). */\n VALIDATION: 3,\n /** Authentication is required but absent. */\n AUTH_REQUIRED: 4,\n /** Authenticated, but not permitted. */\n FORBIDDEN: 5,\n /** A named resource (project, file, definition) was not found. */\n NOT_FOUND: 6,\n /** The command cannot run in the current state (idempotency / ordering). */\n INVALID_STATE: 7,\n /** A network operation failed. */\n NETWORK: 8,\n /** A precondition failed (a required tool, config, or file is missing). */\n PRECONDITION_FAILED: 9,\n /** A remote/publish gate rejected the submission. */\n GATE_FAILED: 10,\n} as const;\n\nexport type ExitCode = (typeof EXIT_CODES)[keyof typeof EXIT_CODES];\n\n/**\n * Stable string codes carried in the JSON error envelope. Kept alongside the\n * numeric exits so a machine reader can switch on a name without memorizing an\n * integer. One code per numeric exit that this taxonomy can emit.\n */\nexport const ERROR_CODES = {\n INTERNAL: 'internal',\n USAGE: 'usage',\n VALIDATION: 'validation',\n AUTH_REQUIRED: 'auth_required',\n FORBIDDEN: 'forbidden',\n NOT_FOUND: 'not_found',\n INVALID_STATE: 'invalid_state',\n NETWORK: 'network',\n PRECONDITION_FAILED: 'precondition_failed',\n GATE_FAILED: 'gate_failed',\n} as const;\n\nexport type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];\n"]}
|