argsbarg 2.1.0 → 3.0.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/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.0.0] - 2026-06-20
11
+
12
+ ### Added
13
+
14
+ - **`version` built-in** — `myapp version` prints `CliProgram.version` (always available; reserved command name).
15
+
16
+ ### Changed
17
+
18
+ - **`CliProgram.version`** (required) — single source of truth for the `version` built-in and MCP `serverInfo.version`. Removed `mcpServer.version` and automatic `package.json` lookup.
19
+ - **MCP opt-in** — `mcpServer: { enabled: true }` enables MCP; omit `mcpServer` to disable. Empty `mcpServer: {}` is rejected at validation.
20
+ - **MCP identity from `key`** — removed `mcpServer.name`. MCP `serverInfo.name`, schema URI, and `mcp.json` entry keys use `sanitizeToolSegment(root.key)` (e.g. `nested.ts` → `nested_ts://schema`). Shell `command` stays the raw `key`.
21
+
22
+ ## [2.1.1] - 2026-06-20
23
+
24
+ ### Changed
25
+
26
+ - **`install` built-in** — no longer gated on compiled binaries; available whenever `install.enabled !== false` (default on). Removed `isCompiledExecutable()` and `src/install/compiled.ts`.
27
+
10
28
  ## [2.1.0] - 2026-06-20
11
29
 
12
30
 
@@ -180,7 +198,9 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
180
198
  - Migrate schemas: rename every `children` property to **`commands`**; move positional definitions to **`CliPositional`** objects on `positionals` and strip `positional` / `argMin` / `argMax` from flag definitions under `options` (flags only carry `name`, `description`, `kind`, and optional `shortName`).
181
199
  - Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
182
200
 
