argsbarg 5.1.0 → 5.1.1
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 +12 -1
- package/docs/configure.md +1 -1
- package/package.json +1 -1
- package/src/builtins/completion-bash.ts +2 -100
- package/src/builtins/completion-simulate-shared.ts +120 -0
- package/src/builtins/completion-zsh.ts +4 -102
- package/src/configure/prompt.ts +2 -4
- package/src/install/install-validate.test.ts +11 -0
- package/src/install/target-effective.ts +14 -2
- package/src/install/target-registry.ts +17 -0
- package/src/install/targets.test.ts +2 -0
- package/src/parse.ts +51 -172
- package/src/validate.ts +12 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [5.1.1] - 2026-07-04
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Configure without MCP** — skill-only apps (`mcpServer` unset) no longer prompt for MCP host config during interactive `configure`; MCP install targets default to disabled unless `mcpServer.enabled` is true.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **Internal DRY refactors** — shared bash/zsh completion simulate emitters, module-level `errorResult()` in the parser, unified configure stdin prompt via `readPromptLine`, and a shared `mcpServerRequiredForArtifact` predicate linking install defaults with schema validation.
|
|
19
|
+
|
|
10
20
|
## [5.1.0] - 2026-07-04
|
|
11
21
|
|
|
12
22
|
### Changed
|
|
@@ -585,7 +595,8 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
|
|
|
585
595
|
- 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`).
|
|
586
596
|
- Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
|
|
587
597
|
|
|
588
|
-
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v5.1.
|
|
598
|
+
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v5.1.1...HEAD
|
|
599
|
+
[5.1.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.1
|
|
589
600
|
[5.1.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.0
|
|
590
601
|
[5.0.3]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.0.3
|
|
591
602
|
[5.0.2]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.0.2
|
package/docs/configure.md
CHANGED
|
@@ -64,7 +64,7 @@ Non-interactive / CI: pass **`--yes`** (or **`--json`**, **`--sync`**, **`--remo
|
|
|
64
64
|
| Cursor skill | Y/n prompt | `~/.cursor/skills/<dir>/` when `~/.cursor` exists |
|
|
65
65
|
| Claude skill | Y/n prompt | `~/.claude/skills/<dir>/` when `~/.claude` exists |
|
|
66
66
|
| Codex / OpenCode / OpenClaw skills | Y/n prompt | Agent-specific dirs when available |
|
|
67
|
-
| MCP config | Y/n prompt | Cursor, Claude Code/Desktop, OpenCode, Codex, OpenClaw, ChatGPT desktop |
|
|
67
|
+
| MCP config | Y/n prompt when `mcpServer.enabled` | Cursor, Claude Code/Desktop, OpenCode, Codex, OpenClaw, ChatGPT desktop |
|
|
68
68
|
| App config | auto-runs wizard | Interactive wizard writes `~/.local/lib/<key>/config.json` |
|
|
69
69
|
|
|
70
70
|
### Externally managed binary (Homebrew)
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CliOptionKind, type CliRouter } from "../types.ts";
|
|
2
|
+
import { emitConsumeLong, emitConsumeShort, emitMatchChild } from "./completion-simulate-shared.ts";
|
|
2
3
|
import { collectScopes, type ScopeRec } from "./scopes.ts";
|
|
3
4
|
import {
|
|
4
5
|
escShellSingleQuoted,
|
|
@@ -8,105 +9,6 @@ import {
|
|
|
8
9
|
mainName,
|
|
9
10
|
} from "./shell-helpers.ts";
|
|
10
11
|
|
|
11
|
-
function emitConsumeLong(ident: string, scopes: ScopeRec[]): string {
|
|
12
|
-
let o = "_${ident}_nac_consume_long() {\n".replace("${ident}", ident);
|
|
13
|
-
o += ' local sid="$1" w="$2" nw="$3"\n';
|
|
14
|
-
o += " case $sid in\n";
|
|
15
|
-
for (const [i, sc] of scopes.entries()) {
|
|
16
|
-
o += ` ${i})\n`;
|
|
17
|
-
o += " case $w in\n";
|
|
18
|
-
o +=
|
|
19
|
-
" " +
|
|
20
|
-
kHelpLong +
|
|
21
|
-
"|${kHelpLong}=*|${kHelpShort}) echo 1 ;;\n"
|
|
22
|
-
.replace(/\$\{kHelpLong\}/g, kHelpLong)
|
|
23
|
-
.replace(/\$\{kHelpShort\}/g, kHelpShort);
|
|
24
|
-
for (const op of sc.opts) {
|
|
25
|
-
const base = `--${op.name}`;
|
|
26
|
-
if (op.kind === "presence") {
|
|
27
|
-
o += ` ${base}${"|${base}=*) echo 1 ;;\n".replace(/\$\{base\}/g, base)}`;
|
|
28
|
-
} else {
|
|
29
|
-
o += ` ${base}=*) echo 1 ;;\n`;
|
|
30
|
-
o += ` ${base}) echo 2 ;;\n`;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
o += " *) echo 0 ;;\n";
|
|
34
|
-
o += " esac\n";
|
|
35
|
-
o += " ;;\n";
|
|
36
|
-
}
|
|
37
|
-
o += " *) echo 0 ;;\n";
|
|
38
|
-
o += " esac\n";
|
|
39
|
-
o += "}\n";
|
|
40
|
-
return o;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function emitConsumeShort(ident: string, scopes: ScopeRec[]): string {
|
|
44
|
-
let o = "_${ident}_nac_consume_short() {\n".replace("${ident}", ident);
|
|
45
|
-
o += ' local sid="$1" w="$2"\n';
|
|
46
|
-
o += " case $sid in\n";
|
|
47
|
-
for (const [i, sc] of scopes.entries()) {
|
|
48
|
-
o += ` ${i})\n`;
|
|
49
|
-
o += " local rest=${w#-}\n";
|
|
50
|
-
o += " local ch\n";
|
|
51
|
-
o += " local saw=0\n";
|
|
52
|
-
o += " while [[ -n $rest ]]; do\n";
|
|
53
|
-
o += " ch=${rest:0:1}\n";
|
|
54
|
-
o += " rest=${rest:1}\n";
|
|
55
|
-
o += " case $ch in\n";
|
|
56
|
-
let boolChars = "";
|
|
57
|
-
for (const op of sc.opts) {
|
|
58
|
-
if (!op.shortName) continue;
|
|
59
|
-
if (op.kind === "presence") {
|
|
60
|
-
boolChars += `${op.shortName}|`;
|
|
61
|
-
} else {
|
|
62
|
-
o += ` ${op.shortName})\n`;
|
|
63
|
-
o += " if [[ $saw -ne 0 || -n $rest ]]; then echo 0; return; fi\n";
|
|
64
|
-
o += " echo 2; return ;;\n";
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (boolChars.length > 0) {
|
|
68
|
-
boolChars = boolChars.slice(0, -1);
|
|
69
|
-
o += ` ${boolChars}) ;;\n`;
|
|
70
|
-
}
|
|
71
|
-
o += " *) echo 0; return ;;\n";
|
|
72
|
-
o += " esac\n";
|
|
73
|
-
o += " saw=1\n";
|
|
74
|
-
o += " done\n";
|
|
75
|
-
o += " echo 1\n";
|
|
76
|
-
o += " ;;\n";
|
|
77
|
-
}
|
|
78
|
-
o += " *) echo 0 ;;\n";
|
|
79
|
-
o += " esac\n";
|
|
80
|
-
o += "}\n";
|
|
81
|
-
return o;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function emitMatchChild(
|
|
85
|
-
ident: string,
|
|
86
|
-
scopes: ScopeRec[],
|
|
87
|
-
pathIndex: Record<string, number>,
|
|
88
|
-
): string {
|
|
89
|
-
let o = "_${ident}_nac_match_child() {\n".replace("${ident}", ident);
|
|
90
|
-
o += ' local sid="$1" w="$2"\n';
|
|
91
|
-
o += " case $sid in\n";
|
|
92
|
-
for (const [sid, sc] of scopes.entries()) {
|
|
93
|
-
if (sc.kids.length === 0) continue;
|
|
94
|
-
o += ` ${sid})\n`;
|
|
95
|
-
o += " case $w in\n";
|
|
96
|
-
for (const ch of sc.kids) {
|
|
97
|
-
const childPath = sc.path === "" ? ch.key : `${sc.path}/${ch.key}`;
|
|
98
|
-
const cid = pathIndex[childPath] ?? 0;
|
|
99
|
-
o += ` ${ch.key}) echo ${cid}; return 0 ;;\n`;
|
|
100
|
-
}
|
|
101
|
-
o += " esac\n";
|
|
102
|
-
o += " ;;\n";
|
|
103
|
-
}
|
|
104
|
-
o += " esac\n";
|
|
105
|
-
o += " return 1\n";
|
|
106
|
-
o += "}\n";
|
|
107
|
-
return o;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
12
|
function emitSimulate(ident: string): string {
|
|
111
13
|
let o = "_${ident}_nac_simulate() {\n".replace("${ident}", ident);
|
|
112
14
|
o += " local i=1 sid=0 w steps next\n";
|
|
@@ -247,7 +149,7 @@ export function completionBashScript(schema: CliRouter): string {
|
|
|
247
149
|
}
|
|
248
150
|
|
|
249
151
|
out += emitConsumeLong(ident, scopes);
|
|
250
|
-
out += emitConsumeShort(ident, scopes);
|
|
152
|
+
out += emitConsumeShort(ident, scopes, "bash");
|
|
251
153
|
out += emitMatchChild(ident, scopes, pathIndex);
|
|
252
154
|
out += emitSimulate(ident);
|
|
253
155
|
out += emitEnumReplyBash(ident, scopes);
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Shared shell completion emitters for bash and zsh simulate helpers.
|
|
3
|
+
Generates _nac_consume_long, _nac_consume_short, and _nac_match_child functions
|
|
4
|
+
used while walking argv to determine completion scope.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ScopeRec } from "./scopes.ts";
|
|
8
|
+
import { kHelpLong, kHelpShort } from "./shell-helpers.ts";
|
|
9
|
+
|
|
10
|
+
/** Which shell dialect to emit for bundled short-flag parsing. */
|
|
11
|
+
export type CompletionShellDialect = "bash" | "zsh";
|
|
12
|
+
|
|
13
|
+
/** Emits `_<ident>_nac_consume_long` — identical for bash and zsh. */
|
|
14
|
+
export function emitConsumeLong(ident: string, scopes: ScopeRec[]): string {
|
|
15
|
+
let o = "_${ident}_nac_consume_long() {\n".replace("${ident}", ident);
|
|
16
|
+
o += ' local sid="$1" w="$2" nw="$3"\n';
|
|
17
|
+
o += " case $sid in\n";
|
|
18
|
+
for (const [i, sc] of scopes.entries()) {
|
|
19
|
+
o += ` ${i})\n`;
|
|
20
|
+
o += " case $w in\n";
|
|
21
|
+
o +=
|
|
22
|
+
" " +
|
|
23
|
+
kHelpLong +
|
|
24
|
+
"|${kHelpLong}=*|${kHelpShort}) echo 1 ;;\n"
|
|
25
|
+
.replace(/\$\{kHelpLong\}/g, kHelpLong)
|
|
26
|
+
.replace(/\$\{kHelpShort\}/g, kHelpShort);
|
|
27
|
+
for (const op of sc.opts) {
|
|
28
|
+
const base = `--${op.name}`;
|
|
29
|
+
if (op.kind === "presence") {
|
|
30
|
+
o += ` ${base}${"|${base}=*) echo 1 ;;\n".replace(/\$\{base\}/g, base)}`;
|
|
31
|
+
} else {
|
|
32
|
+
o += ` ${base}=*) echo 1 ;;\n`;
|
|
33
|
+
o += ` ${base}) echo 2 ;;\n`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
o += " *) echo 0 ;;\n";
|
|
37
|
+
o += " esac\n";
|
|
38
|
+
o += " ;;\n";
|
|
39
|
+
}
|
|
40
|
+
o += " *) echo 0 ;;\n";
|
|
41
|
+
o += " esac\n";
|
|
42
|
+
o += "}\n";
|
|
43
|
+
return o;
|
|
44
|
+
}
|
|
45
|
+
|
|
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 {
|
|
52
|
+
const firstChar = dialect === "bash" ? "ch=${rest:0:1}" : "ch=${rest[1,1]}";
|
|
53
|
+
const restAdvance = dialect === "bash" ? "rest=${rest:1}" : "rest=${rest[2,-1]}";
|
|
54
|
+
|
|
55
|
+
let o = "_${ident}_nac_consume_short() {\n".replace("${ident}", ident);
|
|
56
|
+
o += ' local sid="$1" w="$2"\n';
|
|
57
|
+
o += " case $sid in\n";
|
|
58
|
+
for (const [i, sc] of scopes.entries()) {
|
|
59
|
+
o += ` ${i})\n`;
|
|
60
|
+
o += " local rest=${w#-}\n";
|
|
61
|
+
o += " local ch\n";
|
|
62
|
+
o += " local saw=0\n";
|
|
63
|
+
o += " while [[ -n $rest ]]; do\n";
|
|
64
|
+
o += ` ${firstChar}\n`;
|
|
65
|
+
o += ` ${restAdvance}\n`;
|
|
66
|
+
o += " case $ch in\n";
|
|
67
|
+
let boolChars = "";
|
|
68
|
+
for (const op of sc.opts) {
|
|
69
|
+
if (!op.shortName) continue;
|
|
70
|
+
if (op.kind === "presence") {
|
|
71
|
+
boolChars += `${op.shortName}|`;
|
|
72
|
+
} else {
|
|
73
|
+
o += ` ${op.shortName})\n`;
|
|
74
|
+
o += " if [[ $saw -ne 0 || -n $rest ]]; then echo 0; return; fi\n";
|
|
75
|
+
o += " echo 2; return ;;\n";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (boolChars.length > 0) {
|
|
79
|
+
boolChars = boolChars.slice(0, -1);
|
|
80
|
+
o += ` ${boolChars}) ;;\n`;
|
|
81
|
+
}
|
|
82
|
+
o += " *) echo 0; return ;;\n";
|
|
83
|
+
o += " esac\n";
|
|
84
|
+
o += " saw=1\n";
|
|
85
|
+
o += " done\n";
|
|
86
|
+
o += " echo 1\n";
|
|
87
|
+
o += " ;;\n";
|
|
88
|
+
}
|
|
89
|
+
o += " *) echo 0 ;;\n";
|
|
90
|
+
o += " esac\n";
|
|
91
|
+
o += "}\n";
|
|
92
|
+
return o;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 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 {
|
|
101
|
+
let o = "_${ident}_nac_match_child() {\n".replace("${ident}", ident);
|
|
102
|
+
o += ' local sid="$1" w="$2"\n';
|
|
103
|
+
o += " case $sid in\n";
|
|
104
|
+
for (const [sid, sc] of scopes.entries()) {
|
|
105
|
+
if (sc.kids.length === 0) continue;
|
|
106
|
+
o += ` ${sid})\n`;
|
|
107
|
+
o += " case $w in\n";
|
|
108
|
+
for (const ch of sc.kids) {
|
|
109
|
+
const childPath = sc.path === "" ? ch.key : `${sc.path}/${ch.key}`;
|
|
110
|
+
const cid = pathIndex[childPath] ?? 0;
|
|
111
|
+
o += ` ${ch.key}) echo ${cid}; return 0 ;;\n`;
|
|
112
|
+
}
|
|
113
|
+
o += " esac\n";
|
|
114
|
+
o += " ;;\n";
|
|
115
|
+
}
|
|
116
|
+
o += " esac\n";
|
|
117
|
+
o += " return 1\n";
|
|
118
|
+
o += "}\n";
|
|
119
|
+
return o;
|
|
120
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CliOptionKind, type CliRouter } from "../types.ts";
|
|
2
|
+
import { emitConsumeLong, emitConsumeShort, emitMatchChild } from "./completion-simulate-shared.ts";
|
|
2
3
|
import { collectScopes, type ScopeRec } from "./scopes.ts";
|
|
3
4
|
import {
|
|
4
5
|
escShellSingleQuoted,
|
|
@@ -53,105 +54,6 @@ function emitScopeArraysZsh(ident: string, scopes: ScopeRec[]): string {
|
|
|
53
54
|
return out;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
function emitConsumeLongZsh(ident: string, scopes: ScopeRec[]): string {
|
|
57
|
-
let o = "_${ident}_nac_consume_long() {\n".replace("${ident}", ident);
|
|
58
|
-
o += ' local sid="$1" w="$2" nw="$3"\n';
|
|
59
|
-
o += " case $sid in\n";
|
|
60
|
-
for (const [i, sc] of scopes.entries()) {
|
|
61
|
-
o += ` ${i})\n`;
|
|
62
|
-
o += " case $w in\n";
|
|
63
|
-
o +=
|
|
64
|
-
" " +
|
|
65
|
-
kHelpLong +
|
|
66
|
-
"|${kHelpLong}=*|${kHelpShort}) echo 1 ;;\n"
|
|
67
|
-
.replace(/\$\{kHelpLong\}/g, kHelpLong)
|
|
68
|
-
.replace(/\$\{kHelpShort\}/g, kHelpShort);
|
|
69
|
-
for (const op of sc.opts) {
|
|
70
|
-
const base = `--${op.name}`;
|
|
71
|
-
if (op.kind === "presence") {
|
|
72
|
-
o += ` ${base}${"|${base}=*) echo 1 ;;\n".replace(/\$\{base\}/g, base)}`;
|
|
73
|
-
} else {
|
|
74
|
-
o += ` ${base}=*) echo 1 ;;\n`;
|
|
75
|
-
o += ` ${base}) echo 2 ;;\n`;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
o += " *) echo 0 ;;\n";
|
|
79
|
-
o += " esac\n";
|
|
80
|
-
o += " ;;\n";
|
|
81
|
-
}
|
|
82
|
-
o += " *) echo 0 ;;\n";
|
|
83
|
-
o += " esac\n";
|
|
84
|
-
o += "}\n";
|
|
85
|
-
return o;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function emitConsumeShortZsh(ident: string, scopes: ScopeRec[]): string {
|
|
89
|
-
let o = "_${ident}_nac_consume_short() {\n".replace("${ident}", ident);
|
|
90
|
-
o += ' local sid="$1" w="$2"\n';
|
|
91
|
-
o += " case $sid in\n";
|
|
92
|
-
for (const [i, sc] of scopes.entries()) {
|
|
93
|
-
o += ` ${i})\n`;
|
|
94
|
-
o += " local rest=${w#-}\n";
|
|
95
|
-
o += " local ch\n";
|
|
96
|
-
o += " local saw=0\n";
|
|
97
|
-
o += " while [[ -n $rest ]]; do\n";
|
|
98
|
-
o += " ch=${rest[1,1]}\n";
|
|
99
|
-
o += " rest=${rest[2,-1]}\n";
|
|
100
|
-
o += " case $ch in\n";
|
|
101
|
-
let boolChars = "";
|
|
102
|
-
for (const op of sc.opts) {
|
|
103
|
-
if (!op.shortName) continue;
|
|
104
|
-
if (op.kind === "presence") {
|
|
105
|
-
boolChars += `${op.shortName}|`;
|
|
106
|
-
} else {
|
|
107
|
-
o += ` ${op.shortName})\n`;
|
|
108
|
-
o += " if [[ $saw -ne 0 || -n $rest ]]; then echo 0; return; fi\n";
|
|
109
|
-
o += " echo 2; return ;;\n";
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (boolChars.length > 0) {
|
|
113
|
-
boolChars = boolChars.slice(0, -1);
|
|
114
|
-
o += ` ${boolChars}) ;;\n`;
|
|
115
|
-
}
|
|
116
|
-
o += " *) echo 0; return ;;\n";
|
|
117
|
-
o += " esac\n";
|
|
118
|
-
o += " saw=1\n";
|
|
119
|
-
o += " done\n";
|
|
120
|
-
o += " echo 1\n";
|
|
121
|
-
o += " ;;\n";
|
|
122
|
-
}
|
|
123
|
-
o += " *) echo 0 ;;\n";
|
|
124
|
-
o += " esac\n";
|
|
125
|
-
o += "}\n";
|
|
126
|
-
return o;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function emitMatchChildZsh(
|
|
130
|
-
ident: string,
|
|
131
|
-
scopes: ScopeRec[],
|
|
132
|
-
pathIndex: Record<string, number>,
|
|
133
|
-
): string {
|
|
134
|
-
let o = "_${ident}_nac_match_child() {\n".replace("${ident}", ident);
|
|
135
|
-
o += ' local sid="$1" w="$2"\n';
|
|
136
|
-
o += " case $sid in\n";
|
|
137
|
-
for (const [sid, sc] of scopes.entries()) {
|
|
138
|
-
if (sc.kids.length === 0) continue;
|
|
139
|
-
o += ` ${sid})\n`;
|
|
140
|
-
o += " case $w in\n";
|
|
141
|
-
for (const ch of sc.kids) {
|
|
142
|
-
const childPath = sc.path === "" ? ch.key : `${sc.path}/${ch.key}`;
|
|
143
|
-
const cid = pathIndex[childPath] ?? 0;
|
|
144
|
-
o += ` ${ch.key}) echo ${cid}; return 0 ;;\n`;
|
|
145
|
-
}
|
|
146
|
-
o += " esac\n";
|
|
147
|
-
o += " ;;\n";
|
|
148
|
-
}
|
|
149
|
-
o += " esac\n";
|
|
150
|
-
o += " return 1\n";
|
|
151
|
-
o += "}\n";
|
|
152
|
-
return o;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
57
|
function emitSimulateZsh(ident: string): string {
|
|
156
58
|
let o = "_${ident}_nac_simulate() {\n".replace("${ident}", ident);
|
|
157
59
|
o += " local i=2 sid=0 w steps next\n";
|
|
@@ -264,9 +166,9 @@ export function completionZshScript(schema: CliRouter): string {
|
|
|
264
166
|
|
|
265
167
|
let out = `#compdef ${schema.key}\n\n`;
|
|
266
168
|
out += emitScopeArraysZsh(ident, scopes);
|
|
267
|
-
out +=
|
|
268
|
-
out +=
|
|
269
|
-
out +=
|
|
169
|
+
out += emitConsumeLong(ident, scopes);
|
|
170
|
+
out += emitConsumeShort(ident, scopes, "zsh");
|
|
171
|
+
out += emitMatchChild(ident, scopes, pathIndex);
|
|
270
172
|
out += emitSimulateZsh(ident);
|
|
271
173
|
out += emitEnumReplyZsh(ident, scopes);
|
|
272
174
|
out += emitMainBodyZsh(schema, ident);
|
package/src/configure/prompt.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
TTY prompts for per-target install, skip, or uninstall during interactive `configure`.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { readSync } from "node:fs";
|
|
6
5
|
import type { CliInstallArtifactKey } from "../install/target-types.ts";
|
|
6
|
+
import { readPromptLine } from "../prompt.ts";
|
|
7
7
|
|
|
8
8
|
/** Human-readable labels for each install artifact key in interactive prompts. */
|
|
9
9
|
const LABELS: Record<CliInstallArtifactKey, string> = {
|
|
@@ -35,9 +35,7 @@ export type TargetPromptAction = "install" | "skip" | "uninstall";
|
|
|
35
35
|
export function promptTargetAction(label: string, installed: boolean): TargetPromptAction | null {
|
|
36
36
|
const hint = installed ? `${label} [y/N]: ` : `${label} [Y/n]: `;
|
|
37
37
|
process.stderr.write(hint);
|
|
38
|
-
const
|
|
39
|
-
const n = readSync(0, buf, { length: 256 });
|
|
40
|
-
const ans = buf.toString("utf8", 0, n).trim().toLowerCase();
|
|
38
|
+
const ans = readPromptLine().trim().toLowerCase();
|
|
41
39
|
if (installed) {
|
|
42
40
|
if (ans === "n") return "uninstall";
|
|
43
41
|
return "skip";
|
|
@@ -63,4 +63,15 @@ describe("validateConfigureConfig", () => {
|
|
|
63
63
|
};
|
|
64
64
|
expect(() => cliValidateProgram(program)).not.toThrow();
|
|
65
65
|
});
|
|
66
|
+
|
|
67
|
+
test("rejects explicit MCP target without mcpServer", () => {
|
|
68
|
+
const program: CliProgram = {
|
|
69
|
+
...base,
|
|
70
|
+
configure: {
|
|
71
|
+
agentIntegration: "both",
|
|
72
|
+
targets: { cursorMcp: true },
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
expect(() => cliValidateProgram(program)).toThrow(/mcpServer\.enabled/);
|
|
76
|
+
});
|
|
66
77
|
});
|
|
@@ -7,7 +7,12 @@ import type {
|
|
|
7
7
|
InstallTargetSpec,
|
|
8
8
|
} from "../types.ts";
|
|
9
9
|
import type { InstallPaths } from "./paths.ts";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
AGENT_PAIRS,
|
|
12
|
+
INSTALL_ARTIFACT_KEYS,
|
|
13
|
+
installTargetForKey,
|
|
14
|
+
mcpServerRequiredForArtifact,
|
|
15
|
+
} from "./target-registry.ts";
|
|
11
16
|
import type { CliInstallArtifactKey, InstallPlanMode } from "./target-types.ts";
|
|
12
17
|
|
|
13
18
|
export type { InstallTargetSpec, ResolvedInstallTarget } from "../types.ts";
|
|
@@ -44,9 +49,13 @@ export function resolveInstallTargetSpec(
|
|
|
44
49
|
function artifactDefaults(
|
|
45
50
|
key: CliInstallArtifactKey,
|
|
46
51
|
integration: InstallAgentIntegration,
|
|
52
|
+
mcpServerEnabled: boolean,
|
|
47
53
|
): { enabled: boolean; includedInAll: boolean } {
|
|
48
54
|
const target = installTargetForKey(key);
|
|
49
55
|
const includedInAll = target?.defaultIncludedInAll(integration) ?? false;
|
|
56
|
+
if (!mcpServerRequiredForArtifact(key, mcpServerEnabled)) {
|
|
57
|
+
return { enabled: false, includedInAll: false };
|
|
58
|
+
}
|
|
50
59
|
return { enabled: true, includedInAll };
|
|
51
60
|
}
|
|
52
61
|
|
|
@@ -86,7 +95,10 @@ export function resolveEffectiveInstallTargets(
|
|
|
86
95
|
const user = configure?.targets;
|
|
87
96
|
const out = {} as Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>;
|
|
88
97
|
for (const key of INSTALL_ARTIFACT_KEYS) {
|
|
89
|
-
out[key] = resolveInstallTargetSpec(
|
|
98
|
+
out[key] = resolveInstallTargetSpec(
|
|
99
|
+
user?.[key],
|
|
100
|
+
artifactDefaults(key, integration, mcpEnabled),
|
|
101
|
+
);
|
|
90
102
|
}
|
|
91
103
|
applyAgentPairDedupe(out, user, integration);
|
|
92
104
|
return out;
|
|
@@ -37,6 +37,23 @@ export function installTargetForKey(key: CliInstallArtifactKey): InstallTarget |
|
|
|
37
37
|
return targetByKey.get(key);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/** True when the artifact key is an MCP install target (requires mcpServer.enabled). */
|
|
41
|
+
export function isMcpArtifactKey(key: CliInstallArtifactKey): boolean {
|
|
42
|
+
return installTargetForKey(key)?.category === "mcp";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* MCP install targets are inactive without mcpServer.enabled.
|
|
47
|
+
* Defaults: artifactDefaults in target-effective.ts.
|
|
48
|
+
* Explicit spec rejection: validateConfigureConfig in validate.ts.
|
|
49
|
+
*/
|
|
50
|
+
export function mcpServerRequiredForArtifact(
|
|
51
|
+
key: CliInstallArtifactKey,
|
|
52
|
+
mcpServerEnabled: boolean,
|
|
53
|
+
): boolean {
|
|
54
|
+
return !isMcpArtifactKey(key) || mcpServerEnabled;
|
|
55
|
+
}
|
|
56
|
+
|
|
40
57
|
/** Lookup by plan action kind. */
|
|
41
58
|
export function installTargetForActionKind(kind: InstallActionKind): InstallTarget | undefined {
|
|
42
59
|
return INSTALL_TARGETS.find((t) => t.actionKind === kind);
|
|
@@ -50,7 +50,9 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
50
50
|
const program: Pick<CliProgram, "mcpServer"> = {};
|
|
51
51
|
const t = resolveEffectiveInstallTargets(undefined, program);
|
|
52
52
|
expect(t.cursorSkill.includedInAll).toBe(true);
|
|
53
|
+
expect(t.cursorSkill.enabled).toBe(true);
|
|
53
54
|
expect(t.cursorMcp.includedInAll).toBe(false);
|
|
55
|
+
expect(t.cursorMcp.enabled).toBe(false);
|
|
54
56
|
});
|
|
55
57
|
|
|
56
58
|
test("mcp mode includes MCP in --all not paired skills", () => {
|
package/src/parse.ts
CHANGED
|
@@ -249,21 +249,6 @@ function finishLeaf(
|
|
|
249
249
|
optionDefs: CliOption[],
|
|
250
250
|
forcePositionalsIn: boolean,
|
|
251
251
|
): ParseResult {
|
|
252
|
-
/** Builds a parse error for positional consumption failures. */
|
|
253
|
-
function errorResult(msg: string): ParseResult {
|
|
254
|
-
const pr: ParseResult = {
|
|
255
|
-
kind: ParseKind.Error,
|
|
256
|
-
path: [],
|
|
257
|
-
opts: {},
|
|
258
|
-
args: [],
|
|
259
|
-
helpExplicit: false,
|
|
260
|
-
helpPath: [],
|
|
261
|
-
errorMsg: msg,
|
|
262
|
-
errorHelpPath: path,
|
|
263
|
-
};
|
|
264
|
-
return pr;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
252
|
let idx = startIdx;
|
|
268
253
|
const args: string[] = [];
|
|
269
254
|
let forcePositionals = forcePositionalsIn;
|
|
@@ -273,7 +258,7 @@ function finishLeaf(
|
|
|
273
258
|
if (argMax === 1) {
|
|
274
259
|
if (argMin >= 1) {
|
|
275
260
|
if (idx >= argv.length) {
|
|
276
|
-
return errorResult(`Missing positional argument: ${p.name}
|
|
261
|
+
return errorResult(`Missing positional argument: ${p.name}`, path, []);
|
|
277
262
|
}
|
|
278
263
|
args.push(argv[idx]);
|
|
279
264
|
idx += 1;
|
|
@@ -308,7 +293,7 @@ function finishLeaf(
|
|
|
308
293
|
// MUST be false — lenient mode swallows unknown flags as positionals silently
|
|
309
294
|
const tailRep = consumeOptions(optionDefs, false, argv, idx, opts);
|
|
310
295
|
if (tailRep.report.err) {
|
|
311
|
-
return errorResult(tailRep.report.err);
|
|
296
|
+
return errorResult(tailRep.report.err, path, []);
|
|
312
297
|
}
|
|
313
298
|
if (tailRep.report.sawDoubleDash) {
|
|
314
299
|
forcePositionals = true;
|
|
@@ -317,7 +302,7 @@ function finishLeaf(
|
|
|
317
302
|
idx = tailRep.nextIndex;
|
|
318
303
|
continue;
|
|
319
304
|
}
|
|
320
|
-
return errorResult(`Unexpected option token: ${tok}
|
|
305
|
+
return errorResult(`Unexpected option token: ${tok}`, path, []);
|
|
321
306
|
}
|
|
322
307
|
|
|
323
308
|
args.push(tok);
|
|
@@ -332,13 +317,17 @@ function finishLeaf(
|
|
|
332
317
|
}
|
|
333
318
|
}
|
|
334
319
|
if (count < argMin) {
|
|
335
|
-
return errorResult(
|
|
320
|
+
return errorResult(
|
|
321
|
+
`Expected at least ${argMin} argument(s) for ${p.name}, got ${count}`,
|
|
322
|
+
path,
|
|
323
|
+
[],
|
|
324
|
+
);
|
|
336
325
|
}
|
|
337
326
|
}
|
|
338
327
|
|
|
339
328
|
if (idx < argv.length) {
|
|
340
329
|
if (forcePositionals) {
|
|
341
|
-
return errorResult("Unexpected extra arguments");
|
|
330
|
+
return errorResult("Unexpected extra arguments", path, []);
|
|
342
331
|
}
|
|
343
332
|
|
|
344
333
|
if (isHelpTok(argv[idx])) {
|
|
@@ -347,12 +336,12 @@ function finishLeaf(
|
|
|
347
336
|
|
|
348
337
|
const tailRep = consumeOptions(optionDefs, false, argv, idx, opts);
|
|
349
338
|
if (tailRep.report.err) {
|
|
350
|
-
return errorResult(tailRep.report.err);
|
|
339
|
+
return errorResult(tailRep.report.err, path, []);
|
|
351
340
|
}
|
|
352
341
|
idx = tailRep.nextIndex;
|
|
353
342
|
|
|
354
343
|
if (idx < argv.length) {
|
|
355
|
-
return errorResult("Unexpected extra arguments");
|
|
344
|
+
return errorResult("Unexpected extra arguments", path, []);
|
|
356
345
|
}
|
|
357
346
|
}
|
|
358
347
|
|
|
@@ -370,6 +359,24 @@ function finishLeaf(
|
|
|
370
359
|
|
|
371
360
|
// ── Main Parser ───────────────────────────────────────────────────────────────
|
|
372
361
|
|
|
362
|
+
/** Builds a user-error parse result; `path` defaults to `errorHelpPath`. */
|
|
363
|
+
function errorResult(
|
|
364
|
+
errorMsg: string,
|
|
365
|
+
errorHelpPath: string[] = [],
|
|
366
|
+
path: string[] = errorHelpPath,
|
|
367
|
+
): ParseResult {
|
|
368
|
+
return {
|
|
369
|
+
kind: ParseKind.Error,
|
|
370
|
+
path,
|
|
371
|
+
opts: {},
|
|
372
|
+
args: [],
|
|
373
|
+
helpExplicit: false,
|
|
374
|
+
helpPath: [],
|
|
375
|
+
errorMsg,
|
|
376
|
+
errorHelpPath,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
373
380
|
/** Builds a help-request result for the current routing path. */
|
|
374
381
|
function helpResult(p: string[], explicit: boolean): ParseResult {
|
|
375
382
|
return {
|
|
@@ -401,16 +408,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
401
408
|
// Consume root-level options first
|
|
402
409
|
const rootRep = consumeOptions(root.options ?? [], rootLenient, argv, i, opts);
|
|
403
410
|
if (rootRep.report.err) {
|
|
404
|
-
return
|
|
405
|
-
kind: ParseKind.Error,
|
|
406
|
-
path: [],
|
|
407
|
-
opts: {},
|
|
408
|
-
args: [],
|
|
409
|
-
helpExplicit: false,
|
|
410
|
-
helpPath: [],
|
|
411
|
-
errorMsg: rootRep.report.err,
|
|
412
|
-
errorHelpPath: [],
|
|
413
|
-
};
|
|
411
|
+
return errorResult(rootRep.report.err);
|
|
414
412
|
}
|
|
415
413
|
i = rootRep.nextIndex;
|
|
416
414
|
let forcePositionals = rootRep.report.sawDoubleDash;
|
|
@@ -436,16 +434,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
436
434
|
cmdName = root.fallbackCommand;
|
|
437
435
|
node = findChild(root.commands, cmdName);
|
|
438
436
|
if (!node) {
|
|
439
|
-
return {
|
|
440
|
-
kind: ParseKind.Error,
|
|
441
|
-
path: [],
|
|
442
|
-
opts: {},
|
|
443
|
-
args: [],
|
|
444
|
-
helpExplicit: false,
|
|
445
|
-
helpPath: [],
|
|
446
|
-
errorMsg: `Unknown command: ${cmdName}`,
|
|
447
|
-
errorHelpPath: path,
|
|
448
|
-
};
|
|
437
|
+
return errorResult(`Unknown command: ${cmdName}`, path, []);
|
|
449
438
|
}
|
|
450
439
|
} else {
|
|
451
440
|
return helpResult([], false);
|
|
@@ -469,34 +458,20 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
469
458
|
cmdName = fallbackCommand;
|
|
470
459
|
node = findChild(root.commands, cmdName);
|
|
471
460
|
if (!node) {
|
|
472
|
-
return {
|
|
473
|
-
kind: ParseKind.Error,
|
|
474
|
-
path: [],
|
|
475
|
-
opts: {},
|
|
476
|
-
args: [],
|
|
477
|
-
helpExplicit: false,
|
|
478
|
-
helpPath: [],
|
|
479
|
-
errorMsg: `Unknown command: ${cmdName}`,
|
|
480
|
-
errorHelpPath: path,
|
|
481
|
-
};
|
|
461
|
+
return errorResult(`Unknown command: ${cmdName}`, path, []);
|
|
482
462
|
}
|
|
483
463
|
} else {
|
|
484
464
|
cmdName = peek;
|
|
485
465
|
if (!forcePositionals) i += 1;
|
|
486
466
|
node = findChild(root.commands, cmdName);
|
|
487
467
|
if (!node) {
|
|
488
|
-
return
|
|
489
|
-
|
|
490
|
-
path: [],
|
|
491
|
-
opts: {},
|
|
492
|
-
args: [],
|
|
493
|
-
helpExplicit: false,
|
|
494
|
-
helpPath: [],
|
|
495
|
-
errorMsg: forcePositionals
|
|
468
|
+
return errorResult(
|
|
469
|
+
forcePositionals
|
|
496
470
|
? `Expected subcommand but got positional: ${cmdName}`
|
|
497
471
|
: `Unknown command: ${cmdName}`,
|
|
498
|
-
|
|
499
|
-
|
|
472
|
+
path,
|
|
473
|
+
[],
|
|
474
|
+
);
|
|
500
475
|
}
|
|
501
476
|
}
|
|
502
477
|
}
|
|
@@ -504,16 +479,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
504
479
|
|
|
505
480
|
path.push(cmdName);
|
|
506
481
|
if (!node) {
|
|
507
|
-
return {
|
|
508
|
-
kind: ParseKind.Error,
|
|
509
|
-
path,
|
|
510
|
-
opts: {},
|
|
511
|
-
args: [],
|
|
512
|
-
helpExplicit: false,
|
|
513
|
-
helpPath: [],
|
|
514
|
-
errorMsg: `Unknown command: ${cmdName}`,
|
|
515
|
-
errorHelpPath: path,
|
|
516
|
-
};
|
|
482
|
+
return errorResult(`Unknown command: ${cmdName}`, path);
|
|
517
483
|
}
|
|
518
484
|
let current = node;
|
|
519
485
|
|
|
@@ -522,16 +488,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
522
488
|
if (!forcePositionals) {
|
|
523
489
|
const orep = consumeOptions(collectOptionDefs(root, path), false, argv, i, opts);
|
|
524
490
|
if (orep.report.err) {
|
|
525
|
-
return
|
|
526
|
-
kind: ParseKind.Error,
|
|
527
|
-
path,
|
|
528
|
-
opts: {},
|
|
529
|
-
args: [],
|
|
530
|
-
helpExplicit: false,
|
|
531
|
-
helpPath: [],
|
|
532
|
-
errorMsg: orep.report.err,
|
|
533
|
-
errorHelpPath: path,
|
|
534
|
-
};
|
|
491
|
+
return errorResult(orep.report.err, path);
|
|
535
492
|
}
|
|
536
493
|
i = orep.nextIndex;
|
|
537
494
|
if (orep.report.sawDoubleDash) {
|
|
@@ -576,16 +533,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
576
533
|
|
|
577
534
|
const tok = argv[i];
|
|
578
535
|
if (!forcePositionals && tok.startsWith("-")) {
|
|
579
|
-
return {
|
|
580
|
-
kind: ParseKind.Error,
|
|
581
|
-
path,
|
|
582
|
-
opts: {},
|
|
583
|
-
args: [],
|
|
584
|
-
helpExplicit: false,
|
|
585
|
-
helpPath: [],
|
|
586
|
-
errorMsg: `Unexpected option token: ${tok}`,
|
|
587
|
-
errorHelpPath: path,
|
|
588
|
-
};
|
|
536
|
+
return errorResult(`Unexpected option token: ${tok}`, path);
|
|
589
537
|
}
|
|
590
538
|
|
|
591
539
|
if (!forcePositionals && isCliRouter(current)) {
|
|
@@ -614,18 +562,12 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
614
562
|
}
|
|
615
563
|
}
|
|
616
564
|
|
|
617
|
-
return
|
|
618
|
-
|
|
619
|
-
path,
|
|
620
|
-
opts: {},
|
|
621
|
-
args: [],
|
|
622
|
-
helpExplicit: false,
|
|
623
|
-
helpPath: [],
|
|
624
|
-
errorMsg: forcePositionals
|
|
565
|
+
return errorResult(
|
|
566
|
+
forcePositionals
|
|
625
567
|
? `Expected subcommand but got positional: ${tok}`
|
|
626
568
|
: `Unknown subcommand: ${tok}`,
|
|
627
|
-
|
|
628
|
-
|
|
569
|
+
path,
|
|
570
|
+
);
|
|
629
571
|
}
|
|
630
572
|
|
|
631
573
|
if (!isCliLeaf(current)) {
|
|
@@ -656,29 +598,11 @@ export function postParseValidate(root: CliNode, pr: ParseResult): ParseResult {
|
|
|
656
598
|
|
|
657
599
|
for (const seg of pr.path) {
|
|
658
600
|
if (!isCliRouter(node)) {
|
|
659
|
-
return
|
|
660
|
-
kind: ParseKind.Error,
|
|
661
|
-
path: pr.path,
|
|
662
|
-
opts: {},
|
|
663
|
-
args: [],
|
|
664
|
-
helpExplicit: false,
|
|
665
|
-
helpPath: [],
|
|
666
|
-
errorMsg: "Internal path error",
|
|
667
|
-
errorHelpPath: pr.path,
|
|
668
|
-
};
|
|
601
|
+
return errorResult("Internal path error", pr.path);
|
|
669
602
|
}
|
|
670
603
|
const ch = findChild(node.commands, seg);
|
|
671
604
|
if (!ch) {
|
|
672
|
-
return
|
|
673
|
-
kind: ParseKind.Error,
|
|
674
|
-
path: pr.path,
|
|
675
|
-
opts: {},
|
|
676
|
-
args: [],
|
|
677
|
-
helpExplicit: false,
|
|
678
|
-
helpPath: [],
|
|
679
|
-
errorMsg: "Internal path error",
|
|
680
|
-
errorHelpPath: pr.path,
|
|
681
|
-
};
|
|
605
|
+
return errorResult("Internal path error", pr.path);
|
|
682
606
|
}
|
|
683
607
|
defs.push(...(ch.options ?? []));
|
|
684
608
|
node = ch;
|
|
@@ -693,60 +617,24 @@ export function postParseValidate(root: CliNode, pr: ParseResult): ParseResult {
|
|
|
693
617
|
|
|
694
618
|
for (const d of defs) {
|
|
695
619
|
if (d.required && !(d.name in opts)) {
|
|
696
|
-
return {
|
|
697
|
-
kind: ParseKind.Error,
|
|
698
|
-
path: pr.path,
|
|
699
|
-
opts: {},
|
|
700
|
-
args: [],
|
|
701
|
-
helpExplicit: false,
|
|
702
|
-
helpPath: [],
|
|
703
|
-
errorMsg: `Missing required option: --${d.name}`,
|
|
704
|
-
errorHelpPath: pr.path,
|
|
705
|
-
};
|
|
620
|
+
return errorResult(`Missing required option: --${d.name}`, pr.path);
|
|
706
621
|
}
|
|
707
622
|
}
|
|
708
623
|
|
|
709
624
|
for (const [k, v] of Object.entries(opts)) {
|
|
710
625
|
const d = findOptionByName(defs, k);
|
|
711
626
|
if (!d) {
|
|
712
|
-
return {
|
|
713
|
-
kind: ParseKind.Error,
|
|
714
|
-
path: pr.path,
|
|
715
|
-
opts: {},
|
|
716
|
-
args: [],
|
|
717
|
-
helpExplicit: false,
|
|
718
|
-
helpPath: [],
|
|
719
|
-
errorMsg: `Unknown option key: ${k}`,
|
|
720
|
-
errorHelpPath: pr.path,
|
|
721
|
-
};
|
|
627
|
+
return errorResult(`Unknown option key: ${k}`, pr.path);
|
|
722
628
|
}
|
|
723
629
|
if (d.kind === CliOptionKind.Number) {
|
|
724
630
|
if (!fullStringIsDouble(v)) {
|
|
725
|
-
return {
|
|
726
|
-
kind: ParseKind.Error,
|
|
727
|
-
path: pr.path,
|
|
728
|
-
opts: {},
|
|
729
|
-
args: [],
|
|
730
|
-
helpExplicit: false,
|
|
731
|
-
helpPath: [],
|
|
732
|
-
errorMsg: `Invalid number for option --${k}: ${v}`,
|
|
733
|
-
errorHelpPath: pr.path,
|
|
734
|
-
};
|
|
631
|
+
return errorResult(`Invalid number for option --${k}: ${v}`, pr.path);
|
|
735
632
|
}
|
|
736
633
|
}
|
|
737
634
|
if (d.kind === CliOptionKind.Enum) {
|
|
738
635
|
const choices = d.choices ?? [];
|
|
739
636
|
if (!choices.includes(v)) {
|
|
740
|
-
return {
|
|
741
|
-
kind: ParseKind.Error,
|
|
742
|
-
path: pr.path,
|
|
743
|
-
opts: {},
|
|
744
|
-
args: [],
|
|
745
|
-
helpExplicit: false,
|
|
746
|
-
helpPath: [],
|
|
747
|
-
errorMsg: `Option --${k}: '${v}' is not one of: ${choices.join(", ")}`,
|
|
748
|
-
errorHelpPath: pr.path,
|
|
749
|
-
};
|
|
637
|
+
return errorResult(`Option --${k}: '${v}' is not one of: ${choices.join(", ")}`, pr.path);
|
|
750
638
|
}
|
|
751
639
|
}
|
|
752
640
|
if (d.kind === CliOptionKind.String && (d.format !== undefined || d.pattern !== undefined)) {
|
|
@@ -759,16 +647,7 @@ export function postParseValidate(root: CliNode, pr: ParseResult): ParseResult {
|
|
|
759
647
|
: err instanceof Error
|
|
760
648
|
? err.message
|
|
761
649
|
: String(err);
|
|
762
|
-
return {
|
|
763
|
-
kind: ParseKind.Error,
|
|
764
|
-
path: pr.path,
|
|
765
|
-
opts: {},
|
|
766
|
-
args: [],
|
|
767
|
-
helpExplicit: false,
|
|
768
|
-
helpPath: [],
|
|
769
|
-
errorMsg: `Invalid value for option --${k}: ${msg}`,
|
|
770
|
-
errorHelpPath: pr.path,
|
|
771
|
-
};
|
|
650
|
+
return errorResult(`Invalid value for option --${k}: ${msg}`, pr.path);
|
|
772
651
|
}
|
|
773
652
|
}
|
|
774
653
|
}
|
package/src/validate.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { reservedCommandNames, resolveCapabilities } from "./capabilities.ts";
|
|
|
6
6
|
import { reservedDocsTopicResourceUris } from "./docs/mcp-resources.ts";
|
|
7
7
|
import { DOCS_BUILTIN_TOPIC_KEYS } from "./docs/resolve.ts";
|
|
8
8
|
import { validateFormatValue } from "./formats.ts";
|
|
9
|
-
import { AGENT_PAIRS } from "./install/target-registry.ts";
|
|
9
|
+
import { AGENT_PAIRS, MCP_KEYS, mcpServerRequiredForArtifact } from "./install/target-registry.ts";
|
|
10
10
|
import { resolveMcpSchemaUri } from "./mcp/tools.ts";
|
|
11
11
|
import {
|
|
12
12
|
type CliLeaf,
|
|
@@ -150,6 +150,8 @@ function validateConfigureConfig(program: CliProgram): void {
|
|
|
150
150
|
const integration: InstallAgentIntegration =
|
|
151
151
|
configure.agentIntegration ?? (program.mcpServer?.enabled === true ? "mcp" : "skill");
|
|
152
152
|
|
|
153
|
+
const mcpEnabled = program.mcpServer?.enabled === true;
|
|
154
|
+
|
|
153
155
|
for (const [mcpKey, skillKey] of AGENT_PAIRS) {
|
|
154
156
|
const mcpSpec = targets[mcpKey];
|
|
155
157
|
const skillSpec = targets[skillKey];
|
|
@@ -174,6 +176,15 @@ function validateConfigureConfig(program: CliProgram): void {
|
|
|
174
176
|
);
|
|
175
177
|
}
|
|
176
178
|
}
|
|
179
|
+
|
|
180
|
+
for (const mcpKey of MCP_KEYS) {
|
|
181
|
+
if (
|
|
182
|
+
!mcpServerRequiredForArtifact(mcpKey, mcpEnabled) &&
|
|
183
|
+
installTargetExplicitTruthy(targets[mcpKey])
|
|
184
|
+
) {
|
|
185
|
+
throw new CliSchemaValidationError(`configure.targets.${mcpKey} requires mcpServer.enabled`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
177
188
|
}
|
|
178
189
|
|
|
179
190
|
/** Validates a program schema. */
|