argsbarg 4.1.1 → 5.0.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +5 -5
  3. package/docs/README.md +3 -3
  4. package/docs/ai-skills.md +9 -9
  5. package/docs/bundled-docs.md +4 -4
  6. package/docs/cli-program.md +8 -8
  7. package/docs/config-schema.md +2 -2
  8. package/docs/configure.md +177 -0
  9. package/docs/developing.md +1 -1
  10. package/docs/distribution-homebrew.md +10 -9
  11. package/docs/mcp.md +9 -9
  12. package/examples/full-example/README.md +3 -3
  13. package/examples/full-example/justfile +6 -6
  14. package/examples/full-example/scripts/formula-shared.ts +2 -2
  15. package/examples/full-example/src/program.ts +3 -3
  16. package/examples/nested.ts +1 -1
  17. package/index.d.ts +23 -16
  18. package/package.json +1 -1
  19. package/src/builtins/builtins.test.ts +82 -61
  20. package/src/builtins/completion-group.ts +4 -6
  21. package/src/builtins/configure-copy.ts +86 -0
  22. package/src/builtins/configure.ts +70 -0
  23. package/src/builtins/dispatch.ts +13 -33
  24. package/src/builtins/index.ts +1 -1
  25. package/src/builtins/mcp.ts +2 -2
  26. package/src/builtins/registry.ts +6 -6
  27. package/src/capabilities.ts +22 -13
  28. package/src/cli-tool/cli-smoke.test.ts +13 -3
  29. package/src/cli-tool/create.test.ts +23 -1
  30. package/src/cli-tool/create.ts +26 -4
  31. package/src/cli-tool/full-example-capabilities.test.ts +2 -2
  32. package/src/cli-tool/program.ts +20 -5
  33. package/src/cli-tool/run-create.ts +8 -19
  34. package/src/config/bootstrap.ts +11 -9
  35. package/src/config/file.test.ts +1 -1
  36. package/src/config/resolve.ts +2 -2
  37. package/src/configure/configure.test.ts +148 -0
  38. package/src/configure/index.ts +284 -0
  39. package/src/configure/prompt.ts +40 -0
  40. package/src/docs/builtin.ts +3 -5
  41. package/src/docs/docs.test.ts +5 -5
  42. package/src/docs/mcp-guide.ts +4 -4
  43. package/src/index.ts +2 -2
  44. package/src/install/install-validate.test.ts +5 -5
  45. package/src/install/opts.ts +17 -0
  46. package/src/install/target-effective.ts +8 -8
  47. package/src/install/target-scope.ts +11 -8
  48. package/src/install/targets/configure.ts +1 -1
  49. package/src/install/targets.test.ts +4 -4
  50. package/src/invoke.test.ts +1 -1
  51. package/src/mcp/tools.ts +1 -1
  52. package/src/mcp.integration.test.ts +4 -4
  53. package/src/parse.test.ts +11 -13
  54. package/src/schema.ts +1 -9
  55. package/src/skill/hint.ts +2 -2
  56. package/src/types.ts +22 -14
  57. package/src/validate.ts +17 -17
  58. package/docs/install.md +0 -206
  59. package/src/builtins/install.ts +0 -106
  60. package/src/builtins/uninstall.ts +0 -80
  61. package/src/install/index.ts +0 -409
  62. package/src/install/install.test.ts +0 -317
