argsbarg 5.0.3 → 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.
@@ -1,14 +1,19 @@
1
1
  /*
2
- Built-in `config get` / `config set` commands.
2
+ Built-in `configure get` / `configure set` subcommands.
3
3
  */
4
4
 
5
5
  import { bootstrapAppConfig } from "../config/bootstrap.ts";
6
- import { configEntrySensitive, defaultConfigEntryTitle } from "../config/entry.ts";
6
+ import {
7
+ configCommandsEnabled,
8
+ configEntrySensitive,
9
+ configMcpSetEnabled,
10
+ defaultConfigEntryTitle,
11
+ } from "../config/entry.ts";
7
12
  import { writeAppConfigFile } from "../config/file.ts";
8
13
  import { captureMappedHostEnv, exportConfigToEnv, resolveAppConfig } from "../config/resolve.ts";
9
14
  import { configPropertySchema, effectiveJsonSchema } from "../config/schema.ts";
10
15
  import { parseConfigSetValue } from "../config/validate.ts";
11
- import type { CliLeaf, CliOption, CliProgram, CliRouter } from "../types.ts";
16
+ import type { CliLeaf, CliOption, CliProgram } from "../types.ts";
12
17
  import { CliOptionKind } from "../types.ts";
13
18
 
14
19
  const JSON_OPTION: CliOption = {
@@ -182,16 +187,16 @@ function configSetLeaf(program: CliProgram, mcpSetEnabled: boolean): CliLeaf {
182
187
  handler: (ctx) => {
183
188
  const key = ctx.args[0];
184
189
  if (!key) {
185
- process.stderr.write("config set requires a key.\n");
190
+ process.stderr.write("configure set requires a key.\n");
186
191
  process.exit(1);
187
192
  }
188
193
  const raw = ctx.args[1];
189
194
  if (raw === undefined || raw.length === 0) {
190
195
  if (!ctx.hasFlag("json")) {
191
- process.stderr.write("config set requires a value (or --json).\n");
196
+ process.stderr.write("configure set requires a value (or --json).\n");
192
197
  process.exit(1);
193
198
  }
194
- process.stderr.write("config set requires a value.\n");
199
+ process.stderr.write("configure set requires a value.\n");
195
200
  process.exit(1);
196
201
  }
197
202
  configSetRun(program, key, raw, ctx.hasFlag("json"));
@@ -199,23 +204,15 @@ function configSetLeaf(program: CliProgram, mcpSetEnabled: boolean): CliLeaf {
199
204
  };
200
205
  }
201
206
 
202
- import { configCommandsEnabled, configMcpSetEnabled } from "../config/entry.ts";
203
-
204
- /** Built-in `config` router when program.appConfig is set. */
205
- export function cliBuiltinConfigGroup(program: CliProgram): CliRouter {
207
+ /** `configure get` / `configure set` leaves when app config commands are enabled. */
208
+ export function configureConfigSubcommands(
209
+ program: CliProgram,
210
+ mcpSetEnabled = configMcpSetEnabled(program),
211
+ ): CliLeaf[] {
206
212
  if (!program.appConfig) {
207
- throw new Error("config not enabled");
213
+ throw new Error("configure config subcommands require program.appConfig");
208
214
  }
209
- return {
210
- key: "config",
211
- description: "Read or write app configuration.",
212
- commands: [configGetLeaf(program), configSetLeaf(program, configMcpSetEnabled(program))],
213
- };
215
+ return [configGetLeaf(program), configSetLeaf(program, mcpSetEnabled)];
214
216
  }
215
217
 
216
- export function cliBuiltinConfigGroupIfEnabled(program: CliProgram): CliRouter | undefined {
217
- if (!configCommandsEnabled(program)) {
218
- return undefined;
219
- }
220
- return cliBuiltinConfigGroup(program);
221
- }
218
+ export { configCommandsEnabled };
@@ -1,11 +1,23 @@
1
1
  import { resolveCapabilities } from "../capabilities.ts";
2
- import { type CliLeaf, type CliOption, CliOptionKind, type CliProgram } from "../types.ts";
2
+ import { configCommandsEnabled, configMcpSetEnabled } from "../config/entry.ts";
3
+ import {
4
+ CliFallbackMode,
5
+ type CliLeaf,
6
+ type CliOption,
7
+ CliOptionKind,
8
+ type CliProgram,
9
+ type CliRouter,
10
+ } from "../types.ts";
11
+ import { configureConfigSubcommands } from "./config.ts";
3
12
  import {
4
13
  configureCommandDescription,
5
14
  configureCommandNotes,
6
15
  configureSyncOptionDescription,
7
16
  } from "./configure-copy.ts";
8
17
 
18
+ /** Hidden fallback leaf for bare `configure` (interactive / flag modes). */
19
+ export const CONFIGURE_RUN_KEY = "run";
20
+
9
21
  /** Configure command options. */
10
22
  export function configureBuiltinOptions(root: CliProgram): CliOption[] {
11
23
  const caps = resolveCapabilities(root);
@@ -57,14 +69,33 @@ export function configureBuiltinOptions(root: CliProgram): CliOption[] {
57
69
  return opts;
58
70
  }
59
71
 
60
- /** Builds the `configure` built-in command. */
61
- export function cliBuiltinConfigureCommand(root: CliProgram): CliLeaf {
72
+ /** True when argv resolved to `configure get` or `configure set`. */
73
+ export function isConfigureConfigPath(path: string[]): boolean {
74
+ return path.length >= 2 && (path[1] === "get" || path[1] === "set");
75
+ }
76
+
77
+ /** Builds the `configure` built-in router. */
78
+ export function cliBuiltinConfigureCommand(root: CliProgram): CliRouter {
62
79
  const caps = resolveCapabilities(root);
80
+ const run: CliLeaf = {
81
+ key: CONFIGURE_RUN_KEY,
82
+ hidden: true,
83
+ description: "Interactive or flag-driven configure (skills, MCP, app config).",
84
+ handler: () => {},
85
+ };
86
+
87
+ const commands: CliLeaf[] = [run];
88
+ if (configCommandsEnabled(root)) {
89
+ commands.push(...configureConfigSubcommands(root, configMcpSetEnabled(root)));
90
+ }
91
+
63
92
  return {
64
93
  key: "configure",
65
94
  description: configureCommandDescription(root, caps),
66
95
  options: configureBuiltinOptions(root),
67
96
  notes: configureCommandNotes(root, caps),
68
- handler: () => {},
97
+ fallbackCommand: CONFIGURE_RUN_KEY,
98
+ fallbackMode: CliFallbackMode.MissingOnly,
99
+ commands,
69
100
  };
70
101
  }
@@ -11,8 +11,11 @@ import { completionBashScript } from "./completion-bash.ts";
11
11
  import { completionFishScript } from "./completion-fish.ts";
12
12
  import { cliBuiltinCompletionGroup as completionGroup } from "./completion-group.ts";
13
13
  import { completionZshScript } from "./completion-zsh.ts";
14
- import { cliBuiltinConfigGroupIfEnabled } from "./config.ts";
15
- import { cliBuiltinConfigureCommand } from "./configure.ts";
14
+ import {
15
+ CONFIGURE_RUN_KEY,
16
+ cliBuiltinConfigureCommand,
17
+ isConfigureConfigPath,
18
+ } from "./configure.ts";
16
19
  import { cliBuiltinMcpCommand } from "./mcp.ts";
17
20
  import { cliPresentationRoot } from "./presentation.ts";
18
21
  import { cliBuiltinVersionCommand } from "./version.ts";
@@ -101,7 +104,11 @@ export async function dispatchBuiltin(
101
104
  process.stderr.write(capabilityDeniedMessage("configure"));
102
105
  process.exit(1);
103
106
  }
104
- if (pr.path.length !== 1) {
107
+ if (isConfigureConfigPath(pr.path)) {
108
+ return;
109
+ }
110
+ const runSeg = pr.path[1];
111
+ if (pr.path.length > 2 || (pr.path.length === 2 && runSeg !== CONFIGURE_RUN_KEY)) {
105
112
  process.stderr.write(`Unknown subcommand: configure ${pr.path.slice(1).join(" ")}\n`);
106
113
  process.exit(1);
107
114
  }
@@ -177,17 +184,5 @@ export function builtinInterceptRoot(
177
184
  };
178
185
  }
179
186
 
180
- const configGroup = cliBuiltinConfigGroupIfEnabled(program);
181
- if (first === "config" && configGroup) {
182
- return {
183
- parseRoot: {
184
- key: program.key,
185
- description: program.description,
186
- commands: [configGroup],
187
- },
188
- isLeafCompletionIntercept: false,
189
- };
190
- }
191
-
192
187
  return { parseRoot: program, isLeafCompletionIntercept: false };
193
188
  }
@@ -2,7 +2,6 @@ import type { CliCapabilities } from "../capabilities.ts";
2
2
  import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
3
3
  import type { CliNode, CliProgram } from "../types.ts";
4
4
  import { cliBuiltinCompletionGroup } from "./completion-group.ts";
5
- import { cliBuiltinConfigGroupIfEnabled } from "./config.ts";
6
5
  import { cliBuiltinConfigureCommand } from "./configure.ts";
7
6
  import { cliBuiltinMcpCommand } from "./mcp.ts";
8
7
  import { cliBuiltinVersionCommand } from "./version.ts";
@@ -37,6 +36,5 @@ export function resolveBuiltins(program: CliProgram, caps: CliCapabilities): Cli
37
36
  if (caps.mcp) {
38
37
  pushBuiltin(builtins, program, (p) => cliBuiltinMcpCommand(p));
39
38
  }
40
- pushBuiltin(builtins, program, (p) => cliBuiltinConfigGroupIfEnabled(p) ?? null);
41
39
  return builtins;
42
40
  }
@@ -42,17 +42,17 @@ export function reservedCommandNames(caps: CliCapabilities): string[] {
42
42
  if (caps.mcp) {
43
43
  names.push("mcp");
44
44
  }
45
- if (caps.configCommands) {
46
- names.push("config");
47
- }
48
45
  return names;
49
46
  }
50
47
 
51
48
  /** Commands that may run without required appConfig values (read-only / config introspection). */
52
49
  export function skipsRequiredAppConfigExit(path: string[], caps: CliCapabilities): boolean {
53
50
  const root = path[0];
54
- if (root === "config" && caps.configCommands) {
55
- return true;
51
+ if (root === "configure" && caps.configCommands) {
52
+ const sub = path[1];
53
+ if (sub === "get" || sub === "set") {
54
+ return true;
55
+ }
56
56
  }
57
57
  if (root === "docs" && caps.docs) {
58
58
  return true;
@@ -60,7 +60,7 @@ export function skipsRequiredAppConfigExit(path: string[], caps: CliCapabilities
60
60
  return false;
61
61
  }
62
62
 
63
- export type CapabilityFeature = "mcp" | "configure" | "docs" | "config" | "completion";
63
+ export type CapabilityFeature = "mcp" | "configure" | "docs" | "completion";
64
64
 
65
65
  /** Stderr message when a disabled built-in is invoked from the CLI. */
66
66
  export function capabilityDeniedMessage(feature: CapabilityFeature): string {
@@ -73,8 +73,6 @@ export function capabilityDeniedMessage(feature: CapabilityFeature): string {
73
73
  return "Configure is not available for this app.\n";
74
74
  case "docs":
75
75
  return "Documentation commands are not available for this app.\n";
76
- case "config":
77
- return "Configuration commands are not available for this app.\n";
78
76
  }
79
77
  }
80
78
 
@@ -100,8 +98,4 @@ export function assertBuiltinAllowed(argv: string[], caps: CliCapabilities): voi
100
98
  process.stderr.write(capabilityDeniedMessage("docs"));
101
99
  process.exit(1);
102
100
  }
103
- if (first === "config" && !caps.configCommands) {
104
- process.stderr.write(capabilityDeniedMessage("config"));
105
- process.exit(1);
106
- }
107
101
  }
@@ -29,7 +29,7 @@ export function configEntryRequired(
29
29
  return true;
30
30
  }
31
31
 
32
- /** Whether prompts and `config get` should redact this entry. */
32
+ /** Whether prompts and `configure get` should redact this entry. */
33
33
  export function configEntrySensitive(key: string, entry: CliAppConfigEntry): boolean {
34
34
  return entry.sensitive ?? defaultConfigEntrySensitive(key);
35
35
  }
@@ -56,7 +56,7 @@ export function jsonSchemaRequiredKeys(
56
56
  return new Set(required.filter((k): k is string => typeof k === "string"));
57
57
  }
58
58
 
59
- /** Whether built-in config get/set commands are enabled. */
59
+ /** Whether built-in `configure get` / `configure set` subcommands are enabled. */
60
60
  export function configCommandsEnabled(program: CliProgram): boolean {
61
61
  if (!program.appConfig) {
62
62
  return false;
@@ -227,7 +227,7 @@ export function formatMissingConfigMessage(program: CliProgram, keys: string[]):
227
227
  return [
228
228
  `Missing required configuration: ${list}`,
229
229
  `Configure interactively: ${program.key} configure`,
230
- `Or set via: ${program.key} config set <key> <value>`,
230
+ `Or set via: ${program.key} configure set <key> <value>`,
231
231
  `Config file: ${path}`,
232
232
  `See: ${program.key} docs mcp`,
233
233
  ].join("\n");
@@ -240,7 +240,7 @@ export function formatMcpMissingConfigMessage(program: CliProgram, keys: string[
240
240
  return [
241
241
  `Missing required configuration: ${list}`,
242
242
  `Configure: ${program.key} configure`,
243
- `Or set via: ${program.key} config set`,
243
+ `Or set via: ${program.key} configure set`,
244
244
  `Config file: ${path}`,
245
245
  ].join("\n");
246
246
  }
@@ -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 buf = Buffer.alloc(256);
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";
@@ -167,7 +167,7 @@ test("skipsRequiredAppConfigExit includes docs and config builtins", () => {
167
167
  };
168
168
  const caps = resolveCapabilities(program);
169
169
  expect(skipsRequiredAppConfigExit(["docs", "api"], caps)).toBe(true);
170
- expect(skipsRequiredAppConfigExit(["config", "get"], caps)).toBe(true);
170
+ expect(skipsRequiredAppConfigExit(["configure", "get"], caps)).toBe(true);
171
171
  expect(skipsRequiredAppConfigExit(["run"], caps)).toBe(false);
172
172
  });
173
173
 
@@ -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 { AGENT_PAIRS, INSTALL_ARTIFACT_KEYS, installTargetForKey } from "./target-registry.ts";
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(user?.[key], artifactDefaults(key, integration));
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", () => {