argsbarg 5.0.2 → 5.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 (46) hide show
  1. package/CHANGELOG.md +16 -2
  2. package/docs/cli-program.md +4 -4
  3. package/docs/config-schema.md +9 -9
  4. package/docs/configure.md +4 -0
  5. package/docs/mcp.md +1 -1
  6. package/examples/full-example/README.md +2 -2
  7. package/examples/full-example/src/program.ts +0 -3
  8. package/index.d.ts +12 -7
  9. package/package.json +1 -1
  10. package/src/builtins/builtins.test.ts +9 -14
  11. package/src/builtins/config.test.ts +4 -4
  12. package/src/builtins/config.ts +19 -22
  13. package/src/builtins/configure.ts +35 -4
  14. package/src/builtins/dispatch.ts +10 -15
  15. package/src/builtins/registry.ts +0 -2
  16. package/src/capabilities.ts +6 -12
  17. package/src/cli-tool/cli-smoke.test.ts +0 -3
  18. package/src/cli-tool/create.test.ts +0 -3
  19. package/src/cli-tool/full-example-capabilities.test.ts +0 -2
  20. package/src/config/context.test.ts +0 -1
  21. package/src/config/entry.ts +2 -2
  22. package/src/config/file.test.ts +0 -5
  23. package/src/config/resolve.test.ts +0 -7
  24. package/src/config/resolve.ts +2 -2
  25. package/src/config/validate.test.ts +0 -6
  26. package/src/configure/configure.test.ts +0 -11
  27. package/src/docs/api-guide.test.ts +0 -4
  28. package/src/docs/docs.test.ts +1 -21
  29. package/src/docs/mcp-resources.test.ts +0 -6
  30. package/src/formats.test.ts +0 -5
  31. package/src/headless.test.ts +0 -4
  32. package/src/headless.ts +5 -4
  33. package/src/hidden-mcpb.test.ts +0 -12
  34. package/src/install/binary-placement.test.ts +0 -5
  35. package/src/install/gh-release-update.test.ts +0 -5
  36. package/src/install/install-validate.test.ts +0 -5
  37. package/src/install/mcp-opencode.test.ts +0 -4
  38. package/src/install/targets.test.ts +0 -10
  39. package/src/invoke.test.ts +0 -7
  40. package/src/mcp/claude.test.ts +0 -4
  41. package/src/mcp/env.test.ts +0 -5
  42. package/src/mcp/zip.test.ts +0 -1
  43. package/src/mcp.integration.test.ts +0 -22
  44. package/src/parse.test.ts +0 -28
  45. package/src/schema.ts +1 -1
  46. package/src/types.ts +2 -2
package/CHANGELOG.md CHANGED
@@ -7,11 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [5.1.0] - 2026-07-04
11
+
12
+ ### Changed
13
+
14
+ - **Breaking: `config get`/`set` → `configure get`/`set`** — top-level `config` removed; app config introspection lives under the `configure` built-in.
15
+
16
+ ## [5.0.3] - 2026-07-04
17
+
18
+ ### Changed
19
+
20
+ - **JSDoc style** — short `test()` callbacks no longer carry redundant one-liners; `describe` blocks still documented. `requireYesInNonTty` uses per-parameter JSDoc instead of `@param`. Pure re-export barrels stay comment-free.
21
+
10
22
  ## [5.0.2] - 2026-07-04
11
23
 
12
24
  ### Changed
13
25
 
14
- - **JSDoc style** — every `describe` and `test` block in `src/**/*.test.ts` now has a human-readable JSDoc; `configure` modules brought in line with file headers and symbol docs.
26
+ - **JSDoc style** — every `describe` block in `src/**/*.test.ts` now has a human-readable JSDoc; `configure` modules brought in line with file headers and symbol docs.
15
27
 
16
28
  ## [5.0.1] - 2026-07-04
17
29
 
