cli-agent-mode 0.1.1 → 0.2.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/README.md CHANGED
@@ -16,7 +16,7 @@ Two lines turn your CLI into one that agents can drive reliably:
16
16
  - **Stable exit codes** — `0` ok, `1` runtime error, `2` usage error.
17
17
  - **Clean stdout** — stray `console.log` inside actions is redirected to stderr in JSON mode.
18
18
 
19
- Zero dependencies. ~4 kB gzipped. ESM and CommonJS, Node 20+, commander 12+.
19
+ Zero dependencies. ~4 kB gzipped. ESM and CommonJS, Node 22+ (tested on 22, 24, 26), commander 12–15 (tested in CI).
20
20
 
21
21
  ```bash
22
22
  npm i cli-agent-mode
@@ -131,6 +131,7 @@ If you are already on oclif, you have `--json`. If you are on commander — the
131
131
  - `--json` is reserved: it is removed from argv before parsing (except after `--`).
132
132
  - In JSON mode `console.log/info/debug` are redirected to stderr, but direct `process.stdout.write` calls inside actions are not — return data instead.
133
133
  - Typed `@commander-js/extra-typings` is not supported yet.
134
+ - commander 15 is ESM-only: TypeScript projects compiled as CommonJS need TypeScript 5.9+ to import it (a commander limitation, not specific to this package).
134
135
 
135
136
  ## License
136
137
 
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let commander = require("commander");
3
3
  let node_util = require("node:util");
4
- //#region src/detect.ts
4
+ // src/detect.ts
5
5
  /**
6
6
  * Synchronous, dependency-free detection of AI coding agents.
7
7
  * Env variables mirror @vercel/detect-agent (Nov 2026) plus the `AI_AGENT` convention.
@@ -21,8 +21,7 @@ function detectAgent(env = process.env) {
21
21
  if (env.COPILOT_MODEL || env.COPILOT_ALLOW_ALL) return "github-copilot";
22
22
  if (env.REPL_ID) return "replit";
23
23
  }
24
- //#endregion
25
- //#region src/describe.ts
24
+ // src/describe.ts
26
25
  const clean = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== "" && v !== false && !(Array.isArray(v) && v.length === 0)));
27
26
  function describeArgument(a) {
28
27
  return clean({
@@ -71,8 +70,7 @@ function findCommand(root, path) {
71
70
  }
72
71
  return cur;
73
72
  }
74
- //#endregion
75
- //#region src/errors.ts
73
+ // src/errors.ts
76
74
  /**
77
75
  * An error with a stable machine-readable `code`.
78
76
  *
@@ -101,9 +99,9 @@ const EXIT = {
101
99
  ERROR: 1,
102
100
  USAGE: 2
103
101
  };
104
- //#endregion
105
- //#region src/agent-mode.ts
102
+ // src/agent-mode.ts
106
103
  const states = /* @__PURE__ */ new WeakMap();
104
+ const hooked = /* @__PURE__ */ new WeakSet();
107
105
  const rootOf = (cmd) => {
108
106
  let c = cmd;
109
107
  while (c.parent) c = c.parent;
@@ -198,11 +196,18 @@ async function run(program, runOptions = {}) {
198
196
  const json = runOptions.json ?? (extracted.json || (forced ?? (state.options.autoDetect && detectAgent(env) !== void 0)));
199
197
  const mode = json ? "json" : "human";
200
198
  let outBuf = "";
201
- let errorCommand = program;
199
+ state.current = program;
202
200
  const restore = [];
203
201
  walk(program, (cmd) => {
204
- const prevOutput = cmd.configureOutput();
205
- const prevHelp = cmd.configureHelp();
202
+ if (!hooked.has(cmd)) {
203
+ hooked.add(cmd);
204
+ cmd.hook("preSubcommand", (_parent, sub) => {
205
+ const s = states.get(rootOf(sub));
206
+ if (s) s.current = sub;
207
+ });
208
+ }
209
+ const prevOutput = { ...cmd.configureOutput() };
210
+ const prevHelp = { ...cmd.configureHelp() };
206
211
  restore.push(() => {
207
212
  cmd.configureOutput(prevOutput);
208
213
  cmd.configureHelp(prevHelp);
@@ -213,7 +218,6 @@ async function run(program, runOptions = {}) {
213
218
  writeOut: (s) => json ? outBuf += s : stdout.write(s),
214
219
  writeErr: (s) => json ? void 0 : stderr.write(s),
215
220
  outputError: (s, write) => {
216
- errorCommand = cmd;
217
221
  if (!json) write(s);
218
222
  }
219
223
  });
@@ -264,7 +268,7 @@ async function run(program, runOptions = {}) {
264
268
  exitCode = EXIT.OK;
265
269
  if (!json) printed = true;
266
270
  } else {
267
- const { payload, code } = toPayload(err, program, errorCommand, state.options.describeCommand);
271
+ const { payload, code } = toPayload(err, program, state.current ?? program, state.options.describeCommand);
268
272
  envelope = {
269
273
  ok: false,
270
274
  error: payload
@@ -327,7 +331,6 @@ function toPayload(err, root, at, describeName) {
327
331
  };
328
332
  }
329
333
  const strip = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
330
- //#endregion
331
334
  exports.AgentError = AgentError;
332
335
  exports.EXIT = EXIT;
333
336
  exports.agentMode = agentMode;
package/dist/index.d.cts CHANGED
@@ -1,7 +1,5 @@
1
1
  /// <reference path="./commander-augment.d.ts" />
2
2
  import { Command } from "commander";
3
-
4
- //#region src/errors.d.ts
5
3
  interface AgentErrorOptions {
6
4
  /** One concrete next step the agent (or human) can follow literally. */
7
5
  suggestion?: string;
@@ -18,7 +16,7 @@ interface AgentErrorOptions {
18
16
  * suggestion: 'Run "mycli env list" to see available environments.',
19
17
  * });
20
18
  */
21
- declare class AgentError extends Error {
19
+ export declare class AgentError extends Error {
22
20
  readonly code: string;
23
21
  readonly suggestion?: string;
24
22
  readonly details?: unknown;
@@ -39,13 +37,11 @@ type Envelope<T = unknown> = {
39
37
  error: ErrorPayload;
40
38
  };
41
39
  /** Exit codes used by cli-agent-mode. */
42
- declare const EXIT: {
40
+ export declare const EXIT: {
43
41
  readonly OK: 0;
44
42
  readonly ERROR: 1;
45
43
  readonly USAGE: 2;
46
44
  };
47
- //#endregion
48
- //#region src/agent-mode.d.ts
49
45
  interface AgentModeOptions {
50
46
  /** Switch to JSON automatically when an AI agent is detected (AI_AGENT, CLAUDECODE, CURSOR_AGENT, ...). Default: true. */
51
47
  autoDetect?: boolean;
@@ -83,11 +79,9 @@ interface RunResult {
83
79
  * program.command('deploy <env>').action(async (env) => ({ url: `https://${env}.example.com` }));
