cli-agent-mode 0.0.0-stage → 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gordey Levchenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,133 @@
1
- # Temporary Holding Version
1
+ # cli-agent-mode
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Make any [commander.js](https://github.com/tj/commander.js) CLI usable by AI agents** (Claude Code, Cursor, Codex, Gemini CLI, Copilot) **without breaking it for humans.**
4
+
5
+ Two lines turn your CLI into one that agents can drive reliably:
6
+
7
+ - **`--json` output envelope** — every command prints exactly one JSON document on stdout: `{"ok":true,"data":...}` or `{"ok":false,"error":{...}}`.
8
+ - **Machine-readable errors** — stable `code`, a human `message`, and a `suggestion` the agent can follow literally. Commander usage errors are mapped too (`UNKNOWN_OPTION`, `MISSING_ARGUMENT`, ...).
9
+ - **Self-description** — `mycli describe [command]` (and `--help --json`) print the full CLI schema in one call: arguments, option flags, defaults, choices, subcommands. Compact by design (falsy fields and empty lists are omitted) to save agent tokens.
10
+ - **Auto-detects agents** — JSON turns on automatically under `AI_AGENT`, `CLAUDECODE`, `CURSOR_AGENT`, `GEMINI_CLI`, `CODEX_SANDBOX`, ... (precedence: `--json` > `CLI_AGENT_MODE` > auto-detect)
11
+ - **Escape hatch** — `CLI_AGENT_MODE=human` forces human output even under an agent, `CLI_AGENT_MODE=json` forces JSON.
12
+ - **Stable exit codes** — `0` ok, `1` runtime error, `2` usage error.
13
+ - **Clean stdout** — stray `console.log` inside actions is redirected to stderr in JSON mode.
14
+
15
+ Zero dependencies. ~4 kB gzipped. ESM and CommonJS, Node 20+, commander 12+.
16
+
17
+ ```bash
18
+ npm i cli-agent-mode
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```ts
24
+ import { Command } from 'commander';
25
+ import { AgentError, agentMode, run } from 'cli-agent-mode';
26
+
27
+ const program = agentMode(new Command('deployctl')).version('1.0.0'); // 1. wrap the program
28
+
29
+ program
30
+ .command('deploy')
31
+ .argument('<env>', 'environment name')
32
+ .option('--dry-run', 'show the plan without deploying')
33
+ .action(async (env, opts) => {
34
+ if (!['staging', 'prod'].includes(env)) {
35
+ throw new AgentError('ENV_NOT_FOUND', `Unknown environment "${env}"`, {
36
+ suggestion: 'Use one of: staging, prod.',
37
+ });
38
+ }
39
+ return { env, url: `https://${env}.example.com` }; // return data instead of printing it
40
+ });
41
+
42
+ await run(program); // 2. replace program.parseAsync()
43
+ ```
44
+
45
+ What an agent sees:
46
+
47
+ ```console
48
+ $ deployctl deploy prod --json
49
+ {"ok":true,"data":{"env":"prod","url":"https://prod.example.com"}}
50
+
51
+ $ deployctl deploy mars --json # exit code 1
52
+ {"ok":false,"error":{"code":"ENV_NOT_FOUND","message":"Unknown environment \"mars\"","suggestion":"Use one of: staging, prod."}}
53
+
54
+ $ deployctl deploy --json # exit code 2
55
+ {"ok":false,"error":{"code":"MISSING_ARGUMENT","message":"missing required argument 'env'","suggestion":"Run \"deployctl describe deploy\" to see accepted arguments and options."}}
56
+
57
+ $ deployctl describe deploy --json
58
+ {"ok":true,"data":{"name":"deploy","description":"Deploy an environment","usage":"deployctl deploy [options] <env>","arguments":[{"name":"env","description":"environment name","required":true}],"options":[{"flags":"--dry-run","description":"show the plan without deploying"}]}}
59
+ ```
60
+
61
+ What a human sees (same CLI, no agent detected):
62
+
63
+ ```console
64
+ $ deployctl deploy mars
65
+ error: Unknown environment "mars"
66
+ hint: Use one of: staging, prod.
67
+ ```
68
+
69
+ ## API
70
+
71
+ ### `agentMode(program, options?)`
72
+
73
+ Call right after `new Command()`, **before** defining subcommands. Adds the `--json` flag and the `describe` command, and lets `.action()` handlers return a result.
74
+
75
+ | option | default | |
76
+ |---|---|---|
77
+ | `autoDetect` | `true` | enable JSON when an AI agent is detected |
78
+ | `describeCommand` | `'describe'` | name of the schema command, `false` to disable |
79
+ | `formatHuman` | strings as-is, other values as pretty JSON | `(data, command) => string \| undefined` |
80
+
81
+ ### `run(program, options?)` → `Promise<{ mode, exitCode, envelope }>`
82
+
83
+ Parses `process.argv`, runs the action, prints the result/error, sets `process.exitCode`. For tests pass `args`, `env`, `stdout`, `stderr`, `json`, `setExitCode: false`.
84
+
85
+ ### `new AgentError(code, message, { suggestion?, details?, exitCode? })`
86
+
87
+ Throw it from an action for a structured error. Anything else thrown becomes `INTERNAL_ERROR`.
88
+
89
+ ### `describeCommand(cmd)`, `detectAgent(env?)`
90
+
91
+ Lower-level helpers: the schema generator and the agent detector.
92
+
93
+ ### Error codes from commander
94
+
95
+ `UNKNOWN_OPTION`, `UNKNOWN_COMMAND`, `MISSING_ARGUMENT`, `MISSING_OPTION_VALUE`, `MISSING_REQUIRED_OPTION`, `INVALID_ARGUMENT`, `EXCESS_ARGUMENTS`, `CONFLICTING_OPTIONS`, `MISSING_COMMAND`, `USAGE_ERROR`.
96
+
97
+ ## Benchmark: does it actually help an agent?
98
+
99
+ Same tiny notes CLI, written twice: plain commander vs. with `cli-agent-mode`. Claude Code (headless, Claude Haiku) gets the same task: add notes, mark one done, delete a tag (the CLI refuses to delete unfinished notes without `--force`), hit a missing id, report the result. 6 runs per variant:
100
+
101
+ | | plain commander | cli-agent-mode |
102
+ |---|:-:|:-:|
103
+ | task completed | 6/6 | 6/6 |
104
+ | CLI calls (avg, range) | 17.3 (16–19) | **11.3 (9–12)** |
105
+ | `--help` calls per run | 4 | **1** |
106
+ | failed calls (avg) | 3.0 | **1.8** |
107
+ | CLI output (avg bytes) | ~1100 | ~2000 |
108
+
109
+ **−35% CLI calls with the same success rate.** The agent learns the whole CLI from one `--help` (which returns the schema of every command) instead of reading help page by page, and recovers from errors using `code` + `suggestion`. The trade-off is ~1.8× more output (≈250 tokens per task here).
110
+
111
+ Reproduce: [`examples/playground/agent-bench.sh`](examples/playground/agent-bench.sh) `5 haiku` (requires Claude Code).
112
+
113
+ ## Why not ...
114
+
115
+ | | `--json` from return values | structured errors + suggestion | schema command | agent auto-detect |
116
+ |---|:-:|:-:|:-:|:-:|
117
+ | plain commander | ✗ | ✗ | ✗ | ✗ |
118
+ | oclif (`enableJsonFlag`) | ✓ | partial | ✗ | ✗ |
119
+ | hand-rolled `if (opts.json)` | per command | usually ✗ | ✗ | ✗ |
120
+ | **cli-agent-mode** | ✓ | ✓ | ✓ | ✓ |
121
+
122
+ If you are already on oclif, you have `--json`. If you are on commander — the most used Node CLI framework — this is the missing piece.
123
+
124
+ ## Limitations
125
+
126
+ - Subcommands must be created with `program.command()` (or added via `addCommand()` *before* their `.action()` is set) so return values can be captured.
127
+ - `--json` is reserved: it is removed from argv before parsing (except after `--`).
128
+ - In JSON mode `console.log/info/debug` are redirected to stderr, but direct `process.stdout.write` calls inside actions are not — return data instead.
129
+ - Typed `@commander-js/extra-typings` is not supported yet.
130
+
131
+ ## License
132
+
133
+ MIT
@@ -0,0 +1,8 @@
1
+ import 'commander';
2
+
3
+ declare module 'commander' {
4
+ interface Command {
5
+ /** With cli-agent-mode the handler may return a JSON-serializable result. */
6
+ action(fn: (this: this, ...args: any[]) => unknown): this;
7
+ }
8
+ }
package/dist/index.cjs ADDED
@@ -0,0 +1,337 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let commander = require("commander");
3
+ let node_util = require("node:util");
4
+ //#region src/detect.ts
5
+ /**
6
+ * Synchronous, dependency-free detection of AI coding agents.
7
+ * Env variables mirror @vercel/detect-agent (Nov 2026) plus the `AI_AGENT` convention.
8
+ * CURSOR_TRACE_ID is deliberately ignored: it is also set in Cursor's terminal for humans.
9
+ * Returns the agent name or `undefined`.
10
+ */
11
+ function detectAgent(env = process.env) {
12
+ const aiAgent = env.AI_AGENT?.trim();
13
+ if (aiAgent) return aiAgent;
14
+ if (env.CLAUDECODE || env.CLAUDE_CODE) return "claude";
15
+ if (env.CURSOR_AGENT || env.CURSOR_EXTENSION_HOST_ROLE === "agent-exec") return "cursor-cli";
16
+ if (env.GEMINI_CLI) return "gemini";
17
+ if (env.CODEX_SANDBOX || env.CODEX_CI || env.CODEX_THREAD_ID) return "codex";
18
+ if (env.ANTIGRAVITY_AGENT) return "antigravity";
19
+ if (env.AUGMENT_AGENT) return "augment-cli";
20
+ if (env.OPENCODE === "1" || env.OPENCODE_CLIENT) return "opencode";
21
+ if (env.COPILOT_MODEL || env.COPILOT_ALLOW_ALL) return "github-copilot";
22
+ if (env.REPL_ID) return "replit";
23
+ }
24
+ //#endregion
25
+ //#region src/describe.ts
26
+ const clean = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== "" && v !== false && !(Array.isArray(v) && v.length === 0)));
27
+ function describeArgument(a) {
28
+ return clean({
29
+ name: a.name(),
30
+ description: a.description,
31
+ required: a.required || void 0,
32
+ variadic: a.variadic || void 0,
33
+ default: a.defaultValue,
34
+ choices: a.argChoices
35
+ });
36
+ }
37
+ function describeOption(o) {
38
+ return clean({
39
+ flags: o.flags,
40
+ description: o.description,
41
+ required: o.mandatory || void 0,
42
+ default: o.defaultValue,
43
+ choices: o.argChoices,
44
+ env: o.envVar
45
+ });
46
+ }
47
+ function commandPath(cmd) {
48
+ const parts = [];
49
+ for (let c = cmd; c; c = c.parent) parts.unshift(c.name());
50
+ return parts.join(" ");
51
+ }
52
+ const isHidden = (c) => c._hidden === true;
53
+ /** Compact JSON schema of a command and all of its visible subcommands. */
54
+ function describeCommand(cmd) {
55
+ return clean({
56
+ name: cmd.name(),
57
+ description: cmd.description(),
58
+ usage: `${commandPath(cmd)} ${cmd.usage()}`.trim(),
59
+ aliases: cmd.aliases(),
60
+ arguments: cmd.registeredArguments.map(describeArgument),
61
+ options: cmd.options.filter((o) => !o.hidden).map(describeOption),
62
+ commands: cmd.commands.filter((c) => !isHidden(c)).map(describeCommand)
63
+ });
64
+ }
65
+ /** Resolve `["deploy", "preview"]` to the nested subcommand (by name or alias). */
66
+ function findCommand(root, path) {
67
+ let cur = root;
68
+ for (const part of path) {
69
+ cur = cur?.commands.find((c) => c.name() === part || c.aliases().includes(part));
70
+ if (!cur) return void 0;
71
+ }
72
+ return cur;
73
+ }
74
+ //#endregion
75
+ //#region src/errors.ts
76
+ /**
77
+ * An error with a stable machine-readable `code`.
78
+ *
79
+ * @example
80
+ * throw new AgentError('ENV_NOT_FOUND', `Environment "${env}" does not exist`, {
81
+ * suggestion: 'Run "mycli env list" to see available environments.',
82
+ * });
83
+ */
84
+ var AgentError = class extends Error {
85
+ code;
86
+ suggestion;
87
+ details;
88
+ exitCode;
89
+ constructor(code, message, options = {}) {
90
+ super(message);
91
+ this.name = "AgentError";
92
+ this.code = code;
93
+ this.suggestion = options.suggestion;
94
+ this.details = options.details;
95
+ this.exitCode = options.exitCode ?? 1;
96
+ }
97
+ };
98
+ /** Exit codes used by cli-agent-mode. */
99
+ const EXIT = {
100
+ OK: 0,
101
+ ERROR: 1,
102
+ USAGE: 2
103
+ };
104
+ //#endregion
105
+ //#region src/agent-mode.ts
106
+ const states = /* @__PURE__ */ new WeakMap();
107
+ const rootOf = (cmd) => {
108
+ let c = cmd;
109
+ while (c.parent) c = c.parent;
110
+ return c;
111
+ };
112
+ /** Make every `.action()` handler's return value available as the command result. */
113
+ function patch(cmd) {
114
+ const proto = Object.getPrototypeOf(cmd);
115
+ cmd.action = function(fn) {
116
+ return proto.action.call(this, async function(...args) {
117
+ const value = await fn.apply(this, args);
118
+ const state = states.get(rootOf(this));
119
+ if (state) state.result = {
120
+ value,
121
+ command: this
122
+ };
123
+ });
124
+ };
125
+ cmd.createCommand = (name) => patch(proto.createCommand.call(cmd, name));
126
+ const addCommand = cmd.addCommand;
127
+ cmd.addCommand = function(sub, opts) {
128
+ walk(sub, patch);
129
+ return addCommand.call(this, sub, opts);
130
+ };
131
+ return cmd;
132
+ }
133
+ function walk(cmd, fn) {
134
+ fn(cmd);
135
+ for (const sub of cmd.commands) walk(sub, fn);
136
+ }
137
+ /**
138
+ * Turn a commander program into an agent-friendly CLI.
139
+ * Call it right after creating the program, before defining subcommands.
140
+ *
141
+ * @example
142
+ * const program = agentMode(new Command('mycli'));
143
+ * program.command('deploy <env>').action(async (env) => ({ url: `https://${env}.example.com` }));
144
+ * await run(program);
145
+ */
146
+ function agentMode(program, options = {}) {
147
+ const state = { options: {
148
+ autoDetect: options.autoDetect ?? true,
149
+ describeCommand: options.describeCommand ?? "describe",
150
+ formatHuman: options.formatHuman
151
+ } };
152
+ states.set(program, state);
153
+ walk(program, patch);
154
+ program.option("--json", "print one JSON envelope on stdout (auto-on for AI agents)");
155
+ const name = state.options.describeCommand;
156
+ if (name) program.command(`${name} [command...]`).description("print the CLI schema as JSON").action((path = []) => {
157
+ const target = findCommand(program, path);
158
+ if (!target) throw new AgentError("UNKNOWN_COMMAND", `Unknown command: ${path.join(" ")}`, {
159
+ suggestion: `Run "${program.name()} ${name}" to list all commands.`,
160
+ exitCode: EXIT.USAGE
161
+ });
162
+ return describeCommand(target);
163
+ });
164
+ return program;
165
+ }
166
+ const USAGE_CODES = {
167
+ "commander.unknownOption": "UNKNOWN_OPTION",
168
+ "commander.unknownCommand": "UNKNOWN_COMMAND",
169
+ "commander.missingArgument": "MISSING_ARGUMENT",
170
+ "commander.optionMissingArgument": "MISSING_OPTION_VALUE",
171
+ "commander.missingMandatoryOptionValue": "MISSING_REQUIRED_OPTION",
172
+ "commander.invalidArgument": "INVALID_ARGUMENT",
173
+ "commander.excessArguments": "EXCESS_ARGUMENTS",
174
+ "commander.conflictingOption": "CONFLICTING_OPTIONS",
175
+ "commander.help": "MISSING_COMMAND"
176
+ };
177
+ function extractJsonFlag(args) {
178
+ const end = args.indexOf("--");
179
+ const head = end === -1 ? args : args.slice(0, end);
180
+ const tail = end === -1 ? [] : args.slice(end);
181
+ const json = head.includes("--json");
182
+ return {
183
+ args: [...head.filter((a) => a !== "--json"), ...tail],
184
+ json
185
+ };
186
+ }
187
+ const defaultHuman = (data) => data === void 0 || data === null ? void 0 : typeof data === "string" ? data : JSON.stringify(data, null, 2);
188
+ /** Parse arguments, run the matching action and print the result or error in the right format. */
189
+ async function run(program, runOptions = {}) {
190
+ const state = states.get(program);
191
+ if (!state) throw new Error("cli-agent-mode: call agentMode(program) before run(program)");
192
+ state.result = void 0;
193
+ const env = runOptions.env ?? process.env;
194
+ const stdout = runOptions.stdout ?? process.stdout;
195
+ const stderr = runOptions.stderr ?? process.stderr;
196
+ const extracted = extractJsonFlag(runOptions.args ?? process.argv.slice(2));
197
+ const forced = env.CLI_AGENT_MODE === "json" ? true : env.CLI_AGENT_MODE === "human" ? false : void 0;
198
+ const json = runOptions.json ?? (extracted.json || (forced ?? (state.options.autoDetect && detectAgent(env) !== void 0)));
199
+ const mode = json ? "json" : "human";
200
+ let outBuf = "";
201
+ let errorCommand = program;
202
+ const restore = [];
203
+ walk(program, (cmd) => {
204
+ const prevOutput = cmd.configureOutput();
205
+ const prevHelp = cmd.configureHelp();
206
+ restore.push(() => {
207
+ cmd.configureOutput(prevOutput);
208
+ cmd.configureHelp(prevHelp);
209
+ });
210
+ cmd.exitOverride();
211
+ cmd.configureOutput({
212
+ ...prevOutput,
213
+ writeOut: (s) => json ? outBuf += s : stdout.write(s),
214
+ writeErr: (s) => json ? void 0 : stderr.write(s),
215
+ outputError: (s, write) => {
216
+ errorCommand = cmd;
217
+ if (!json) write(s);
218
+ }
219
+ });
220
+ if (json) cmd.configureHelp({
221
+ ...prevHelp,
222
+ formatHelp: (c) => JSON.stringify({
223
+ ok: true,
224
+ data: describeCommand(c)
225
+ }) + "\n"
226
+ });
227
+ });
228
+ const consoleKeys = [
229
+ "log",
230
+ "info",
231
+ "debug"
232
+ ];
233
+ const savedConsole = consoleKeys.map((k) => console[k]);
234
+ if (json) for (const k of consoleKeys) console[k] = (...a) => void stderr.write((0, node_util.format)(...a) + "\n");
235
+ let envelope;
236
+ let exitCode = EXIT.OK;
237
+ let printed = false;
238
+ try {
239
+ await program.parseAsync(extracted.args, { from: "user" });
240
+ const result = state.result;
241
+ const value = result?.value;
242
+ envelope = {
243
+ ok: true,
244
+ data: value ?? null
245
+ };
246
+ if (!json) {
247
+ const text = (state.options.formatHuman ?? defaultHuman)(value, result?.command ?? program);
248
+ if (text !== void 0) stdout.write(text + "\n");
249
+ printed = true;
250
+ }
251
+ } catch (err) {
252
+ if (err instanceof commander.CommanderError && (err.code === "commander.helpDisplayed" || err.code === "commander.help" && err.exitCode === 0)) {
253
+ if (json) stdout.write(outBuf);
254
+ return finish({
255
+ ok: true,
256
+ data: null
257
+ }, EXIT.OK, true);
258
+ }
259
+ if (err instanceof commander.CommanderError && err.code === "commander.version") {
260
+ envelope = {
261
+ ok: true,
262
+ data: { version: outBuf.trim() || program.version() }
263
+ };
264
+ exitCode = EXIT.OK;
265
+ if (!json) printed = true;
266
+ } else {
267
+ const { payload, code } = toPayload(err, program, errorCommand, state.options.describeCommand);
268
+ envelope = {
269
+ ok: false,
270
+ error: payload
271
+ };
272
+ exitCode = code;
273
+ if (!json) {
274
+ if (!(err instanceof commander.CommanderError)) stderr.write(`error: ${payload.message}\n`);
275
+ if (payload.suggestion) stderr.write(`hint: ${payload.suggestion}\n`);
276
+ printed = true;
277
+ }
278
+ }
279
+ } finally {
280
+ consoleKeys.forEach((k, i) => console[k] = savedConsole[i]);
281
+ restore.forEach((r) => r());
282
+ }
283
+ return finish(envelope, exitCode, printed);
284
+ function finish(env, code, done) {
285
+ if (json && !done) stdout.write(JSON.stringify(env) + "\n");
286
+ if (runOptions.setExitCode ?? true) process.exitCode = code;
287
+ return {
288
+ mode,
289
+ exitCode: code,
290
+ envelope: env
291
+ };
292
+ }
293
+ }
294
+ function toPayload(err, root, at, describeName) {
295
+ if (err instanceof AgentError) return {
296
+ payload: strip({
297
+ code: err.code,
298
+ message: err.message,
299
+ suggestion: err.suggestion,
300
+ details: err.details
301
+ }),
302
+ code: err.exitCode
303
+ };
304
+ if (err instanceof commander.CommanderError) {
305
+ const sub = commandPath(at).split(" ").slice(1).join(" ");
306
+ const suggestion = describeName ? `Run "${[
307
+ root.name(),
308
+ describeName,
309
+ sub
310
+ ].filter(Boolean).join(" ")}" to see accepted arguments and options.` : `Run "${commandPath(at)} --help" to see usage.`;
311
+ const message = err.code === "commander.help" ? "A subcommand is required." : err.message.replace(/^error:\s*/, "");
312
+ return {
313
+ payload: {
314
+ code: USAGE_CODES[err.code] ?? "USAGE_ERROR",
315
+ message,
316
+ suggestion
317
+ },
318
+ code: EXIT.USAGE
319
+ };
320
+ }
321
+ return {
322
+ payload: {
323
+ code: "INTERNAL_ERROR",
324
+ message: err instanceof Error ? err.message : String(err)
325
+ },
326
+ code: EXIT.ERROR
327
+ };
328
+ }
329
+ const strip = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
330
+ //#endregion
331
+ exports.AgentError = AgentError;
332
+ exports.EXIT = EXIT;
333
+ exports.agentMode = agentMode;
334
+ exports.describeCommand = describeCommand;
335
+ exports.detectAgent = detectAgent;
336
+ exports.findCommand = findCommand;
337
+ exports.run = run;
@@ -0,0 +1,135 @@
1
+ /// <reference path="./commander-augment.d.ts" />
2
+ import { Command } from "commander";
3
+
4
+ //#region src/errors.d.ts
5
+ interface AgentErrorOptions {
6
+ /** One concrete next step the agent (or human) can follow literally. */
7
+ suggestion?: string;
8
+ /** Any JSON-serializable context: ids, paths, limits, etc. */
9
+ details?: unknown;
10
+ /** Process exit code. Defaults to 1. */
11
+ exitCode?: number;
12
+ }
13
+ /**
14
+ * An error with a stable machine-readable `code`.
15
+ *
16
+ * @example
17
+ * throw new AgentError('ENV_NOT_FOUND', `Environment "${env}" does not exist`, {
18
+ * suggestion: 'Run "mycli env list" to see available environments.',
19
+ * });
20
+ */
21
+ declare class AgentError extends Error {
22
+ readonly code: string;
23
+ readonly suggestion?: string;
24
+ readonly details?: unknown;
25
+ readonly exitCode: number;
26
+ constructor(code: string, message: string, options?: AgentErrorOptions);
27
+ }
28
+ interface ErrorPayload {
29
+ code: string;
30
+ message: string;
31
+ suggestion?: string;
32
+ details?: unknown;
33
+ }
34
+ type Envelope<T = unknown> = {
35
+ ok: true;
36
+ data: T;
37
+ } | {
38
+ ok: false;
39
+ error: ErrorPayload;
40
+ };
41
+ /** Exit codes used by cli-agent-mode. */
42
+ declare const EXIT: {
43
+ readonly OK: 0;
44
+ readonly ERROR: 1;
45
+ readonly USAGE: 2;
46
+ };
47
+ //#endregion
48
+ //#region src/agent-mode.d.ts
49
+ interface AgentModeOptions {
50
+ /** Switch to JSON automatically when an AI agent is detected (AI_AGENT, CLAUDECODE, CURSOR_AGENT, ...). Default: true. */
51
+ autoDetect?: boolean;
52
+ /** Name of the self-description command, or `false` to skip adding it. Default: "describe". */
53
+ describeCommand?: string | false;
54
+ /** Render action results for humans. Default: strings as-is, everything else as pretty JSON. */
55
+ formatHuman?: (data: unknown, command: Command) => string | undefined;
56
+ }
57
+ interface RunOptions {
58
+ /** User arguments (without node and script path). Default: process.argv.slice(2). */
59
+ args?: string[];
60
+ /** Force JSON (`true`) or human (`false`) output, bypassing flag and agent detection. */
61
+ json?: boolean;
62
+ env?: NodeJS.ProcessEnv;
63
+ stdout?: {
64
+ write(chunk: string): unknown;
65
+ };
66
+ stderr?: {
67
+ write(chunk: string): unknown;
68
+ };
69
+ /** Set `process.exitCode`. Default: true. */
70
+ setExitCode?: boolean;
71
+ }
72
+ interface RunResult {
73
+ mode: 'json' | 'human';
74
+ exitCode: number;
75
+ envelope: Envelope;
76
+ }
77
+ /**
78
+ * Turn a commander program into an agent-friendly CLI.
79
+ * Call it right after creating the program, before defining subcommands.
80
+ *
81
+ * @example
82
+ * const program = agentMode(new Command('mycli'));
83
+ * program.command('deploy <env>').action(async (env) => ({ url: `https://${env}.example.com` }));
84
+ * await run(program);
85
+ */
86
+ declare function agentMode<T extends Command>(program: T, options?: AgentModeOptions): T;
87
+ /** Parse arguments, run the matching action and print the result or error in the right format. */
88
+ declare function run(program: Command, runOptions?: RunOptions): Promise<RunResult>;
89
+ //#endregion
90
+ //#region src/describe.d.ts
91
+ interface ArgumentSchema {
92
+ name: string;
93
+ description?: string;
94
+ /** present only when true */
95
+ required?: true;
96
+ /** present only when true */
97
+ variadic?: true;
98
+ default?: unknown;
99
+ choices?: readonly string[];
100
+ }
101
+ interface OptionSchema {
102
+ /** commander flags, e.g. "-t, --tag <tag>" */
103
+ flags: string;
104
+ description?: string;
105
+ /** present only when the option itself must be passed */
106
+ required?: true;
107
+ default?: unknown;
108
+ choices?: readonly string[];
109
+ env?: string;
110
+ }
111
+ interface CommandSchema {
112
+ name: string;
113
+ description?: string;
114
+ /** full invocation, e.g. "mycli deploy [options] <env>" */
115
+ usage: string;
116
+ aliases?: string[];
117
+ arguments?: ArgumentSchema[];
118
+ options?: OptionSchema[];
119
+ commands?: CommandSchema[];
120
+ }
121
+ /** Compact JSON schema of a command and all of its visible subcommands. */
122
+ declare function describeCommand(cmd: Command): CommandSchema;
123
+ /** Resolve `["deploy", "preview"]` to the nested subcommand (by name or alias). */
124
+ declare function findCommand(root: Command, path: string[]): Command | undefined;
125
+ //#endregion
126
+ //#region src/detect.d.ts
127
+ /**
128
+ * Synchronous, dependency-free detection of AI coding agents.
129
+ * Env variables mirror @vercel/detect-agent (Nov 2026) plus the `AI_AGENT` convention.
130
+ * CURSOR_TRACE_ID is deliberately ignored: it is also set in Cursor's terminal for humans.
131
+ * Returns the agent name or `undefined`.
132
+ */
133
+ declare function detectAgent(env?: NodeJS.ProcessEnv): string | undefined;
134
+ //#endregion
135
+ export { AgentError, type AgentErrorOptions, type AgentModeOptions, type ArgumentSchema, type CommandSchema, EXIT, type Envelope, type ErrorPayload, type OptionSchema, type RunOptions, type RunResult, agentMode, describeCommand, detectAgent, findCommand, run };
@@ -0,0 +1,135 @@
1
+ /// <reference path="./commander-augment.d.ts" />
2
+ import { Command } from "commander";
3
+
4
+ //#region src/errors.d.ts
5
+ interface AgentErrorOptions {
6
+ /** One concrete next step the agent (or human) can follow literally. */
7
+ suggestion?: string;
8
+ /** Any JSON-serializable context: ids, paths, limits, etc. */
9
+ details?: unknown;
10
+ /** Process exit code. Defaults to 1. */
11
+ exitCode?: number;
12
+ }
13
+ /**
14
+ * An error with a stable machine-readable `code`.
15
+ *
16
+ * @example
17
+ * throw new AgentError('ENV_NOT_FOUND', `Environment "${env}" does not exist`, {
18
+ * suggestion: 'Run "mycli env list" to see available environments.',
19
+ * });
20
+ */
21
+ declare class AgentError extends Error {
22
+ readonly code: string;
23
+ readonly suggestion?: string;
24
+ readonly details?: unknown;
25
+ readonly exitCode: number;
26
+ constructor(code: string, message: string, options?: AgentErrorOptions);
27
+ }
28
+ interface ErrorPayload {
29
+ code: string;
30
+ message: string;
31
+ suggestion?: string;
32
+ details?: unknown;
33
+ }
34
+ type Envelope<T = unknown> = {
35
+ ok: true;
36
+ data: T;
37
+ } | {
38
+ ok: false;
39
+ error: ErrorPayload;
40
+ };
41
+ /** Exit codes used by cli-agent-mode. */
42
+ declare const EXIT: {
43
+ readonly OK: 0;
44
+ readonly ERROR: 1;
45
+ readonly USAGE: 2;
46
+ };
47
+ //#endregion
48
+ //#region src/agent-mode.d.ts
49
+ interface AgentModeOptions {
50
+ /** Switch to JSON automatically when an AI agent is detected (AI_AGENT, CLAUDECODE, CURSOR_AGENT, ...). Default: true. */
51
+ autoDetect?: boolean;
52
+ /** Name of the self-description command, or `false` to skip adding it. Default: "describe". */
53
+ describeCommand?: string | false;
54
+ /** Render action results for humans. Default: strings as-is, everything else as pretty JSON. */
55
+ formatHuman?: (data: unknown, command: Command) => string | undefined;
56
+ }
57
+ interface RunOptions {
58
+ /** User arguments (without node and script path). Default: process.argv.slice(2). */
59
+ args?: string[];
60
+ /** Force JSON (`true`) or human (`false`) output, bypassing flag and agent detection. */
61
+ json?: boolean;
62
+ env?: NodeJS.ProcessEnv;
63
+ stdout?: {
64
+ write(chunk: string): unknown;
65
+ };
66
+ stderr?: {
67
+ write(chunk: string): unknown;
68
+ };
69
+ /** Set `process.exitCode`. Default: true. */
70
+ setExitCode?: boolean;
71
+ }
72
+ interface RunResult {
73
+ mode: 'json' | 'human';
74
+ exitCode: number;
75
+ envelope: Envelope;
76
+ }
77
+ /**
78
+ * Turn a commander program into an agent-friendly CLI.
79
+ * Call it right after creating the program, before defining subcommands.
80
+ *
81
+ * @example
82
+ * const program = agentMode(new Command('mycli'));
83
+ * program.command('deploy <env>').action(async (env) => ({ url: `https://${env}.example.com` }));
84
+ * await run(program);
85
+ */
86
+ declare function agentMode<T extends Command>(program: T, options?: AgentModeOptions): T;
87
+ /** Parse arguments, run the matching action and print the result or error in the right format. */
88
+ declare function run(program: Command, runOptions?: RunOptions): Promise<RunResult>;
89
+ //#endregion
90
+ //#region src/describe.d.ts
91
+ interface ArgumentSchema {
92
+ name: string;
93
+ description?: string;
94
+ /** present only when true */
95
+ required?: true;
96
+ /** present only when true */
97
+ variadic?: true;
98
+ default?: unknown;
99
+ choices?: readonly string[];
100
+ }
101
+ interface OptionSchema {
102
+ /** commander flags, e.g. "-t, --tag <tag>" */
103
+ flags: string;
104
+ description?: string;
105
+ /** present only when the option itself must be passed */
106
+ required?: true;
107
+ default?: unknown;
108
+ choices?: readonly string[];
109
+ env?: string;
110
+ }
111
+ interface CommandSchema {
112
+ name: string;
113
+ description?: string;
114
+ /** full invocation, e.g. "mycli deploy [options] <env>" */
115
+ usage: string;
116
+ aliases?: string[];
117
+ arguments?: ArgumentSchema[];
118
+ options?: OptionSchema[];
119
+ commands?: CommandSchema[];
120
+ }
121
+ /** Compact JSON schema of a command and all of its visible subcommands. */
122
+ declare function describeCommand(cmd: Command): CommandSchema;
123
+ /** Resolve `["deploy", "preview"]` to the nested subcommand (by name or alias). */
124
+ declare function findCommand(root: Command, path: string[]): Command | undefined;
125
+ //#endregion
126
+ //#region src/detect.d.ts
127
+ /**
128
+ * Synchronous, dependency-free detection of AI coding agents.
129
+ * Env variables mirror @vercel/detect-agent (Nov 2026) plus the `AI_AGENT` convention.
130
+ * CURSOR_TRACE_ID is deliberately ignored: it is also set in Cursor's terminal for humans.
131
+ * Returns the agent name or `undefined`.
132
+ */
133
+ declare function detectAgent(env?: NodeJS.ProcessEnv): string | undefined;
134
+ //#endregion
135
+ export { AgentError, type AgentErrorOptions, type AgentModeOptions, type ArgumentSchema, type CommandSchema, EXIT, type Envelope, type ErrorPayload, type OptionSchema, type RunOptions, type RunResult, agentMode, describeCommand, detectAgent, findCommand, run };
package/dist/index.mjs ADDED
@@ -0,0 +1,330 @@
1
+ import { CommanderError } from "commander";
2
+ import { format } from "node:util";
3
+ //#region src/detect.ts
4
+ /**
5
+ * Synchronous, dependency-free detection of AI coding agents.
6
+ * Env variables mirror @vercel/detect-agent (Nov 2026) plus the `AI_AGENT` convention.
7
+ * CURSOR_TRACE_ID is deliberately ignored: it is also set in Cursor's terminal for humans.
8
+ * Returns the agent name or `undefined`.
9
+ */
10
+ function detectAgent(env = process.env) {
11
+ const aiAgent = env.AI_AGENT?.trim();
12
+ if (aiAgent) return aiAgent;
13
+ if (env.CLAUDECODE || env.CLAUDE_CODE) return "claude";
14
+ if (env.CURSOR_AGENT || env.CURSOR_EXTENSION_HOST_ROLE === "agent-exec") return "cursor-cli";
15
+ if (env.GEMINI_CLI) return "gemini";
16
+ if (env.CODEX_SANDBOX || env.CODEX_CI || env.CODEX_THREAD_ID) return "codex";
17
+ if (env.ANTIGRAVITY_AGENT) return "antigravity";
18
+ if (env.AUGMENT_AGENT) return "augment-cli";
19
+ if (env.OPENCODE === "1" || env.OPENCODE_CLIENT) return "opencode";
20
+ if (env.COPILOT_MODEL || env.COPILOT_ALLOW_ALL) return "github-copilot";
21
+ if (env.REPL_ID) return "replit";
22
+ }
23
+ //#endregion
24
+ //#region src/describe.ts
25
+ const clean = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== "" && v !== false && !(Array.isArray(v) && v.length === 0)));
26
+ function describeArgument(a) {
27
+ return clean({
28
+ name: a.name(),
29
+ description: a.description,
30
+ required: a.required || void 0,
31
+ variadic: a.variadic || void 0,
32
+ default: a.defaultValue,
33
+ choices: a.argChoices
34
+ });
35
+ }
36
+ function describeOption(o) {
37
+ return clean({
38
+ flags: o.flags,
39
+ description: o.description,
40
+ required: o.mandatory || void 0,
41
+ default: o.defaultValue,
42
+ choices: o.argChoices,
43
+ env: o.envVar
44
+ });
45
+ }
46
+ function commandPath(cmd) {
47
+ const parts = [];
48
+ for (let c = cmd; c; c = c.parent) parts.unshift(c.name());
49
+ return parts.join(" ");
50
+ }
51
+ const isHidden = (c) => c._hidden === true;
52
+ /** Compact JSON schema of a command and all of its visible subcommands. */
53
+ function describeCommand(cmd) {
54
+ return clean({
55
+ name: cmd.name(),
56
+ description: cmd.description(),
57
+ usage: `${commandPath(cmd)} ${cmd.usage()}`.trim(),
58
+ aliases: cmd.aliases(),
59
+ arguments: cmd.registeredArguments.map(describeArgument),
60
+ options: cmd.options.filter((o) => !o.hidden).map(describeOption),
61
+ commands: cmd.commands.filter((c) => !isHidden(c)).map(describeCommand)
62
+ });
63
+ }
64
+ /** Resolve `["deploy", "preview"]` to the nested subcommand (by name or alias). */
65
+ function findCommand(root, path) {
66
+ let cur = root;
67
+ for (const part of path) {
68
+ cur = cur?.commands.find((c) => c.name() === part || c.aliases().includes(part));
69
+ if (!cur) return void 0;
70
+ }
71
+ return cur;
72
+ }
73
+ //#endregion
74
+ //#region src/errors.ts
75
+ /**
76
+ * An error with a stable machine-readable `code`.
77
+ *
78
+ * @example
79
+ * throw new AgentError('ENV_NOT_FOUND', `Environment "${env}" does not exist`, {
80
+ * suggestion: 'Run "mycli env list" to see available environments.',
81
+ * });
82
+ */
83
+ var AgentError = class extends Error {
84
+ code;
85
+ suggestion;
86
+ details;
87
+ exitCode;
88
+ constructor(code, message, options = {}) {
89
+ super(message);
90
+ this.name = "AgentError";
91
+ this.code = code;
92
+ this.suggestion = options.suggestion;
93
+ this.details = options.details;
94
+ this.exitCode = options.exitCode ?? 1;
95
+ }
96
+ };
97
+ /** Exit codes used by cli-agent-mode. */
98
+ const EXIT = {
99
+ OK: 0,
100
+ ERROR: 1,
101
+ USAGE: 2
102
+ };
103
+ //#endregion
104
+ //#region src/agent-mode.ts
105
+ const states = /* @__PURE__ */ new WeakMap();
106
+ const rootOf = (cmd) => {
107
+ let c = cmd;
108
+ while (c.parent) c = c.parent;
109
+ return c;
110
+ };
111
+ /** Make every `.action()` handler's return value available as the command result. */
112
+ function patch(cmd) {
113
+ const proto = Object.getPrototypeOf(cmd);
114
+ cmd.action = function(fn) {
115
+ return proto.action.call(this, async function(...args) {
116
+ const value = await fn.apply(this, args);
117
+ const state = states.get(rootOf(this));
118
+ if (state) state.result = {
119
+ value,
120
+ command: this
121
+ };
122
+ });
123
+ };
124
+ cmd.createCommand = (name) => patch(proto.createCommand.call(cmd, name));
125
+ const addCommand = cmd.addCommand;
126
+ cmd.addCommand = function(sub, opts) {
127
+ walk(sub, patch);
128
+ return addCommand.call(this, sub, opts);
129
+ };
130
+ return cmd;
131
+ }
132
+ function walk(cmd, fn) {
133
+ fn(cmd);
134
+ for (const sub of cmd.commands) walk(sub, fn);
135
+ }
136
+ /**
137
+ * Turn a commander program into an agent-friendly CLI.
138
+ * Call it right after creating the program, before defining subcommands.
139
+ *
140
+ * @example
141
+ * const program = agentMode(new Command('mycli'));
142
+ * program.command('deploy <env>').action(async (env) => ({ url: `https://${env}.example.com` }));
143
+ * await run(program);
144
+ */
145
+ function agentMode(program, options = {}) {
146
+ const state = { options: {
147
+ autoDetect: options.autoDetect ?? true,
148
+ describeCommand: options.describeCommand ?? "describe",
149
+ formatHuman: options.formatHuman
150
+ } };
151
+ states.set(program, state);
152
+ walk(program, patch);
153
+ program.option("--json", "print one JSON envelope on stdout (auto-on for AI agents)");
154
+ const name = state.options.describeCommand;
155
+ if (name) program.command(`${name} [command...]`).description("print the CLI schema as JSON").action((path = []) => {
156
+ const target = findCommand(program, path);
157
+ if (!target) throw new AgentError("UNKNOWN_COMMAND", `Unknown command: ${path.join(" ")}`, {
158
+ suggestion: `Run "${program.name()} ${name}" to list all commands.`,
159
+ exitCode: EXIT.USAGE
160
+ });
161
+ return describeCommand(target);
162
+ });
163
+ return program;
164
+ }
165
+ const USAGE_CODES = {
166
+ "commander.unknownOption": "UNKNOWN_OPTION",
167
+ "commander.unknownCommand": "UNKNOWN_COMMAND",
168
+ "commander.missingArgument": "MISSING_ARGUMENT",
169
+ "commander.optionMissingArgument": "MISSING_OPTION_VALUE",
170
+ "commander.missingMandatoryOptionValue": "MISSING_REQUIRED_OPTION",
171
+ "commander.invalidArgument": "INVALID_ARGUMENT",
172
+ "commander.excessArguments": "EXCESS_ARGUMENTS",
173
+ "commander.conflictingOption": "CONFLICTING_OPTIONS",
174
+ "commander.help": "MISSING_COMMAND"
175
+ };
176
+ function extractJsonFlag(args) {
177
+ const end = args.indexOf("--");
178
+ const head = end === -1 ? args : args.slice(0, end);
179
+ const tail = end === -1 ? [] : args.slice(end);
180
+ const json = head.includes("--json");
181
+ return {
182
+ args: [...head.filter((a) => a !== "--json"), ...tail],
183
+ json
184
+ };
185
+ }
186
+ const defaultHuman = (data) => data === void 0 || data === null ? void 0 : typeof data === "string" ? data : JSON.stringify(data, null, 2);
187
+ /** Parse arguments, run the matching action and print the result or error in the right format. */
188
+ async function run(program, runOptions = {}) {
189
+ const state = states.get(program);
190
+ if (!state) throw new Error("cli-agent-mode: call agentMode(program) before run(program)");
191
+ state.result = void 0;
192
+ const env = runOptions.env ?? process.env;
193
+ const stdout = runOptions.stdout ?? process.stdout;
194
+ const stderr = runOptions.stderr ?? process.stderr;
195
+ const extracted = extractJsonFlag(runOptions.args ?? process.argv.slice(2));
196
+ const forced = env.CLI_AGENT_MODE === "json" ? true : env.CLI_AGENT_MODE === "human" ? false : void 0;
197
+ const json = runOptions.json ?? (extracted.json || (forced ?? (state.options.autoDetect && detectAgent(env) !== void 0)));
198
+ const mode = json ? "json" : "human";
199
+ let outBuf = "";
200
+ let errorCommand = program;
201
+ const restore = [];
202
+ walk(program, (cmd) => {
203
+ const prevOutput = cmd.configureOutput();
204
+ const prevHelp = cmd.configureHelp();
205
+ restore.push(() => {
206
+ cmd.configureOutput(prevOutput);
207
+ cmd.configureHelp(prevHelp);
208
+ });
209
+ cmd.exitOverride();
210
+ cmd.configureOutput({
211
+ ...prevOutput,
212
+ writeOut: (s) => json ? outBuf += s : stdout.write(s),
213
+ writeErr: (s) => json ? void 0 : stderr.write(s),
214
+ outputError: (s, write) => {
215
+ errorCommand = cmd;
216
+ if (!json) write(s);
217
+ }
218
+ });
219
+ if (json) cmd.configureHelp({
220
+ ...prevHelp,
221
+ formatHelp: (c) => JSON.stringify({
222
+ ok: true,
223
+ data: describeCommand(c)
224
+ }) + "\n"
225
+ });
226
+ });
227
+ const consoleKeys = [
228
+ "log",
229
+ "info",
230
+ "debug"
231
+ ];
232
+ const savedConsole = consoleKeys.map((k) => console[k]);
233
+ if (json) for (const k of consoleKeys) console[k] = (...a) => void stderr.write(format(...a) + "\n");
234
+ let envelope;
235
+ let exitCode = EXIT.OK;
236
+ let printed = false;
237
+ try {
238
+ await program.parseAsync(extracted.args, { from: "user" });
239
+ const result = state.result;
240
+ const value = result?.value;
241
+ envelope = {
242
+ ok: true,
243
+ data: value ?? null
244
+ };
245
+ if (!json) {
246
+ const text = (state.options.formatHuman ?? defaultHuman)(value, result?.command ?? program);
247
+ if (text !== void 0) stdout.write(text + "\n");
248
+ printed = true;
249
+ }
250
+ } catch (err) {
251
+ if (err instanceof CommanderError && (err.code === "commander.helpDisplayed" || err.code === "commander.help" && err.exitCode === 0)) {
252
+ if (json) stdout.write(outBuf);
253
+ return finish({
254
+ ok: true,
255
+ data: null
256
+ }, EXIT.OK, true);
257
+ }
258
+ if (err instanceof CommanderError && err.code === "commander.version") {
259
+ envelope = {
260
+ ok: true,
261
+ data: { version: outBuf.trim() || program.version() }
262
+ };
263
+ exitCode = EXIT.OK;
264
+ if (!json) printed = true;
265
+ } else {
266
+ const { payload, code } = toPayload(err, program, errorCommand, state.options.describeCommand);
267
+ envelope = {
268
+ ok: false,
269
+ error: payload
270
+ };
271
+ exitCode = code;
272
+ if (!json) {
273
+ if (!(err instanceof CommanderError)) stderr.write(`error: ${payload.message}\n`);
274
+ if (payload.suggestion) stderr.write(`hint: ${payload.suggestion}\n`);
275
+ printed = true;
276
+ }
277
+ }
278
+ } finally {
279
+ consoleKeys.forEach((k, i) => console[k] = savedConsole[i]);
280
+ restore.forEach((r) => r());
281
+ }
282
+ return finish(envelope, exitCode, printed);
283
+ function finish(env, code, done) {
284
+ if (json && !done) stdout.write(JSON.stringify(env) + "\n");
285
+ if (runOptions.setExitCode ?? true) process.exitCode = code;
286
+ return {
287
+ mode,
288
+ exitCode: code,
289
+ envelope: env
290
+ };
291
+ }
292
+ }
293
+ function toPayload(err, root, at, describeName) {
294
+ if (err instanceof AgentError) return {
295
+ payload: strip({
296
+ code: err.code,
297
+ message: err.message,
298
+ suggestion: err.suggestion,
299
+ details: err.details
300
+ }),
301
+ code: err.exitCode
302
+ };
303
+ if (err instanceof CommanderError) {
304
+ const sub = commandPath(at).split(" ").slice(1).join(" ");
305
+ const suggestion = describeName ? `Run "${[
306
+ root.name(),
307
+ describeName,
308
+ sub
309
+ ].filter(Boolean).join(" ")}" to see accepted arguments and options.` : `Run "${commandPath(at)} --help" to see usage.`;
310
+ const message = err.code === "commander.help" ? "A subcommand is required." : err.message.replace(/^error:\s*/, "");
311
+ return {
312
+ payload: {
313
+ code: USAGE_CODES[err.code] ?? "USAGE_ERROR",
314
+ message,
315
+ suggestion
316
+ },
317
+ code: EXIT.USAGE
318
+ };
319
+ }
320
+ return {
321
+ payload: {
322
+ code: "INTERNAL_ERROR",
323
+ message: err instanceof Error ? err.message : String(err)
324
+ },
325
+ code: EXIT.ERROR
326
+ };
327
+ }
328
+ const strip = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
329
+ //#endregion
330
+ export { AgentError, EXIT, agentMode, describeCommand, detectAgent, findCommand, run };
package/package.json CHANGED
@@ -1,6 +1,69 @@
1
1
  {
2
2
  "name": "cli-agent-mode",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Make any commander.js CLI agent-friendly: --json output envelopes, machine-readable errors with suggestions, a `describe` command that emits the CLI schema, and stable exit codes.",
5
+ "keywords": [
6
+ "cli",
7
+ "commander",
8
+ "ai-agent",
9
+ "llm",
10
+ "agent",
11
+ "json-output",
12
+ "machine-readable",
13
+ "claude-code",
14
+ "cursor",
15
+ "codex",
16
+ "mcp",
17
+ "exit-codes",
18
+ "structured-errors"
19
+ ],
20
+ "license": "MIT",
21
+ "type": "module",
22
+ "sideEffects": false,
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "scripts": {
30
+ "build": "tsdown src/index.ts --dts --format esm,cjs && node scripts/postbuild.mjs",
31
+ "test": "vitest run",
32
+ "typecheck": "tsc --noEmit",
33
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
34
+ },
35
+ "peerDependencies": {
36
+ "commander": ">=12"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^20.19.43",
40
+ "commander": "^14.0.3",
41
+ "tsdown": "^0.21.10",
42
+ "tsx": "^4.23.15",
43
+ "typescript": "^5.9.3",
44
+ "vitest": "^4.1.12"
45
+ },
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/missingbits/cli-agent-mode.git"
49
+ },
50
+ "homepage": "https://github.com/missingbits/cli-agent-mode#readme",
51
+ "bugs": {
52
+ "url": "https://github.com/missingbits/cli-agent-mode/issues"
53
+ },
54
+ "author": "Gordey Levchenko (https://github.com/lgordey)",
55
+ "exports": {
56
+ ".": {
57
+ "import": {
58
+ "types": "./dist/index.d.mts",
59
+ "default": "./dist/index.mjs"
60
+ },
61
+ "require": {
62
+ "types": "./dist/index.d.cts",
63
+ "default": "./dist/index.cjs"
64
+ }
65
+ }
66
+ },
67
+ "main": "./dist/index.cjs",
68
+ "types": "./dist/index.d.cts"
69
+ }