@@ -573,7 +585,9 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
573
585
  - 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`).
574
586
  - Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
575
587
 
576
- [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v5.0.2...HEAD
588
+ [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v5.1.0...HEAD
589
+ [5.1.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.0
590
+ [5.0.3]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.0.3
577
591
  [5.0.2]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.0.2
578
592
  [5.0.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.0.1
579
593
  [5.0.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.0.0
@@ -412,11 +412,11 @@ await cli.run();
412
412
 
413
413
  | Field | Default | Purpose |
414
414
  | --- | --- | --- |
415
- | `description` | *(required)* | Shown in prompts, `config get`, and bundle manifests |
415
+ | `description` | *(required)* | Shown in prompts, `configure get`, and bundle manifests |
416
416
  | `title` | config key | Short label in interactive `configure` |
417
417
  | `default` | — | Used when `jsonSchema` omitted (all-string mode) |
418
418
  | `required` | `true` | When `false`, optional unless required by `jsonSchema` |
419
- | `sensitive` | name heuristic (`token`, `secret`, …) | Redact in prompts, `config get`, and status |
419
+ | `sensitive` | name heuristic (`token`, `secret`, …) | Redact in prompts, `configure get`, and status |
420
420
  | `env` | — | When set: non-empty host env overrides file; consulted again after `resolve` when `resolve` returns `undefined`; exported to `process.env` after resolve |
421
421
  | `resolve` | — | Optional fallback after file; return `undefined` to fall back to `env` (if set) and defaults |
422
422
 
@@ -425,7 +425,7 @@ await cli.run();
425
425
  - Default: `$XDG_CONFIG_HOME/<sanitized-key>/config` or `%APPDATA%/<key>/config`.
426
426
  - JSON: flat object keyed by schema names — `{ "apiToken": "…", "maxRetries": 5 }`.
427
427
  - **Strict:** unknown keys rejected on load.
428
- - **CLI:** missing required config exits 1 before the leaf handler (TTY prompt when interactive). Built-in `docs` and `config get`/`set` skip this exit.
428
+ - **CLI:** missing required config exits 1 before the leaf handler (TTY prompt when interactive). Built-in `docs` and `configure get`/`set` skip this exit.
429
429
  - **MCP:** server stays up; missing config returns `isError: true` at `tools/call`.
430
430
  - **Configure:** interactive `configure` runs the app config wizard; **`configure --sync`** refreshes agent artifacts.
431
431
  - **Agent integration:** `configure.agentIntegration` (`mcp` | `skill` | `both`) sets default sync targets; see [configure.md](configure.md#configuretargets).
@@ -436,7 +436,7 @@ See [config-schema.md](config-schema.md) for codegen, [configure.md](configure.m
436
436
 
437
437
  ## Reserved names
438
438
 
439
- Do not declare user commands named `completion`, `configure`, `mcp`, `version`, `docs`, or `config` at the root — ArgsBarg injects these when configured.
439
+ Do not declare user commands named `completion`, `configure`, `mcp`, `version`, or `docs` at the root — ArgsBarg injects these when configured. App config uses `configure get` / `configure set` subcommands (not a top-level `config` command).
440
440
 
441
441
  ## Cursor rule for consumer repos
442
442
 
@@ -40,7 +40,7 @@ await cli.run();
40
40
  | --- | --- |
41
41
  | Config file | Flat JSON keyed by schema names; strict load (unknown keys rejected) |
42
42
  | Interactive `configure` / `--status` | Auto-runs config wizard when `entries` is non-empty; `--status` for read-only inventory |
43
- | Built-in `config get` / `config set` | Read/write resolved values (opt-out via `commands: false`) |
43
+ | Built-in `configure get` / `configure set` | Read/write resolved values (opt-out via `commands: false`) |
44
44
  | MCP bundle / Claude plugin | `userConfig` for entries with `env` set |
45
45
  | `ctx.appConfig` in handlers | `get`, `require`, `set`, `read`, `path`, `dir` — prefer over `process.env` |
46
46
 
@@ -48,7 +48,7 @@ await cli.run();
48
48
 
49
49
  **No public config I/O exports** — consumers use `program.appConfig` for authoring and `ctx.appConfig` in handlers.
50
50
 
51
- See [cli-program.md — Configuration](cli-program.md#configuration-programappconfig) for resolution order, bootstrap timing, and reserved `config` command.
51
+ See [cli-program.md — Configuration](cli-program.md#configuration-programappconfig) for resolution order, bootstrap timing, and `configure get`/`set`.
52
52
 
53
53
  ## `CliAppConfig` and `CliAppConfigEntry`
54
54
 
@@ -191,26 +191,26 @@ appConfig: {
191
191
 
192
192
  All file values are strings. Defaults come from `entry.default`.
193
193
 
194
- ## Built-in `config` command
194
+ ## Built-in `configure get` / `configure set`
195
195
 
196
196
  When `program.appConfig` is set and `commands !== false`:
197
197
 
198
198
  | Subcommand | Purpose |
199
199
  | --- | --- |
200
- | `config get [key]` | Resolved value(s); `--json`; `--json --pretty` |
201
- | `config set <key> <value>` | One key; full document re-validated after merge |
200
+ | `configure get [key]` | Resolved value(s); `--json`; `--json --pretty` |
201
+ | `configure set <key> <value>` | One key; full document re-validated after merge |
202
202
 
203
- `config get`/`set` skip required-config exit and TTY prompts. Sensitive values redact on `get` (`REDACTED` / `{ "set": true }` with `--json`).
203
+ `configure get`/`set` skip required-config exit and TTY prompts. Sensitive values redact on `get` (`REDACTED` / `{ "set": true }` with `--json`).
204
204
 
205
- Object/array/`$ref` properties require `--json` on `config set`.
205
+ Object/array/`$ref` properties require `--json` on `configure set`.
206
206
 
207
207
  ## Example in this repo
208
208
 
209
209
  | Example | Role |
210
210
  | --- | --- |
211
- | [`examples/full-example/`](../examples/full-example/) | **Copy template** — schemagen discovery, `APP_CONFIG_JSON_SCHEMA` bridge, `program.appConfig`, built-in `config get`/`set` |
211
+ | [`examples/full-example/`](../examples/full-example/) | **Copy template** — schemagen discovery, `APP_CONFIG_JSON_SCHEMA` bridge, `program.appConfig`, built-in `configure get`/`set` |
212
212
 
213
213
  ```bash