84
80
  * await run(program);
85
81
  */
86
- declare function agentMode<T extends Command>(program: T, options?: AgentModeOptions): T;
82
+ export declare function agentMode<T extends Command>(program: T, options?: AgentModeOptions): T;
87
83
  /** 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
84
+ export declare function run(program: Command, runOptions?: RunOptions): Promise<RunResult>;
91
85
  interface ArgumentSchema {
92
86
  name: string;
93
87
  description?: string;
@@ -119,17 +113,14 @@ interface CommandSchema {
119
113
  commands?: CommandSchema[];
120
114
  }
121
115
  /** Compact JSON schema of a command and all of its visible subcommands. */
122
- declare function describeCommand(cmd: Command): CommandSchema;
116
+ export declare function describeCommand(cmd: Command): CommandSchema;
123
117
  /** 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
118
+ export declare function findCommand(root: Command, path: string[]): Command | undefined;
127
119
  /**
128
120
  * Synchronous, dependency-free detection of AI coding agents.
129
121
  * Env variables mirror @vercel/detect-agent (Nov 2026) plus the `AI_AGENT` convention.
130
122
  * CURSOR_TRACE_ID is deliberately ignored: it is also set in Cursor's terminal for humans.
131
123
  * Returns the agent name or `undefined`.
132
124
  */
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 };
125
+ export declare function detectAgent(env?: NodeJS.ProcessEnv): string | undefined;
126
+ export type { AgentErrorOptions, AgentModeOptions, ArgumentSchema, CommandSchema, Envelope, ErrorPayload, OptionSchema, RunOptions, RunResult };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,5 @@
1
1
  /// <reference path="./commander-augment.d.ts" />
