argsbarg 3.1.0 → 3.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/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.2.0] - 2026-06-20
11
+
12
+ ### Added
13
+
14
+ - **`docs schema`** — print the full command tree as JSON (`myapp docs schema`). Replaces root `--schema` (requires `docs.enabled`).
15
+ - **`docs api`** — print the command tree as markdown (`myapp docs api`). Human-readable companion to `docs schema`.
16
+ - **`docs skill`** — print generated Cursor `SKILL.md` content to stdout (`myapp docs skill`).
17
+ - **`update` built-in** — when `install.updateGetLatest` is set, `myapp update` downloads the latest binary and reinstalls installed artifacts.
18
+ - **`install --reinstall`** — replaces `--update` (still accepted as a deprecated alias). Optional `--from <path>` for the binary source.
19
+
20
+ ### Changed
21
+
22
+ - **Breaking:** root **`--schema`** removed — use **`docs schema`** when `docs.enabled` is `true`.
23
+ - **Breaking:** **`install --update`** renamed to **`install --reinstall`**.
24
+
10
25
  ## [3.1.0] - 2026-06-20
11
26
 
12
27
  ### Added
@@ -208,7 +223,8 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
208
223
  - 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`).
209
224
  - Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
210
225
 
211
- [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.1.0...HEAD
226
+ [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.2.0...HEAD
227
+ [3.2.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.2.0
212
228
  [3.1.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.1.0
213
229
  [3.0.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.0.0
214
230
  [2.1.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v2.1.1
package/README.md CHANGED
@@ -95,22 +95,20 @@ Everything you need for a first-class CLI:
95
95
  Every app gets:
96
96
 
97
97
  - `-h` / `--help` at any routing depth (scoped help).
98
- - **`--schema`** at the program root — print the full command tree as JSON (for tooling and agents).
99
98
  - **`completion bash` / `completion zsh` / `completion fish`** — print shell completion scripts to stdout (injected by `cliRun`).
100
99
  - **`version`** — print `CliProgram.version` (`myapp version`).
101
100
  - **`mcp`** — when `mcpServer.enabled` is `true`, run as an MCP stdio server (`myapp mcp`).
102
- - **`docs`** — when `docs.enabled` is `true`, print bundled markdown topics (`myapp docs`, `myapp docs readme`, …). See [docs/bundled-docs.md](docs/bundled-docs.md).
101
+ - **`docs`** — when `docs.enabled` is `true`, print bundled markdown topics, schema JSON, API markdown, and generated skill content (`myapp docs`, `myapp docs readme`, `myapp docs schema`, `myapp docs api`, `myapp docs skill`, …). See [docs/bundled-docs.md](docs/bundled-docs.md).
103
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).
104
103
 
105
104
  Do not declare a top-level command named **`completion`**, **`version`**, or **`install`** — they are reserved.
106
105
  When **`mcpServer.enabled`** is `true`, do not declare a top-level command named **`mcp`** — it is reserved for the MCP built-in.
107
106
  When **`docs.enabled`** is `true`, do not declare a top-level command named **`docs`** — it is reserved for the docs built-in.
108
- Do not declare an option named **`schema`** — it is reserved for `--schema`.
109
107
 
110
108
 
111
109
  ### MCP (AI agents)
112
110
 
113
- 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.
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 docs schema`). Handlers can read `ctx.invocation` and use `cliInvoke` for headless testing.
114
112
 
115
113
  See **[docs/mcp.md](docs/mcp.md)** for configuration, env bootstrapping, custom resources, Cursor setup, and protocol details.
116
114
 
@@ -124,7 +122,7 @@ myapp install --all --yes
124
122
 
125
123
  This copies the binary to `~/.local/bin`, installs shell completions (bash/zsh/fish when each shell is on PATH), writes Cursor/Claude skills when agent directories exist, and merges MCP server entries into Cursor and Claude config files.
126
124
 