183
- [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v2.1.0...HEAD
201
+ [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.0.0...HEAD
202
+ [3.0.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.0.0
203
+ [2.1.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v2.1.1
184
204
  [2.1.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v2.1.0
185
205
  [2.0.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v2.0.1
186
206
  [2.0.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v2.0.0
package/README.md CHANGED
@@ -16,7 +16,7 @@ Why another CLI parser?
16
16
 
17
17
  *Shell completions* — `completion bash`, `completion zsh`, and `completion fish` built-ins generate installable scripts from your schema so users get tab completion for commands, flags, and positionals without extra tooling.
18
18
 
19
- *Optional MCP server* — set `mcpServer: {}` on the program root to expose leaf commands as MCP tools and the full CLI tree as a schema resource (`myapp mcp` over stdio). See [docs/mcp.md](docs/mcp.md). Compiled binaries can install binary, completions, skills, and MCP config with `myapp install` — see [docs/install.md](docs/install.md).
19
+ *Optional MCP server* — set `mcpServer: { enabled: true }` on the program root to expose leaf commands as MCP tools and the full CLI tree as a schema resource (`myapp mcp` over stdio). See [docs/mcp.md](docs/mcp.md). Compiled binaries can install binary, completions, skills, and MCP config with `myapp install` — see [docs/install.md](docs/install.md).
20
20
 
21
21
  *Bun-optimized* — built from the ground up for Bun and TypeScript, leveraging Bun’s performance and modern JavaScript features without any extra dependencies.
22
22
 
@@ -39,6 +39,7 @@ import { cliRun, type CliProgram, CliOptionKind } from "argsbarg";
39
39
 
40
40
  const cli = {
41
41
  key: "helloapp",
42
+ version: "1.0.0",
42
43
  description: "Tiny demo.",
43
44
  positionals: [
44
45
  {
@@ -96,23 +97,24 @@ Every app gets:
96
97
  - `-h` / `--help` at any routing depth (scoped help).
97
98
  - **`--schema`** at the program root — print the full command tree as JSON (for tooling and agents).
98
99
  - **`completion bash` / `completion zsh` / `completion fish`** — print shell completion scripts to stdout (injected by `cliRun`).
99
- - **`mcp`** — when `mcpServer` is set on the program root, run as an MCP stdio server (`myapp mcp`).
100
- - **`install`** — when running as a compiled binary (`bun build --compile`), install the binary, completions, skills, and MCP config to the user environment (`myapp install --all --yes`). See [docs/install.md](docs/install.md).
100
+ - **`version`** — print `CliProgram.version` (`myapp version`).
101
+ - **`mcp`** — when `mcpServer.enabled` is `true`, run as an MCP stdio server (`myapp mcp`).
102
+ - **`install`** — install the binary, completions, skills, and MCP config to the user environment (`myapp install --all --yes`). See [docs/install.md](docs/install.md).
101
103
 
102
- Do not declare a top-level command named **`completion`** or **`install`** — they are reserved.
103
- When **`mcpServer`** is set, do not declare a top-level command named **`mcp`** — it is reserved for the MCP built-in.
104
+ Do not declare a top-level command named **`completion`**, **`version`**, or **`install`** — they are reserved.
105
+ When **`mcpServer.enabled`** is `true`, do not declare a top-level command named **`mcp`** — it is reserved for the MCP built-in.
104
106
  Do not declare an option named **`schema`** — it is reserved for `--schema`.
105
107
 
106
108
 
107
109
  ### MCP (AI agents)
108
110
 
109
- Opt in on the program root with `mcpServer: {}` (or `{ name, version, … }`), then run `myapp mcp` for a stdio MCP server. Each leaf command becomes a tool; the CLI tree is available as resource `argsbarg://schema`. Handlers can read `ctx.invocation` and use `cliInvoke` for headless testing.
111
+ Opt in on the program root with `mcpServer: { enabled: true }`, then run `myapp mcp` for a stdio MCP server. Each leaf command becomes a tool; the CLI tree is available as resource `<sanitized-key>://schema` (same as `myapp --schema`). Handlers can read `ctx.invocation` and use `cliInvoke` for headless testing.
110
112
 
111
113
  See **[docs/mcp.md](docs/mcp.md)** for configuration, env bootstrapping, custom resources, Cursor setup, and protocol details.
112
114
 
113
- ### Install (compiled binaries)
115
+ ### Install
114
116
 
115
- After `bun build --compile`, ship a self-contained binary and let users run:
117
+ After `bun build --compile` (or when running via `bun`), ship your CLI and let users run:
116
118
 
117
119
  ```bash
118
120
  myapp install --all --yes
@@ -185,7 +187,7 @@ Add `CliPositional` entries to the command’s `positionals` list (separate from
185
187
 
186
188
  ### Capabilities (built-ins)
187
189
 
188
- `completion`, `install`, and `mcp` are not part of your schema — they are injected at runtime from program-level config (`mcpServer`, compiled binary + `install`). Reserved command names follow from that config: `completion` and `install` are always reserved; `mcp` is reserved when `mcpServer` is set.
190
+ `completion`, `version`, `install`, and `mcp` are not part of your schema — they are injected at runtime from program-level config (`mcpServer`, `install`). Reserved command names: `completion` and `version` always; `install` unless `install.enabled: false`; `mcp` when `mcpServer.enabled` is `true`.
189
191
 
190
192
 
191
193
 
@@ -226,7 +228,7 @@ The package root (`argsbarg` / `src/index.ts`) exports the types and runtime you
226
228
  | `cliInvoke(root, argv)` | Parse and dispatch without exiting; returns captured stdout/stderr. |
227
229
  | `cliErrWithHelp(ctx, msg)` | Print error + scoped help on stderr, exit 1. |
228
230
 
229
- Reserved identifiers (validated at startup): root commands **`completion`**, **`install`**, and **`mcp`** (only when `mcpServer` is set).
231
+ Reserved identifiers (validated at startup): root commands **`completion`**, **`version`**, **`install`**, and **`mcp`** (only when `mcpServer.enabled` is `true`).
230
232
 
231
233
  ---
232
234
 
package/docs/ai-skills.md CHANGED
@@ -4,7 +4,7 @@ ArgsBarg can generate Cursor and Claude Code skill directories (`SKILL.md` + `re
4
4
 
5
5
  ## Install via `install` (recommended)
6
6
 
7
- In a **compiled binary**, install skills to the user environment:
7
+ Install skills to the user environment:
8
8
 
9
9
  ```bash
10
10
  myapp install --skill --yes
package/docs/install.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Install command
2
2
 
3
- The `install` built-in is available only in **compiled binaries** (`bun build --compile`). It is hidden from help, `--schema`, and shell completions when running via `bun run`.
3
+ The `install` built-in installs the binary, shell completions, agent skills, and MCP config. Opt out with `install: { enabled: false }` on the program root.
4
4
 
5
5
  ## Quick start
6
6
 
@@ -30,7 +30,7 @@ myapp install --uninstall --yes
30
30
  | Claude skill | `--skill` | `~/.claude/skills/<dir>/` when `~/.claude` exists |
31
31
  | MCP config | `--mcp` | `~/.cursor/mcp.json` and `~/.claude.json` when MCP is enabled |
32
32
 
33
- `--all` expands to `--bin`, `--completions`, `--skill`, and `--mcp` (when `mcpServer` is set).
33
+ `--all` expands to `--bin`, `--completions`, `--skill`, and `--mcp` (when `mcpServer.enabled` is `true`).
34
34
 
35
35
  Shells not on PATH are skipped silently (no warnings).
36
36
 
@@ -64,7 +64,7 @@ Environment:
64
64
 
65
65
  ## MCP merge behavior
66
66
 
67
- When `--mcp` runs, entries are merged into `mcpServers[<name>]` with:
67
+ When `--mcp` runs, entries are merged into `mcpServers[<sanitized-key>]` with:
68
68
 
69
69
  ```json
70
70
  { "command": "<root.key>", "args": ["mcp"] }
package/docs/mcp.md CHANGED
@@ -9,15 +9,18 @@ MCP is **opt-in**. Apps that do not set `mcpServer` on the program root behave e
9
9
  1. Add `mcpServer` to your program root:
10
10
 
11
11
  ```typescript
12
+ import pkg from "../package.json" with { type: "json" };
13
+
12
14
  const cli = {
13
15
  key: "myapp",
16
+ version: pkg.version,
14
17
  description: "My app.",
15
- mcpServer: { name: "myapp", version: "1.0.0" },
18
+ mcpServer: { enabled: true },
16
19
  commands: [/* ... */],
17
20
  } satisfies CliProgram;
18
21
  ```
19
22
 
20
- `mcpServer: {}` is enough to enable the server. Optional fields override defaults (see [Configuration](#configuration)).
23
+ `mcpServer: { enabled: true }` opts in. Omit `mcpServer` entirely to disable MCP. Empty `mcpServer: {}` is rejected at validation.
21
24
 
22
25
  2. Run the MCP server:
23
26
 
@@ -66,26 +69,27 @@ Set `mcpServer` on the **program root only** (the `CliProgram` passed to `cliRun
66
69
 
67
70
  | Field | Default | Purpose |
68
71
  | --- | --- | --- |
69
- | `name` | root `key` | `serverInfo.name` in the `initialize` response |
70
- | `version` | `package.json` `version` in cwd, else `"0.0.0"` | `serverInfo.version` |
71
- | `schemaResourceUri` | `"argsbarg://schema"` | URI for the schema resource |
72
+ | `enabled` | *(required)* | Must be `true` when `mcpServer` is set |
73
+ | `schemaResourceUri` | `<sanitized root key>://schema` | URI for the built-in schema resource |
72
74
  | `shellEnv` | off | Capture login-shell `env` at startup (`true` uses `$SHELL`, or pass a shell path) |
73
75
  | `envFile` | off | Load a `.env` file after `shellEnv` (`~` supported); warns on stderr if missing |
74
- | `resources` | `[]` | Custom `CliMcpResource` entries for `resources/list` and `resources/read` |
76
+ | `resources` | `[]` | Custom `CliMcpResource` entries (additive; schema resource is always included) |
77
+
78
+ MCP `serverInfo.name` and the default schema URI use the sanitized program `key` (non-alphanumeric characters become `_`). Program `version` comes from `CliProgram.version` (also used by the `version` built-in).
75
79
 
76
- Example with all fields:
80
+ Example with optional fields:
77
81
 
78
82
  ```typescript
79
83
  mcpServer: {
80
- name: "nested-demo",
81
- version: "1.0.0",
82
- schemaResourceUri: "argsbarg://schema",
84
+ enabled: true,
85
+ shellEnv: true,
86
+ envFile: "~/.config/myapp/mcp.env",
83
87
  }
84
88
  ```
85
89
 
86
90
  ## Tools
87
91
 
88
- Every **user-defined leaf command** in your schema becomes one MCP tool. Built-ins (`completion`, `ai`) are not exposed as tools.
92
+ Every **user-defined leaf command** in your schema becomes one MCP tool. Built-ins (`completion`, `version`, `install`, `mcp`) are not exposed as tools.
89
93
 
90
94
  ### Tool names
91
95
 
@@ -172,11 +176,11 @@ Help and `--schema` are not available through tool calls; use the schema resourc
172
176
 
173
177
  ## Schema and custom resources
174
178
 
175
- The built-in resource `argsbarg://schema` (or `schemaResourceUri`) exposes your full CLI tree as JSON — the same output as `myapp --schema`.
179
+ The built-in schema resource (default URI `<sanitized-key>://schema`, e.g. `nested.ts` `nested_ts://schema`) exposes your full CLI tree as JSON — the same output as `myapp --schema`. Override with `schemaResourceUri` if needed.
176
180
 
177
181
  | Property | Value |
178
182
  | --- | --- |
179
- | Default URI | `argsbarg://schema` |
183
+ | Default URI | `<sanitized root key>://schema` |
180
184
  | MIME type | `application/json` |
181
185
  | Contents | `cliSchemaJson(root)` — handlers omitted, built-ins excluded |
182
186
 
@@ -184,6 +188,7 @@ Add custom resources on the program root:
184
188
 
185
189
  ```typescript
186
190
  mcpServer: {
191
+ enabled: true,
187
192
  resources: [
188
193
  {
189
194
  uri: "myapp://config",
@@ -9,10 +9,10 @@ const envFilePath = process.env.ARGS_TEST_ENV_FILE;
9
9
 
10
10
  const cli = {
11
11
  key: "mcp-test",
12
+ version: "0.0.0-test",
12
13
  description: "MCP integration test fixture.",
13
14
  mcpServer: {
14
- name: "mcp-test",
15
- version: "0.0.0-test",
15
+ enabled: true,
16
16
  ...(envFilePath ? { envFile: envFilePath } : {}),
17
17
  resources: [
18
18
  {
@@ -7,10 +7,12 @@ readers can copy the pattern into their own scripts quickly.
7
7
  It demonstrates the minimal Bun integration path.
8
8
  */
9
9
 
10
+ import pkg from "../package.json" with { type: "json" };
10
11
  import { cliRun, CliProgram, CliOptionKind } from "../src/index.ts";
11
12
 
12
13
  const cli = {
13
14
  key: "minimal.ts",
15
+ version: pkg.version,
14
16
  description: "Tiny demo.",
15
17
  positionals: [
16
18
  {
@@ -7,12 +7,14 @@ and fallback commands fit together in one schema.
7
7
  It demonstrates how the schema scales beyond one command.
8
8
  */
9
9
 
10
+ import pkg from "../package.json" with { type: "json" };
10
11
  import { cliRun, CliProgram, CliOptionKind, CliFallbackMode } from "../src/index.ts";
11
12
 
12
13
  const cli = {
13
14
  key: "nested.ts",
15
+ version: pkg.version,
14
16
  description: "Nested groups demo.",
15
- mcpServer: { name: "nested-demo", version: "1.0.0" },
17
+ mcpServer: { enabled: true },
16
18
  commands: [
17
19
  {
18
20
  key: "stat",
@@ -7,10 +7,12 @@ readers can copy the pattern into their own scripts quickly.
7
7
  It demonstrates the minimal Bun integration path.
8
8
  */
9
9
 
10
+ import pkg from "../package.json" with { type: "json" };
10
11
  import { cliRun, CliProgram, CliOptionKind, CliFallbackMode, isInteractiveTty } from "../src/index.ts";
11
12
 
12
13
  const cli = {
13
14
  key: "option-required.ts",
15
+ version: pkg.version,
14
16
  description: "Demo of a required option.",
15
17
  options: [
16
18
  {
package/index.d.ts CHANGED
@@ -106,13 +106,12 @@ export interface CliPositional {
106
106
  }
107
107
  /**
108
108
  * Enables `myapp mcp` and MCP stdio server metadata (program root only).
109
+ * Must include `enabled: true`; omit `mcpServer` entirely to disable MCP.
109
110
  */
110
111
  export interface CliMcpServerConfig {
111
- /** `initialize` serverInfo.name (default: root `key`). */
112
- name?: string;
113
- /** `initialize` serverInfo.version (default: see resolveMcpVersion). */
114
- version?: string;
115
- /** Resource URI for schema export (default: `"argsbarg://schema"`). */
112
+ /** When `true`, enables the `mcp` built-in and MCP stdio server. */
113
+ enabled: boolean;
114
+ /** Resource URI for schema export (default: `<sanitized root key>://schema`). */
116
115
  schemaResourceUri?: string;
117
116
  /**
118
117
  * Capture the user's login shell environment at MCP server start and merge it
@@ -127,7 +126,7 @@ export interface CliMcpServerConfig {
127
126
  */
128
127
  envFile?: string;
129
128
  /**
130
- * Custom MCP resources exposed alongside the built-in argsbarg://schema resource.
129
+ * Custom MCP resources exposed alongside the built-in schema resource.
131
130
  * URIs must be unique and must not equal schemaResourceUri.
132
131
  */
133
132
  resources?: CliMcpResource[];
@@ -166,7 +165,7 @@ export interface CliMcpToolConfig {
166
165
  requiresEnv?: string[];
167
166
  }
168
167
  /**
169
- * Opt-out and defaults for the `install` built-in (compiled binaries only; program root only).
168
+ * Opt-out and defaults for the `install` built-in (program root only).
170
169
  */
171
170
  export interface CliInstallConfig {
172
171
  /** When `false`, hide/disable `install` (default: enabled). */
@@ -218,9 +217,11 @@ export type CliNode = CliLeaf | CliRouter;
218
217
  * May be a leaf or router, plus optional program-level MCP and install config.
219
218
  */
220
219
  export type CliProgram = CliNode & {
221
- /** When set, enables the `mcp` built-in subcommand. */
220
+ /** Program version (printed by the `version` built-in and MCP serverInfo). */
221
+ version: string;
222
+ /** When set with `enabled: true`, enables the `mcp` built-in subcommand. */
222
223
  mcpServer?: CliMcpServerConfig;
223
- /** Opt-out and defaults for `install` (compiled binaries only). */
224
+ /** Opt-out and defaults for `install`. */
224
225
  install?: CliInstallConfig;
225
226
  };
226
227
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "2.1.0",
3
+ "version": "3.0.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "//just": "echo this app uses justfile for development tasks"
@@ -1,16 +1,16 @@
1
- import { describe, expect, test, afterEach } from "bun:test";
1
+ import { describe, expect, test } from "bun:test";
2
2
  import { cliBuiltinInstallCommand, installBuiltinOptions } from "./install.ts";
3
3
  import { cliBuiltinMcpCommand } from "./mcp.ts";
4
4
  import { cliPresentationRoot } from "./presentation.ts";
5
5
  import { completionBashScript, completionFishScript, completionZshScript } from "./index.ts";
6
6
  import { exportPresentationBuiltins } from "./export.ts";
7
7
  import { CliProgram } from "../types.ts";
8
- import { setCompiledExecutableOverride } from "../install/compiled.ts";
9
8
 
10
9
  const fixture: CliProgram = {
11
10
  key: "myapp",
11
+ version: "0.0.0",
12
12
  description: "Demo app.",
13
- mcpServer: { name: "myapp" },
13
+ mcpServer: { enabled: true },
14
14
  commands: [
15
15
  {
16
16
  key: "hello",
@@ -20,16 +20,11 @@ const fixture: CliProgram = {
20
20
  ],
21
21
  };
22
22
 
23
- afterEach(() => {
24
- setCompiledExecutableOverride(null);
25
- });
26
-
27
23
  describe("builtins help copy", () => {
28
- test("install command includes description and option text when compiled", () => {
29
- setCompiledExecutableOverride(true);
24
+ test("install command includes description and option text", () => {
30
25
  const install = cliBuiltinInstallCommand(fixture);
31
26
  expect(install.description).toContain("Install the binary");
32
- expect(install.notes).toContain("bun build --compile");
27
+ expect(install.notes).toContain("install --all");
33
28
  const names = installBuiltinOptions(fixture).map((o) => o.name);
34
29
  expect(names).toContain("all");
35
30
  expect(names).toContain("mcp");
@@ -37,8 +32,7 @@ describe("builtins help copy", () => {
37
32
  });
38
33
 
39
34
  test("install omits --mcp option when mcpServer unset", () => {
40
- setCompiledExecutableOverride(true);
41
- const noMcp: CliProgram = { key: "x", description: "x", handler: () => {} };
35
+ const noMcp: CliProgram = { key: "x", version: "0.0.0", description: "x", handler: () => {} };
42
36
  const names = installBuiltinOptions(noMcp).map((o) => o.name);
43
37
  expect(names).not.toContain("mcp");
44
38
  });
@@ -51,23 +45,26 @@ describe("builtins help copy", () => {
51
45
  });
52
46
 
53
47
  describe("presentation root", () => {
54
- test("includes mcp when mcpServer set", () => {
55
- setCompiledExecutableOverride(false);
48
+ test("includes mcp and install when enabled", () => {
56
49
  const root = cliPresentationRoot(fixture);
57
- expect(root.commands?.map((c) => c.key)).toContain("mcp");
58
- expect(root.commands?.map((c) => c.key)).not.toContain("install");
50
+ const keys = root.commands?.map((c) => c.key) ?? [];
51
+ expect(keys).toContain("mcp");
52
+ expect(keys).toContain("install");
59
53
  });
60
54
 
61
- test("includes install when compiled", () => {
62
- setCompiledExecutableOverride(true);
55
+ test("omits install when install.enabled is false", () => {
56
+ const disabled: CliProgram = { ...fixture, install: { enabled: false } };
57
+ const root = cliPresentationRoot(disabled);
58
+ expect(root.commands?.map((c) => c.key)).not.toContain("install");
59
+ });
60
+ test("includes version builtin", () => {
63
61
  const root = cliPresentationRoot(fixture);
64
- expect(root.commands?.map((c) => c.key)).toContain("install");
62
+ expect(root.commands?.map((c) => c.key)).toContain("version");
65
63
  });
66
64
  });
67
65
 
68
66
  describe("completion emitters", () => {
69
67
  test("fish script references app key and subcommands", () => {
70
- setCompiledExecutableOverride(true);
71
68
  const schema = cliPresentationRoot(fixture);
72
69
  const fish = completionFishScript(schema);
73
70
  expect(fish).toContain("complete -c myapp");
@@ -75,8 +72,7 @@ describe("completion emitters", () => {
75
72
  expect(fish).toContain("install");
76
73
  });
77
74
 
78
- test("bash script includes install flags when compiled", () => {
79
- setCompiledExecutableOverride(true);
75
+ test("bash script includes install flags", () => {
80
76
  const schema = cliPresentationRoot(fixture);
81
77
  const bash = completionBashScript(schema);
82
78
  expect(bash).toContain("--all");
@@ -84,7 +80,7 @@ describe("completion emitters", () => {
84
80
  });
85
81
 
86
82
  test("zsh script registers compdef", () => {
87
- const schema = cliPresentationRoot({ key: "zapp", description: "z", handler: () => {} });
83
+ const schema = cliPresentationRoot({ key: "zapp", version: "0.0.0", description: "z", handler: () => {} });
88
84
  const zsh = completionZshScript(schema);
89
85
  expect(zsh).toContain("#compdef zapp");
90
86
  expect(zsh).toContain("compdef _zapp zapp");
@@ -92,8 +88,7 @@ describe("completion emitters", () => {
92
88
  });
93
89
 
94
90
  describe("schema export builtins", () => {
95
- test("exportPresentationBuiltins includes install options when compiled", () => {
96
- setCompiledExecutableOverride(true);
91
+ test("exportPresentationBuiltins includes install options", () => {
97
92
  const builtins = exportPresentationBuiltins(fixture);
98
93
  const install = builtins.find((b) => b.key === "install");
99
94
  expect(install?.options?.find((o) => o.name === "all")?.description).toContain("binary");
@@ -6,11 +6,11 @@ import { completionFishScript } from "./completion-fish.ts";
6
6
  import { completionZshScript } from "./completion-zsh.ts";
7
7
  import { cliBuiltinInstallCommand } from "./install.ts";
8
8
  import { cliBuiltinMcpCommand } from "./mcp.ts";
9
+ import { cliBuiltinVersionCommand } from "./version.ts";
9
10
  import { cliBuiltinCompletionGroup as completionGroup } from "./completion-group.ts";
10
11
  import { cliPresentationRoot } from "./presentation.ts";
11
12
  import { cliMcpServeStdio } from "../mcp.ts";
12
13
  import { cliInstall } from "../install/index.ts";
13
- import { isCompiledExecutable } from "../install/compiled.ts";
14
14
  import type { ParseResult } from "../parse.ts";
15
15
  import { ParseKind } from "../parse.ts";
16
16
 
@@ -57,9 +57,18 @@ export async function dispatchBuiltin(
57
57
  return;
58
58
  }
59
59
 
60
+ if (pr.path[0] === "version") {
61
+ if (pr.path.length !== 1) {
62
+ process.stderr.write("Unknown subcommand: version " + pr.path.slice(1).join(" ") + "\n");
63
+ process.exit(1);
64
+ }
65
+ process.stdout.write(program.version + "\n");
66
+ process.exit(0);
67
+ }
68
+
60
69
  if (pr.path[0] === "mcp") {
61
70
  if (!caps.mcp) {
62
- process.stderr.write("MCP is not enabled. Set mcpServer on the program root.\n");
71
+ process.stderr.write("MCP is not enabled. Set mcpServer: { enabled: true } on the program root.\n");
63
72
  process.exit(1);
64
73
  }
65
74
  if (pr.path.length !== 1) {
@@ -71,12 +80,6 @@ export async function dispatchBuiltin(
71
80
  }
72
81
 
73
82
  if (pr.path[0] === "install") {
74
- if (!isCompiledExecutable()) {
75
- process.stderr.write(
76
- "install is only available in compiled binaries (bun build --compile).\n",
77
- );
78
- process.exit(1);
79
- }
80
83
  if (!caps.install) {
81
84
  process.stderr.write("install is disabled. Remove install.enabled: false from the program root.\n");
82
85
  process.exit(1);
@@ -134,5 +137,16 @@ export function builtinInterceptRoot(
134
137
  };
135
138
  }
136
139
 
140
+ if (first === "version") {
141
+ return {
142
+ parseRoot: {
143
+ key: program.key,
144
+ description: program.description,
145
+ commands: [cliBuiltinVersionCommand()],
146
+ },
147
+ isLeafCompletionIntercept: false,
148
+ };
149
+ }
150
+
137
151
  return { parseRoot: program, isLeafCompletionIntercept: false };
138
152
  }
@@ -3,6 +3,7 @@ import type { CliFallbackMode, CliOption, CliPositional, CliProgram } from "../t
3
3
  import { cliBuiltinCompletionGroup } from "./completion-group.ts";
4
4
  import { cliBuiltinInstallCommand } from "./install.ts";
5
5
  import { cliBuiltinMcpCommand } from "./mcp.ts";
6
+ import { cliBuiltinVersionCommand } from "./version.ts";
6
7
 
7
8
  /** JSON-safe command node (no handlers). */
8
9
  export interface CliSchemaExport {
@@ -42,7 +43,10 @@ function exportBuiltinNode(cmd: {
42
43
  /** Built-in subtrees matching help visibility for `--schema` export. */
43
44
  export function exportPresentationBuiltins(program: CliProgram, caps?: CliCapabilities): CliSchemaExport[] {
44
45
  const resolved = caps ?? resolveCapabilities(program);
45
- const builtins: CliSchemaExport[] = [exportBuiltinNode(cliBuiltinCompletionGroup(program.key))];
46
+ const builtins: CliSchemaExport[] = [
47
+ exportBuiltinNode(cliBuiltinCompletionGroup(program.key)),
48
+ exportBuiltinNode(cliBuiltinVersionCommand()),
49
+ ];
46
50
  if (resolved.install) {
47
51
  builtins.push(exportBuiltinNode(cliBuiltinInstallCommand(program)));
48
52
  }
@@ -1,3 +1,4 @@
1
+ import { resolveCapabilities } from "../capabilities.ts";
1
2
  import { CliProgram, CliOption, CliOptionKind, type CliLeaf } from "../types.ts";
2
3
 
3
4
  /** Install command options (dynamic: `--mcp` only when MCP is enabled). */
@@ -66,7 +67,7 @@ export function installBuiltinOptions(root: CliProgram): CliOption[] {
66
67
  },
67
68
  ];
68
69
 
69
- if (root.mcpServer !== undefined) {
70
+ if (resolveCapabilities(root).mcp) {
70
71
  opts.splice(4, 0, {
71
72
  name: "mcp",
72
73
  description: "Add or update MCP server entries in Cursor and Claude config files.",
@@ -77,13 +78,12 @@ export function installBuiltinOptions(root: CliProgram): CliOption[] {
77
78
  return opts;
78
79
  }
79
80
 
80
- /** Builds the `install` built-in command (compiled binaries only). */
81
+ /** Builds the `install` built-in command. */
81
82
  export function cliBuiltinInstallCommand(root: CliProgram): CliLeaf {
82
83
  return {
83
84
  key: "install",
84
85
  description: "Install the binary, shell completions, agent skills, and MCP config to your user environment.",
85
86
  notes:
86
- "Requires a compiled binary (bun build --compile).\n\n" +
87
87
  "First-time setup:\n" +
88
88
  ` {app} install --all --yes\n\n` +
89
89
  "Refresh after upgrading:\n" +
@@ -5,10 +5,14 @@ import { isCliLeaf, isCliRouter } from "../types.ts";
5
5
  import { cliBuiltinCompletionGroup } from "./completion-group.ts";
6
6
  import { cliBuiltinInstallCommand } from "./install.ts";
7
7
  import { cliBuiltinMcpCommand } from "./mcp.ts";
8
+ import { cliBuiltinVersionCommand } from "./version.ts";
8
9
 
9
10
  /** Built-in command nodes injected for help, schema, and completions. */
10
11
  export function presentationBuiltins(program: CliProgram, caps: CliCapabilities): CliNode[] {
11
- const builtins: CliNode[] = [cliBuiltinCompletionGroup(program.key)];
12
+ const builtins: CliNode[] = [
13
+ cliBuiltinCompletionGroup(program.key),
14
+ cliBuiltinVersionCommand(),
15
+ ];
12
16
  if (caps.install) {
13
17
  builtins.push(cliBuiltinInstallCommand(program));
14
18
  }
@@ -0,0 +1,10 @@
1
+ import { type CliLeaf } from "../types.ts";
2
+
3
+ /** Top-level `version` built-in (leaf). */
4
+ export function cliBuiltinVersionCommand(): CliLeaf {
5
+ return {
6
+ key: "version",
7
+ description: "Print the program version.",
8
+ handler: () => {},
9
+ };
10
+ }
@@ -4,7 +4,6 @@ Not exported from the public package barrel.
4
4
  */
5
5
 
6
6
  import type { CliProgram } from "./types.ts";
7
- import { isCompiledExecutable } from "./install/compiled.ts";
8
7
 
9
8
  /** Platform builtins derived from program config and runtime. */
10
9
  export interface CliCapabilities {
@@ -17,14 +16,17 @@ export interface CliCapabilities {
17
16
  export function resolveCapabilities(program: CliProgram): CliCapabilities {
18
17
  return {
19
18
  completion: true,
20
- mcp: program.mcpServer !== undefined,
21
- install: isCompiledExecutable() && program.install?.enabled !== false,
19
+ mcp: program.mcpServer?.enabled === true,
20
+ install: program.install?.enabled !== false,
22
21
  };
23
22
  }
24
23
 
25
24
  /** Reserved top-level command names for the given capabilities. */
26
25
  export function reservedCommandNames(caps: CliCapabilities): string[] {
27
- const names = ["completion", "install"];
26
+ const names = ["completion", "version"];
27
+ if (caps.install) {
28
+ names.push("install");
29
+ }
28
30
  if (caps.mcp) {
29
31
  names.push("mcp");
30
32
  }