@duyquangnvx/agent-cli 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.
Files changed (87) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/built-ins.d.ts +4 -0
  3. package/dist/built-ins.js +20 -0
  4. package/dist/built-ins.js.map +1 -0
  5. package/dist/confirm.d.ts +8 -0
  6. package/dist/confirm.js +22 -0
  7. package/dist/confirm.js.map +1 -0
  8. package/dist/define.d.ts +108 -0
  9. package/dist/define.js +8 -0
  10. package/dist/define.js.map +1 -0
  11. package/dist/doctor-rows.d.ts +3 -0
  12. package/dist/doctor-rows.js +115 -0
  13. package/dist/doctor-rows.js.map +1 -0
  14. package/dist/doctor.d.ts +3 -0
  15. package/dist/doctor.js +100 -0
  16. package/dist/doctor.js.map +1 -0
  17. package/dist/errors.d.ts +34 -0
  18. package/dist/errors.js +72 -0
  19. package/dist/errors.js.map +1 -0
  20. package/dist/execute.d.ts +12 -0
  21. package/dist/execute.js +18 -0
  22. package/dist/execute.js.map +1 -0
  23. package/dist/fields.d.ts +14 -0
  24. package/dist/fields.js +71 -0
  25. package/dist/fields.js.map +1 -0
  26. package/dist/flags.d.ts +12 -0
  27. package/dist/flags.js +22 -0
  28. package/dist/flags.js.map +1 -0
  29. package/dist/global-install.d.ts +11 -0
  30. package/dist/global-install.js +50 -0
  31. package/dist/global-install.js.map +1 -0
  32. package/dist/help-json.d.ts +4 -0
  33. package/dist/help-json.js +60 -0
  34. package/dist/help-json.js.map +1 -0
  35. package/dist/help.d.ts +6 -0
  36. package/dist/help.js +61 -0
  37. package/dist/help.js.map +1 -0
  38. package/dist/index.d.ts +11 -0
  39. package/dist/index.js +7 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/input.d.ts +22 -0
  42. package/dist/input.js +70 -0
  43. package/dist/input.js.map +1 -0
  44. package/dist/interrupts.d.ts +18 -0
  45. package/dist/interrupts.js +118 -0
  46. package/dist/interrupts.js.map +1 -0
  47. package/dist/invocation.d.ts +9 -0
  48. package/dist/invocation.js +37 -0
  49. package/dist/invocation.js.map +1 -0
  50. package/dist/npm.d.ts +24 -0
  51. package/dist/npm.js +181 -0
  52. package/dist/npm.js.map +1 -0
  53. package/dist/page.d.ts +26 -0
  54. package/dist/page.js +49 -0
  55. package/dist/page.js.map +1 -0
  56. package/dist/parse-input.d.ts +5 -0
  57. package/dist/parse-input.js +114 -0
  58. package/dist/parse-input.js.map +1 -0
  59. package/dist/run-cli.d.ts +20 -0
  60. package/dist/run-cli.js +211 -0
  61. package/dist/run-cli.js.map +1 -0
  62. package/dist/run-process.d.ts +2 -0
  63. package/dist/run-process.js +37 -0
  64. package/dist/run-process.js.map +1 -0
  65. package/dist/semver.d.ts +9 -0
  66. package/dist/semver.js +70 -0
  67. package/dist/semver.js.map +1 -0
  68. package/dist/skills.d.ts +11 -0
  69. package/dist/skills.js +58 -0
  70. package/dist/skills.js.map +1 -0
  71. package/dist/style.d.ts +13 -0
  72. package/dist/style.js +38 -0
  73. package/dist/style.js.map +1 -0
  74. package/dist/suggest.d.ts +2 -0
  75. package/dist/suggest.js +41 -0
  76. package/dist/suggest.js.map +1 -0
  77. package/dist/update.d.ts +3 -0
  78. package/dist/update.js +91 -0
  79. package/dist/update.js.map +1 -0
  80. package/dist/upgrade-notes.d.ts +9 -0
  81. package/dist/upgrade-notes.js +41 -0
  82. package/dist/upgrade-notes.js.map +1 -0
  83. package/dist/usage.d.ts +13 -0
  84. package/dist/usage.js +72 -0
  85. package/dist/usage.js.map +1 -0
  86. package/package.json +32 -3
  87. package/README.md +0 -3
