argsbarg 4.1.1 → 5.0.2

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 (82) hide show
  1. package/CHANGELOG.md +34 -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 +12 -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 +3 -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 +105 -61
  20. package/src/builtins/completion-group.ts +4 -6
  21. package/src/builtins/config.test.ts +8 -0
  22. package/src/builtins/configure-copy.ts +86 -0
  23. package/src/builtins/configure.ts +70 -0
  24. package/src/builtins/dispatch.ts +13 -33
  25. package/src/builtins/index.ts +1 -1
  26. package/src/builtins/mcp.ts +2 -2
  27. package/src/builtins/registry.ts +6 -6
  28. package/src/capabilities.ts +22 -13
  29. package/src/cli-tool/cli-smoke.test.ts +21 -3
  30. package/src/cli-tool/create.test.ts +36 -1
  31. package/src/cli-tool/create.ts +26 -4
  32. package/src/cli-tool/full-example-capabilities.test.ts +10 -2
  33. package/src/cli-tool/main.ts +0 -0
  34. package/src/cli-tool/program.ts +20 -5
  35. package/src/cli-tool/run-create.ts +8 -19
  36. package/src/config/bootstrap.ts +11 -9
  37. package/src/config/context.test.ts +8 -0
  38. package/src/config/file.test.ts +13 -1
  39. package/src/config/resolve.test.ts +18 -0
  40. package/src/config/resolve.ts +2 -2
  41. package/src/config/validate.test.ts +11 -0
  42. package/src/config.integration.test.ts +5 -0
  43. package/src/configure/configure.test.ts +170 -0
  44. package/src/configure/index.ts +298 -0
  45. package/src/configure/prompt.ts +47 -0
  46. package/src/docs/api-guide.test.ts +10 -0
  47. package/src/docs/builtin.ts +3 -5
  48. package/src/docs/docs.test.ts +32 -5
  49. package/src/docs/mcp-guide.ts +4 -4
  50. package/src/docs/mcp-resources.test.ts +10 -0
  51. package/src/formats.test.ts +9 -0
  52. package/src/headless.test.ts +10 -0
  53. package/src/hidden-mcpb.test.ts +22 -0
  54. package/src/index.ts +2 -2
  55. package/src/install/binary-placement.test.ts +14 -0
  56. package/src/install/gh-release-update.test.ts +9 -0
  57. package/src/install/install-validate.test.ts +15 -5
  58. package/src/install/mcp-codex.test.ts +7 -0
  59. package/src/install/mcp-openclaw.test.ts +6 -0
  60. package/src/install/mcp-opencode.test.ts +10 -0
  61. package/src/install/opts.ts +17 -0
  62. package/src/install/status.test.ts +9 -0
  63. package/src/install/target-effective.ts +8 -8
  64. package/src/install/target-scope.ts +11 -8
  65. package/src/install/targets/configure.ts +1 -1
  66. package/src/install/targets.test.ts +23 -4
  67. package/src/invoke.test.ts +15 -1
  68. package/src/mcp/claude.test.ts +10 -0
  69. package/src/mcp/env.test.ts +12 -0
  70. package/src/mcp/tools.ts +1 -1
  71. package/src/mcp/zip.test.ts +5 -0
  72. package/src/mcp.integration.test.ts +43 -4
  73. package/src/parse.test.ts +82 -13
  74. package/src/schema.ts +1 -9
  75. package/src/skill/hint.ts +2 -2
  76. package/src/types.ts +22 -14
  77. package/src/validate.ts +17 -17
  78. package/docs/install.md +0 -206
  79. package/src/builtins/install.ts +0 -106
  80. package/src/builtins/uninstall.ts +0 -80
  81. package/src/install/index.ts +0 -409
  82. package/src/install/install.test.ts +0 -317
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for docs/mcp-resources module behavior.
3
+ */
4
+
1
5
  import { expect, test } from "bun:test";
2
6
  import type { CliProgram } from "../types.ts";