2
2
  import { Command } from "commander";
3
-
4
- //#region src/errors.d.ts
5
3
  interface AgentErrorOptions {
6
4
  /** One concrete next step the agent (or human) can follow literally. */
7
5
  suggestion?: string;
@@ -18,7 +16,7 @@ interface AgentErrorOptions {
18
16
  * suggestion: 'Run "mycli env list" to see available environments.',
19
17
  * });
20
18
  */
21
- declare class AgentError extends Error {
19
+ export declare class AgentError extends Error {
22
20
  readonly code: string;
23
21
  readonly suggestion?: string;
24
22
  readonly details?: unknown;
@@ -39,13 +37,11 @@ type Envelope<T = unknown> = {
39
37
  error: ErrorPayload;
40
38
  };
41
39
  /** Exit codes used by cli-agent-mode. */
42
- declare const EXIT: {
40
+ export declare const EXIT: {
43
41
  readonly OK: 0;
44
42
  readonly ERROR: 1;
45
43
  readonly USAGE: 2;
46
44
  };
47
- //#endregion
48
- //#region src/agent-mode.d.ts
49
45
  interface AgentModeOptions {
50
46
  /** Switch to JSON automatically when an AI agent is detected (AI_AGENT, CLAUDECODE, CURSOR_AGENT, ...). Default: true. */
51
47
  autoDetect?: boolean;
@@ -83,11 +79,9 @@ interface RunResult {
83
79
  * program.command('deploy <env>').action(async (env) => ({ url: `https://${env}.example.com` }));
84
80
  * await run(program);
85
81
  */
86
- declare function agentMode<T extends Command>(program: T, options?: AgentModeOptions): T;
82
+ export declare function agentMode<T extends Command>(program: T, options?: AgentModeOptions): T;
87
83
  /** 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
84
+ export declare function run(program: Command, runOptions?: RunOptions): Promise<RunResult>;
91
85
  interface ArgumentSchema {
92
86
  name: string;
93
87
  description?: string;
@@ -119,17 +113,14 @@ interface CommandSchema {
119
113
  commands?: CommandSchema[];
120
114
  }
121
115
  /** Compact JSON schema of a command and all of its visible subcommands. */
122
- declare function describeCommand(cmd: Command): CommandSchema;
116
+ export declare function describeCommand(cmd: Command): CommandSchema;
123
117
  /** 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
118
+ export declare function findCommand(root: Command, path: string[]): Command | undefined;
127
119
  /**
128
120
  * Synchronous, dependency-free detection of AI coding agents.
129
121
  * Env variables mirror @vercel/detect-agent (Nov 2026) plus the `AI_AGENT` convention.
130
122
  * CURSOR_TRACE_ID is deliberately ignored: it is also set in Cursor's terminal for humans.
131
123
  * Returns the agent name or `undefined`.
132
124
  */
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 };
125
+ export declare function detectAgent(env?: NodeJS.ProcessEnv): string | undefined;
126
+ export type { AgentErrorOptions, AgentModeOptions, ArgumentSchema, CommandSchema, Envelope, ErrorPayload, OptionSchema, RunOptions, RunResult };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CommanderError } from "commander";
2
2
  import { format } from "node:util";
3
- //#region src/detect.ts
3
+ // src/detect.ts
4
4
  /**
5
5
  * Synchronous, dependency-free detection of AI coding agents.
6
6
  * Env variables mirror @vercel/detect-agent (Nov 2026) plus the `AI_AGENT` convention.
@@ -20,8 +20,7 @@ function detectAgent(env = process.env) {
20
20
  if (env.COPILOT_MODEL || env.COPILOT_ALLOW_ALL) return "github-copilot";
21
21
  if (env.REPL_ID) return "replit";
22
22
  }
23
- //#endregion
24
- //#region src/describe.ts
23
+ // src/describe.ts
25
24
  const clean = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== "" && v !== false && !(Array.isArray(v) && v.length === 0)));
26
25
  function describeArgument(a) {
27
26
  return clean({
@@ -70,8 +69,7 @@ function findCommand(root, path) {
70
69
  }
71
70
  return cur;
72
71
  }
73
- //#endregion
74
- //#region src/errors.ts
72
+ // src/errors.ts
75
73
  /**
76
74
  * An error with a stable machine-readable `code`.
77
75
  *
@@ -100,9 +98,9 @@ const EXIT = {
100
98
  ERROR: 1,
101
99
  USAGE: 2
102
100
  };
103
- //#endregion
104
- //#region src/agent-mode.ts
101
+ // src/agent-mode.ts
105
102
  const states = /* @__PURE__ */ new WeakMap();
103
+ const hooked = /* @__PURE__ */ new WeakSet();
106
104
  const rootOf = (cmd) => {
107
105
  let c = cmd;
108
106
  while (c.parent) c = c.parent;
@@ -197,11 +195,18 @@ async function run(program, runOptions = {}) {
197
195
  const json = runOptions.json ?? (extracted.json || (forced ?? (state.options.autoDetect && detectAgent(env) !== void 0)));
198
196
  const mode = json ? "json" : "human";
199
197
  let outBuf = "";
200
- let errorCommand = program;
198
+ state.current = program;
201
199
  const restore = [];
202
200
  walk(program, (cmd) => {
203
- const prevOutput = cmd.configureOutput();
204
- const prevHelp = cmd.configureHelp();
201
+ if (!hooked.has(cmd)) {
202
+ hooked.add(cmd);
203
+ cmd.hook("preSubcommand", (_parent, sub) => {
204
+ const s = states.get(rootOf(sub));
205
+ if (s) s.current = sub;
206
+ });
207
+ }
208
+ const prevOutput = { ...cmd.configureOutput() };
209
+ const prevHelp = { ...cmd.configureHelp() };
205
210
  restore.push(() => {
206
211
  cmd.configureOutput(prevOutput);
207
212
  cmd.configureHelp(prevHelp);
@@ -212,7 +217,6 @@ async function run(program, runOptions = {}) {
212
217
  writeOut: (s) => json ? outBuf += s : stdout.write(s),
213
218
  writeErr: (s) => json ? void 0 : stderr.write(s),
214
219
  outputError: (s, write) => {
215
- errorCommand = cmd;
216
220
  if (!json) write(s);
217
221
  }
218
222
  });
@@ -263,7 +267,7 @@ async function run(program, runOptions = {}) {
263
267
  exitCode = EXIT.OK;
264
268
  if (!json) printed = true;
265
269
  } else {
266
- const { payload, code } = toPayload(err, program, errorCommand, state.options.describeCommand);
270
+ const { payload, code } = toPayload(err, program, state.current ?? program, state.options.describeCommand);
267
271
  envelope = {
268
272
  ok: false,
269
273
  error: payload
@@ -326,5 +330,4 @@ function toPayload(err, root, at, describeName) {
326
330
  };
327
331
  }
328
332
  const strip = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
329
- //#endregion
330
333
  export { AgentError, EXIT, agentMode, describeCommand, detectAgent, findCommand, run };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cli-agent-mode",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
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
5
  "keywords": [
6
6
  "cli",
@@ -24,7 +24,7 @@
24
24
  "dist"
25
25
  ],
26
26
  "engines": {
27
- "node": ">=20"
27
+ "node": ">=22"
28
28
  },
29
29
  "scripts": {
30
30
  "build": "tsdown src/index.ts --dts --format esm,cjs && node scripts/postbuild.mjs",
@@ -36,12 +36,12 @@
36
36
  "commander": ">=12"
37
37
  },
38
38
  "devDependencies": {
39
- "@types/node": "^20.19.43",
40
- "commander": "^14.0.3",
41
- "tsdown": "^0.21.10",
39
+ "@types/node": "^22.20.5",
40
+ "commander": "^15.1.0",
41
+ "tsdown": "^0.23.7",
42
42
  "tsx": "^4.23.15",
43
- "typescript": "^5.9.3",
44
- "vitest": "^4.1.12"
43
+ "typescript": "^7.0.2",
44
+ "vitest": "^5.1.0"
45
45
  },
46
46
  "repository": {
47
47
  "type": "git",
@@ -65,5 +65,9 @@
65
65
  }
66
66
  },
67
67
  "main": "./dist/index.cjs",
68
- "types": "./dist/index.d.cts"
68
+ "types": "./dist/index.d.cts",
69
+ "allowScripts": {
70
+ "esbuild": false,
71
+ "fsevents": false
72
+ }
69
73
  }