package/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ What changed in each release of `@duyquangnvx/agent-cli` (the Runtime) and `@duyquangnvx/create-agent-cli` (the Scaffolder), which are released together at one version, newest first, in the format of [Keep a Changelog 2.0](https://keepachangelog.com/en/2.0.0/). Each change lands under `## Unreleased` as it merges, grouped under `### Added`, `### Changed`, `### Fixed` or `### Removed`. A breaking change starts with `**Breaking:**` and names what breaks: an export, a Cli or Command field, a built-in Command, a flag, a `--json` field, an exit code, the scaffolded template or the skill. A release turns `## Unreleased` into `## vX.Y.Z (YYYY-MM-DD)`.
4
+
5
+ ## Unreleased
6
+
7
+ ## v0.1.0 (2026-10-18)
8
+
9
+ ### Added
10
+
11
+ - The Runtime, `@duyquangnvx/agent-cli`: `defineCli` and `defineCommand` declare Commands with Zod arguments and options and a read, write or destructive Effect, and the Runtime owns parsing, layered help, `--json` output, errors with codes and hints, and exit codes 0, 1 and 2. Write and destructive Commands get `--dry-run` from their Plan, destructive ones a confirmation, and `pageOptions`, `pageOf` and `paginate` page a list. `runCli` runs a CLI in-process for tests; `runProcess` runs it as a bin, which exits once its output is written.
12
+ - The Scaffolder, `@duyquangnvx/create-agent-cli`: `npm create @duyquangnvx/agent-cli <name>` creates a repo for a new Agent CLI with one example Command, its test, a skeleton skill, the gate scripts and a first commit.
13
+ - Every multi-Command CLI has a built-in `doctor`: one row per check (`node`, `install`, `registry`, `skill`, then the CLI's own `doctor` rows), each under its own 10 s deadline, followed by the commands that fix each problem. It exits 1 with `DOCTOR_PROBLEMS` when a row is a problem; `--json` returns the rows as data.
14
+ - Every multi-Command CLI has a built-in `update`, which installs the latest version from the CLI's registry with `npm install --global`, prints the upgrade notes from the new `CHANGELOG.md` for each version it passed, and says to update the skill. `--dry-run` shows the version it would install. Ctrl-C never stops npm halfway: once the install has started it finishes, and `update` fails with `INTERRUPTED` saying the new version is installed. It fails with `NOT_GLOBAL_INSTALL`, `REGISTRY_UNREACHABLE`, `REGISTRY_AUTH`, `NO_LATEST` or `INSTALL_FAILED`.
15
+ - A Cli declares `install` (a global npm install of its package from its registry), `engines` and `doctor` rows, which `doctor` and `update` read.
16
+ - A Command whose `output` is a `pageOf(row)` schema accepts `--fields`, which keeps only the named columns in both text and `--json` output; an unknown name is a usage error listing the valid ones.
17
+ - `--help --json` at any level returns the Commands, arguments, options, Effects and examples as data, with each Command's output JSON Schema from its `output`.
18
+ - Ctrl-C or SIGTERM aborts the Command's `ctx.signal` and the run fails with `INTERRUPTED`; a second signal exits at once with 130 or 143. `ctx.deadline(ms)` returns a signal that aborts with `DEADLINE_EXCEEDED`, `ctx.runToEnd(work)` marks a step the process finishes before it exits, and `runCli` takes a `signal` to cancel a run in-process.
19
+ - A Cli with `build` prints `--version` as `X.Y.Z (sha)`, or `X.Y.Z (sha+dirty)` for a build from an unclean tree.
20
+ - A scaffolded CLI bundles the Runtime and zod with tsdown, which stamps its version and commit into `--version`; both are devDependencies, so its own version alone decides its behaviour.
21
+ - A scaffolded CLI declares a global install from npmjs and releases with `@duyquangnvx/release`, which stamps its skill and checks its `--version` before publishing.
22
+ - The agent-cli skill, which teaches a Coding agent to build an Agent CLI on the Runtime: `npx skills add` installs it from this repo's `release` branch, and it names the Runtime version it describes.
@@ -0,0 +1,4 @@
1
+ import type { Cli, Commands, CommandTreeCli } from "./define.js";
2
+ /** The Commands the Runtime adds to a multi-Command CLI: `update` only when it declares an install. */
3
+ export declare function builtInCommands(cli: CommandTreeCli, bin: string | undefined): Commands;
4
+ export declare function defineCli(cli: Cli): Cli;
@@ -0,0 +1,20 @@
1
+ import { doctorCommand } from "./doctor.js";
2
+ import { updateCommand } from "./update.js";
3
+ /** The Commands the Runtime adds to a multi-Command CLI: `update` only when it declares an install. */
4
+ export function builtInCommands(cli, bin) {
5
+ return {
6
+ doctor: doctorCommand(cli, bin),
7
+ ...(cli.install === undefined ? {} : { update: updateCommand(cli, cli.install, bin) }),
8
+ };
9
+ }
10
+ export function defineCli(cli) {
11
+ if (cli.commands === undefined)
12
+ return cli;
13
+ const { commands } = cli;
14
+ const taken = Object.keys(builtInCommands(cli, undefined)).filter((name) => commands[name] !== undefined);
15
+ if (taken.length > 0) {
16
+ throw new Error(`${cli.name} defines ${taken.join(", ")}, a built-in Command it cannot replace`);
17
+ }
18
+ return cli;
19
+ }
20
+ //# sourceMappingURL=built-ins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"built-ins.js","sourceRoot":"","sources":["../src/built-ins.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,uGAAuG;AACvG,MAAM,UAAU,eAAe,CAAC,GAAmB,EAAE,GAAuB;IAC1E,OAAO;QACL,MAAM,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC;QAC/B,GAAG,CAAC,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;KACvF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAQ;IAChC,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IAC3C,MAAM,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC;IACzB,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAC/D,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,SAAS,CACvC,CAAC;IACF,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,GAAG,GAAG,CAAC,IAAI,YAAY,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,wCAAwC,CAChF,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type Invocation } from "./invocation.js";
2
+ export declare const CONFIRM_OPTION = "--confirm";
3
+ export interface Confirmation {
4
+ readonly invocation: Invocation;
5
+ readonly commandLine: string;
6
+ readonly given: string | undefined;
7
+ }
8
+ export declare function checkConfirmation({ invocation, commandLine, given }: Confirmation, expected: string): void;
@@ -0,0 +1,22 @@
1
+ import { UsageError } from "./errors.js";
2
+ import { commandLineWith } from "./invocation.js";
3
+ export const CONFIRM_OPTION = "--confirm";
4
+ export function checkConfirmation({ invocation, commandLine, given }, expected) {
5
+ if (given === expected) {
6
+ return;
7
+ }
8
+ const hint = `to confirm, run \`${commandLineWith(invocation, CONFIRM_OPTION, expected)}\``;
9
+ if (given === undefined) {
10
+ throw new UsageError({
11
+ code: "CONFIRMATION_REQUIRED",
12
+ message: `\`${commandLine}\` is destructive and needs ${CONFIRM_OPTION}`,
13
+ hint,
14
+ });
15
+ }
16
+ throw new UsageError({
17
+ code: "CONFIRMATION_MISMATCH",
18
+ message: `${CONFIRM_OPTION} ${JSON.stringify(given)} does not match the target of \`${commandLine}\``,
19
+ hint,
20
+ });
21
+ }
22
+ //# sourceMappingURL=confirm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm.js","sourceRoot":"","sources":["../src/confirm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAmB,MAAM,iBAAiB,CAAC;AAEnE,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAC;AAQ1C,MAAM,UAAU,iBAAiB,CAC/B,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAgB,EAChD,QAAgB;IAEhB,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvB,OAAO;IACT,CAAC;IACD,MAAM,IAAI,GAAG,qBAAqB,eAAe,CAAC,UAAU,EAAE,cAAc,EAAE,QAAQ,CAAC,IAAI,CAAC;IAC5F,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,UAAU,CAAC;YACnB,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,KAAK,WAAW,+BAA+B,cAAc,EAAE;YACxE,IAAI;SACL,CAAC,CAAC;IACL,CAAC;IACD,MAAM,IAAI,UAAU,CAAC;QACnB,IAAI,EAAE,uBAAuB;QAC7B,OAAO,EAAE,GAAG,cAAc,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,mCAAmC,WAAW,IAAI;QACrG,IAAI;KACL,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,108 @@
1
+ import type * as z from "zod";
2
+ import type { Style } from "./style.js";
3
+ export type Env = Readonly<Record<string, string | undefined>>;
4
+ export interface Example {
5
+ readonly command: string;
6
+ readonly note: string;
7
+ }
8
+ export interface Context {
9
+ readonly cwd: string;
10
+ readonly env: Env;
11
+ /** Writes a plain line to stderr, for long work; it never reaches stdout. */
12
+ progress(message: string): void;
13
+ /** Aborts when the run is interrupted (Ctrl-C); pass it to `fetch` and child processes. */
14
+ readonly signal: AbortSignal;
15
+ /** Returns a signal that also aborts, with `DEADLINE_EXCEEDED`, once `ms` have passed. */
16
+ deadline(ms: number): AbortSignal;
17
+ /** Runs a step that must not stop halfway: the run waits for it even when interrupted. */
18
+ runToEnd<T>(work: () => Promise<T>): Promise<T>;
19
+ }
20
+ export type InputSchema = z.ZodObject;
21
+ export type NoInput = z.ZodObject<Record<never, never>>;
22
+ type OutputOf<Schema extends InputSchema> = keyof Schema["shape"] extends never ? unknown : z.output<Schema>;
23
+ type Flatten<T> = {
24
+ [K in keyof T]: T[K];
25
+ };
26
+ export type Input<Args extends InputSchema, Options extends InputSchema> = Flatten<OutputOf<Args> & OutputOf<Options>>;
27
+ interface CommandBase<Args extends InputSchema, Options extends InputSchema> {
28
+ readonly name: string;
29
+ readonly description: string;
30
+ readonly examples: readonly Example[];
31
+ readonly args?: Args;
32
+ readonly options?: Options;
33
+ readonly output?: z.ZodType;
34
+ }
35
+ export interface ReadCommand<Args extends InputSchema, Options extends InputSchema, Data> extends CommandBase<Args, Options> {
36
+ readonly effect: "read";
37
+ run(input: Input<Args, Options>, ctx: Context): Data | Promise<Data>;
38
+ render(data: Data, style: Style): string;
39
+ }
40
+ /** A Command that changes something: `plan` has no side effects, `apply` performs the Plan. */
41
+ interface PlannedCommand<Args extends InputSchema, Options extends InputSchema, Plan, Data> extends CommandBase<Args, Options> {
42
+ plan(input: Input<Args, Options>, ctx: Context): Plan | Promise<Plan>;
43
+ apply(plan: Plan, ctx: Context): Data | Promise<Data>;
44
+ render(data: Data, style: Style): string;
45
+ renderPlan(plan: Plan, style: Style): string;
46
+ }
47
+ export interface WriteCommand<Args extends InputSchema, Options extends InputSchema, Plan, Data> extends PlannedCommand<Args, Options, Plan, Data> {
48
+ readonly effect: "write";
49
+ }
50
+ export interface DestructiveCommand<Args extends InputSchema, Options extends InputSchema, Plan, Data> extends PlannedCommand<Args, Options, Plan, Data> {
51
+ readonly effect: "destructive";
52
+ /** The value the caller must repeat as `--confirm`, naming the target the Plan changes. */
53
+ confirm(plan: Plan): string;
54
+ }
55
+ export type Command = ReadCommand<InputSchema, InputSchema, unknown> | WriteCommand<InputSchema, InputSchema, unknown, unknown> | DestructiveCommand<InputSchema, InputSchema, unknown, unknown>;
56
+ export interface Commands {
57
+ readonly [key: string]: Command | Commands;
58
+ }
59
+ interface CliBase {
60
+ readonly name: string;
61
+ readonly version: string;
62
+ /** The commit the build was made from, stamped by the bundler; `--version` prints it. */
63
+ readonly build?: {
64
+ readonly sha: string;
65
+ readonly dirty: boolean;
66
+ };
67
+ }
68
+ /** Where an Agent CLI is published and how it is installed; `doctor` and `update` read it. */
69
+ export interface GlobalInstall {
70
+ readonly mode: "global";
71
+ readonly package: string;
72
+ readonly registry: string;
73
+ }
74
+ export interface DoctorFinding {
75
+ readonly state: "ok" | "problem";
76
+ readonly message: string;
77
+ /** The command that fixes the problem. */
78
+ readonly fix?: string;
79
+ }
80
+ /** A row the CLI adds to `doctor`, for a system it depends on. */
81
+ export interface DoctorCheck {
82
+ readonly name: string;
83
+ /** `ctx.signal` aborts at the 10 s deadline. A thrown error is reported as this row's problem. */
84
+ run(ctx: Context): DoctorFinding | Promise<DoctorFinding>;
85
+ }
86
+ /** An Agent CLI whose Commands are reached by name, as in `tool voices list`. */
87
+ export interface CommandTreeCli extends CliBase {
88
+ readonly commands: Commands;
89
+ readonly command?: never;
90
+ /** Absent for a CLI that is not published: `doctor` then checks no install or registry. */
91
+ readonly install?: GlobalInstall;
92
+ /** The package.json `engines`; `doctor` checks `node` when it is `>=X[.Y[.Z]]`. */
93
+ readonly engines?: {
94
+ readonly node?: string;
95
+ };
96
+ readonly doctor?: readonly DoctorCheck[];
97
+ }
98
+ /** An Agent CLI that is one Command, run with no Command name, as in `tool <args>`. */
99
+ export interface SingleCommandCli extends CliBase {
100
+ readonly command: Command;
101
+ readonly commands?: never;
102
+ }
103
+ export type Cli = CommandTreeCli | SingleCommandCli;
104
+ export declare function defineCommand<Data, Args extends InputSchema = NoInput, Options extends InputSchema = NoInput>(command: ReadCommand<Args, Options, Data>): ReadCommand<Args, Options, Data>;
105
+ export declare function defineCommand<Plan, Data, Args extends InputSchema = NoInput, Options extends InputSchema = NoInput>(command: WriteCommand<Args, Options, Plan, Data>): WriteCommand<Args, Options, Plan, Data>;
106
+ export declare function defineCommand<Plan, Data, Args extends InputSchema = NoInput, Options extends InputSchema = NoInput>(command: DestructiveCommand<Args, Options, Plan, Data>): DestructiveCommand<Args, Options, Plan, Data>;
107
+ export declare function isCommand(node: Command | Commands): node is Command;
108
+ export {};
package/dist/define.js ADDED
@@ -0,0 +1,8 @@
1
+ export function defineCommand(command) {
2
+ return command;
3
+ }
4
+ // A group's values are always objects, so a string `effect` can only belong to a Command.
5
+ export function isCommand(node) {
6
+ return typeof node.effect === "string";
7
+ }
8
+ //# sourceMappingURL=define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.js","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AA0JA,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,SAAS,CAAC,IAAwB;IAChD,OAAO,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC;AACzC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { CommandTreeCli, DoctorCheck } from "./define.js";
2
+ /** The rows every `doctor` reports first, in this order. */
3
+ export declare function fixedRows(cli: CommandTreeCli, bin: string | undefined): DoctorCheck[];
@@ -0,0 +1,115 @@
1
+ import { CliError } from "./errors.js";
2
+ import { globalInstallOf } from "./global-install.js";
3
+ import { installCommandLine, latestVersion, npmContext } from "./npm.js";
4
+ import { engineMinimum, meetsMinimum, standingOf } from "./semver.js";
5
+ import { skillCopies, skillUpdateCommand } from "./skills.js";
6
+ function ok(message) {
7
+ return { state: "ok", message };
8
+ }
9
+ function nodeRow(cli) {
10
+ return {
11
+ name: "node",
12
+ run: () => {
13
+ const found = process.versions.node;
14
+ const range = cli.engines?.node;
15
+ if (range === undefined) {
16
+ return ok(`Node ${found}; ${cli.name} declares no engines.node to check it against`);
17
+ }
18
+ const minimum = engineMinimum(range);
19
+ if (minimum === undefined) {
20
+ return {
21
+ state: "problem",
22
+ message: `${cli.name} declares engines.node "${range}", which doctor cannot read: only >=X.Y.Z is supported`,
23
+ };
24
+ }
25
+ if (meetsMinimum(found, minimum)) {
26
+ return ok(`Node ${found} meets ${range}`);
27
+ }
28
+ const major = minimum[0];
29
+ return {
30
+ state: "problem",
31
+ message: `${cli.name} runs on Node ${found}, older than the ${range} it needs; install Node ${minimum.join(".")} or newer and make it the default`,
32
+ fix: `nvm install ${major} && nvm alias default ${major}`,
33
+ };
34
+ },
35
+ };
36
+ }
37
+ function installRow(cli, bin) {
38
+ return {
39
+ name: "install",
40
+ run: async (ctx) => {
41
+ const { install } = cli;
42
+ if (install === undefined) {
43
+ return ok(`${cli.name} declares no install, so it has no \`update\``);
44
+ }
45
+ try {
46
+ const copy = await globalInstallOf(bin, npmContext(cli, install, ctx));
47
+ return ok(`a global npm install under ${copy.prefix}`);
48
+ }
49
+ catch (error) {
50
+ if (error instanceof CliError && error.code === "NOT_GLOBAL_INSTALL") {
51
+ return { state: "problem", message: error.message, fix: installCommandLine(install) };
52
+ }
53
+ throw error;
54
+ }
55
+ },
56
+ };
57
+ }
58
+ function registryRow(cli) {
59
+ return {
60
+ name: "registry",
61
+ run: async (ctx) => {
62
+ const { install } = cli;
63
+ if (install === undefined) {
64
+ return ok(`${cli.name} declares no registry to compare this build with`);
65
+ }
66
+ const latest = await latestVersion(npmContext(cli, install, ctx));
67
+ const standing = standingOf(cli.version, latest);
68
+ const head = `${install.registry} has ${latest} as latest: this build`;
69
+ if (standing === "behind") {
70
+ return {
71
+ state: "problem",
72
+ message: `${head} (${cli.version}) is older`,
73
+ fix: `${cli.name} update`,
74
+ };
75
+ }
76
+ return ok(`${head} is ${standing === "current" ? "current" : `newer (${cli.version})`}`);
77
+ },
78
+ };
79
+ }
80
+ const SKILL_STATE_NOTES = {
81
+ current: "the same as this build",
82
+ behind: "older than this build",
83
+ ahead: "newer than this build",
84
+ unversioned: "states no metadata.version",
85
+ unreadable: "could not be read",
86
+ };
87
+ function describeCopy({ path, version, state }) {
88
+ return `${path}${version === undefined ? "" : ` ${version}`} (${SKILL_STATE_NOTES[state]})`;
89
+ }
90
+ function skillFix(cli, copies) {
91
+ if (copies.some(({ state }) => state === "behind" || state === "unversioned")) {
92
+ return skillUpdateCommand(cli.name);
93
+ }
94
+ return copies.some(({ state }) => state === "ahead") ? `${cli.name} update` : undefined;
95
+ }
96
+ // A skill describes the CLI but never stops it working, so this row is always ok.
97
+ function skillRow(cli) {
98
+ return {
99
+ name: "skill",
100
+ run: async (ctx) => {
101
+ const copies = await skillCopies(cli.name, cli.version, ctx.cwd, ctx.env);
102
+ if (copies.length === 0) {
103
+ return ok(`no copy of the ${cli.name} skill is installed`);
104
+ }
105
+ const fix = skillFix(cli, copies);
106
+ const message = copies.map(describeCopy).join("; ");
107
+ return fix === undefined ? ok(message) : { state: "ok", message, fix };
108
+ },
109
+ };
110
+ }
111
+ /** The rows every `doctor` reports first, in this order. */
112
+ export function fixedRows(cli, bin) {
113
+ return [nodeRow(cli), installRow(cli, bin), registryRow(cli), skillRow(cli)];
114
+ }
115
+ //# sourceMappingURL=doctor-rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-rows.js","sourceRoot":"","sources":["../src/doctor-rows.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type Command, type CommandTreeCli } from "./define.js";
2
+ /** The built-in `doctor`: the fixed rows, then the CLI's own, each under its own deadline. */
3
+ export declare function doctorCommand(cli: CommandTreeCli, bin: string | undefined): Command;
package/dist/doctor.js ADDED
@@ -0,0 +1,100 @@
1
+ import * as z from "zod";
2
+ import { defineCommand, } from "./define.js";
3
+ import { fixedRows } from "./doctor-rows.js";
4
+ import { CliError, FailureWithData, messageOf } from "./errors.js";
5
+ const PROBE_DEADLINE_MS = 10_000;
6
+ const MS_PER_SECOND = 1000;
7
+ const MIN_LABEL_WIDTH = 8;
8
+ const rowSchema = z.object({
9
+ check: z.string(),
10
+ state: z.enum(["ok", "problem"]),
11
+ message: z.string(),
12
+ fix: z.string().optional(),
13
+ });
14
+ const reportSchema = z.object({
15
+ rows: z.array(rowSchema),
16
+ next: z.array(z.string()),
17
+ });
18
+ function untilAborted(signal) {
19
+ return new Promise((_resolve, reject) => {
20
+ if (signal.aborted) {
21
+ reject(signal.reason);
22
+ return;
23
+ }
24
+ signal.addEventListener("abort", () => reject(signal.reason), { once: true });
25
+ });
26
+ }
27
+ function problemOf(check, error, { signal, deadline }) {
28
+ if (!signal.aborted && deadline.aborted && error === deadline.reason) {
29
+ return {
30
+ state: "problem",
31
+ message: `${check.name} did not answer within ${PROBE_DEADLINE_MS / MS_PER_SECOND} s`,
32
+ };
33
+ }
34
+ if (error instanceof CliError) {
35
+ return { state: "problem", message: `${error.message}; ${error.hint}` };
36
+ }
37
+ return { state: "problem", message: `the ${check.name} row hit a bug: ${messageOf(error)}` };
38
+ }
39
+ // Racing the deadline ends a row whose check ignores its signal.
40
+ async function rowOf(check, ctx) {
41
+ const signal = ctx.deadline(PROBE_DEADLINE_MS);
42
+ let finding;
43
+ try {
44
+ finding = await Promise.race([
45
+ Promise.resolve().then(() => check.run({ ...ctx, signal })),
46
+ untilAborted(signal),
47
+ ]);
48
+ }
49
+ catch (error) {
50
+ finding = problemOf(check, error, { signal: ctx.signal, deadline: signal });
51
+ }
52
+ return { check: check.name, ...finding };
53
+ }
54
+ function nextOf(rows) {
55
+ return [...new Set(rows.flatMap(({ fix }) => (fix === undefined ? [] : [fix])))];
56
+ }
57
+ function render({ rows, next }, style) {
58
+ const width = Math.max(MIN_LABEL_WIDTH, ...rows.map(({ check }) => check.length)) + 2;
59
+ const stateWidth = Math.max(...rows.map(({ state }) => state.length)) + 2;
60
+ const lines = rows.map(({ check, state, message }) => {
61
+ const label = state === "problem" ? style.red(state) : state;
62
+ return `${check.padEnd(width)}${label}${" ".repeat(stateWidth - state.length)}${message}`;
63
+ });
64
+ return [...lines, ...next.map((fix) => `${"next".padEnd(width)}${fix}`)].join("\n");
65
+ }
66
+ function failure(cliName, report) {
67
+ const problems = report.rows.filter(({ state }) => state === "problem").map(({ check }) => check);
68
+ return new FailureWithData({
69
+ code: "DOCTOR_PROBLEMS",
70
+ message: `${cliName} doctor found ${problems.length === 1 ? "a problem" : `${problems.length} problems`}: ${problems.join(", ")}`,
71
+ hint: report.next.length === 0
72
+ ? "each problem row says what is wrong"
73
+ : "run the commands under next, then run doctor again",
74
+ retryable: false,
75
+ }, report);
76
+ }
77
+ /** The built-in `doctor`: the fixed rows, then the CLI's own, each under its own deadline. */
78
+ export function doctorCommand(cli, bin) {
79
+ return defineCommand({
80
+ name: "doctor",
81
+ description: "check the setup and say what fixes each problem",
82
+ examples: [
83
+ { command: `${cli.name} doctor`, note: "check the setup and list the commands that fix it" },
84
+ { command: `${cli.name} doctor --json`, note: "get the rows and fixes as data" },
85
+ ],
86
+ effect: "read",
87
+ output: reportSchema,
88
+ run: async (_input, ctx) => {
89
+ const checks = [...fixedRows(cli, bin), ...(cli.doctor ?? [])];
90
+ const rows = await Promise.all(checks.map((check) => rowOf(check, ctx)));
91
+ const report = { rows, next: nextOf(rows) };
92
+ if (rows.some(({ state }) => state === "problem")) {
93
+ throw failure(cli.name, report);
94
+ }
95
+ return report;
96
+ },
97
+ render,
98
+ });
99
+ }
100
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { Env } from "./define.js";
2
+ import type { Style } from "./style.js";
3
+ export interface CliErrorDetails {
4
+ readonly code: string;
5
+ readonly message: string;
6
+ readonly hint: string;
7
+ readonly retryable: boolean;
8
+ }
9
+ export declare class CliError extends Error {
10
+ readonly code: string;
11
+ readonly hint: string;
12
+ readonly retryable: boolean;
13
+ constructor({ code, message, hint, retryable }: CliErrorDetails, options?: ErrorOptions);
14
+ }
15
+ export type UsageErrorDetails = Omit<CliErrorDetails, "retryable">;
16
+ /** The caller called the Agent CLI wrong; retrying the same call can't succeed. */
17
+ export declare class UsageError extends CliError {
18
+ constructor(details: UsageErrorDetails);
19
+ }
20
+ export interface ErrorFormat {
21
+ readonly cliName: string;
22
+ readonly json: boolean;
23
+ readonly env: Env;
24
+ readonly style: Style;
25
+ }
26
+ export declare function isMissingFile(error: unknown): boolean;
27
+ export declare function debugVariable(cliName: string): string;
28
+ export declare function messageOf(error: unknown): string;
29
+ export declare function formatRuntimeError(error: unknown, format: ErrorFormat): string;
30
+ /** A failure that still prints the Command's data on stdout, as `doctor` does with its rows. */
31
+ export declare class FailureWithData extends CliError {
32
+ readonly data: unknown;
33
+ constructor(details: CliErrorDetails, data: unknown);
34
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,72 @@
1
+ export class CliError extends Error {
2
+ code;
3
+ hint;
4
+ retryable;
5
+ constructor({ code, message, hint, retryable }, options) {
6
+ super(message, options);
7
+ this.name = "CliError";
8
+ this.code = code;
9
+ this.hint = hint;
10
+ this.retryable = retryable;
11
+ }
12
+ }
13
+ /** The caller called the Agent CLI wrong; retrying the same call can't succeed. */
14
+ export class UsageError extends CliError {
15
+ constructor(details) {
16
+ super({ ...details, retryable: false });
17
+ this.name = "UsageError";
18
+ }
19
+ }
20
+ const INTERNAL_CODE = "INTERNAL";
21
+ export function isMissingFile(error) {
22
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
23
+ }
24
+ export function debugVariable(cliName) {
25
+ return `${cliName.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_DEBUG`;
26
+ }
27
+ export function messageOf(error) {
28
+ return error instanceof Error ? error.message : String(error);
29
+ }
30
+ function isDebug({ env, cliName }) {
31
+ return Boolean(env[debugVariable(cliName)]);
32
+ }
33
+ function detailsOf(error, format) {
34
+ if (error instanceof CliError) {
35
+ const { code, message, hint, retryable } = error;
36
+ return { code, message, hint, retryable };
37
+ }
38
+ return {
39
+ code: INTERNAL_CODE,
40
+ message: messageOf(error),
41
+ hint: isDebug(format)
42
+ ? "the stack trace shows where it failed"
43
+ : `rerun with ${debugVariable(format.cliName)}=1 to see the stack trace`,
44
+ retryable: false,
45
+ };
46
+ }
47
+ function stackOf(error, format) {
48
+ return isDebug(format) && !(error instanceof CliError) && error instanceof Error
49
+ ? error.stack
50
+ : undefined;
51
+ }
52
+ export function formatRuntimeError(error, format) {
53
+ const details = detailsOf(error, format);
54
+ const stack = stackOf(error, format);
55
+ if (format.json) {
56
+ return `${JSON.stringify({ ...details, stack })}\n`;
57
+ }
58
+ const { style } = format;
59
+ const label = style.red(error instanceof CliError ? "error:" : "internal error:");
60
+ const trace = stack === undefined ? "" : `${stack}\n`;
61
+ return `${label} ${details.message}\n${trace}${style.cyan("hint:")} ${details.hint}\n`;
62
+ }
63
+ /** A failure that still prints the Command's data on stdout, as `doctor` does with its rows. */
64
+ export class FailureWithData extends CliError {
65
+ data;
66
+ constructor(details, data) {
67
+ super(details);
68
+ this.name = "FailureWithData";
69
+ this.data = data;
70
+ }
71
+ }
72
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { type Confirmation } from "./confirm.js";
2
+ import type { Command, Context } from "./define.js";
3
+ import type { Style } from "./style.js";
4
+ export interface Outcome {
5
+ readonly value: unknown;
6
+ render(style: Style): string;
7
+ }
8
+ export interface ExecuteFlags {
9
+ readonly dryRun: boolean;
10
+ readonly confirmation: Confirmation;
11
+ }
12
+ export declare function execute(command: Command, input: Record<string, unknown>, ctx: Context, { dryRun, confirmation }: ExecuteFlags): Promise<Outcome>;
@@ -0,0 +1,18 @@
1
+ import { checkConfirmation } from "./confirm.js";
2
+ function outcomeOf(value, render) {
3
+ return { value, render: (style) => render(value, style) };
4
+ }
5
+ export async function execute(command, input, ctx, { dryRun, confirmation }) {
6
+ if (command.effect === "read") {
7
+ return outcomeOf(await command.run(input, ctx), (data, style) => command.render(data, style));
8
+ }
9
+ const plan = await command.plan(input, ctx);
10
+ if (dryRun) {
11
+ return outcomeOf(plan, (value, style) => command.renderPlan(value, style));
12
+ }
13
+ if (command.effect === "destructive") {
14
+ checkConfirmation(confirmation, command.confirm(plan));
15
+ }
16
+ return outcomeOf(await command.apply(plan, ctx), (data, style) => command.render(data, style));
17
+ }
18
+ //# sourceMappingURL=execute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute.js","sourceRoot":"","sources":["../src/execute.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAcpE,SAAS,SAAS,CAAQ,KAAY,EAAE,MAA8C;IACpF,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;AAC5D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,OAAgB,EAChB,KAA8B,EAC9B,GAAY,EACZ,EAAE,MAAM,EAAE,YAAY,EAAgB;IAEtC,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC9B,OAAO,SAAS,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAChG,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC5C,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,SAAS,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IAC7E,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;QACrC,iBAAiB,CAAC,YAAY,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,SAAS,CAAC,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AACjG,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { Command } from "./define.js";
2
+ import type { InputField } from "./input.js";
3
+ export declare const FIELDS_OPTION = "--fields";
4
+ type Row = Readonly<Record<string, unknown>>;
5
+ /** The fields a list Command's rows have, or undefined for a Command that is not a list. */
6
+ export declare function rowFieldsOf(command: Command): readonly string[] | undefined;
7
+ export declare function fieldsDescription(rowFields: readonly string[]): string;
8
+ export declare function fieldsOption(rowFields: readonly string[]): InputField;
9
+ /** The fields the caller asked for, in their order, or undefined when they gave no `--fields`. */
10
+ export declare function selectedFields(raw: string | boolean | undefined, rowFields: readonly string[], commandLine: string): readonly string[] | undefined;
11
+ export declare function projected(rows: readonly unknown[], fields: readonly string[]): Row[];
12
+ /** Selected rows as text: a header line of the field names, then one tab-separated line per row. */
13
+ export declare function renderRows(rows: readonly Row[], fields: readonly string[]): string;
14
+ export {};