argsbarg 5.1.7 → 5.1.9
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 +22 -1
- package/docs/cli-program.md +3 -1
- package/docs/config-schema.md +5 -3
- package/docs/configure.md +13 -7
- package/docs/distribution-homebrew.md +3 -2
- package/examples/full-example/justfile +2 -5
- package/examples/full-example/scripts/with-dev-formula.ts +6 -1
- package/index.d.ts +10 -1
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +1 -3
- package/src/builtins/completion-bash.ts +6 -27
- package/src/builtins/completion-group.ts +1 -2
- package/src/builtins/completion-simulate-shared.ts +2 -10
- package/src/builtins/completion-zsh.ts +7 -33
- package/src/builtins/config.test.ts +28 -0
- package/src/builtins/config.ts +40 -10
- package/src/builtins/configure-copy.ts +3 -6
- package/src/builtins/dispatch.ts +2 -10
- package/src/builtins/export.ts +1 -3
- package/src/builtins/presentation.ts +2 -7
- package/src/builtins/registry.ts +1 -5
- package/src/cli-tool/create.test.ts +3 -6
- package/src/cli-tool/create.ts +5 -22
- package/src/cli-tool/post-create.ts +1 -3
- package/src/cli-tool/program.ts +1 -2
- package/src/cli-tool/run-create.ts +2 -8
- package/src/cli.ts +6 -27
- package/src/config/bindings.test.ts +72 -0
- package/src/config/bindings.ts +115 -0
- package/src/config/bootstrap.ts +80 -65
- package/src/config/context.test.ts +45 -0
- package/src/config/context.ts +74 -14
- package/src/config/entry.ts +1 -3
- package/src/config/file.test.ts +46 -3
- package/src/config/file.ts +59 -19
- package/src/config/resolve.ts +1 -4
- package/src/config/schema.ts +1 -3
- package/src/config/validate.test.ts +3 -11
- package/src/config/validate.ts +30 -30
- package/src/config.integration.test.ts +1 -5
- package/src/configure/configure.test.ts +19 -5
- package/src/configure/index.ts +17 -15
- package/src/docs/api-guide.ts +1 -6
- package/src/docs/builtin.ts +1 -5
- package/src/docs/docs.test.ts +2 -6
- package/src/docs/mcp-guide.ts +3 -13
- package/src/docs/mcp-resources.test.ts +1 -3
- package/src/docs/mcp-resources.ts +1 -6
- package/src/docs/save.ts +2 -10
- package/src/formats.test.ts +1 -7
- package/src/formats.ts +1 -5
- package/src/headless.test.ts +5 -17
- package/src/help.ts +9 -51
- package/src/hidden-mcpb.test.ts +3 -12
- package/src/hidden.ts +1 -3
- package/src/install/binary-placement.test.ts +2 -6
- package/src/install/binary-placement.ts +1 -4
- package/src/install/gh-release-update.ts +5 -22
- package/src/install/mcp-codex.ts +1 -2
- package/src/install/mcp-config.ts +2 -12
- package/src/install/mcp-opencode.test.ts +1 -3
- package/src/install/mcp-opencode.ts +3 -15
- package/src/install/plan.ts +2 -10
- package/src/install/status.ts +3 -5
- package/src/install/target-base.ts +2 -11
- package/src/install/target-detect.ts +1 -5
- package/src/install/target-effective.ts +3 -13
- package/src/install/target-mcp-cli.ts +4 -26
- package/src/install/target-mcp-json.ts +3 -14
- package/src/install/target-plan-build.ts +1 -5
- package/src/install/target-registry.ts +13 -21
- package/src/install/target-scope.ts +8 -25
- package/src/install/target-types.ts +1 -6
- package/src/install/targets/app.ts +2 -2
- package/src/install/targets/opencode-mcp.ts +1 -6
- package/src/install/targets.test.ts +3 -10
- package/src/install/uninstall.ts +2 -9
- package/src/invoke.test.ts +1 -3
- package/src/mcp/bundle.ts +4 -19
- package/src/mcp/claude.test.ts +3 -3
- package/src/mcp/claude.ts +4 -17
- package/src/mcp/env.ts +1 -3
- package/src/mcp/server.ts +3 -12
- package/src/mcp/tools.ts +2 -11
- package/src/mcp.integration.test.ts +4 -10
- package/src/parse.test.ts +9 -34
- package/src/parse.ts +10 -44
- package/src/schema.ts +2 -10
- package/src/skill/hint.ts +1 -5
- package/src/skill/install.ts +1 -5
- package/src/test-fixtures.ts +1 -3
- package/src/validate.ts +29 -90
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [5.1.9] - 2026-07-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`configure --sync` config bootstrap** — creates `~/.local/lib/<key>/config.json` as `{}` when missing (all apps; Homebrew `post_install`).
|
|
15
|
+
- **`_bindings` metadata** — per-key intent (`env` | `file` | `skip`) in config file; wizard persists env/skip choices; `configure set --from-env`.
|
|
16
|
+
- **`ctx.appConfig` unsafe I/O** — `getUnsafe`, `setUnsafe`, `readUnsafe` for raw file access (works without `program.appConfig`).
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **App config detection** — `appConfigInstalled` / `--status` use file existence only (not directory-only).
|
|
21
|
+
- **Configure wizard** — skips addressed keys; Enter at env prompt persists `_bindings`; accurate write messaging.
|
|
22
|
+
- **`configure --status`** — binding hints on required keys (`set (env)`, etc.).
|
|
23
|
+
- **Partial config validation** — single-key / bindings-only writes skip required-property checks.
|
|
24
|
+
- biome lineLength=120
|
|
25
|
+
|
|
26
|
+
## [5.1.8] - 2026-07-05
|
|
27
|
+
|
|
28
|
+
|
|
10
29
|
## [5.1.7] - 2026-07-05
|
|
11
30
|
|
|
12
31
|
### Changed
|
|
@@ -627,7 +646,9 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
|
|
|
627
646
|
- 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`).
|
|
628
647
|
- Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
|
|
629
648
|
|
|
630
|
-
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v5.1.
|
|
649
|
+
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v5.1.9...HEAD
|
|
650
|
+
[5.1.9]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.9
|
|
651
|
+
[5.1.8]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.8
|
|
631
652
|
[5.1.7]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.7
|
|
632
653
|
[5.1.6]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.6
|
|
633
654
|
[5.1.5]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.5
|
package/docs/cli-program.md
CHANGED
|
@@ -432,7 +432,9 @@ await cli.run();
|
|
|
432
432
|
|
|
433
433
|
See [config-schema.md](config-schema.md) for codegen, [configure.md](configure.md) (`configure.targets`), and [mcp.md](mcp.md).
|
|
434
434
|
|
|
435
|
-
**Handler access (`ctx.appConfig`):** `get`, `require`, `set`, `read`, `
|
|
435
|
+
**Handler access (`ctx.appConfig`):** `get`, `require`, `set`, `read` (schema-aware, resolved values); `getUnsafe`, `setUnsafe`, `readUnsafe` (raw file, works without `program.appConfig`); `path`, `dir`. Prefer `get`/`set` when `appConfig` is declared. Env export remains for subprocess inheritance. `path` is `~/.local/lib/<key>/config.json`; `dir` is its parent.
|
|
436
|
+
|
|
437
|
+
**`_bindings`:** reserved metadata for per-key intent (`env`, `file`, `skip`). Set by the wizard, `configure set --from-env`, or `ctx.appConfig.set`. Does not change resolve order (env still wins when set).
|
|
436
438
|
|
|
437
439
|
## Reserved names
|
|
438
440
|
|
package/docs/config-schema.md
CHANGED
|
@@ -42,11 +42,13 @@ await cli.run();
|
|
|
42
42
|
| Interactive `configure` / `--status` | Auto-runs config wizard when `entries` is non-empty; `--status` for read-only inventory |
|
|
43
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
|
-
| `ctx.appConfig` in handlers | `get`, `require`, `set`, `read`, `path`, `dir` — prefer
|
|
45
|
+
| `ctx.appConfig` in handlers | `get`, `require`, `set`, `read`, `getUnsafe`, `setUnsafe`, `readUnsafe`, `path`, `dir` — prefer `get`/`set` when `appConfig` is set |
|
|
46
46
|
|
|
47
|
-
**
|
|
47
|
+
**Handler access** — with `program.appConfig`: `get` / `set` / `require` use schema validation and resolved values. `getUnsafe` / `setUnsafe` / `readUnsafe` read and write the raw file (for `_bindings` and ad-hoc keys). Without `program.appConfig`, only `path`, `dir`, and the `*Unsafe` methods work.
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
**`_bindings`** — reserved top-level metadata: `{ "_bindings": { "apiToken": "env" } }`. Set via wizard (Enter to use env), `configure set --from-env`, or `ctx.appConfig.set` (marks `file`). Optional keys can be bound to `skip`.
|
|
50
|
+
|
|
51
|
+
**Validation at runtime** — argsbarg validates the config file and `configure set` / `ctx.appConfig.set` against the effective JSON Schema. Partial writes (bindings only, single-key updates) skip required-property checks.
|
|
50
52
|
|
|
51
53
|
See [cli-program.md — Configuration](cli-program.md#configuration-programappconfig) for resolution order, bootstrap timing, and `configure get`/`set`.
|
|
52
54
|
|
package/docs/configure.md
CHANGED
|
@@ -54,7 +54,7 @@ Dev flow matches release: formula `install` copies the binary and generates comp
|
|
|
54
54
|
|
|
55
55
|
# Read or write app config (non-interactive; when program.appConfig is set)
|
|
56
56
|
<key> configure get [key] [--json] [--pretty]
|
|
57
|
-
<key> configure set <key> <value> [--json]
|
|
57
|
+
<key> configure set <key> <value> [--json] [--from-env]
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
Non-interactive / CI: pass **`--yes`** (or **`--json`**, **`--sync`**, **`--remove-all`**, **`--remove-config`**) — see [Confirmation](#confirmation).
|
|
@@ -69,14 +69,14 @@ Non-interactive / CI: pass **`--yes`** (or **`--json`**, **`--sync`**, **`--remo
|
|
|
69
69
|
| Claude skill | Y/n prompt | `~/.claude/skills/<dir>/` when `~/.claude` exists |
|
|
70
70
|
| Codex / OpenCode / OpenClaw skills | Y/n prompt | Agent-specific dirs when available |
|
|
71
71
|
| MCP config | Y/n prompt when `mcpServer.enabled` | Cursor, Claude Code/Desktop, OpenCode, Codex, OpenClaw, ChatGPT desktop |
|
|
72
|
-
| App config | auto-runs wizard | Interactive wizard
|
|
72
|
+
| App config | auto-runs wizard | Interactive wizard may update `~/.local/lib/<key>/config.json` when values change; `--sync` bootstraps an empty file on install |
|
|
73
73
|
|
|
74
74
|
### Externally managed binary (Homebrew)
|
|
75
75
|
|
|
76
76
|
When **`PATH`** resolves the program key to the **running executable** (e.g. after `brew install`):
|
|
77
77
|
|
|
78
78
|
- **`configure --status`** shows `app: system (PATH)`
|
|
79
|
-
- **`--sync`** refreshes skills and MCP
|
|
79
|
+
- **`--sync`** refreshes skills and MCP; also creates `~/.local/lib/<key>/config.json` as `{}` when missing (all apps)
|
|
80
80
|
|
|
81
81
|
MCP config uses the command name on **`PATH`**, not a Cellar path.
|
|
82
82
|
|
|
@@ -110,13 +110,19 @@ Artifact keys: `chatgptMcp`, `claudeCodeMcp`, `claudeDesktopMcp`, `claudeSkill`,
|
|
|
110
110
|
|
|
111
111
|
## App config (`program.appConfig`)
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
Every app gets `~/.local/lib/<sanitized-key>/config.json` on first **`configure --sync`** (Homebrew `post_install`), even without `program.appConfig`.
|
|
114
|
+
|
|
115
|
+
When `program.appConfig` is set, ArgsBarg manages schema-driven values in that file.
|
|
114
116
|
|
|
115
117
|
| Mode | Description |
|
|
116
118
|
| --- | --- |
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
+
| `configure --sync` | Bootstraps `config.json` as `{}` when missing |
|
|
120
|
+
| Interactive `configure` | Config wizard when `entries` is non-empty; writes only when values or `_bindings` change |
|
|
121
|
+
| `--status` | Shows config path, required keys (`set` / `missing`), and binding hints (`env`, `file`, `skip`) |
|
|
119
122
|
| `--remove-config --yes` | Removes the config directory |
|
|
123
|
+
| `configure set --from-env` | Bind a key to its mapped env var (stores `_bindings`, no literal secret) |
|
|
124
|
+
|
|
125
|
+
Per-key intent is stored under the reserved `_bindings` object (e.g. `"apiToken": "env"`). Env still wins at resolve time when set; bindings record user choice and suppress re-prompts.
|
|
120
126
|
|
|
121
127
|
Export helpers from `argsbarg`: `resolveAppConfigPath`, `displayAppConfigPath`.
|
|
122
128
|
|
|
@@ -127,7 +133,7 @@ Export helpers from `argsbarg`: `resolveAppConfigPath`, `displayAppConfigPath`.
|
|
|
127
133
|
| Flag | Description |
|
|
128
134
|
| --- | --- |
|
|
129
135
|
| `--status` | Read-only inventory |
|
|
130
|
-
| `--sync` | Refresh installed agent artifacts (Homebrew `post_install`; greenfield → full sync plan) |
|
|
136
|
+
| `--sync` | Refresh installed agent artifacts; bootstrap `config.json` when missing (Homebrew `post_install`; greenfield → full sync plan) |
|
|
131
137
|
| `--remove-all` | Remove all detected agent artifacts |
|
|
132
138
|
| `--remove-config` | Remove app config directory only |
|
|
133
139
|
|
|
@@ -8,7 +8,8 @@ Argsbarg apps distribute the **binary and shell completions** through Homebrew,
|
|
|
8
8
|
| --- | --- |
|
|
9
9
|
| Binary + completions | Formula `install` block |
|
|
10
10
|
| Skills + MCP | Formula `post_install` → `{key} configure --sync --yes` |
|
|
11
|
-
| App config |
|
|
11
|
+
| App config file | Bootstrapped as `{}` on `post_install` via `--sync` (`~/.local/lib/<key>/config.json`) |
|
|
12
|
+
| App config values | User opt-in: `{key} configure` (interactive wizard when `program.appConfig` has entries) |
|
|
12
13
|
| App config cleanup | Formula `uninstall` → `{key} configure --remove-config --yes` |
|
|
13
14
|
|
|
14
15
|
**Only tap-from-repo** — in-repo `Formula/` or GitHub tap. Not Homebrew core.
|
|
@@ -89,7 +90,7 @@ Local dev formulae (`just install-local`) use a plain `file://` URL and do not n
|
|
|
89
90
|
|
|
90
91
|
Completions require users to configure their shell per [Homebrew Shell Completion](https://docs.brew.sh/Shell-Completion).
|
|
91
92
|
|
|
92
|
-
**Why
|
|
93
|
+
**Why the wizard is separate from `post_install`:** prompting for secrets requires a TTY. `post_install` only bootstraps an empty `config.json` and refreshes skills/MCP. Apps with `appConfig` print a one-line configure hint in formula `caveats` for the interactive wizard.
|
|
93
94
|
|
|
94
95
|
**MCP hosts:** when `mcpServer.enabled` is true, add a caveats line that chat apps (Cursor, Claude Desktop, etc.) must be **restarted** after `brew install` / `brew upgrade` — `post_install` updates MCP config on disk, but hosts typically load it only at startup.
|
|
95
96
|
|
|
@@ -20,10 +20,8 @@ build:
|
|
|
20
20
|
@rm -f .*.bun-build
|
|
21
21
|
|
|
22
22
|
# Run schemagen, git diff check, typecheck, and format
|
|
23
|
-
check: schemagen
|
|
23
|
+
check: schemagen format typecheck
|
|
24
24
|
git diff --exit-code schemas/
|
|
25
|
-
just typecheck
|
|
26
|
-
just format
|
|
27
25
|
|
|
28
26
|
# Run the CLI from source with optional args; restarts on file changes
|
|
29
27
|
dev *ARGS:
|
|
@@ -38,7 +36,7 @@ alias fmt := format
|
|
|
38
36
|
|
|
39
37
|
# Format and lint sources (auto-fix)
|
|
40
38
|
format:
|
|
41
|
-
bun run biome check ./src ./scripts --write
|
|
39
|
+
bun run biome check ./src ./scripts --write --unsafe
|
|
42
40
|
|
|
43
41
|
# Alias for backward compatibility
|
|
44
42
|
install: install-local
|
|
@@ -81,7 +79,6 @@ run *ARGS:
|
|
|
81
79
|
# Generate JSON Schema artifacts from TypeScript types
|
|
82
80
|
schemagen:
|
|
83
81
|
bun run schemagen
|
|
84
|
-
just format
|
|
85
82
|
|
|
86
83
|
# Install bun/npm dependencies
|
|
87
84
|
setup:
|
|
@@ -50,6 +50,11 @@ if (sep === -1 || sep === process.argv.length - 1) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const cmd = process.argv.slice(sep + 1);
|
|
53
|
+
const executable = cmd[0];
|
|
54
|
+
if (!executable) {
|
|
55
|
+
process.stderr.write("Usage: bun scripts/with-dev-formula.ts -- <command...>\n");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
53
58
|
let restored = false;
|
|
54
59
|
|
|
55
60
|
function restore(): void {
|
|
@@ -63,7 +68,7 @@ function restore(): void {
|
|
|
63
68
|
try {
|
|
64
69
|
backupReleaseFormula();
|
|
65
70
|
writeDevFormula();
|
|
66
|
-
const result = spawnSync(
|
|
71
|
+
const result = spawnSync(executable, cmd.slice(1), { stdio: "inherit" });
|
|
67
72
|
process.exitCode = result.status === null ? 1 : result.status;
|
|
68
73
|
} finally {
|
|
69
74
|
restore();
|
package/index.d.ts
CHANGED
|
@@ -7,11 +7,15 @@ export declare function displayAppConfigPath(program: CliProgram): string;
|
|
|
7
7
|
export type ResolvedConfig = Record<string, unknown>;
|
|
8
8
|
declare class EmptyAppConfigSnapshot {
|
|
9
9
|
private readonly program;
|
|
10
|
-
|
|
10
|
+
private fileData;
|
|
11
|
+
constructor(program: CliProgram, fileData?: Record<string, unknown>);
|
|
11
12
|
get(_key: string): undefined;
|
|
12
13
|
require(key: string): never;
|
|
13
14
|
set(_key: string, _value: unknown): void;
|
|
14
15
|
read(): ResolvedConfig;
|
|
16
|
+
readUnsafe(): Record<string, unknown>;
|
|
17
|
+
getUnsafe(key: string): unknown;
|
|
18
|
+
setUnsafe(key: string, value: unknown): void;
|
|
15
19
|
/** Resolved absolute path to the app JSON config file (OS default from `program.key`). */
|
|
16
20
|
get path(): string;
|
|
17
21
|
/** Resolved absolute directory containing the config file. */
|
|
@@ -26,13 +30,18 @@ declare class AppConfigSnapshot {
|
|
|
26
30
|
require(key: string): unknown;
|
|
27
31
|
set(key: string, value: unknown): void;
|
|
28
32
|
read(): ResolvedConfig;
|
|
33
|
+
readUnsafe(): Record<string, unknown>;
|
|
34
|
+
getUnsafe(key: string): unknown;
|
|
35
|
+
setUnsafe(key: string, value: unknown): void;
|
|
29
36
|
/** Resolved absolute path to the app JSON config file (`~/.local/lib/<key>/config.json`). */
|
|
30
37
|
get path(): string;
|
|
31
38
|
/** Resolved absolute directory containing the config file. */
|
|
32
39
|
get dir(): string;
|
|
33
40
|
/** Replace snapshot after external bootstrap (internal). */
|
|
34
41
|
refresh(fileData: Record<string, unknown>, resolved: ResolvedConfig): void;
|
|
42
|
+
private persistFileData;
|
|
35
43
|
private assertEntryKey;
|
|
44
|
+
private assertUnsafeKey;
|
|
36
45
|
}
|
|
37
46
|
export type AnyAppConfigSnapshot = AppConfigSnapshot | EmptyAppConfigSnapshot;
|
|
38
47
|
/** Coerced leaf inputs keyed by option and positional names. */
|
package/package.json
CHANGED
|
@@ -52,9 +52,7 @@ describe("builtins help copy", () => {
|
|
|
52
52
|
|
|
53
53
|
test("configure copy omits MCP when mcpServer unset", () => {
|
|
54
54
|
const caps = resolveCapabilities(noMcp);
|
|
55
|
-
expect(configureCommandDescription(noMcp, caps)).toBe(
|
|
56
|
-
"Set up agent skills for this app (binary via Homebrew).",
|
|
57
|
-
);
|
|
55
|
+
expect(configureCommandDescription(noMcp, caps)).toBe("Set up agent skills for this app (binary via Homebrew).");
|
|
58
56
|
expect(configureCommandDescription(noMcp, caps)).not.toContain("MCP");
|
|
59
57
|
expect(configureSyncOptionDescription(noMcp, caps)).not.toContain("MCP");
|
|
60
58
|
const configure = cliBuiltinConfigureCommand(noMcp);
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { CliOptionKind, type CliRouter } from "../types.ts";
|
|
2
2
|
import { emitConsumeLong, emitConsumeShort, emitMatchChild } from "./completion-simulate-shared.ts";
|
|
3
3
|
import { collectScopes, type ScopeRec } from "./scopes.ts";
|
|
4
|
-
import {
|
|
5
|
-
escShellSingleQuoted,
|
|
6
|
-
identToken,
|
|
7
|
-
kHelpLong,
|
|
8
|
-
kHelpShort,
|
|
9
|
-
mainName,
|
|
10
|
-
} from "./shell-helpers.ts";
|
|
4
|
+
import { escShellSingleQuoted, identToken, kHelpLong, kHelpShort, mainName } from "./shell-helpers.ts";
|
|
11
5
|
|
|
12
6
|
function emitSimulate(ident: string): string {
|
|
13
7
|
let o = "_${ident}_nac_simulate() {\n".replace("${ident}", ident);
|
|
@@ -18,10 +12,7 @@ function emitSimulate(ident: string): string {
|
|
|
18
12
|
o += " ((i++)); continue\n";
|
|
19
13
|
o += " fi\n";
|
|
20
14
|
o += " if [[ $w == --* ]]; then\n";
|
|
21
|
-
o += ' steps=$(_${ident}_nac_consume_long "$sid" "$w" "${COMP_WORDS[i+1]}")\n'.replace(
|
|
22
|
-
"${ident}",
|
|
23
|
-
ident,
|
|
24
|
-
);
|
|
15
|
+
o += ' steps=$(_${ident}_nac_consume_long "$sid" "$w" "${COMP_WORDS[i+1]}")\n'.replace("${ident}", ident);
|
|
25
16
|
o += " case $steps in\n";
|
|
26
17
|
o += " 0) break ;;\n";
|
|
27
18
|
o += " 1) ((i++)) ;;\n";
|
|
@@ -54,20 +45,13 @@ function emitEnumReplyBash(ident: string, scopes: ScopeRec[]): string {
|
|
|
54
45
|
o += ' local sid="$1" prev="$2" cur="$3"\n';
|
|
55
46
|
o += " case $sid in\n";
|
|
56
47
|
for (const [i, sc] of scopes.entries()) {
|
|
57
|
-
const enumOpts = sc.opts.filter(
|
|
58
|
-
(op) => op.kind === CliOptionKind.Enum && (op.choices?.length ?? 0) > 0,
|
|
59
|
-
);
|
|
48
|
+
const enumOpts = sc.opts.filter((op) => op.kind === CliOptionKind.Enum && (op.choices?.length ?? 0) > 0);
|
|
60
49
|
if (enumOpts.length === 0) continue;
|
|
61
50
|
o += ` ${i})\n`;
|
|
62
51
|
o += " case $prev in\n";
|
|
63
52
|
for (const op of enumOpts) {
|
|
64
53
|
const words = (op.choices ?? []).map((c) => escShellSingleQuoted(c)).join(" ");
|
|
65
|
-
o +=
|
|
66
|
-
" --" +
|
|
67
|
-
op.name +
|
|
68
|
-
") COMPREPLY=( $(compgen -W '" +
|
|
69
|
-
words +
|
|
70
|
-
'\' -- "$cur") ); return 0 ;;\n';
|
|
54
|
+
o += ` --${op.name}) COMPREPLY=( $(compgen -W '${words}' -- "$cur") ); return 0 ;;\n`;
|
|
71
55
|
}
|
|
72
56
|
o += " esac\n";
|
|
73
57
|
o += " ;;\n";
|
|
@@ -85,10 +69,7 @@ function emitMainBodyBash(schema: CliRouter, ident: string): string {
|
|
|
85
69
|
o += ' local prev="${COMP_WORDS[COMP_CWORD-1]:-}"\n';
|
|
86
70
|
o += " _${ident}_nac_simulate\n".replace("${ident}", ident);
|
|
87
71
|
o += " local sid=$REPLY_SID\n";
|
|
88
|
-
o += ' if _${ident}_nac_enum_reply "$sid" "$prev" "$cur"; then return; fi\n'.replace(
|
|
89
|
-
"${ident}",
|
|
90
|
-
ident,
|
|
91
|
-
);
|
|
72
|
+
o += ' if _${ident}_nac_enum_reply "$sid" "$prev" "$cur"; then return; fi\n'.replace("${ident}", ident);
|
|
92
73
|
o += " if [[ $cur == -* ]]; then\n";
|
|
93
74
|
o += ' local oname="A_${ident}_${sid}_opts"\n'.replace("${ident}", ident);
|
|
94
75
|
o += " local -a optsarr\n";
|
|
@@ -111,9 +92,7 @@ function emitMainBodyBash(schema: CliRouter, ident: string): string {
|
|
|
111
92
|
o += " fi\n";
|
|
112
93
|
o += " fi\n";
|
|
113
94
|
o += "}\n\n";
|
|
114
|
-
o += "complete -F _${main} ${schema.key}\n"
|
|
115
|
-
.replace("${main}", main)
|
|
116
|
-
.replace("${schema.key}", schema.key);
|
|
95
|
+
o += "complete -F _${main} ${schema.key}\n".replace("${main}", main).replace("${schema.key}", schema.key);
|
|
117
96
|
return o;
|
|
118
97
|
}
|
|
119
98
|
|
|
@@ -45,7 +45,6 @@ export function cliBuiltinCompletionGroup(program: CliProgram): import("../types
|
|
|
45
45
|
],
|
|
46
46
|
};
|
|
47
47
|
router.notes =
|
|
48
|
-
"Completions are installed by Homebrew during formula install.\n\n" +
|
|
49
|
-
"See: https://docs.brew.sh/Shell-Completion";
|
|
48
|
+
"Completions are installed by Homebrew during formula install.\n\n" + "See: https://docs.brew.sh/Shell-Completion";
|
|
50
49
|
return router;
|
|
51
50
|
}
|
|
@@ -44,11 +44,7 @@ export function emitConsumeLong(ident: string, scopes: ScopeRec[]): string {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** Emits `_<ident>_nac_consume_short`; dialect selects substring syntax. */
|
|
47
|
-
export function emitConsumeShort(
|
|
48
|
-
ident: string,
|
|
49
|
-
scopes: ScopeRec[],
|
|
50
|
-
dialect: CompletionShellDialect,
|
|
51
|
-
): string {
|
|
47
|
+
export function emitConsumeShort(ident: string, scopes: ScopeRec[], dialect: CompletionShellDialect): string {
|
|
52
48
|
const firstChar = dialect === "bash" ? "ch=${rest:0:1}" : "ch=${rest[1,1]}";
|
|
53
49
|
const restAdvance = dialect === "bash" ? "rest=${rest:1}" : "rest=${rest[2,-1]}";
|
|
54
50
|
|
|
@@ -93,11 +89,7 @@ export function emitConsumeShort(
|
|
|
93
89
|
}
|
|
94
90
|
|
|
95
91
|
/** Emits `_<ident>_nac_match_child` — identical for bash and zsh. */
|
|
96
|
-
export function emitMatchChild(
|
|
97
|
-
ident: string,
|
|
98
|
-
scopes: ScopeRec[],
|
|
99
|
-
pathIndex: Record<string, number>,
|
|
100
|
-
): string {
|
|
92
|
+
export function emitMatchChild(ident: string, scopes: ScopeRec[], pathIndex: Record<string, number>): string {
|
|
101
93
|
let o = "_${ident}_nac_match_child() {\n".replace("${ident}", ident);
|
|
102
94
|
o += ' local sid="$1" w="$2"\n';
|
|
103
95
|
o += " case $sid in\n";
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { CliOptionKind, type CliRouter } from "../types.ts";
|
|
2
2
|
import { emitConsumeLong, emitConsumeShort, emitMatchChild } from "./completion-simulate-shared.ts";
|
|
3
3
|
import { collectScopes, type ScopeRec } from "./scopes.ts";
|
|
4
|
-
import {
|
|
5
|
-
escShellSingleQuoted,
|
|
6
|
-
identToken,
|
|
7
|
-
kHelpLong,
|
|
8
|
-
kHelpShort,
|
|
9
|
-
mainName,
|
|
10
|
-
} from "./shell-helpers.ts";
|
|
4
|
+
import { escShellSingleQuoted, identToken, kHelpLong, kHelpShort, mainName } from "./shell-helpers.ts";
|
|
11
5
|
|
|
12
6
|
function emitScopeArraysZsh(ident: string, scopes: ScopeRec[]): string {
|
|
13
7
|
let out = "";
|
|
@@ -25,19 +19,9 @@ function emitScopeArraysZsh(ident: string, scopes: ScopeRec[]): string {
|
|
|
25
19
|
escShellSingleQuoted("Show help for this command.") +
|
|
26
20
|
"'";
|
|
27
21
|
for (const o of sc.opts) {
|
|
28
|
-
out +=
|
|
29
|
-
" '" +
|
|
30
|
-
escShellSingleQuoted(`--${o.name}`) +
|
|
31
|
-
":" +
|
|
32
|
-
escShellSingleQuoted(o.description) +
|
|
33
|
-
"'";
|
|
22
|
+
out += ` '${escShellSingleQuoted(`--${o.name}`)}:${escShellSingleQuoted(o.description)}'`;
|
|
34
23
|
if (o.shortName) {
|
|
35
|
-
out +=
|
|
36
|
-
" '" +
|
|
37
|
-
escShellSingleQuoted(`-${o.shortName}`) +
|
|
38
|
-
":" +
|
|
39
|
-
escShellSingleQuoted(o.description) +
|
|
40
|
-
"'";
|
|
24
|
+
out += ` '${escShellSingleQuoted(`-${o.shortName}`)}:${escShellSingleQuoted(o.description)}'`;
|
|
41
25
|
}
|
|
42
26
|
}
|
|
43
27
|
out += ")\n";
|
|
@@ -63,10 +47,7 @@ function emitSimulateZsh(ident: string): string {
|
|
|
63
47
|
o += " ((i++)); continue\n";
|
|
64
48
|
o += " fi\n";
|
|
65
49
|
o += " if [[ $w == --* ]]; then\n";
|
|
66
|
-
o += ' steps=$(_${ident}_nac_consume_long "$sid" "$w" "${words[i+1]}")\n'.replace(
|
|
67
|
-
"${ident}",
|
|
68
|
-
ident,
|
|
69
|
-
);
|
|
50
|
+
o += ' steps=$(_${ident}_nac_consume_long "$sid" "$w" "${words[i+1]}")\n'.replace("${ident}", ident);
|
|
70
51
|
o += " case $steps in\n";
|
|
71
52
|
o += " 0) break ;;\n";
|
|
72
53
|
o += " 1) ((i++)) ;;\n";
|
|
@@ -99,9 +80,7 @@ function emitEnumReplyZsh(ident: string, scopes: ScopeRec[]): string {
|
|
|
99
80
|
o += " local sid=$1 prev=$2\n";
|
|
100
81
|
o += " case $sid in\n";
|
|
101
82
|
for (const [i, sc] of scopes.entries()) {
|
|
102
|
-
const enumOpts = sc.opts.filter(
|
|
103
|
-
(op) => op.kind === CliOptionKind.Enum && (op.choices?.length ?? 0) > 0,
|
|
104
|
-
);
|
|
83
|
+
const enumOpts = sc.opts.filter((op) => op.kind === CliOptionKind.Enum && (op.choices?.length ?? 0) > 0);
|
|
105
84
|
if (enumOpts.length === 0) continue;
|
|
106
85
|
o += ` ${i})\n`;
|
|
107
86
|
o += " case $prev in\n";
|
|
@@ -124,10 +103,7 @@ function emitMainBodyZsh(schema: CliRouter, ident: string): string {
|
|
|
124
103
|
o += ' local curcontext="$curcontext" ret=1\n';
|
|
125
104
|
o += " _${ident}_nac_simulate\n".replace("${ident}", ident);
|
|
126
105
|
o += " local sid=$REPLY_SID\n";
|
|
127
|
-
o += ' if _${ident}_nac_enum_reply "$sid" "$words[CURRENT-1]"; then return 0; fi\n'.replace(
|
|
128
|
-
"${ident}",
|
|
129
|
-
ident,
|
|
130
|
-
);
|
|
106
|
+
o += ' if _${ident}_nac_enum_reply "$sid" "$words[CURRENT-1]"; then return 0; fi\n'.replace("${ident}", ident);
|
|
131
107
|
o += " if [[ $PREFIX == -* ]]; then\n";
|
|
132
108
|
o += " local -a optsarr\n";
|
|
133
109
|
o += ' local oname="A_${ident}_${sid}_opts"\n'.replace("${ident}", ident);
|
|
@@ -149,9 +125,7 @@ function emitMainBodyZsh(schema: CliRouter, ident: string): string {
|
|
|
149
125
|
o += " fi\n";
|
|
150
126
|
o += " return ret\n";
|
|
151
127
|
o += "}\n\n";
|
|
152
|
-
o += "compdef _${main} ${schema.key}\n"
|
|
153
|
-
.replace("${main}", main)
|
|
154
|
-
.replace("${schema.key}", schema.key);
|
|
128
|
+
o += "compdef _${main} ${schema.key}\n".replace("${main}", main).replace("${schema.key}", schema.key);
|
|
155
129
|
return o;
|
|
156
130
|
}
|
|
157
131
|
|
|
@@ -6,6 +6,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
6
6
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
|
+
import { readBindings } from "../config/bindings.ts";
|
|
9
10
|
import { resolveAppConfigPath, writeAppConfigFile } from "../config/file.ts";
|
|
10
11
|
import { Cli, type CliProgram } from "../index.ts";
|
|
11
12
|
|
|
@@ -93,4 +94,31 @@ describe("builtins/config", () => {
|
|
|
93
94
|
rmSync(dir, { recursive: true, force: true });
|
|
94
95
|
}
|
|
95
96
|
});
|
|
97
|
+
|
|
98
|
+
test("config set --from-env stores binding without literal", async () => {
|
|
99
|
+
const dir = mkdtempSync(join(tmpdir(), "cfg-from-env-"));
|
|
100
|
+
const prevHome = process.env.HOME;
|
|
101
|
+
process.env.HOME = dir;
|
|
102
|
+
const prev = process.env.API_TOKEN;
|
|
103
|
+
process.env.API_TOKEN = "from-env";
|
|
104
|
+
try {
|
|
105
|
+
const program = configFixture();
|
|
106
|
+
writeAppConfigFile(program, { apiToken: "seed" }, { partial: true });
|
|
107
|
+
const result = await new Cli(program).invoke(["configure", "set", "apiToken", "--from-env"]);
|
|
108
|
+
expect(result.exitCode).toBe(0);
|
|
109
|
+
const raw = await new Cli(program).invoke(["configure", "get", "apiToken"]);
|
|
110
|
+
expect(raw.stdout.trim()).toBe("REDACTED");
|
|
111
|
+
const configPath = resolveAppConfigPath(program);
|
|
112
|
+
const { readFileSync } = await import("node:fs");
|
|
113
|
+
const onDisk = JSON.parse(readFileSync(configPath, "utf8")) as Record<string, unknown>;
|
|
114
|
+
expect(onDisk.apiToken).toBeUndefined();
|
|
115
|
+
expect(readBindings(onDisk).apiToken).toBe("env");
|
|
116
|
+
} finally {
|
|
117
|
+
if (prevHome === undefined) delete process.env.HOME;
|
|
118
|
+
else process.env.HOME = prevHome;
|
|
119
|
+
if (prev === undefined) delete process.env.API_TOKEN;
|
|
120
|
+
else process.env.API_TOKEN = prev;
|
|
121
|
+
rmSync(dir, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
96
124
|
});
|
package/src/builtins/config.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Built-in `configure get` / `configure set` subcommands.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { clearFileValue, setBinding } from "../config/bindings.ts";
|
|
5
6
|
import { bootstrapAppConfig } from "../config/bootstrap.ts";
|
|
6
7
|
import {
|
|
7
8
|
configCommandsEnabled,
|
|
@@ -28,12 +29,7 @@ const PRETTY_OPTION: CliOption = {
|
|
|
28
29
|
kind: CliOptionKind.Presence,
|
|
29
30
|
};
|
|
30
31
|
|
|
31
|
-
function configGetOutput(
|
|
32
|
-
program: CliProgram,
|
|
33
|
-
key: string | undefined,
|
|
34
|
-
json: boolean,
|
|
35
|
-
pretty: boolean,
|
|
36
|
-
): void {
|
|
32
|
+
function configGetOutput(program: CliProgram, key: string | undefined, json: boolean, pretty: boolean): void {
|
|
37
33
|
const appConfig = program.appConfig;
|
|
38
34
|
if (!appConfig) {
|
|
39
35
|
return;
|
|
@@ -106,6 +102,31 @@ function configGetOutput(
|
|
|
106
102
|
}
|
|
107
103
|
}
|
|
108
104
|
|
|
105
|
+
const FROM_ENV_OPTION: CliOption = {
|
|
106
|
+
name: "from-env",
|
|
107
|
+
description: "Bind this key to its mapped environment variable (no literal value stored).",
|
|
108
|
+
kind: CliOptionKind.Presence,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
function configSetFromEnv(program: CliProgram, key: string): void {
|
|
112
|
+
const appConfig = program.appConfig;
|
|
113
|
+
if (!appConfig) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const entry = appConfig.entries[key];
|
|
117
|
+
if (!entry?.env) {
|
|
118
|
+
process.stderr.write(`Configuration key '${key}' has no env mapping for --from-env.\n`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const hostEnv = captureMappedHostEnv(program);
|
|
122
|
+
const { fileData } = bootstrapAppConfig(program, { validateFile: true });
|
|
123
|
+
let next = clearFileValue(fileData, key);
|
|
124
|
+
next = setBinding(next, key, "env");
|
|
125
|
+
writeAppConfigFile(program, next, { partial: true });
|
|
126
|
+
const resolved = resolveAppConfig(program, next, hostEnv);
|
|
127
|
+
exportConfigToEnv(program, resolved, hostEnv);
|
|
128
|
+
}
|
|
129
|
+
|
|
109
130
|
function configSetRun(program: CliProgram, key: string, rawValue: string, useJson: boolean): void {
|
|
110
131
|
const appConfig = program.appConfig;
|
|
111
132
|
if (!appConfig) {
|
|
@@ -135,8 +156,8 @@ function configSetRun(program: CliProgram, key: string, rawValue: string, useJso
|
|
|
135
156
|
|
|
136
157
|
const hostEnv = captureMappedHostEnv(program);
|
|
137
158
|
const { fileData } = bootstrapAppConfig(program, { validateFile: true });
|
|
138
|
-
const next = { ...fileData, [key]: parsed };
|
|
139
|
-
writeAppConfigFile(program, next);
|
|
159
|
+
const next = setBinding({ ...fileData, [key]: parsed }, key, "file");
|
|
160
|
+
writeAppConfigFile(program, next, { partial: true });
|
|
140
161
|
const resolved = resolveAppConfig(program, next, hostEnv);
|
|
141
162
|
exportConfigToEnv(program, resolved, hostEnv);
|
|
142
163
|
}
|
|
@@ -166,7 +187,7 @@ function configSetLeaf(program: CliProgram, mcpSetEnabled: boolean): CliLeaf {
|
|
|
166
187
|
return {
|
|
167
188
|
key: "set",
|
|
168
189
|
description: "Write one configuration key to the config file.",
|
|
169
|
-
options: [JSON_OPTION],
|
|
190
|
+
options: [JSON_OPTION, FROM_ENV_OPTION],
|
|
170
191
|
mcpTool: mcpSetEnabled ? undefined : { enabled: false },
|
|
171
192
|
positionals: [
|
|
172
193
|
{
|
|
@@ -191,10 +212,19 @@ function configSetLeaf(program: CliProgram, mcpSetEnabled: boolean): CliLeaf {
|
|
|
191
212
|
process.stderr.write("configure set requires a key.\n");
|
|
192
213
|
process.exit(1);
|
|
193
214
|
}
|
|
215
|
+
if (ctx.hasFlag("from-env")) {
|
|
216
|
+
const raw = ctx.args[1];
|
|
217
|
+
if (raw !== undefined && raw.length > 0) {
|
|
218
|
+
process.stderr.write("configure set --from-env does not accept a value.\n");
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
configSetFromEnv(program, key);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
194
224
|
const raw = ctx.args[1];
|
|
195
225
|
if (raw === undefined || raw.length === 0) {
|
|
196
226
|
if (!ctx.hasFlag("json")) {
|
|
197
|
-
process.stderr.write("configure set requires a value (or --
|
|
227
|
+
process.stderr.write("configure set requires a value (or --from-env).\n");
|
|
198
228
|
process.exit(1);
|
|
199
229
|
}
|
|
200
230
|
process.stderr.write("configure set requires a value.\n");
|
|
@@ -20,7 +20,7 @@ function enabledKinds(program: CliProgram, caps: CliCapabilities): Kind[] {
|
|
|
20
20
|
|
|
21
21
|
function joinEnglish(items: string[]): string {
|
|
22
22
|
if (items.length === 0) return "agent artifacts";
|
|
23
|
-
if (items.length === 1) return items[0]
|
|
23
|
+
if (items.length === 1) return items[0] ?? "agent artifacts";
|
|
24
24
|
if (items.length === 2) return `${items[0]} and ${items[1]}`;
|
|
25
25
|
return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
|
|
26
26
|
}
|
|
@@ -41,7 +41,7 @@ export function configureSyncOptionDescription(program: CliProgram, caps: CliCap
|
|
|
41
41
|
return `Refresh installed ${short(program, caps)}. Used by Homebrew post_install.`;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export function docsSkillTopicDescription(
|
|
44
|
+
export function docsSkillTopicDescription(_program: CliProgram, caps: CliCapabilities): string {
|
|
45
45
|
if (caps.configure) {
|
|
46
46
|
return "Print a reference agent SKILL; run `configure` to install an optimized copy.";
|
|
47
47
|
}
|
|
@@ -78,9 +78,6 @@ export function configureCommandNotes(program: CliProgram, _caps: CliCapabilitie
|
|
|
78
78
|
if (program.appConfig) {
|
|
79
79
|
lines.push("Remove app config only:", ` ${app} configure --remove-config --yes`, "");
|
|
80
80
|
}
|
|
81
|
-
lines.push(
|
|
82
|
-
"Use --dry to preview changes without writing files.",
|
|
83
|
-
"Use --json for machine-readable output.",
|
|
84
|
-
);
|
|
81
|
+
lines.push("Use --dry to preview changes without writing files.", "Use --json for machine-readable output.");
|
|
85
82
|
return lines.join("\n");
|
|
86
83
|
}
|
package/src/builtins/dispatch.ts
CHANGED
|
@@ -11,11 +11,7 @@ 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 {
|
|
15
|
-
CONFIGURE_RUN_KEY,
|
|
16
|
-
cliBuiltinConfigureCommand,
|
|
17
|
-
isConfigureConfigPath,
|
|
18
|
-
} from "./configure.ts";
|
|
14
|
+
import { CONFIGURE_RUN_KEY, cliBuiltinConfigureCommand, isConfigureConfigPath } from "./configure.ts";
|
|
19
15
|
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
20
16
|
import { cliPresentationRoot } from "./presentation.ts";
|
|
21
17
|
import { cliBuiltinVersionCommand } from "./version.ts";
|
|
@@ -35,11 +31,7 @@ function completionSchema(program: CliProgram, opts: DispatchBuiltinOpts): CliRo
|
|
|
35
31
|
/**
|
|
36
32
|
* Handles built-in commands after parse.
|
|
37
33
|
*/
|
|
38
|
-
export async function dispatchBuiltin(
|
|
39
|
-
program: CliProgram,
|
|
40
|
-
pr: ParseResult,
|
|
41
|
-
opts: DispatchBuiltinOpts,
|
|
42
|
-
): Promise<void> {
|
|
34
|
+
export async function dispatchBuiltin(program: CliProgram, pr: ParseResult, opts: DispatchBuiltinOpts): Promise<void> {
|
|
43
35
|
if (pr.kind !== ParseKind.Ok) {
|
|
44
36
|
return;
|
|
45
37
|
}
|
package/src/builtins/export.ts
CHANGED
|
@@ -41,9 +41,7 @@ function exportBuiltinNode(cmd: CliNode): CliSchemaExport | null {
|
|
|
41
41
|
if (cmd.fallbackMode !== undefined) {
|
|
42
42
|
out.fallbackMode = cmd.fallbackMode;
|
|
43
43
|
}
|
|
44
|
-
const children = cmd.commands
|
|
45
|
-
.map((ch) => exportBuiltinNode(ch))
|
|
46
|
-
.filter((ch): ch is CliSchemaExport => ch !== null);
|
|
44
|
+
const children = cmd.commands.map((ch) => exportBuiltinNode(ch)).filter((ch): ch is CliSchemaExport => ch !== null);
|
|
47
45
|
if (children.length > 0) {
|
|
48
46
|
out.commands = children;
|
|
49
47
|
}
|