127
- See **[docs/install.md](docs/install.md)** for `--update`, `--status`, `--uninstall`, and flags.
125
+ See **[docs/install.md](docs/install.md)** for `--reinstall`, `update`, `--status`, `--uninstall`, and flags.
128
126
 
129
127
 
130
128
  ### Shell completions
@@ -189,7 +187,7 @@ Add `CliPositional` entries to the command’s `positionals` list (separate from
189
187
 
190
188
  ### Capabilities (built-ins)
191
189
 
192
- `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`.
190
+ `completion`, `version`, `install`, `update`, and `mcp` are not part of your schema — they are injected at runtime from program-level config (`mcpServer`, `install`, `docs`). Reserved command names: `completion` and `version` always; `install` unless `install.enabled: false`; `update` when `install.updateGetLatest` is set; `mcp` when `mcpServer.enabled` is `true`; `docs` when `docs.enabled` is `true`.
193
191
 
194
192
 
195
193
 
package/docs/ai-skills.md CHANGED
@@ -32,7 +32,7 @@ For library use, call `cliSkillInstall(root, "cursor" | "claude", { global: true
32
32
  ## Generated content
33
33
 
34
34
  - **`SKILL.md`** — YAML frontmatter, when-to-use guidance, shell command catalog, pitfalls
35
- - **`reference.md`** — full `--schema` JSON export
35
+ - **`reference.md`** — full `docs schema` JSON export
36
36
 
37
37
  Skills describe **shell invocation only** — no MCP setup, `mcp.json`, or `tools/call` guidance. Use **`myapp docs mcp`** (when `docs` and `mcpServer` are enabled) or connect the MCP server for agent execution.
38
38
 
@@ -27,6 +27,9 @@ const cli = {
27
27
  myapp docs # first topic (readme) via fallback
28
28
  myapp docs readme
29
29
  myapp docs architecture
30
+ myapp docs schema # full command tree as JSON
31
+ myapp docs api # command tree as markdown
32
+ myapp docs skill # generated Cursor SKILL.md
30
33
  myapp docs all # all user topics; includes auto mcp when MCP enabled
31
34
  myapp docs mcp # auto-generated when mcpServer.enabled
32
35
  ```
@@ -40,7 +43,7 @@ myapp docs mcp # auto-generated when mcpServer.enabled
40
43
  | `defaultTopic` | first key in `topics` | `fallbackCommand` for bare `myapp docs` |
41
44
  | `topics` | *(required)* | Topic key → `{ text, description? }` |
42
45
 
43
- Reserved topic keys in `topics`: **`mcp`**, **`all`** (supplied by the built-in).
46
+ Reserved topic keys in `topics`: **`mcp`**, **`all`**, **`schema`**, **`api`**, **`skill`** (supplied by the built-in).
44
47
 
45
48
  When `description` is omitted on a topic, ArgsBarg generates leaf help (`readme` → "Print README (user guide).").
46
49
 
@@ -56,6 +59,14 @@ Bun embeds the file when you `bun build --compile`. ArgsBarg does not read the f
56
59
 
57
60
  For several topics, use a barrel file (e.g. `src/docs/topics.ts`) so `index.tsx` stays small.
58
61
 
62
+ ## Schema, API, and skill (`docs schema`, `docs api`, `docs skill`)
63
+
64
+ When `docs.enabled` is `true`:
65
+
66
+ - **`docs schema`** — same JSON as the former root `--schema` flag (handlers omitted; built-in subtrees included for leaf roots).
67
+ - **`docs api`** — markdown rendering of the same command tree (options, positionals, subcommands, fallback routing).
68
+ - **`docs skill`** — prints generated Cursor `SKILL.md` content (same prose as `install --skill`, without writing files).
69
+
59
70
  ## MCP guide (`docs mcp`)
60
71
 
61
72
  When both `docs.enabled` and `mcpServer.enabled` are `true`, ArgsBarg injects a **`docs mcp`** topic with an auto-generated guide: tool list, `requiresEnv`, schema resource URI, `install --mcp`, and protocol notes.
@@ -70,8 +81,11 @@ All `docs` subcommands are hidden from MCP `tools/list` (`mcpTool: { enabled: fa
70
81
 
71
82
  | Channel | Role |
72
83
  | --- | --- |
73
- | `install --skill` | Shell command catalog + `--schema` JSON (no MCP setup) |
74
- | `docs` | Bundled markdown on stdout |
84
+ | `install --skill` | Writes shell command catalog + `reference.md` to disk |
85
+ | `docs skill` | Print generated `SKILL.md` to stdout |
86
+ | `docs api` | Print command tree markdown to stdout |
87
+ | `docs schema` | Print command tree JSON to stdout |
88
+ | `docs` | Bundled markdown topics on stdout |
75
89
  | `mcp` | Callable tools + schema resource |
76
90
 
77
91
  Do not declare a top-level command named **`docs`** when `docs.enabled` is `true` — it is reserved.
package/docs/install.md CHANGED
@@ -8,8 +8,11 @@ The `install` built-in installs the binary, shell completions, agent skills, and
8
8
  # First-time setup
9
9
  myapp install --all --yes
10
10
 
11
- # Refresh after upgrading
12
- myapp install --update
11
+ # Refresh after upgrading (re-copy running binary + refresh installed artifacts)
12
+ myapp install --reinstall
13
+
14
+ # Download latest release (when install.updateGetLatest is configured)
15
+ myapp update
13
16
 
14
17
  # See what is installed
15
18
  myapp install --status
@@ -42,9 +45,15 @@ On the program root:
42
45
  install: {
43
46
  enabled: false, // opt out of the install built-in
44
47
  prefix: "~/.local/bin", // default bin directory
48
+ updateGetLatest: async ({ version }) => {
49
+ // download or locate latest binary; return { path, version, cleanup }
50
+ return { path: "/tmp/myapp", version: "2.0.0" };
51
+ },
45
52
  }
46
53
  ```
47
54
 
55
+ When `updateGetLatest` is set, ArgsBarg also registers the **`update`** built-in (`myapp update`).
56
+
48
57
  Environment:
49
58
 
50
59
  - `INSTALL_PREFIX` — same as `install.prefix` / `--prefix`
@@ -53,15 +62,18 @@ Environment:
53
62
 
54
63
  | Flag | Description |
55
64
  | --- | --- |
56
- | `--yes` | Skip confirmation (required for non-TTY unless `--json` / `--update`) |
65
+ | `--yes` | Skip confirmation (required for non-TTY unless `--json` / `--reinstall`) |
57
66
  | `--dry` | Preview changes; per-step messages on stderr with `[dry run]` |
58
67
  | `--json` | Machine-readable output on stdout (implies `--yes`) |
59
68
  | `--quiet` | Suppress summaries and per-step messages (requires `--yes`) |
60
69
  | `--prefix <dir>` | Override binary install directory |
61
- | `--update` | Update only artifacts already installed (implies `--bin` + `--yes`) |
70
+ | `--reinstall` | Reinstall artifacts already on disk (implies `--bin` + `--yes`) |
71
+ | `--from <path>` | Binary to copy with `--reinstall` (default: running executable) |
62
72
  | `--status` | Read-only inventory |
63
73
  | `--uninstall` | Remove detected artifacts (scope with `--bin`, `--completions`, `--skill`, `--mcp`) |
64
74
 
75
+ `--update` is accepted as a deprecated alias for `--reinstall`.
76
+
65
77
  ## MCP merge behavior
66
78
 
67
79
  When `--mcp` runs, entries are merged into `mcpServers[<sanitized-key>]` with:
package/docs/mcp.md CHANGED
@@ -113,7 +113,7 @@ Each tool’s `description` includes the human CLI path and the leaf’s help te
113
113
 
114
114
  ### Per-leaf visibility
115
115
 
116
- Set `mcpTool: { enabled: false }` on a **leaf command** to hide it from `tools/list` while keeping it in the CLI and in `--schema` output:
116
+ Set `mcpTool: { enabled: false }` on a **leaf command** to hide it from `tools/list` while keeping it in the CLI and in `docs schema` output:
117
117
 
118
118
  ```typescript
119
119
  {
@@ -172,11 +172,11 @@ On success (`isError: false`):
172
172
 
173
173
  On failure (parse error, validation error, non-zero exit, thrown error), the message is returned as text content with `isError: true`. Handler stderr is included when present.
174
174
 
175
- Help and `--schema` are not available through tool calls; use the schema resource or run the CLI directly for those.
175
+ Help and `docs schema` are not available through tool calls; use the schema resource or run the CLI directly for those.
176
176
 
177
177
  ## Schema and custom resources
178
178
 
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.
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 docs schema`. Override with `schemaResourceUri` if needed.
180
180
 
181
181
  | Property | Value |
182
182
  | --- | --- |
@@ -291,9 +291,7 @@ You should get one JSON line on stdout with `result.capabilities` and `result.se
291
291
 
292
292
  When MCP is enabled:
293
293
 
294
- - Do not declare a top-level command named **`ai`** — it is reserved for the built-in AI integration group.
295
294
  - Do not declare a top-level command named **`completion`** — reserved for shell completions.
296
- - Do not declare an option named **`schema`** — reserved for `--schema`.
297
295
 
298
296
  Running `myapp mcp` without `mcpServer` on the root fails with an error (exit 1).
299
297
 
@@ -304,4 +302,4 @@ Running `myapp mcp` without `mcpServer` on the root fails with an error (exit 1)
304
302
  - **User schema only** — tool dispatch uses your program root, not merged presentation builtins.
305
303
  - **Buffered output** — MCP tool results are sent after the handler finishes. Incremental stdout (log tail, progress) is not streamed; a future release may add MCP progress notifications.
306
304
 
307
- For the `--schema` export used by the resource, see the main README built-ins section.
305
+ For the `docs schema` export used by the resource, see [docs/bundled-docs.md](bundled-docs.md).
@@ -14,6 +14,12 @@ const cli = {
14
14
  key: "minimal.ts",
15
15
  version: pkg.version,
16
16
  description: "Tiny demo.",
17
+ docs: {
18
+ enabled: true,
19
+ topics: {
20
+ readme: { text: "# minimal.ts\n\nTiny demo.\n" },
21
+ },
22
+ },
17
23
  positionals: [
18
24
  {
19
25
  name: "name",
package/index.d.ts CHANGED
@@ -167,11 +167,28 @@ export interface CliMcpToolConfig {
167
167
  /**
168
168
  * Opt-out and defaults for the `install` built-in (program root only).
169
169
  */
170
+ export interface CliUpdateArtifact {
171
+ /** Path to an executable binary to copy into the install location. */
172
+ path: string;
173
+ /** Release version of `path` (used for already-current checks and success messages). */
174
+ version?: string;
175
+ /** Called after reinstall completes (e.g. remove a temp download directory). */
176
+ cleanup?: () => void | Promise<void>;
177
+ }
178
+ /** Fetches the latest release binary for the `update` built-in. */
179
+ export type CliUpdateGetLatest = (ctx: {
180
+ version: string;
181
+ }) => Promise<CliUpdateArtifact>;
170
182
  export interface CliInstallConfig {
171
183
  /** When `false`, hide/disable `install` (default: enabled). */
172
184
  enabled?: boolean;
173
185
  /** Default bin directory (default: `~/.local/bin`). Overridden by `INSTALL_PREFIX` env and `--prefix`. */
174
186
  prefix?: string;
187
+ /**
188
+ * When set, enables the `update` built-in (`myapp update`).
189
+ * Should download or locate the latest release binary and return its path.
190
+ */
191
+ updateGetLatest?: CliUpdateGetLatest;
175
192
  }
176
193
  /**
177
194
  * One bundled documentation topic for the `docs` built-in (program root only).
@@ -265,7 +282,7 @@ export declare class CliSchemaValidationError extends Error {
265
282
  constructor(message: string);
266
283
  }
267
284
  /** Outcome of a non-exiting CLI invocation. */
268
- export type CliInvokeKind = "ok" | "help" | "schema" | "error";
285
+ export type CliInvokeKind = "ok" | "help" | "error";
269
286
  /** Result of cliInvoke: captured output and exit metadata without process.exit. */
270
287
  export interface CliInvokeResult {
271
288
  /** Invocation outcome. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "bun": ">=1.3"
@@ -5,7 +5,6 @@ import {
5
5
  identToken,
6
6
  kHelpLong,
7
7
  kHelpShort,
8
- kSchemaLong,
9
8
  mainName,
10
9
  } from "./shell-helpers.ts";
11
10
 
@@ -17,9 +16,6 @@ function emitConsumeLong(ident: string, scopes: ScopeRec[]): string {
17
16
  o += " " + i + ")\n";
18
17
  o += " case $w in\n";
19
18
  o += " " + kHelpLong + "|${kHelpLong}=*|${kHelpShort}) echo 1 ;;\n".replace(/\$\{kHelpLong\}/g, kHelpLong).replace(/\$\{kHelpShort\}/g, kHelpShort);
20
- if (sc.path === "") {
21
- o += " " + kSchemaLong + ") echo 1 ;;\n";
22
- }
23
19
  for (const op of sc.opts) {
24
20
  const base = "--" + op.name;
25
21
  if (op.kind === "presence") {
@@ -107,7 +103,7 @@ function emitSimulate(ident: string): string {
107
103
  o += " local i=1 sid=0 w steps next\n";
108
104
  o += " while (( i < COMP_CWORD )); do\n";
109
105
  o += " w=\"${COMP_WORDS[i]}\"\n";
110
- o += " if [[ $w == " + kHelpShort + " || $w == " + kHelpLong + " || $w == " + kSchemaLong + " ]]; then\n";
106
+ o += " if [[ $w == " + kHelpShort + " || $w == " + kHelpLong + " ]]; then\n";
111
107
  o += " ((i++)); continue\n";
112
108
  o += " fi\n";
113
109
  o += " if [[ $w == --* ]]; then\n";
@@ -209,9 +205,6 @@ export function completionBashScript(schema: CliRouter): string {
209
205
  for (const [i, sc] of scopes.entries()) {
210
206
  out += "A_" + ident + "_" + i + "_opts=()\n";
211
207
  out += "A_" + ident + "_" + i + "_opts+=('" + kHelpLong + "' '" + kHelpShort + "')\n";
212
- if (sc.path === "") {
213
- out += "A_" + ident + "_" + i + "_opts+=('" + kSchemaLong + "')\n";
214
- }
215
208
  for (const o of sc.opts) {
216
209
  out += "A_" + ident + "_" + i + "_opts+=('--" + o.name + "')\n";
217
210
  if (o.shortName) {
@@ -5,8 +5,6 @@ import {
5
5
  identToken,
6
6
  kHelpLong,
7
7
  kHelpShort,
8
- kSchemaDesc,
9
- kSchemaLong,
10
8
  } from "./shell-helpers.ts";
11
9
 
12
10
  function scopeCondition(ident: string, scopeIndex: number, path: string): string {
@@ -43,9 +41,6 @@ export function completionFishScript(schema: CliRouter): string {
43
41
  }
44
42
 
45
43
  out += `complete -c ${app} -n '${cond}' -s h -l help -d '${escFishSingleQuoted("Show help for this command.")}'\n`;
46
- if (sc.path === "") {
47
- out += `complete -c ${app} -n '${cond}' -l schema -d '${escFishSingleQuoted(kSchemaDesc)}'\n`;
48
- }
49
44
 
50
45
  for (const op of sc.opts) {
51
46
  if (op.kind === CliOptionKind.Presence) {
@@ -5,8 +5,6 @@ import {
5
5
  identToken,
6
6
  kHelpLong,
7
7
  kHelpShort,
8
- kSchemaDesc,
9
- kSchemaLong,
10
8
  mainName,
11
9
  } from "./shell-helpers.ts";
12
10
 
@@ -16,9 +14,6 @@ function emitScopeArraysZsh(ident: string, scopes: ScopeRec[]): string {
16
14
  out += "typeset -g A_" + ident + "_" + i + "_opts\n";
17
15
  out += "A_" + ident + "_" + i + "_opts=(";
18
16
  out += "'" + escShellSingleQuoted(kHelpLong) + ":" + escShellSingleQuoted("Show help for this command.") + "' '" + escShellSingleQuoted(kHelpShort) + ":" + escShellSingleQuoted("Show help for this command.") + "'";
19
- if (sc.path === "") {
20
- out += " '" + escShellSingleQuoted(kSchemaLong) + ":" + escShellSingleQuoted(kSchemaDesc) + "'";
21
- }
22
17
  for (const o of sc.opts) {
23
18
  out += " '" + escShellSingleQuoted("--" + o.name) + ":" + escShellSingleQuoted(o.description) + "'";
24
19
  if (o.shortName) {
@@ -47,9 +42,6 @@ function emitConsumeLongZsh(ident: string, scopes: ScopeRec[]): string {
47
42
  o += " " + i + ")\n";
48
43
  o += " case $w in\n";
49
44
  o += " " + kHelpLong + "|${kHelpLong}=*|${kHelpShort}) echo 1 ;;\n".replace(/\$\{kHelpLong\}/g, kHelpLong).replace(/\$\{kHelpShort\}/g, kHelpShort);
50
- if (sc.path === "") {
51
- o += " " + kSchemaLong + ") echo 1 ;;\n";
52
- }
53
45
  for (const op of sc.opts) {
54
46
  const base = "--" + op.name;
55
47
  if (op.kind === "presence") {
@@ -137,7 +129,7 @@ function emitSimulateZsh(ident: string): string {
137
129
  o += " local i=2 sid=0 w steps next\n";
138
130
  o += " while (( i < CURRENT )); do\n";
139
131
  o += " w=$words[i]\n";
140
- o += " if [[ $w == " + kHelpShort + " || $w == " + kHelpLong + " || $w == " + kSchemaLong + " ]]; then\n";
132
+ o += " if [[ $w == " + kHelpShort + " || $w == " + kHelpLong + " ]]; then\n";
141
133
  o += " ((i++)); continue\n";
142
134
  o += " fi\n";
143
135
  o += " if [[ $w == --* ]]; then\n";
@@ -6,12 +6,14 @@ 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 { cliBuiltinUpdateCommand } from "./update.ts";
9
10
  import { cliBuiltinVersionCommand } from "./version.ts";
10
11
  import { cliBuiltinCompletionGroup as completionGroup } from "./completion-group.ts";
11
12
  import { cliPresentationRoot } from "./presentation.ts";
12
13
  import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
13
14
  import { cliMcpServeStdio } from "../mcp.ts";
14
15
  import { cliInstall } from "../install/index.ts";
16
+ import { cliUpdate } from "../install/update.ts";
15
17
  import type { ParseResult } from "../parse.ts";
16
18
  import { ParseKind } from "../parse.ts";
17
19
 
@@ -80,6 +82,20 @@ export async function dispatchBuiltin(
80
82
  process.exit(0);
81
83
  }
82
84
 
85
+ if (pr.path[0] === "update") {
86
+ if (!caps.update) {
87
+ process.stderr.write(
88
+ "update is not enabled. Set install.updateGetLatest on the program root.\n",
89
+ );
90
+ process.exit(1);
91
+ }
92
+ if (pr.path.length !== 1) {
93
+ process.stderr.write("Unknown subcommand: update " + pr.path.slice(1).join(" ") + "\n");
94
+ process.exit(1);
95
+ }
96
+ await cliUpdate(program);
97
+ }
98
+
83
99
  if (pr.path[0] === "install") {
84
100
  if (!caps.install) {
85
101
  process.stderr.write("install is disabled. Remove install.enabled: false from the program root.\n");
@@ -127,6 +143,17 @@ export function builtinInterceptRoot(
127
143
  };
128
144
  }
129
145
 
146
+ if (first === "update" && caps.update) {
147
+ return {
148
+ parseRoot: {
149
+ key: program.key,
150
+ description: program.description,
151
+ commands: [cliBuiltinUpdateCommand(program)],
152
+ },
153
+ isLeafCompletionIntercept: false,
154
+ };
155
+ }
156
+
130
157
  if (first === "mcp" && caps.mcp) {
131
158
  return {
132
159
  parseRoot: {
@@ -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 { cliBuiltinUpdateCommand } from "./update.ts";
6
7
  import { cliBuiltinVersionCommand } from "./version.ts";
7
8
  import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
8
9
 
@@ -51,6 +52,9 @@ export function exportPresentationBuiltins(program: CliProgram, caps?: CliCapabi
51
52
  if (resolved.install) {
52
53
  builtins.push(exportBuiltinNode(cliBuiltinInstallCommand(program)));
53
54
  }
55
+ if (resolved.update) {
56
+ builtins.push(exportBuiltinNode(cliBuiltinUpdateCommand(program)));
57
+ }
54
58
  const docsGroup = cliBuiltinDocsGroupIfEnabled(program);
55
59
  if (docsGroup) {
56
60
  builtins.push(exportBuiltinNode(docsGroup));
@@ -26,10 +26,15 @@ export function installBuiltinOptions(root: CliProgram): CliOption[] {
26
26
  kind: CliOptionKind.Presence,
27
27
  },
28
28
  {
29
- name: "update",
30
- description: "Update only artifacts already installed (always includes the binary).",
29
+ name: "reinstall",
30
+ description: "Reinstall artifacts already on disk (always includes the binary).",
31
31
  kind: CliOptionKind.Presence,
32
32
  },
33
+ {
34
+ name: "from",
35
+ description: "Binary to copy (default: running executable). Used with --reinstall.",
36
+ kind: CliOptionKind.String,
37
+ },
33
38
  {
34
39
  name: "status",
35
40
  description: "Print what is currently installed (read-only).",
@@ -87,7 +92,8 @@ export function cliBuiltinInstallCommand(root: CliProgram): CliLeaf {
87
92
  "First-time setup:\n" +
88
93
  ` {app} install --all --yes\n\n` +
89
94
  "Refresh after upgrading:\n" +
90
- ` {app} install --update\n\n` +
95
+ ` {app} install --reinstall\n` +
96
+ ` {app} update\n\n` +
91
97
  "See what is installed:\n" +
92
98
  ` {app} install --status\n\n` +
93
99
  "Remove everything:\n" +
@@ -5,6 +5,7 @@ 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 { cliBuiltinUpdateCommand } from "./update.ts";
8
9
  import { cliBuiltinVersionCommand } from "./version.ts";
9
10
  import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
10
11
 
@@ -17,6 +18,9 @@ export function presentationBuiltins(program: CliProgram, caps: CliCapabilities)
17
18
  if (caps.install) {
18
19
  builtins.push(cliBuiltinInstallCommand(program));
19
20
  }
21
+ if (caps.update) {
22
+ builtins.push(cliBuiltinUpdateCommand(program));
23
+ }
20
24
  const docsGroup = cliBuiltinDocsGroupIfEnabled(program);
21
25
  if (docsGroup) {
22
26
  builtins.push(docsGroup);
@@ -20,5 +20,3 @@ export function mainName(schemaName: string): string {
20
20
 
21
21
  export const kHelpLong = "--help";
22
22
  export const kHelpShort = "-h";
23
- export const kSchemaLong = "--schema";
24
- export const kSchemaDesc = "Print the full command tree as JSON.";
@@ -0,0 +1,14 @@
1
+ import type { CliLeaf, CliProgram } from "../types.ts";
2
+ import { cliUpdate } from "../install/update.ts";
3
+
4
+ /** Built-in `update` command (enabled when `install.updateGetLatest` is set). */
5
+ export function cliBuiltinUpdateCommand(program: CliProgram): CliLeaf {
6
+ return {
7
+ key: "update",
8
+ description: "Download and install the latest release.",
9
+ mcpTool: { enabled: false },
10
+ handler: async () => {
11
+ await cliUpdate(program);
12
+ },
13
+ };
14
+ }
@@ -11,15 +11,18 @@ export interface CliCapabilities {
11
11
  mcp: boolean;
12
12
  install: boolean;
13
13
  docs: boolean;
14
+ update: boolean;
14
15
  }
15
16
 
16
17
  /** Resolves which capabilities are enabled for a program. */
17
18
  export function resolveCapabilities(program: CliProgram): CliCapabilities {
19
+ const install = program.install?.enabled !== false;
18
20
  return {
19
21
  completion: true,
20
22
  mcp: program.mcpServer?.enabled === true,
21
- install: program.install?.enabled !== false,
23
+ install,
22
24
  docs: program.docs?.enabled === true,
25
+ update: install && typeof program.install?.updateGetLatest === "function",
23
26
  };
24
27
  }
25
28
 
@@ -29,6 +32,9 @@ export function reservedCommandNames(caps: CliCapabilities): string[] {
29
32
  if (caps.install) {
30
33
  names.push("install");
31
34
  }
35
+ if (caps.update) {
36
+ names.push("update");
37
+ }
32
38
  if (caps.docs) {
33
39
  names.push("docs");
34
40
  }
@@ -0,0 +1,55 @@
1
+ import { expect, test } from "bun:test";
2
+ import type { CliProgram } from "../types.ts";
3
+ import { CliOptionKind } from "../types.ts";
4
+ import { generateApiGuide } from "./api-guide.ts";
5
+ import { cliSchemaExport } from "../schema.ts";
6
+
7
+ const nestedFixture: CliProgram = {
8
+ key: "nested.ts",
9
+ version: "1.0.0",
10
+ description: "Nested groups demo.",
11
+ docs: { enabled: true, topics: { readme: { text: "# readme\n" } } },
12
+ commands: [
13
+ {
14
+ key: "stat",
15
+ description: "File metadata.",
16
+ commands: [
17
+ {
18
+ key: "owner",
19
+ description: "Ownership helpers.",
20
+ commands: [
21
+ {
22
+ key: "lookup",
23
+ description: "Resolve owner info.",
24
+ options: [
25
+ {
26
+ name: "user-name",
27
+ description: "User to look up.",
28
+ kind: CliOptionKind.String,
29
+ shortName: "u",
30
+ },
31
+ ],
32
+ positionals: [
33
+ {
34
+ name: "path",
35
+ description: "File or directory.",
36
+ kind: CliOptionKind.String,
37
+ },
38
+ ],
39
+ handler: () => {},
40
+ },
41
+ ],
42
+ },
43
+ ],
44
+ },
45
+ ],
46
+ };
47
+
48
+ test("generateApiGuide covers the same command keys as cliSchemaExport", () => {
49
+ const md = generateApiGuide(nestedFixture);
50
+ const schema = cliSchemaExport(nestedFixture);
51
+ expect(md).toContain("`nested.ts stat owner lookup`");
52
+ expect(md).toContain("`--user-name` (`-u`)");
53
+ expect(md).toContain("`<path>`");
54
+ expect(schema.commands?.map((c) => c.key)).toEqual(["stat"]);
55
+ });