@@ -0,0 +1,284 @@
1
+ import { resolveCapabilities } from "../capabilities.ts";
2
+ import { displayAppConfigPath, runConfigure } from "../config/bootstrap.ts";
3
+ import { resolveInstallPaths } from "../install/paths.ts";
4
+ import { buildInstallPlan, buildUpdatePlan } from "../install/plan.ts";
5
+ import {
6
+ installErr,
7
+ installInfo,
8
+ installOut,
9
+ printInstallStatus,
10
+ writeInteractiveInstallIntro,
11
+ } from "../install/status.ts";
12
+ import { resolveEffectiveInstallTargets } from "../install/target-effective.ts";
13
+ import { runTargetPreflight } from "../install/target-plan-build.ts";
14
+ import { INSTALL_TARGETS, installTargetForKey } from "../install/target-registry.ts";
15
+ import { buildDetectedSnapshot, buildTargetPlanContext } from "../install/target-scope.ts";
16
+ import type {
17
+ CliInstallArtifactKey,
18
+ InstallAction,
19
+ InstallActionKind,
20
+ InstallOpts,
21
+ TargetPlanContext,
22
+ UninstallAction,
23
+ } from "../install/target-types.ts";
24
+ import {
25
+ buildUninstallPlan,
26
+ skillDirFromUninstallSummary,
27
+ uninstallSkillDir,
28
+ } from "../install/uninstall.ts";
29
+ import { cliSkillInstall, skillTargetFromActionKind } from "../skill/install.ts";
30
+ import type { CliProgram } from "../types.ts";
31
+ import { artifactPromptLabel, promptTargetAction } from "./prompt.ts";
32
+
33
+ /** True when interactive `configure` should auto-run the app config wizard. */
34
+ export function appConfigHasEntries(program: CliProgram): boolean {
35
+ const entries = program.appConfig?.entries;
36
+ return !!entries && Object.keys(entries).length > 0;
37
+ }
38
+
39
+ export interface ConfigureOpts {
40
+ sync?: boolean;
41
+ removeAll?: boolean;
42
+ removeConfig?: boolean;
43
+ status?: boolean;
44
+ yes?: boolean;
45
+ dry?: boolean;
46
+ json?: boolean;
47
+ }
48
+
49
+ export function parseConfigureOpts(raw: Record<string, string>): ConfigureOpts {
50
+ const flag = (name: string) => raw[name] === "1";
51
+ return {
52
+ sync: flag("sync"),
53
+ removeAll: flag("remove-all"),
54
+ removeConfig: flag("remove-config"),
55
+ status: flag("status"),
56
+ yes: flag("yes"),
57
+ dry: flag("dry"),
58
+ json: flag("json"),
59
+ };
60
+ }
61
+
62
+ export function validateConfigureOpts(opts: ConfigureOpts): string | null {
63
+ const flags = [opts.sync, opts.removeAll, opts.removeConfig, opts.status].filter(Boolean);
64
+ if (flags.length > 1) {
65
+ return "Use only one of --sync, --remove-all, --remove-config, or --status.";
66
+ }
67
+ if (opts.json) {
68
+ opts.yes = true;
69
+ }
70
+ if ((opts.sync || opts.removeAll || opts.removeConfig) && !opts.yes) {
71
+ return "--yes is required with --sync, --remove-all, or --remove-config.";
72
+ }
73
+ return null;
74
+ }
75
+
76
+ function configureToInstallOpts(opts: ConfigureOpts): InstallOpts {
77
+ if (opts.status) {
78
+ return { status: true, yes: opts.yes, dry: opts.dry, json: opts.json };
79
+ }
80
+ if (opts.sync) {
81
+ return { reinstall: true, yes: true, dry: opts.dry, json: opts.json };
82
+ }
83
+ if (opts.removeAll) {
84
+ return { uninstall: true, all: true, yes: true, dry: opts.dry, json: opts.json };
85
+ }
86
+ if (opts.removeConfig) {
87
+ return { uninstall: true, configure: true, yes: true, dry: opts.dry, json: opts.json };
88
+ }
89
+ return { dry: opts.dry, json: opts.json };
90
+ }
91
+
92
+ function runSkillAction(root: CliProgram, kind: InstallActionKind, opts: InstallOpts): string[] {
93
+ const target = skillTargetFromActionKind(kind);
94
+ if (!target) return [];
95
+ return cliSkillInstall(root, target, {
96
+ global: true,
97
+ rimraf: true,
98
+ dry: opts.dry,
99
+ });
100
+ }
101
+
102
+ function executePlan(
103
+ root: CliProgram,
104
+ actions: Array<InstallAction | UninstallAction>,
105
+ opts: InstallOpts,
106
+ showProgress: boolean,
107
+ ): string[] {
108
+ const changed: string[] = [];
109
+ const paths = resolveInstallPaths(root);
110
+ for (const action of actions) {
111
+ if (showProgress) {
112
+ installInfo(action.message, opts);
113
+ }
114
+ if ("kind" in action && action.kind) {
115
+ const skillTarget = skillTargetFromActionKind(action.kind);
116
+ if (skillTarget) {
117
+ changed.push(...runSkillAction(root, action.kind as InstallActionKind, opts));
118
+ continue;
119
+ }
120
+ }
121
+ if (!("kind" in action) || !action.kind) {
122
+ const skillDir = skillDirFromUninstallSummary(action.summary, paths);
123
+ if (skillDir) {
124
+ changed.push(...uninstallSkillDir(skillDir, !!opts.dry));
125
+ continue;
126
+ }
127
+ }
128
+ changed.push(...action.run());
129
+ }
130
+ return changed;
131
+ }
132
+
133
+ function buildSingleTargetContext(
134
+ root: CliProgram,
135
+ paths: ReturnType<typeof resolveInstallPaths>,
136
+ detected: ReturnType<typeof buildDetectedSnapshot>,
137
+ key: CliInstallArtifactKey,
138
+ mode: "install" | "uninstall",
139
+ opts: InstallOpts,
140
+ ): TargetPlanContext {
141
+ const effective = resolveEffectiveInstallTargets(root.configure, root);
142
+ const base = buildTargetPlanContext(root, paths, opts, detected);
143
+ return {
144
+ ...base,
145
+ mode: mode === "install" ? "install-scoped" : "uninstall-scoped",
146
+ include: (k) => k === key && effective[k].enabled,
147
+ };
148
+ }
149
+
150
+ function actionsForTarget(
151
+ root: CliProgram,
152
+ paths: ReturnType<typeof resolveInstallPaths>,
153
+ key: CliInstallArtifactKey,
154
+ action: "install" | "uninstall",
155
+ opts: InstallOpts,
156
+ ): Array<InstallAction | UninstallAction> {
157
+ const detected = buildDetectedSnapshot(root, paths);
158
+ const target = installTargetForKey(key);
159
+ if (!target) return [];
160
+ const ctx = buildSingleTargetContext(root, paths, detected, key, action, opts);
161
+ if (action === "install") {
162
+ return target.planInstall(ctx);
163
+ }
164
+ return target.planUninstall(ctx);
165
+ }
166
+
167
+ async function runInteractiveConfigure(root: CliProgram, opts: ConfigureOpts): Promise<string[]> {
168
+ if (!process.stdin.isTTY) {
169
+ throw new Error("Interactive configure requires a TTY. Use flags such as --sync --yes.");
170
+ }
171
+
172
+ writeInteractiveInstallIntro(root);
173
+ const paths = resolveInstallPaths(root);
174
+ const detected = buildDetectedSnapshot(root, paths);
175
+ const effective = resolveEffectiveInstallTargets(root.configure, root);
176
+ const mutationOpts: InstallOpts = { dry: opts.dry, json: opts.json };
177
+ const changed: string[] = [];
178
+
179
+ for (const target of INSTALL_TARGETS) {
180
+ if (target.key === "app") continue;
181
+ if (!effective[target.key].enabled) continue;
182
+ if (target.key !== "configure" && !target.isAvailable(root, paths)) continue;
183
+ if (target.key === "configure" && !root.appConfig) continue;
184
+
185
+ if (target.key === "configure") {
186
+ if (!appConfigHasEntries(root)) continue;
187
+
188
+ const result = runConfigure(root, { context: "standalone", showHeading: false });
189
+ if (result.changed) {
190
+ installOut(`Wrote config: ${displayAppConfigPath(root)}`, mutationOpts);
191
+ changed.push(displayAppConfigPath(root));
192
+ }
193
+ continue;
194
+ }
195
+
196
+ const label = artifactPromptLabel(target.key);
197
+ const installed = target.detectedForSnapshot(detected);
198
+
199
+ const statusHint = installed ? "installed" : "not installed";
200
+ process.stderr.write(`\n${label} (${statusHint})\n`);
201
+ const choice = promptTargetAction(label, installed);
202
+ if (!choice || choice === "skip") continue;
203
+
204
+ const actions = actionsForTarget(root, paths, target.key, choice, mutationOpts);
205
+ if (actions.length === 0) continue;
206
+
207
+ const installActions = actions.filter(
208
+ (a): a is InstallAction =>
209
+ "kind" in a && typeof a.kind === "string" && a.kind.includes("-mcp"),
210
+ );
211
+ if (choice === "install" && resolveCapabilities(root).mcp && installActions.length > 0) {
212
+ runTargetPreflight(root, paths, mutationOpts, installActions);
213
+ }
214
+
215
+ changed.push(...executePlan(root, actions, mutationOpts, true));
216
+ }
217
+
218
+ return changed;
219
+ }
220
+
221
+ async function runAutomatedConfigure(root: CliProgram, opts: ConfigureOpts): Promise<string[]> {
222
+ const installOpts = configureToInstallOpts(opts);
223
+ const paths = resolveInstallPaths(root);
224
+
225
+ if (installOpts.status) {
226
+ printInstallStatus(root, installOpts);
227
+ return [];
228
+ }
229
+
230
+ let actions: Array<InstallAction | UninstallAction>;
231
+ if (installOpts.uninstall) {
232
+ actions = buildUninstallPlan(root, paths, installOpts);
233
+ } else if (installOpts.reinstall) {
234
+ actions = buildUpdatePlan(root, paths, installOpts);
235
+ } else {
236
+ actions = buildInstallPlan(root, paths, installOpts);
237
+ }
238
+
239
+ const installActions = actions.filter(
240
+ (a): a is InstallAction => "kind" in a && typeof a.kind === "string" && a.kind.includes("-mcp"),
241
+ );
242
+ if (!installOpts.uninstall && resolveCapabilities(root).mcp && installActions.length > 0) {
243
+ runTargetPreflight(root, paths, installOpts, installActions);
244
+ }
245
+
246
+ return executePlan(root, actions, installOpts, true);
247
+ }
248
+
249
+ /** Main configure command orchestrator. */
250
+ export async function cliConfigure(
251
+ root: CliProgram,
252
+ rawOpts: Record<string, string>,
253
+ ): Promise<never> {
254
+ const opts = parseConfigureOpts(rawOpts);
255
+ const err = validateConfigureOpts(opts);
256
+ if (err) {
257
+ installErr(err);
258
+ process.exit(1);
259
+ }
260
+
261
+ const isInteractive = !opts.sync && !opts.removeAll && !opts.removeConfig && !opts.status;
262
+
263
+ let changed: string[] = [];
264
+ try {
265
+ changed = isInteractive
266
+ ? await runInteractiveConfigure(root, opts)
267
+ : await runAutomatedConfigure(root, opts);
268
+ } catch (mutationErr) {
269
+ installErr(mutationErr instanceof Error ? mutationErr.message : String(mutationErr));
270
+ process.exit(1);
271
+ }
272
+
273
+ if (opts.json && !opts.status) {
274
+ process.stdout.write(`${JSON.stringify(changed, null, 2)}\n`);
275
+ process.exit(0);
276
+ }
277
+
278
+ if (!opts.status && changed.length > 0) {
279
+ const verb = opts.removeAll || opts.removeConfig ? "Removed" : opts.sync ? "Synced" : "Updated";
280
+ installOut(`${verb} ${changed.length} file(s).`, configureToInstallOpts(opts));
281
+ }
282
+
283
+ process.exit(0);
284
+ }
@@ -0,0 +1,40 @@
1
+ import { readSync } from "node:fs";
2
+ import type { CliInstallArtifactKey } from "../install/target-types.ts";
3
+
4
+ const LABELS: Record<CliInstallArtifactKey, string> = {
5
+ app: "App binary",
6
+ cursorSkill: "Cursor skill",
7
+ claudeSkill: "Claude skill",
8
+ codexSkill: "Codex skill",
9
+ opencodeSkill: "OpenCode skill",
10
+ openclawSkill: "OpenClaw skill",
11
+ cursorMcp: "Cursor MCP",
12
+ claudeCodeMcp: "Claude Code MCP",
13
+ claudeDesktopMcp: "Claude Desktop MCP",
14
+ opencodeMcp: "OpenCode MCP",
15
+ codexMcp: "Codex MCP",
16
+ openclawMcp: "OpenClaw MCP",
17
+ chatgptMcp: "ChatGPT desktop MCP",
18
+ configure: "App config",
19
+ };
20
+
21
+ export function artifactPromptLabel(key: CliInstallArtifactKey): string {
22
+ return LABELS[key];
23
+ }
24
+
25
+ export type TargetPromptAction = "install" | "skip" | "uninstall";
26
+
27
+ /** Prompt per target: Y/n when not installed, y/N when installed. */
28
+ export function promptTargetAction(label: string, installed: boolean): TargetPromptAction | null {
29
+ const hint = installed ? `${label} [y/N]: ` : `${label} [Y/n]: `;
30
+ process.stderr.write(hint);
31
+ const buf = Buffer.alloc(256);
32
+ const n = readSync(0, buf, { length: 256 });
33
+ const ans = buf.toString("utf8", 0, n).trim().toLowerCase();
34
+ if (installed) {
35
+ if (ans === "n") return "uninstall";
36
+ return "skip";
37
+ }
38
+ if (ans === "n") return "skip";
39
+ return "install";
40
+ }
@@ -1,3 +1,5 @@
1
+ import { docsSkillTopicDescription } from "../builtins/configure-copy.ts";
2
+ import { resolveCapabilities } from "../capabilities.ts";
1
3
  import {
2
4
  CliFallbackMode,
3
5
  type CliLeaf,
@@ -75,11 +77,7 @@ export function cliBuiltinDocsGroup(program: CliProgram): CliRouter {
75
77
  leaves.push(
76
78
  docsLeaf(program, "schema", "Print the full command tree as JSON."),
77
79
  docsLeaf(program, "api", "Print the full command reference as markdown."),
78
- docsLeaf(
79
- program,
80
- "skill",
81
- "Print a reference agent SKILL, use `install --skill` for optimized.",
82
- ),
80
+ docsLeaf(program, "skill", docsSkillTopicDescription(program, resolveCapabilities(program))),
83
81
  );
84
82
 
85
83
  return {
@@ -106,7 +106,7 @@ test("docs mcp when MCP enabled", async () => {
106
106
  expect(result.stdout).toContain("MCP server (myapp)");
107
107
  expect(result.stdout).toContain("myapp mcp");
108
108
  expect(result.stdout).toContain("claude_desktop_config.json");
109
- expect(result.stdout).toContain("install --mcp --yes");
109
+ expect(result.stdout).toContain("configure --sync --yes");
110
110
  });
111
111
 
112
112
  test("docs rejects unknown subcommand", async () => {
@@ -176,14 +176,14 @@ test("docs skill prints Cursor SKILL.md", async () => {
176
176
  expect(result.stdout).not.toContain("mcp.json");
177
177
  });
178
178
 
179
- test("docs skill help recommends install --skill", async () => {
179
+ test("docs skill help recommends configure", async () => {
180
180
  const presentation = cliPresentationRoot(docsFixture());
181
181
  const docsNode = presentation.commands.find((c) => c.key === "docs");
182
182
  expect(docsNode && "commands" in docsNode).toBe(true);
183
183
  if (docsNode && "commands" in docsNode) {
184
184
  const skill = docsNode.commands.find((c) => c.key === "skill");
185
185
  expect(skill?.description).toContain("reference agent SKILL");
186
- expect(skill?.description).toContain("install --skill");
186
+ expect(skill?.description).toContain("configure");
187
187
  expect(skill?.notes).toBeUndefined();
188
188
  expect(docsNode.notes).toContain("--save");
189
189
  expect(docsNode.notes).not.toContain("install --skill");
@@ -210,14 +210,14 @@ test("completions offer docs subcommands", () => {
210
210
  expect(bash).toContain("skill) echo");
211
211
  });
212
212
 
213
- test("generateMcpGuide includes schema URI and install targets", () => {
213
+ test("generateMcpGuide includes schema URI and configure sync", () => {
214
214
  const guide = generateMcpGuide(docsFixture(true));
215
215
  expect(guide).toContain("myapp://schema");
216
216
  expect(guide).toContain("~/.cursor/mcp.json");
217
217
  expect(guide).toContain("claude_desktop_config.json");
218
218
  expect(guide).toContain("## Installation");
219
219
  expect(guide).toContain("## Running directly");
220
- expect(guide).toContain("install --mcp");
220
+ expect(guide).toContain("configure --sync");
221
221
  expect(guide).toContain("brew install myapp");
222
222
  expect(guide).toContain("OpenAI Codex");
223
223
  expect(guide).toContain("ChatGPT");
@@ -105,11 +105,11 @@ export function generateMcpGuide(root: CliProgram): string {
105
105
  "",
106
106
  "## Installation",
107
107
  "",
108
- "### `install --mcp`",
108
+ "### `configure`",
109
109
  "",
110
110
  ];
111
111
 
112
- if (caps.install) {
112
+ if (caps.configure) {
113
113
  lines.push(
114
114
  `Install the CLI first so \`${root.key}\` is on your PATH (e.g. \`brew install ${root.key}\`). Host configs reference the app by name.`,
115
115
  "",
@@ -123,7 +123,7 @@ export function generateMcpGuide(root: CliProgram): string {
123
123
 
124
124
  lines.push(
125
125
  "```bash",
126
- `${root.key} install --mcp --yes`,
126
+ `${root.key} configure --sync --yes`,
127
127
  "```",
128
128
  "",
129
129
  "Merges the server entry below into host config when each host is present:",
@@ -177,7 +177,7 @@ export function generateMcpGuide(root: CliProgram): string {
177
177
  if (root.appConfig?.entries && Object.keys(root.appConfig.entries).length > 0) {
178
178
  lines.push("## Configuration", "");
179
179
  lines.push(
180
- `Configure before first use in Cursor or Claude Desktop (MCP hosts are non-interactive): \`${root.key} install --configure\`.`,
180
+ `Configure before first use in Cursor or Claude Desktop (MCP hosts are non-interactive): \`${root.key} configure\`.`,
181
181
  "",
182
182
  `Default config file: \`${displayAppConfigPath(root)}\` (flat JSON keys).`,
183
183
  "",
package/src/index.ts CHANGED
@@ -34,11 +34,11 @@ export type {
34
34
  CliAppConfigEntry,
35
35
  CliAppConfigResolveContext,
36
36
  CliAppConfigResolveFn,
37
+ CliConfigureConfig,
38
+ CliConfigureTargets,
37
39
  CliDocsConfig,
38
40
  CliDocsTopic,
39
41
  CliHandler,
40
- CliInstallConfig,
41
- CliInstallTargets,
42
42
  CliInvocation,
43
43
  CliMcpBundleConfig,
44
44
  CliMcpResource,
@@ -10,7 +10,7 @@ const base: CliProgram = {
10
10
  handler: () => {},
11
11
  };
12
12
 
13
- describe("validateInstallConfig", () => {
13
+ describe("validateConfigureConfig", () => {
14
14
  test("accepts empty install config", () => {
15
15
  expect(() => cliValidateProgram(base)).not.toThrow();
16
16
  });
@@ -18,7 +18,7 @@ describe("validateInstallConfig", () => {
18
18
  test("rejects removed allSkills shorthand", () => {
19
19
  const program = {
20
20
  ...base,
21
- install: { targets: { allSkills: true } },
21
+ configure: { targets: { allSkills: true } },
22
22
  } as CliProgram;
23
23
  expect(() => cliValidateProgram(program)).toThrow(CliSchemaValidationError);
24
24
  expect(() => cliValidateProgram(program)).toThrow(/allSkills/);
@@ -28,7 +28,7 @@ describe("validateInstallConfig", () => {
28
28
  const program: CliProgram = {
29
29
  ...base,
30
30
  mcpServer: { enabled: true },
31
- install: {
31
+ configure: {
32
32
  agentIntegration: "mcp",
33
33
  targets: { cursorMcp: true, cursorSkill: true },
34
34
  },
@@ -39,7 +39,7 @@ describe("validateInstallConfig", () => {
39
39
  test("rejects explicit MCP target in skill integration mode", () => {
40
40
  const program: CliProgram = {
41
41
  ...base,
42
- install: {
42
+ configure: {
43
43
  agentIntegration: "skill",
44
44
  targets: { cursorMcp: true },
45
45
  },
@@ -51,7 +51,7 @@ describe("validateInstallConfig", () => {
51
51
  const program: CliProgram = {
52
52
  ...base,
53
53
  mcpServer: { enabled: true },
54
- install: {
54
+ configure: {
55
55
  agentIntegration: "both",
56
56
  targets: { cursorMcp: true, cursorSkill: true },
57
57
  },
@@ -0,0 +1,17 @@
1
+ import type { InstallOpts } from "./target-types.ts";
2
+
3
+ export function parseInstallOpts(raw: Record<string, string>): InstallOpts {
4
+ const flag = (name: string) => raw[name] === "1";
5
+ return {
6
+ all: flag("all"),
7
+ skill: flag("skill"),
8
+ mcp: flag("mcp"),
9
+ reinstall: flag("reinstall"),
10
+ status: flag("status"),
11
+ uninstall: flag("uninstall"),
12
+ configure: flag("configure"),
13
+ yes: flag("yes"),
14
+ dry: flag("dry"),
15
+ json: flag("json"),
16
+ };
17
+ }
@@ -1,7 +1,7 @@
1
1
  import { resolveCapabilities } from "../capabilities.ts";
2
2
  import type {
3
- CliInstallConfig,
4
- CliInstallTargets,
3
+ CliConfigureConfig,
4
+ CliConfigureTargets,
5
5
  CliProgram,
6
6
  InstallAgentIntegration,
7
7
  InstallTargetSpec,
@@ -15,10 +15,10 @@ export type { InstallTargetSpec, ResolvedInstallTarget } from "../types.ts";
15
15
  export type { InstallPlanMode, InstallScope } from "./target-types.ts";
16
16
 
17
17
  export function resolveAgentIntegration(
18
- install?: CliInstallConfig,
18
+ configure?: CliConfigureConfig,
19
19
  mcpEnabled = false,
20
20
  ): InstallAgentIntegration {
21
- return install?.agentIntegration ?? (mcpEnabled ? "mcp" : "skill");
21
+ return configure?.agentIntegration ?? (mcpEnabled ? "mcp" : "skill");
22
22
  }
23
23
 
24
24
  /** Resolves a boolean or object target spec against category defaults. */
@@ -52,7 +52,7 @@ function artifactDefaults(
52
52
 
53
53
  function applyAgentPairDedupe(
54
54
  out: Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>,
55
- user: CliInstallTargets | undefined,
55
+ user: CliConfigureTargets | undefined,
56
56
  integration: InstallAgentIntegration,
57
57
  ): void {
58
58
  if (integration === "both") return;
@@ -78,12 +78,12 @@ function applyAgentPairDedupe(
78
78
 
79
79
  /** Effective per-artifact gates for install.targets. */
80
80
  export function resolveEffectiveInstallTargets(
81
- install?: CliInstallConfig,
81
+ configure?: CliConfigureConfig,
82
82
  program?: Pick<CliProgram, "mcpServer">,
83
83
  ): Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }> {
84
84
  const mcpEnabled = program?.mcpServer?.enabled === true;
85
- const integration = resolveAgentIntegration(install, mcpEnabled);
86
- const user = install?.targets;
85
+ const integration = resolveAgentIntegration(configure, mcpEnabled);
86
+ const user = configure?.targets;
87
87
  const out = {} as Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>;
88
88
  for (const key of INSTALL_ARTIFACT_KEYS) {
89
89
  out[key] = resolveInstallTargetSpec(user?.[key], artifactDefaults(key, integration));
@@ -1,6 +1,6 @@
1
1
  import { resolveCapabilities } from "../capabilities.ts";
2
2
  import { appConfigInstalled } from "../config/file.ts";
3
- import type { CliInstallTargets, CliProgram } from "../types.ts";
3
+ import type { CliConfigureTargets, CliProgram } from "../types.ts";
4
4
  import type { InstallPaths } from "./paths.ts";
5
5
  import {
6
6
  type InstallPlanMode,
@@ -78,7 +78,7 @@ export function buildDetectedSnapshot(root: CliProgram, paths: InstallPaths): De
78
78
  }
79
79
 
80
80
  function targetExplicitlyConfigured(
81
- user: CliInstallTargets | undefined,
81
+ user: CliConfigureTargets | undefined,
82
82
  key: CliInstallArtifactKey,
83
83
  ): boolean {
84
84
  return user?.[key] !== undefined;
@@ -89,7 +89,7 @@ function agentCategoryInScope(
89
89
  categoryKeys: readonly CliInstallArtifactKey[],
90
90
  category: "skill" | "mcp",
91
91
  effective: Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>,
92
- targets: CliInstallTargets | undefined,
92
+ targets: CliConfigureTargets | undefined,
93
93
  root: CliProgram,
94
94
  ): boolean {
95
95
  if (!categoryKeys.includes(key)) return false;
@@ -105,7 +105,7 @@ export function isArtifactInScope(
105
105
  effective: Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>,
106
106
  mode: InstallPlanMode,
107
107
  root: CliProgram,
108
- targets?: CliInstallTargets,
108
+ targets?: CliConfigureTargets,
109
109
  ): boolean {
110
110
  if (mode === "uninstall-all") {
111
111
  return true;
@@ -150,7 +150,7 @@ export function shouldIncludeArtifact(
150
150
  detected?: Partial<Record<CliInstallArtifactKey, boolean>>,
151
151
  ): boolean {
152
152
  const target = installTargetForKey(key);
153
- const targets = root.install?.targets;
153
+ const targets = root.configure?.targets;
154
154
 
155
155
  if (key !== "configure" && target && !target.isAvailable(root, paths)) {
156
156
  return false;
@@ -196,7 +196,7 @@ export function buildTargetPlanContext(
196
196
  opts: InstallOpts,
197
197
  detected: DetectedSnapshot,
198
198
  ): TargetPlanContext {
199
- const effective = resolveEffectiveInstallTargets(root.install, root);
199
+ const effective = resolveEffectiveInstallTargets(root.configure, root);
200
200
  const mode = resolveInstallPlanMode(opts);
201
201
  const scope = resolveInstallScope(opts);
202
202
  const detPartial = Object.fromEntries(
@@ -236,8 +236,11 @@ export function resolveInstallTargetPreview(
236
236
  program: CliProgram,
237
237
  paths: InstallPaths,
238
238
  ): InstallTargetPreview {
239
- const effective = resolveEffectiveInstallTargets(program.install, program);
240
- const integration = resolveAgentIntegration(program.install, program.mcpServer?.enabled === true);
239
+ const effective = resolveEffectiveInstallTargets(program.configure, program);
240
+ const integration = resolveAgentIntegration(
241
+ program.configure,
242
+ program.mcpServer?.enabled === true,
243
+ );
241
244
 
242
245
  const keysForScope = (scope: InstallScope, mode: InstallPlanMode): CliInstallArtifactKey[] =>
243
246
  INSTALL_ARTIFACT_KEYS.filter((key) =>
@@ -11,7 +11,7 @@ import type {
11
11
  UninstallAction,
12
12
  } from "../target-types.ts";
13
13
 
14
- /** App config file (install --configure / wizard). */
14
+ /** App config file (interactive configure / wizard). */
15
15
  class ConfigureInstallTarget extends InstallTarget {
16
16
  readonly key = "configure" as const;
17
17
  readonly actionKind = "configure" as const;
@@ -75,10 +75,10 @@ describe("resolveEffectiveInstallTargets", () => {
75
75
  version: "1",
76
76
  description: "x",
77
77
  mcpServer: { enabled: true },
78
- install: { agentIntegration: "skill" },
78
+ configure: { agentIntegration: "skill" },
79
79
  handler: () => {},
80
80
  };
81
- const effective = resolveEffectiveInstallTargets(program.install, program);
81
+ const effective = resolveEffectiveInstallTargets(program.configure, program);
82
82
  const scope = { mcp: true };
83
83
  expect(isArtifactInScope("cursorMcp", scope, effective, "install-scoped", program)).toBe(false);
84
84
  expect(
@@ -101,10 +101,10 @@ describe("resolveEffectiveInstallTargets", () => {
101
101
  version: "1",
102
102
  description: "x",
103
103
  mcpServer: { enabled: true },
104
- install: { agentIntegration: "both" },
104
+ configure: { agentIntegration: "both" },
105
105
  handler: () => {},
106
106
  };
107
- const effective = resolveEffectiveInstallTargets(program.install, program);
107
+ const effective = resolveEffectiveInstallTargets(program.configure, program);
108
108
  const scope = { mcp: true, skill: true, configure: true };
109
109
  expect(isArtifactInScope("cursorSkill", scope, effective, "install-scoped", program)).toBe(
110
110
  true,
@@ -20,7 +20,7 @@ const program = {
20
20
  key: "t",
21
21
  description: "d",
22
22
  version: "0.0.0",
23
- install: { enabled: false },
23
+ configure: { enabled: false },
24
24
  handler: (ctx) => console.log(ctx.invocation),
25
25
  };
26
26
  await new Cli(program).run([]);
package/src/mcp/tools.ts CHANGED
@@ -224,7 +224,7 @@ export function collectMcpTools(root: CliProgram): McpToolDef[] {
224
224
  if (isCliLeaf(cmd)) {
225
225
  if (
226
226
  cmd.key === "completion" ||
227
- cmd.key === "install" ||
227
+ cmd.key === "configure" ||
228
228
  cmd.key === "mcp" ||
229
229
  cmd.key === "version"
230
230
  ) {