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.
- package/CHANGELOG.md +34 -1
- package/README.md +5 -5
- package/docs/README.md +3 -3
- package/docs/ai-skills.md +9 -9
- package/docs/bundled-docs.md +4 -4
- package/docs/cli-program.md +8 -8
- package/docs/config-schema.md +2 -2
- package/docs/configure.md +177 -0
- package/docs/developing.md +1 -1
- package/docs/distribution-homebrew.md +12 -9
- package/docs/mcp.md +9 -9
- package/examples/full-example/README.md +3 -3
- package/examples/full-example/justfile +6 -6
- package/examples/full-example/scripts/formula-shared.ts +3 -2
- package/examples/full-example/src/program.ts +3 -3
- package/examples/nested.ts +1 -1
- package/index.d.ts +23 -16
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +105 -61
- package/src/builtins/completion-group.ts +4 -6
- package/src/builtins/config.test.ts +8 -0
- package/src/builtins/configure-copy.ts +86 -0
- package/src/builtins/configure.ts +70 -0
- package/src/builtins/dispatch.ts +13 -33
- package/src/builtins/index.ts +1 -1
- package/src/builtins/mcp.ts +2 -2
- package/src/builtins/registry.ts +6 -6
- package/src/capabilities.ts +22 -13
- package/src/cli-tool/cli-smoke.test.ts +21 -3
- package/src/cli-tool/create.test.ts +36 -1
- package/src/cli-tool/create.ts +26 -4
- package/src/cli-tool/full-example-capabilities.test.ts +10 -2
- package/src/cli-tool/main.ts +0 -0
- package/src/cli-tool/program.ts +20 -5
- package/src/cli-tool/run-create.ts +8 -19
- package/src/config/bootstrap.ts +11 -9
- package/src/config/context.test.ts +8 -0
- package/src/config/file.test.ts +13 -1
- package/src/config/resolve.test.ts +18 -0
- package/src/config/resolve.ts +2 -2
- package/src/config/validate.test.ts +11 -0
- package/src/config.integration.test.ts +5 -0
- package/src/configure/configure.test.ts +170 -0
- package/src/configure/index.ts +298 -0
- package/src/configure/prompt.ts +47 -0
- package/src/docs/api-guide.test.ts +10 -0
- package/src/docs/builtin.ts +3 -5
- package/src/docs/docs.test.ts +32 -5
- package/src/docs/mcp-guide.ts +4 -4
- package/src/docs/mcp-resources.test.ts +10 -0
- package/src/formats.test.ts +9 -0
- package/src/headless.test.ts +10 -0
- package/src/hidden-mcpb.test.ts +22 -0
- package/src/index.ts +2 -2
- package/src/install/binary-placement.test.ts +14 -0
- package/src/install/gh-release-update.test.ts +9 -0
- package/src/install/install-validate.test.ts +15 -5
- package/src/install/mcp-codex.test.ts +7 -0
- package/src/install/mcp-openclaw.test.ts +6 -0
- package/src/install/mcp-opencode.test.ts +10 -0
- package/src/install/opts.ts +17 -0
- package/src/install/status.test.ts +9 -0
- package/src/install/target-effective.ts +8 -8
- package/src/install/target-scope.ts +11 -8
- package/src/install/targets/configure.ts +1 -1
- package/src/install/targets.test.ts +23 -4
- package/src/invoke.test.ts +15 -1
- package/src/mcp/claude.test.ts +10 -0
- package/src/mcp/env.test.ts +12 -0
- package/src/mcp/tools.ts +1 -1
- package/src/mcp/zip.test.ts +5 -0
- package/src/mcp.integration.test.ts +43 -4
- package/src/parse.test.ts +82 -13
- package/src/schema.ts +1 -9
- package/src/skill/hint.ts +2 -2
- package/src/types.ts +22 -14
- package/src/validate.ts +17 -17
- package/docs/install.md +0 -206
- package/src/builtins/install.ts +0 -106
- package/src/builtins/uninstall.ts +0 -80
- package/src/install/index.ts +0 -409
- 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(
|
package/src/formats.test.ts
CHANGED
|
@@ -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
|
});
|
package/src/headless.test.ts
CHANGED
|
@@ -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;
|
package/src/hidden-mcpb.test.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
18
|
+
configure?: CliConfigureConfig,
|
|
19
19
|
mcpEnabled = false,
|
|
20
20
|
): InstallAgentIntegration {
|
|
21
|
-
return
|
|
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:
|
|
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
|
-
|
|
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(
|
|
86
|
-
const user =
|
|
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));
|