214
214
  cd examples/full-example && just setup && just schemagen
215
- FULL_EXAMPLE_API_TOKEN=dev just run config get apiToken --json
215
+ FULL_EXAMPLE_API_TOKEN=dev just run configure get apiToken --json
216
216
  ```
package/docs/configure.md CHANGED
@@ -47,6 +47,10 @@ Dev flow matches release: formula `install` copies the binary and generates comp
47
47
 
48
48
  # Remove app config only (not skills/MCP)
49
49
  <key> configure --remove-config --yes
50
+
51
+ # Read or write app config (non-interactive; when program.appConfig is set)
52
+ <key> configure get [key] [--json] [--pretty]
53
+ <key> configure set <key> <value> [--json]
50
54
  ```
51
55
 
52
56
  Non-interactive / CI: pass **`--yes`** (or **`--json`**, **`--sync`**, **`--remove-all`**, **`--remove-config`**) — see [Confirmation](#confirmation).
package/docs/mcp.md CHANGED
@@ -327,7 +327,7 @@ At server start (`Cli.serveMcp()`), before the NDJSON loop:
327
327
  - Loaded at MCP startup; host `process.env` wins for mapped env vars already set.
328
328
  - Missing required config does **not** exit the MCP server — enforced at `tools/call` with a helpful error.
329
329
  - Configure interactively: `myapp configure` (see [configure.md](configure.md)).
330
- - Built-in `config get` / `config set` when `program.appConfig.commands` is enabled (default). Hosts inject `user_config` → env at spawn; they never write the argsbarg config file.
330
+ - Built-in `configure get` / `configure set` when `program.appConfig.commands` is enabled (default). Hosts inject `user_config` → env at spawn; they never write the argsbarg config file.
331
331
 
332
332
  Example:
333
333
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  | Area | Files / wiring |
8
8
  | --- | --- |
9
- | All builtins | `completion`, `version`, `install`, `docs`, `mcp`, `config get`/`set` |
9
+ | All builtins | `completion`, `version`, `configure`, `docs`, `mcp`, `configure get`/`set` |
10
10
  | `program.appConfig` | `src/types.ts` (`AppConfig`) → `schemas/configSchemas.ts` |
11
11
  | `outputSchema` | `src/commands/status/types.ts` (`StatusJsonOutput`) → `schemas/outputSchemas.ts` |
12
12
  | Schemagen | `scripts/schemagen.ts` + `scripts/schemagen/discover-schema-roots.ts` |
@@ -47,7 +47,7 @@ cd examples/full-example
47
47
  just setup
48
48
  just schemagen # after changing src/**/types.ts
49
49
  FULL_EXAMPLE_API_TOKEN=dev just run status --json
50
- FULL_EXAMPLE_API_TOKEN=dev just run config get apiToken --json
50
+ FULL_EXAMPLE_API_TOKEN=dev just run configure get apiToken --json
51
51
  FULL_EXAMPLE_API_TOKEN=dev just run docs readme
52
52
  ```
53
53
 
@@ -50,8 +50,5 @@ export const program = {
50
50
  mcpd: true,
51
51
  claudePlugin: true,
52
52
  },
53
- configure: {
54
- // Defaults: agentIntegration picks skill vs MCP for sync; app config is opt-in in interactive configure.
55
- },
56
53
  commands: [echoCommand, statusCommand],
57
54
  } satisfies CliProgram;
package/index.d.ts CHANGED
@@ -281,7 +281,7 @@ export interface CliAppConfigEntry {
281
281
  /** When `false`, optional for bootstrap and MCP enforcement. Default: `true`. */
282
282
  required?: boolean;
283
283
  /**
284
- * Mask stdin during prompts and redact on `config get`.
284
+ * Mask stdin during prompts and redact on `configure get`.
285
285
  * Default: `/key|token|secret|password/i.test(name)`.
286
286
  */
287
287
  sensitive?: boolean;
@@ -297,7 +297,7 @@ export interface CliAppConfigEntry {
297
297
  * App configuration block on the program root ({@link CliProgram.appConfig}).
298
298
  */
299
299
  export interface CliAppConfig {
300
- /** Built-in `config get` / `config set`. Default: enabled when `appConfig` is set. */
300
+ /** Built-in `configure get` / `configure set`. Default: enabled when `appConfig` is set. */
301
301
  commands?: boolean | {
302
302
  enabled?: boolean;
303
303
  mcpSet?: boolean;
@@ -551,11 +551,16 @@ export declare function shouldRunHeadlessWithYes(ctx: HeadlessContext, opts: {
551
551
  hasRequiredArgs: boolean;
552
552
  dryRun?: boolean;
553
553
  }, interactive?: boolean): boolean;
554
- /**
555
- * Exits when non-interactive mode is used without `--yes`.
556
- * @param hint - Command-specific guidance appended to the error
557
- */
558
- export declare function requireYesInNonTty(yes: boolean, hint: string, dryRun?: boolean, interactive?: boolean): void;
554
+ /** Exits when non-interactive mode is used without `--yes`. */
555
+ export declare function requireYesInNonTty(
556
+ /** True when `--yes` was passed on the command line. */
557
+ yes: boolean,
558
+ /** Command-specific guidance appended to the error message. */
559
+ hint: string,
560
+ /** When true, skip the check (dry-run preview). */
561
+ dryRun?: boolean,
562
+ /** Injectable TTY probe for tests. */
563
+ interactive?: boolean): void;
559
564
  /** Prefixes a success message when running in dry-run mode. */
560
565
  export declare function formatDryRunMessage(message: string, dryRun: boolean): string;
561
566
  /** Resolved paths for `mcp bundle`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "5.0.2",
3
+ "version": "5.1.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "bun": ">=1.3"
@@ -37,7 +37,6 @@ const noMcp: CliProgram = {
37
37
 
38
38
  /** Tests for builtins help copy. */
39
39
  describe("builtins help copy", () => {
40
- /** Configure command includes Homebrew-oriented description. */
41
40
  test("configure command includes Homebrew-oriented description", () => {
42
41
  const configure = cliBuiltinConfigureCommand(fixture);
43
42
  expect(configure.description).toContain("agent skills");
@@ -51,7 +50,6 @@ describe("builtins help copy", () => {
51
50
  expect(yesOpt?.shortName).toBe("y");
52
51
  });
53
52
 
54
- /** Configure copy omits MCP when mcpServer unset. */
55
53
  test("configure copy omits MCP when mcpServer unset", () => {
56
54
  const caps = resolveCapabilities(noMcp);
57
55
  expect(configureCommandDescription(noMcp, caps)).toBe(
@@ -79,7 +77,6 @@ describe("builtins help copy", () => {
79
77
  expect(configureBuiltinOptions(withConfig).map((o) => o.name)).toContain("remove-config");
80
78
  });
81
79
 
82
- /** Configure -y parses as --yes. */
83
80
  test("configure -y parses as --yes", () => {
84
81
  const root = cliParseRoot(fixture);
85
82
  const pr = postParseValidate(root, parse(root, ["configure", "-y", "--sync"]));
@@ -90,7 +87,6 @@ describe("builtins help copy", () => {
90
87
  }
91
88
  });
92
89
 
93
- /** Mcp builtin description is user-facing. */
94
90
  test("mcp builtin description is user-facing", () => {
95
91
  const withDocs: CliProgram = {
96
92
  ...fixture,
@@ -105,7 +101,6 @@ describe("builtins help copy", () => {
105
101
 
106
102
  /** Tests for presentation root. */
107
103
  describe("presentation root", () => {
108
- /** Includes mcp and configure when enabled. */
109
104
  test("includes mcp and configure when enabled", () => {
110
105
  const root = cliPresentationRoot(fixture);
111
106
  const keys = root.commands?.map((c) => c.key) ?? [];
@@ -115,14 +110,12 @@ describe("presentation root", () => {
115
110
  expect(keys).not.toContain("install");
116
111
  });
117
112
 
118
- /** Omits configure when configure.enabled is false. */
119
113
  test("omits configure when configure.enabled is false", () => {
120
114
  const disabled: CliProgram = { ...fixture, configure: { enabled: false } };
121
115
  const root = cliPresentationRoot(disabled);
122
116
  expect(root.commands?.map((c) => c.key)).not.toContain("configure");
123
117
  });
124
118
 
125
- /** Includes version builtin. */
126
119
  test("includes version builtin", () => {
127
120
  const root = cliPresentationRoot(fixture);
128
121
  expect(root.commands?.map((c) => c.key)).toContain("version");
@@ -131,7 +124,6 @@ describe("presentation root", () => {
131
124
 
132
125
  /** Tests for completion emitters. */
133
126
  describe("completion emitters", () => {
134
- /** Tests that fish script references app key and subcommands. */
135
127
  test("fish script references app key and subcommands", () => {
136
128
  const schema = cliPresentationRoot(fixture);
137
129
  const fish = completionFishScript(schema);
@@ -140,7 +132,6 @@ describe("completion emitters", () => {
140
132
  expect(fish).toContain("configure");
141
133
  });
142
134
 
143
- /** Tests that bash script includes configure flags. */
144
135
  test("bash script includes configure flags", () => {
145
136
  const schema = cliPresentationRoot(fixture);
146
137
  const bash = completionBashScript(schema);
@@ -148,7 +139,6 @@ describe("completion emitters", () => {
148
139
  expect(bash).toContain("--sync");
149
140
  });
150
141
 
151
- /** Tests that zsh script registers compdef. */
152
142
  test("zsh script registers compdef", () => {
153
143
  const schema = cliPresentationRoot({
154
144
  key: "zapp",
@@ -164,8 +154,8 @@ describe("completion emitters", () => {
164
154
 
165
155
  /** Tests for schema export builtins. */
166
156
  describe("schema export builtins", () => {
167
- /** ExportPresentationBuiltins includes config when appConfig set. */
168
- test("exportPresentationBuiltins includes config when appConfig set", () => {
157
+ /** ExportPresentationBuiltins nests configure get/set when appConfig set. */
158
+ test("exportPresentationBuiltins nests configure get/set when appConfig set", () => {
169
159
  const withConfig: CliProgram = {
170
160
  ...fixture,
171
161
  appConfig: {
@@ -175,11 +165,16 @@ describe("schema export builtins", () => {
175
165
  },
176
166
  };
177
167
  const builtins = exportPresentationBuiltins(withConfig);
178
- expect(builtins.map((b) => b.key)).toContain("config");
179
168
  expect(builtins.map((b) => b.key)).toContain("configure");
169
+ expect(builtins.map((b) => b.key)).not.toContain("config");
170
+ const configureNode = builtins.find((b) => b.key === "configure");
171
+ expect(configureNode && "commands" in configureNode).toBe(true);
172
+ if (configureNode && "commands" in configureNode) {
173
+ const keys = configureNode.commands?.map((c) => c.key) ?? [];
174
+ expect(keys).toEqual(expect.arrayContaining(["get", "set"]));
175
+ }
180
176
  });
181
177
 
182
- /** ExportPresentationBuiltins omits hidden completion. */
183
178
  test("exportPresentationBuiltins omits hidden completion", () => {
184
179
  const builtins = exportPresentationBuiltins(fixture);
185
180
  expect(builtins.map((b) => b.key)).not.toContain("completion");
@@ -38,7 +38,7 @@ describe("builtins/config", () => {
38
38
  const configPath = resolveAppConfigPath(program);
39
39
  mkdirSync(dirname(configPath), { recursive: true });
40
40
  writeFileSync(configPath, `${JSON.stringify({ apiToken: "secret" })}\n`);
41
- const result = await new Cli(program).invoke(["config", "get", "apiToken"]);
41
+ const result = await new Cli(program).invoke(["configure", "get", "apiToken"]);
42
42
  expect(result.exitCode).toBe(0);
43
43
  expect(result.stdout.trim()).toBe("REDACTED");
44
44
  } finally {
@@ -61,7 +61,7 @@ describe("builtins/config", () => {
61
61
  const configPath = resolveAppConfigPath(program);
62
62
  mkdirSync(dirname(configPath), { recursive: true });
63
63
  writeFileSync(configPath, `${JSON.stringify({ apiToken: "secret" })}\n`);
64
- const result = await new Cli(program).invoke(["config", "get", "apiToken", "--json"]);
64
+ const result = await new Cli(program).invoke(["configure", "get", "apiToken", "--json"]);
65
65
  expect(result.exitCode).toBe(0);
66
66
  expect(JSON.parse(result.stdout)).toEqual({ set: true });
67
67
  } finally {
@@ -82,9 +82,9 @@ describe("builtins/config", () => {
82
82
  try {
83
83
  const program = configFixture();
84
84
  writeAppConfigFile(program, { apiToken: "seed" });
85
- const result = await new Cli(program).invoke(["config", "set", "port", "9090"]);
85
+ const result = await new Cli(program).invoke(["configure", "set", "port", "9090"]);
86
86
  expect(result.exitCode).toBe(0);
87
- const get = await new Cli(program).invoke(["config", "get", "port"]);
87
+ const get = await new Cli(program).invoke(["configure", "get", "port"]);
88
88
  expect(get.stdout.trim()).toBe("9090");
89
89
  } finally {
90
90
  if (prevHome === undefined) delete process.env.HOME;
@@ -1,14 +1,19 @@
1
1
  /*
2
- Built-in `config get` / `config set` commands.
2
+ Built-in `configure get` / `configure set` subcommands.
3
3
  */
4
4
 
5
5
  import { bootstrapAppConfig } from "../config/bootstrap.ts";
6
- import { configEntrySensitive, defaultConfigEntryTitle } from "../config/entry.ts";
6
+ import {
7
+ configCommandsEnabled,
8
+ configEntrySensitive,
9
+ configMcpSetEnabled,
10
+ defaultConfigEntryTitle,
11
+ } from "../config/entry.ts";
7
12
  import { writeAppConfigFile } from "../config/file.ts";
8
13
  import { captureMappedHostEnv, exportConfigToEnv, resolveAppConfig } from "../config/resolve.ts";
9
14
  import { configPropertySchema, effectiveJsonSchema } from "../config/schema.ts";
10
15
  import { parseConfigSetValue } from "../config/validate.ts";
11
- import type { CliLeaf, CliOption, CliProgram, CliRouter } from "../types.ts";
16
+ import type { CliLeaf, CliOption, CliProgram } from "../types.ts";
12
17
  import { CliOptionKind } from "../types.ts";
13
18
 
14
19
  const JSON_OPTION: CliOption = {
@@ -182,16 +187,16 @@ function configSetLeaf(program: CliProgram, mcpSetEnabled: boolean): CliLeaf {
182
187
  handler: (ctx) => {
183
188
  const key = ctx.args[0];
184
189
  if (!key) {
185
- process.stderr.write("config set requires a key.\n");
190
+ process.stderr.write("configure set requires a key.\n");
186
191
  process.exit(1);
187
192
  }
188
193
  const raw = ctx.args[1];
189
194
  if (raw === undefined || raw.length === 0) {
190
195
  if (!ctx.hasFlag("json")) {
191
- process.stderr.write("config set requires a value (or --json).\n");
196
+ process.stderr.write("configure set requires a value (or --json).\n");
192
197
  process.exit(1);
193
198
  }
194
- process.stderr.write("config set requires a value.\n");
199
+ process.stderr.write("configure set requires a value.\n");
195
200
  process.exit(1);
196
201
  }
197
202
  configSetRun(program, key, raw, ctx.hasFlag("json"));
@@ -199,23 +204,15 @@ function configSetLeaf(program: CliProgram, mcpSetEnabled: boolean): CliLeaf {
199
204
  };
200
205
  }
201
206
 
202
- import { configCommandsEnabled, configMcpSetEnabled } from "../config/entry.ts";
203
-
204
- /** Built-in `config` router when program.appConfig is set. */
205
- export function cliBuiltinConfigGroup(program: CliProgram): CliRouter {
207
+ /** `configure get` / `configure set` leaves when app config commands are enabled. */
208
+ export function configureConfigSubcommands(
209
+ program: CliProgram,
210
+ mcpSetEnabled = configMcpSetEnabled(program),
211
+ ): CliLeaf[] {
206
212
  if (!program.appConfig) {
207
- throw new Error("config not enabled");
213
+ throw new Error("configure config subcommands require program.appConfig");
208
214
  }
209
- return {
210
- key: "config",
211
- description: "Read or write app configuration.",
212
- commands: [configGetLeaf(program), configSetLeaf(program, configMcpSetEnabled(program))],
213
- };
215
+ return [configGetLeaf(program), configSetLeaf(program, mcpSetEnabled)];
214
216
  }
215
217
 
216
- export function cliBuiltinConfigGroupIfEnabled(program: CliProgram): CliRouter | undefined {
217
- if (!configCommandsEnabled(program)) {
218
- return undefined;
219
- }
220
- return cliBuiltinConfigGroup(program);
221
- }
218
+ export { configCommandsEnabled };
@@ -1,11 +1,23 @@
1
1
  import { resolveCapabilities } from "../capabilities.ts";
2
- import { type CliLeaf, type CliOption, CliOptionKind, type CliProgram } from "../types.ts";
2
+ import { configCommandsEnabled, configMcpSetEnabled } from "../config/entry.ts";
3
+ import {
4
+ CliFallbackMode,
5
+ type CliLeaf,
6
+ type CliOption,
7
+ CliOptionKind,
8
+ type CliProgram,
9
+ type CliRouter,
10
+ } from "../types.ts";
11
+ import { configureConfigSubcommands } from "./config.ts";
3
12
  import {
4
13
  configureCommandDescription,
5
14
  configureCommandNotes,
6
15
  configureSyncOptionDescription,
7
16
  } from "./configure-copy.ts";
8
17
 
18
+ /** Hidden fallback leaf for bare `configure` (interactive / flag modes). */
19
+ export const CONFIGURE_RUN_KEY = "run";
20
+
9
21
  /** Configure command options. */
10
22
  export function configureBuiltinOptions(root: CliProgram): CliOption[] {
11
23
  const caps = resolveCapabilities(root);
@@ -57,14 +69,33 @@ export function configureBuiltinOptions(root: CliProgram): CliOption[] {
57
69
  return opts;
58
70
  }
59
71
 
60
- /** Builds the `configure` built-in command. */
61
- export function cliBuiltinConfigureCommand(root: CliProgram): CliLeaf {
72
+ /** True when argv resolved to `configure get` or `configure set`. */
73
+ export function isConfigureConfigPath(path: string[]): boolean {
74
+ return path.length >= 2 && (path[1] === "get" || path[1] === "set");
75
+ }
76
+
77
+ /** Builds the `configure` built-in router. */
78
+ export function cliBuiltinConfigureCommand(root: CliProgram): CliRouter {
62
79
  const caps = resolveCapabilities(root);
80
+ const run: CliLeaf = {
81
+ key: CONFIGURE_RUN_KEY,
82
+ hidden: true,
83
+ description: "Interactive or flag-driven configure (skills, MCP, app config).",
84
+ handler: () => {},
85
+ };
86
+
87
+ const commands: CliLeaf[] = [run];
88
+ if (configCommandsEnabled(root)) {
89
+ commands.push(...configureConfigSubcommands(root, configMcpSetEnabled(root)));
90
+ }
91
+
63
92
  return {
64
93
  key: "configure",
65
94
  description: configureCommandDescription(root, caps),
66
95
  options: configureBuiltinOptions(root),
67
96
  notes: configureCommandNotes(root, caps),
68
- handler: () => {},
97
+ fallbackCommand: CONFIGURE_RUN_KEY,
98
+ fallbackMode: CliFallbackMode.MissingOnly,
99
+ commands,
69
100
  };
70
101
  }
@@ -11,8 +11,11 @@ import { completionBashScript } from "./completion-bash.ts";
11
11
  import { completionFishScript } from "./completion-fish.ts";
12
12
  import { cliBuiltinCompletionGroup as completionGroup } from "./completion-group.ts";
13
13
  import { completionZshScript } from "./completion-zsh.ts";
14
- import { cliBuiltinConfigGroupIfEnabled } from "./config.ts";
15
- import { cliBuiltinConfigureCommand } from "./configure.ts";
14
+ import {
15
+ CONFIGURE_RUN_KEY,
16
+ cliBuiltinConfigureCommand,
17
+ isConfigureConfigPath,
18
+ } from "./configure.ts";
16
19
  import { cliBuiltinMcpCommand } from "./mcp.ts";
17
20
  import { cliPresentationRoot } from "./presentation.ts";
18
21
  import { cliBuiltinVersionCommand } from "./version.ts";
@@ -101,7 +104,11 @@ export async function dispatchBuiltin(
101
104
  process.stderr.write(capabilityDeniedMessage("configure"));
102
105
  process.exit(1);
103
106
  }
104
- if (pr.path.length !== 1) {
107
+ if (isConfigureConfigPath(pr.path)) {
108
+ return;
109
+ }
110
+ const runSeg = pr.path[1];
111
+ if (pr.path.length > 2 || (pr.path.length === 2 && runSeg !== CONFIGURE_RUN_KEY)) {
105
112
  process.stderr.write(`Unknown subcommand: configure ${pr.path.slice(1).join(" ")}\n`);
106
113
  process.exit(1);
107
114
  }
@@ -177,17 +184,5 @@ export function builtinInterceptRoot(
177
184
  };
178
185
  }
179
186
 
180
- const configGroup = cliBuiltinConfigGroupIfEnabled(program);
181
- if (first === "config" && configGroup) {
182
- return {
183
- parseRoot: {
184
- key: program.key,
185
- description: program.description,
186
- commands: [configGroup],
187
- },
188
- isLeafCompletionIntercept: false,
189
- };
190
- }
191
-
192
187
  return { parseRoot: program, isLeafCompletionIntercept: false };
193
188
  }
@@ -2,7 +2,6 @@ import type { CliCapabilities } from "../capabilities.ts";
2
2
  import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
3
3
  import type { CliNode, CliProgram } from "../types.ts";
4
4
  import { cliBuiltinCompletionGroup } from "./completion-group.ts";
5
- import { cliBuiltinConfigGroupIfEnabled } from "./config.ts";
6
5
  import { cliBuiltinConfigureCommand } from "./configure.ts";
7
6
  import { cliBuiltinMcpCommand } from "./mcp.ts";
8
7
  import { cliBuiltinVersionCommand } from "./version.ts";
@@ -37,6 +36,5 @@ export function resolveBuiltins(program: CliProgram, caps: CliCapabilities): Cli
37
36
  if (caps.mcp) {
38
37
  pushBuiltin(builtins, program, (p) => cliBuiltinMcpCommand(p));
39
38
  }
40
- pushBuiltin(builtins, program, (p) => cliBuiltinConfigGroupIfEnabled(p) ?? null);
41
39
  return builtins;
42
40
  }
@@ -42,17 +42,17 @@ export function reservedCommandNames(caps: CliCapabilities): string[] {
42
42
  if (caps.mcp) {
43
43
  names.push("mcp");
44
44
  }
45
- if (caps.configCommands) {
46
- names.push("config");
47
- }
48
45
  return names;
49
46
  }
50
47
 
51
48
  /** Commands that may run without required appConfig values (read-only / config introspection). */
52
49
  export function skipsRequiredAppConfigExit(path: string[], caps: CliCapabilities): boolean {
53
50
  const root = path[0];
54
- if (root === "config" && caps.configCommands) {
55
- return true;
51
+ if (root === "configure" && caps.configCommands) {
52
+ const sub = path[1];
53
+ if (sub === "get" || sub === "set") {
54
+ return true;
55
+ }
56
56
  }
57
57
  if (root === "docs" && caps.docs) {
58
58
  return true;
@@ -60,7 +60,7 @@ export function skipsRequiredAppConfigExit(path: string[], caps: CliCapabilities
60
60
  return false;
61
61
  }
62
62
 
63
- export type CapabilityFeature = "mcp" | "configure" | "docs" | "config" | "completion";
63
+ export type CapabilityFeature = "mcp" | "configure" | "docs" | "completion";
64
64
 
65
65
  /** Stderr message when a disabled built-in is invoked from the CLI. */
66
66
  export function capabilityDeniedMessage(feature: CapabilityFeature): string {
@@ -73,8 +73,6 @@ export function capabilityDeniedMessage(feature: CapabilityFeature): string {
73
73
  return "Configure is not available for this app.\n";
74
74
  case "docs":
75
75
  return "Documentation commands are not available for this app.\n";
76
- case "config":
77
- return "Configuration commands are not available for this app.\n";
78
76
  }
79
77
  }
80
78
 
@@ -100,8 +98,4 @@ export function assertBuiltinAllowed(argv: string[], caps: CliCapabilities): voi
100
98
  process.stderr.write(capabilityDeniedMessage("docs"));
101
99
  process.exit(1);
102
100
  }
103
- if (first === "config" && !caps.configCommands) {
104
- process.stderr.write(capabilityDeniedMessage("config"));
105
- process.exit(1);
106
- }
107
101
  }
@@ -10,14 +10,12 @@ const main = join(import.meta.dir, "main.ts");
10
10
 
11
11
  /** Tests for argsbarg cli-tool. */
12
12
  describe("argsbarg cli-tool", () => {
13
- /** Tests that version subcommand prints version. */
14
13
  test("version subcommand prints version", () => {
15
14
  const proc = spawnSync("bun", [main, "version"], { encoding: "utf8" });
16
15
  expect(proc.status).toBe(0);
17
16
  expect(proc.stdout.trim().length).toBeGreaterThan(0);
18
17
  });
19
18
 
20
- /** Tests that help lists create and version only (no install, completion, mcp). */
21
19
  test("help lists create and version only (no install, completion, mcp)", () => {
22
20
  const proc = spawnSync("bun", [main, "--help"], { encoding: "utf8" });
23
21
  expect(proc.status).toBe(0);
@@ -28,7 +26,6 @@ describe("argsbarg cli-tool", () => {
28
26
  expect(proc.stdout).not.toContain("mcp");
29
27
  });
30
28
 
31
- /** Completion subcommand is disabled. */
32
29
  test("completion subcommand is disabled", () => {
33
30
  const proc = spawnSync("bun", [main, "completion", "bash"], { encoding: "utf8" });
34
31
  expect(proc.status).toBe(1);
@@ -44,7 +44,6 @@ describe("argsbarg create", () => {
44
44
  expect(out).not.toContain("{key}");
45
45
  });
46
46
 
47
- /** Tests that classNameFromKey. */
48
47
  test("classNameFromKey", () => {
49
48
  expect(classNameFromKey("sqsp-i18n")).toBe("SqspI18n");
50
49
  expect(classNameFromKey("at1")).toBe("At1");
@@ -67,7 +66,6 @@ describe("argsbarg create", () => {
67
66
  expect(opts.desc).toBe("At1 CLI");
68
67
  });
69
68
 
70
- /** ResolveCreateOptions requires release repo. */
71
69
  test("resolveCreateOptions requires release repo", () => {
72
70
  expect(() => resolveCreateOptions({ key: "at1" })).toThrow(/release repo/i);
73
71
  });
@@ -146,7 +144,6 @@ describe("argsbarg create", () => {
146
144
  }
147
145
  });
148
146
 
149
- /** Tests that --diff captures drift details. */
150
147
  test("--diff captures drift details", () => {
151
148
  const drifts = diffCreateDetails("/nonexistent", { key: "x", releaseRepo: "org/x" });
152
149
  expect(drifts.length).toBeGreaterThan(0);