3
7
  import {
@@ -30,14 +34,17 @@ function fixture(opts?: { docs?: boolean; mcp?: boolean }): CliProgram {
30
34
  };
31
35
  }
32
36
 
37
+ /** DefaultDocsTopicResourceUri. */
33
38
  test("defaultDocsTopicResourceUri", () => {
34
39
  expect(defaultDocsTopicResourceUri("my_app", "readme")).toBe("my_app://docs/readme");
35
40
  });
36
41
 
42
+ /** ResolveDocsTopicResourceUri sanitizes program key. */
37
43
  test("resolveDocsTopicResourceUri sanitizes program key", () => {
38
44
  expect(resolveDocsTopicResourceUri(fixture(), "readme")).toBe("my_app://docs/readme");
39
45
  });
40
46
 
47
+ /** DocsMcpResources when docs and MCP enabled. */
41
48
  test("docsMcpResources when docs and MCP enabled", () => {
42
49
  const resources = docsMcpResources(fixture());
43
50
  expect(resources.map((r) => r.uri)).toEqual(["my_app://docs/readme", "my_app://docs/arch"]);
@@ -47,14 +54,17 @@ test("docsMcpResources when docs and MCP enabled", () => {
47
54
  expect(resources[0]?.load()).toBe("# Readme\n");
48
55
  });
49
56
 
57
+ /** DocsMcpResources empty when docs disabled. */
50
58
  test("docsMcpResources empty when docs disabled", () => {
51
59
  expect(docsMcpResources(fixture({ docs: false }))).toEqual([]);
52
60
  });
53
61
 
62
+ /** DocsMcpResources empty when MCP disabled. */
54
63
  test("docsMcpResources empty when MCP disabled", () => {
55
64
  expect(docsMcpResources(fixture({ mcp: false }))).toEqual([]);
56
65
  });
57
66
 
67
+ /** Tests that reservedDocsTopicResourceUris matches docsMcpResources URIs. */
58
68
  test("reservedDocsTopicResourceUris matches docsMcpResources URIs", () => {
59
69
  const program = fixture();
60
70
  expect(reservedDocsTopicResourceUris(program)).toEqual(
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for formats module behavior.
3
+ */
4
+
1
5
  import { expect, test } from "bun:test";
2
6
  import {
3
7
  parseCommaList,
@@ -8,6 +12,7 @@ import {
8
12
  } from "./formats.ts";
9
13
  import { CliValueFormat } from "./types.ts";
10
14
 
15
+ /** ParseDurationMs parses minutes and hours. */
11
16
  test("parseDurationMs parses minutes and hours", () => {
12
17
  expect(parseDurationMs("30s")).toBe(30_000);
13
18
  expect(parseDurationMs("5m")).toBe(5 * 60 * 1000);
@@ -15,21 +20,25 @@ test("parseDurationMs parses minutes and hours", () => {
15
20
  expect(parseDurationMs("1d")).toBe(24 * 60 * 60 * 1000);
16
21
  });
17
22
 
23
+ /** ParseCommaList splits and trims. */
18
24
  test("parseCommaList splits and trims", () => {
19
25
  expect(parseCommaList("a,b")).toEqual(["a", "b"]);
20
26
  expect(parseCommaList(" a , b , ")).toEqual(["a", "b"]);
21
27
  });
22
28
 
29
+ /** ParseDate validates calendar dates. */
23
30
  test("parseDate validates calendar dates", () => {
24
31
  expect(parseDate("2026-06-22")).toBe("2026-06-22");
25
32
  expect(() => parseDate("2026-02-30")).toThrow();
26
33
  });
27
34
 
35
+ /** ParseDateTime normalizes to UTC ISO. */
28
36
  test("parseDateTime normalizes to UTC ISO", () => {
29
37
  expect(parseDateTime("2026-06-22T15:00:00Z")).toBe("2026-06-22T15:00:00.000Z");
30
38
  expect(() => parseDateTime("2026-06-22")).toThrow();
31
39
  });
32
40
 
41
+ /** ValidateFormatValue rejects invalid duration. */
33
42
  test("validateFormatValue rejects invalid duration", () => {
34
43
  expect(() => validateFormatValue("nope", CliValueFormat.Duration)).toThrow();
35
44
  });
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for headless module behavior.
3
+ */
4
+
1
5
  import { expect, test } from "bun:test";
2
6
  import {
3
7
  formatDryRunMessage,
@@ -8,12 +12,14 @@ import {
8
12
  wantsExplicitJson,
9
13
  } from "./headless.ts";
10
14
 
15
+ /** WantsExplicitJson includes MCP invocation. */
11
16
  test("wantsExplicitJson includes MCP invocation", () => {
12
17
  expect(wantsExplicitJson({ invocation: "cli" }, false)).toBe(false);
13
18
  expect(wantsExplicitJson({ invocation: "mcp" }, false)).toBe(true);
14
19
  expect(wantsExplicitJson({ invocation: "cli" }, true)).toBe(true);
15
20
  });
16
21
 
22
+ /** Tests that shouldRunHeadless is true for MCP and json. */
17
23
  test("shouldRunHeadless is true for MCP and json", () => {
18
24
  expect(shouldRunHeadless({ invocation: "mcp" }, false)).toBe(true);
19
25
  expect(shouldRunHeadless({ invocation: "cli" }, true)).toBe(true);
@@ -21,6 +27,7 @@ test("shouldRunHeadless is true for MCP and json", () => {
21
27
  expect(shouldRunHeadless({ invocation: "cli" }, false, false, false)).toBe(true);
22
28
  });
23
29
 
30
+ /** Tests that shouldRunHeadlessWithPositionals requires positionals in non-tty. */
24
31
  test("shouldRunHeadlessWithPositionals requires positionals in non-tty", () => {
25
32
  expect(shouldRunHeadlessWithPositionals({ invocation: "cli" }, false, [], false, false)).toBe(
26
33
  false,
@@ -30,6 +37,7 @@ test("shouldRunHeadlessWithPositionals requires positionals in non-tty", () => {
30
37
  );
31
38
  });
32
39
 
40
+ /** Tests that shouldRunHeadlessWithYes requires yes in non-tty. */
33
41
  test("shouldRunHeadlessWithYes requires yes in non-tty", () => {
34
42
  expect(
35
43
  shouldRunHeadlessWithYes({ invocation: "cli" }, { yes: true, hasRequiredArgs: true }, false),
@@ -46,11 +54,13 @@ test("shouldRunHeadlessWithYes requires yes in non-tty", () => {
46
54
  ).toBe(true);
47
55
  });
48
56
 
57
+ /** Tests that formatDryRunMessage prefixes dry-run output. */
49
58
  test("formatDryRunMessage prefixes dry-run output", () => {
50
59
  expect(formatDryRunMessage("hello", false)).toBe("hello");
51
60
  expect(formatDryRunMessage("hello", true)).toBe("[DRY RUN] hello");
52
61
  });
53
62
 
63
+ /** Tests that requireYesInNonTty exits without yes in non-tty. */
54
64
  test("requireYesInNonTty exits without yes in non-tty", () => {
55
65
  const originalExit = process.exit;
56
66
  let code: number | undefined;
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for hidden-mcpb module behavior.
3
+ */
4
+
1
5
  import { describe, expect, test } from "bun:test";
2
6
  import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
3
7
  import { tmpdir } from "node:os";
@@ -53,12 +57,15 @@ const hiddenFixture: CliProgram = {
53
57
  ],
54
58
  };
55
59
 
60
+ /** Tests for hidden commands and options. */
56
61
  describe("hidden commands and options", () => {
62
+ /** Parse root includes hidden commands. */
57
63
  test("parse root includes hidden commands", () => {
58
64
  const parse = cliParseRoot(hiddenFixture);
59
65
  expect(parse.commands?.map((c) => c.key)).toContain("secret");
60
66
  });
61
67
 
68
+ /** Presentation root omits hidden commands. */
62
69
  test("presentation root omits hidden commands", () => {
63
70
  const presentation = cliPresentationRoot(hiddenFixture);
64
71
  const keys = presentation.commands?.map((c) => c.key) ?? [];
@@ -66,23 +73,27 @@ describe("hidden commands and options", () => {
66
73
  expect(keys).not.toContain("secret");
67
74
  });
68
75
 
76
+ /** Root help omits hidden commands. */
69
77
  test("root help omits hidden commands", () => {
70
78
  const help = cliHelpRender(cliParseRoot(hiddenFixture), [], false);
71
79
  expect(help).toContain("public");
72
80
  expect(help).not.toContain("secret");
73
81
  });
74
82
 
83
+ /** Hidden command -h still works. */
75
84
  test("hidden command -h still works", () => {
76
85
  const help = cliHelpRender(cliParseRoot(hiddenFixture), ["secret"], false);
77
86
  expect(help).toContain("Hidden command.");
78
87
  });
79
88
 
89
+ /** Tests that help omits hidden options. */
80
90
  test("help omits hidden options", () => {
81
91
  const help = cliHelpRender(cliParseRoot(hiddenFixture), ["flags"], false);
82
92
  expect(help).toContain("--visible");
83
93
  expect(help).not.toContain("secret-flag");
84
94
  });
85
95
 
96
+ /** Tests that schema export omits hidden nodes and options. */
86
97
  test("schema export omits hidden nodes and options", () => {
87
98
  const schema = cliSchemaExport(hiddenFixture);
88
99
  const keys = schema.commands?.map((c) => c.key) ?? [];
@@ -92,13 +103,16 @@ describe("hidden commands and options", () => {
92
103
  expect(flags?.options?.map((o) => o.name)).toEqual(["visible"]);
93
104
  });
94
105
 
106
+ /** MCP tools omit hidden commands. */
95
107
  test("MCP tools omit hidden commands", () => {
96
108
  const tools = collectMcpTools(hiddenFixture);
97
109
  expect(tools.map((t) => t.name)).toEqual(["public", "flags"]);
98
110
  });
99
111
  });
100
112
 
113
+ /** Tests for mcp router. */
101
114
  describe("mcp router", () => {
115
+ /** Presentation exposes mcp bundle but not hidden serve. */
102
116
  test("presentation exposes mcp bundle but not hidden serve", () => {
103
117
  const builtins = exportPresentationBuiltins(hiddenFixture);
104
118
  const mcp = builtins.find((b) => b.key === "mcp");
@@ -107,6 +121,7 @@ describe("mcp router", () => {
107
121
  expect(mcp?.fallbackCommand).toBe("serve");
108
122
  });
109
123
 
124
+ /** Mcp help lists bundle. */
110
125
  test("mcp help lists bundle", () => {
111
126
  const help = cliHelpRender(cliParseRoot(hiddenFixture), ["mcp"], false);
112
127
  expect(help).toContain("bundle");
@@ -114,7 +129,9 @@ describe("mcp router", () => {
114
129
  });
115
130
  });
116
131
 
132
+ /** Tests for mcp bundle. */
117
133
  describe("mcp bundle", () => {
134
+ /** Tests that generateMcpManifest uses mcpServerId and binary entry. */
118
135
  test("generateMcpManifest uses mcpServerId and binary entry", () => {
119
136
  const manifest = generateMcpManifest(hiddenFixture, "myapp");
120
137
  expect(manifest.name).toBe("myapp");
@@ -128,6 +145,7 @@ describe("mcp bundle", () => {
128
145
  expect((manifest.compatibility as { platforms: string[] }).platforms).toEqual(["darwin"]);
129
146
  });
130
147
 
148
+ /** DefaultMcpBundlePaths. */
131
149
  test("defaultMcpBundlePaths", () => {
132
150
  const cwd = "/tmp/work";
133
151
  const paths = defaultMcpBundlePaths(hiddenFixture, cwd);
@@ -135,6 +153,7 @@ describe("mcp bundle", () => {
135
153
  expect(paths.outPath).toBe(join(cwd, "dist", "myapp.mcpb"));
136
154
  });
137
155
 
156
+ /** Tests that packMcpBundle writes zip with manifest and binary. */
138
157
  test("packMcpBundle writes zip with manifest and binary", () => {
139
158
  const work = mkdtempSync(join(tmpdir(), "mcpb-test-"));
140
159
  try {
@@ -155,6 +174,7 @@ describe("mcp bundle", () => {
155
174
  }
156
175
  });
157
176
 
177
+ /** RunMcpBundle prints mcpb and plugin paths. */
158
178
  test("runMcpBundle prints mcpb and plugin paths", () => {
159
179
  const work = mkdtempSync(join(tmpdir(), "mcpb-run-"));
160
180
  const stdout: string[] = [];
@@ -188,6 +208,7 @@ describe("mcp bundle", () => {
188
208
  }
189
209
  });
190
210
 
211
+ /** RunMcpBundle with claudePlugin only prints plugin path. */
191
212
  test("runMcpBundle with claudePlugin only prints plugin path", () => {
192
213
  const work = mkdtempSync(join(tmpdir(), "mcpb-run-"));
193
214
  const stdout: string[] = [];
@@ -220,6 +241,7 @@ describe("mcp bundle", () => {
220
241
  }
221
242
  });
222
243
 
244
+ /** RunMcpBundle errors when no bundle flags enabled. */
223
245
  test("runMcpBundle errors when no bundle flags enabled", () => {
224
246
  const fixture: CliProgram = {
225
247
  ...hiddenFixture,
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,
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for install/binary-placement module behavior.
3
+ */
4
+
1
5
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
6
  import { chmodSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
3
7
  import { tmpdir } from "node:os";
@@ -30,12 +34,15 @@ afterEach(() => {
30
34
  rmSync(tmp, { recursive: true, force: true });
31
35
  });
32
36
 
37
+ /** Tests for isExternallyManagedBinary. */
33
38
  describe("isExternallyManagedBinary", () => {
39
+ /** Tests that false when command is not on PATH. */
34
40
  test("false when command is not on PATH", () => {
35
41
  process.env.PATH = tmp;
36
42
  expect(isExternallyManagedBinary("placementapp-not-on-path")).toBe(false);
37
43
  });
38
44
 
45
+ /** Tests that true when PATH resolves to execPath. */
39
46
  test("true when PATH resolves to execPath", () => {
40
47
  const bin = join(tmp, "placementapp");
41
48
  writeFileSync(bin, "#!/bin/sh\n", "utf8");
@@ -44,6 +51,7 @@ describe("isExternallyManagedBinary", () => {
44
51
  expect(isExternallyManagedBinary("placementapp", bin)).toBe(true);
45
52
  });
46
53
 
54
+ /** Tests that true when PATH entry is a symlink to execPath. */
47
55
  test("true when PATH entry is a symlink to execPath", () => {
48
56
  const bin = join(tmp, "placementapp");
49
57
  const target = join(tmp, "real-binary");
@@ -53,6 +61,7 @@ describe("isExternallyManagedBinary", () => {
53
61
  expect(isExternallyManagedBinary("placementapp", target)).toBe(true);
54
62
  });
55
63
 
64
+ /** Tests that false when PATH points at a different binary. */
56
65
  test("false when PATH points at a different binary", () => {
57
66
  const bin = join(tmp, "placementapp");
58
67
  const other = join(tmp, "other");
@@ -63,7 +72,9 @@ describe("isExternallyManagedBinary", () => {
63
72
  });
64
73
  });
65
74
 
75
+ /** Tests for isAppInstalled. */
66
76
  describe("isAppInstalled", () => {
77
+ /** Tests that true when externally managed. */
67
78
  test("true when externally managed", () => {
68
79
  const bin = join(tmp, program.key);
69
80
  const prevExec = process.execPath;
@@ -78,6 +89,7 @@ describe("isAppInstalled", () => {
78
89
  }
79
90
  });
80
91
 
92
+ /** Tests that false when not on PATH and no local copy. */
81
93
  test("false when not on PATH and no local copy", () => {
82
94
  const home = mkdtempSync(join(tmpdir(), "argsbarg-placement-home-"));
83
95
  const prevHome = process.env.HOME;
@@ -93,7 +105,9 @@ describe("isAppInstalled", () => {
93
105
  });
94
106
  });
95
107
 
108
+ /** Tests for resolvePathCommand. */
96
109
  describe("resolvePathCommand", () => {
110
+ /** Tests that returns undefined when missing. */
97
111
  test("returns undefined when missing", () => {
98
112
  process.env.PATH = tmp;
99
113
  expect(resolvePathCommand("missing-cmd")).toBeUndefined();
@@ -1,22 +1,31 @@
1
+ /*
2
+ Tests for install/gh-release-update module behavior.
3
+ */
4
+
1
5
  import { expect, test } from "bun:test";
2
6
  import { isAlreadyCurrent, parseReleaseTag } from "./gh-release-update.ts";
3
7
 
8
+ /** ParseReleaseTag strips leading v. */
4
9
  test("parseReleaseTag strips leading v", () => {
5
10
  expect(parseReleaseTag("v1.4.3")).toBe("1.4.3");
6
11
  });
7
12
 
13
+ /** ParseReleaseTag leaves bare semver unchanged. */
8
14
  test("parseReleaseTag leaves bare semver unchanged", () => {
9
15
  expect(parseReleaseTag("1.4.3")).toBe("1.4.3");
10
16
  });
11
17
 
18
+ /** ParseReleaseTag throws on empty tag. */
12
19
  test("parseReleaseTag throws on empty tag", () => {
13
20
  expect(() => parseReleaseTag("")).toThrow("Release tag is empty");
14
21
  });
15
22
 
23
+ /** Tests that isAlreadyCurrent returns true when versions match. */
16
24
  test("isAlreadyCurrent returns true when versions match", () => {
17
25
  expect(isAlreadyCurrent("1.4.3", "1.4.3")).toBe(true);
18
26
  });
19
27
 
28
+ /** Tests that isAlreadyCurrent returns false when versions differ. */
20
29
  test("isAlreadyCurrent returns false when versions differ", () => {
21
30
  expect(isAlreadyCurrent("1.4.2", "1.4.3")).toBe(false);
22
31
  });
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for install/install-validate module behavior.
3
+ */
4
+
1
5
  import { describe, expect, test } from "bun:test";
2
6
  import type { CliProgram } from "../types.ts";
3
7
  import { CliSchemaValidationError } from "../types.ts";
@@ -10,25 +14,29 @@ const base: CliProgram = {
10
14
  handler: () => {},
11
15
  };
12
16
 
13
- describe("validateInstallConfig", () => {
17
+ /** Tests for validateConfigureConfig. */
18
+ describe("validateConfigureConfig", () => {
19
+ /** Tests that accepts empty install config. */
14
20
  test("accepts empty install config", () => {
15
21
  expect(() => cliValidateProgram(base)).not.toThrow();
16
22
  });
17
23
 
24
+ /** Rejects removed allSkills shorthand. */
18
25
  test("rejects removed allSkills shorthand", () => {
19
26
  const program = {
20
27
  ...base,
21
- install: { targets: { allSkills: true } },
28
+ configure: { targets: { allSkills: true } },
22
29
  } as CliProgram;
23
30
  expect(() => cliValidateProgram(program)).toThrow(CliSchemaValidationError);
24
31
  expect(() => cliValidateProgram(program)).toThrow(/allSkills/);
25
32
  });
26
33
 
34
+ /** Rejects both sides of pair without both integration. */
27
35
  test("rejects both sides of pair without both integration", () => {
28
36
  const program: CliProgram = {
29
37
  ...base,
30
38
  mcpServer: { enabled: true },
31
- install: {
39
+ configure: {
32
40
  agentIntegration: "mcp",
33
41
  targets: { cursorMcp: true, cursorSkill: true },
34
42
  },
@@ -36,10 +44,11 @@ describe("validateInstallConfig", () => {
36
44
  expect(() => cliValidateProgram(program)).toThrow(/both/);
37
45
  });
38
46
 
47
+ /** Rejects explicit MCP target in skill integration mode. */
39
48
  test("rejects explicit MCP target in skill integration mode", () => {
40
49
  const program: CliProgram = {
41
50
  ...base,
42
- install: {
51
+ configure: {
43
52
  agentIntegration: "skill",
44
53
  targets: { cursorMcp: true },
45
54
  },
@@ -47,11 +56,12 @@ describe("validateInstallConfig", () => {
47
56
  expect(() => cliValidateProgram(program)).toThrow(/cursorMcp/);
48
57
  });
49
58
 
59
+ /** Allows explicit pair targets with both integration. */
50
60
  test("allows explicit pair targets with both integration", () => {
51
61
  const program: CliProgram = {
52
62
  ...base,
53
63
  mcpServer: { enabled: true },
54
- install: {
64
+ configure: {
55
65
  agentIntegration: "both",
56
66
  targets: { cursorMcp: true, cursorSkill: true },
57
67
  },
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for install/mcp-codex module behavior.
3
+ */
4
+
1
5
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
6
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
7
  import { tmpdir } from "node:os";
@@ -19,7 +23,9 @@ afterEach(() => {
19
23
  rmSync(home, { recursive: true, force: true });
20
24
  });
21
25
 
26
+ /** Tests for codex mcp config. */
22
27
  describe("codex mcp config", () => {
28
+ /** ReadCodexMcpEntry parses simple command/args. */
23
29
  test("readCodexMcpEntry parses simple command/args", () => {
24
30
  const path = resolveCodexConfigPath(home);
25
31
  mkdirSync(join(home, ".codex"), { recursive: true });
@@ -38,6 +44,7 @@ args = ["mcp"]
38
44
  expect(codexMcpHasServer(home, "testapp")).toBe(true);
39
45
  });
40
46
 
47
+ /** ReadCodexMcpEntry parses transport stdio block. */
41
48
  test("readCodexMcpEntry parses transport stdio block", () => {
42
49
  const path = resolveCodexConfigPath(home);
43
50
  mkdirSync(join(home, ".codex"), { recursive: true });
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for install/mcp-openclaw module behavior.
3
+ */
4
+
1
5
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
6
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
7
  import { tmpdir } from "node:os";
@@ -19,7 +23,9 @@ afterEach(() => {
19
23
  rmSync(home, { recursive: true, force: true });
20
24
  });
21
25
 
26
+ /** Tests for openclaw mcp config. */
22
27
  describe("openclaw mcp config", () => {
28
+ /** OpenclawMcpHasServer detects configured server. */
23
29
  test("openclawMcpHasServer detects configured server", () => {
24
30
  const path = resolveOpenclawConfigPath(home);
25
31
  mkdirSync(join(home, ".openclaw"), { recursive: true });
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for install/mcp-opencode module behavior.
3
+ */
4
+
1
5
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
6
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
7
  import { tmpdir } from "node:os";
@@ -41,7 +45,9 @@ afterEach(() => {
41
45
  rmSync(home, { recursive: true, force: true });
42
46
  });
43
47
 
48
+ /** Tests for opencode mcp config. */
44
49
  describe("opencode mcp config", () => {
50
+ /** ResolveOpenCodeConfigPathForInstall prefers existing file. */
45
51
  test("resolveOpenCodeConfigPathForInstall prefers existing file", () => {
46
52
  const dir = opencodeConfigDir(home);
47
53
  mkdirSync(dir, { recursive: true });
@@ -50,6 +56,7 @@ describe("opencode mcp config", () => {
50
56
  expect(resolveOpenCodeConfigPathForInstall(home)).toBe(existing);
51
57
  });
52
58
 
59
+ /** ResolveOpenCodeConfigPathForInstall defaults to config.json. */
53
60
  test("resolveOpenCodeConfigPathForInstall defaults to config.json", () => {
54
61
  mkdirSync(opencodeConfigDir(home), { recursive: true });
55
62
  expect(resolveOpenCodeConfigPathForInstall(home)).toBe(
@@ -57,6 +64,7 @@ describe("opencode mcp config", () => {
57
64
  );
58
65
  });
59
66
 
67
+ /** MergeOpenCodeMcpConfig writes mcp block. */
60
68
  test("mergeOpenCodeMcpConfig writes mcp block", () => {
61
69
  const path = resolveOpenCodeConfigPathForInstall(home);
62
70
  mkdirSync(opencodeConfigDir(home), { recursive: true });
@@ -70,6 +78,7 @@ describe("opencode mcp config", () => {
70
78
  expect(data.mcp.testapp).toEqual(entry);
71
79
  });
72
80
 
81
+ /** DetectOpenCodeMcpConfigPath finds entry across filenames. */
73
82
  test("detectOpenCodeMcpConfigPath finds entry across filenames", () => {
74
83
  const dir = opencodeConfigDir(home);
75
84
  mkdirSync(dir, { recursive: true });
@@ -86,6 +95,7 @@ describe("opencode mcp config", () => {
86
95
  expect(detectOpenCodeMcpConfigPath(home, "testapp")).toBe(path);
87
96
  });
88
97
 
98
+ /** RemoveOpenCodeMcpConfig deletes server entry. */
89
99
  test("removeOpenCodeMcpConfig deletes server entry", () => {
90
100
  const path = resolveOpenCodeConfigPathForInstall(home);
91
101
  mkdirSync(opencodeConfigDir(home), { recursive: true });
@@ -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,3 +1,7 @@
1
+ /*
2
+ Tests for install/status module behavior.
3
+ */
4
+
1
5
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
6
  import { mkdtempSync, rmSync } from "node:fs";
3
7
  import { tmpdir } from "node:os";
@@ -22,7 +26,9 @@ afterEach(() => {
22
26
  rmSync(home, { recursive: true, force: true });
23
27
  });
24
28
 
29
+ /** Tests for resolveInstallTargetPreview. */
25
30
  describe("resolveInstallTargetPreview", () => {
31
+ /** Mcp app previews MCP keys for all and mcp scopes. */
26
32
  test("mcp app previews MCP keys for all and mcp scopes", () => {
27
33
  const program: CliProgram = {
28
34
  key: "mcpapp",
@@ -39,6 +45,7 @@ describe("resolveInstallTargetPreview", () => {
39
45
  expect(preview.all.every((k) => k.endsWith("Mcp") || k === "configure")).toBe(true);
40
46
  });
41
47
 
48
+ /** Tests that shell app previews skill keys. */
42
49
  test("shell app previews skill keys", () => {
43
50
  const program: CliProgram = {
44
51
  key: "cliapp",
@@ -55,7 +62,9 @@ describe("resolveInstallTargetPreview", () => {
55
62
  });
56
63
  });
57
64
 
65
+ /** Tests for printInstallStatus json. */
58
66
  describe("printInstallStatus json", () => {
67
+ /** Includes agentIntegration and effective scopes. */
59
68
  test("includes agentIntegration and effective scopes", () => {
60
69
  const program: CliProgram = {
61
70
  key: "app",
@@ -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));