argsbarg 4.1.0 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -1
- package/README.md +90 -84
- package/docs/README.md +6 -6
- package/docs/bundled-docs.md +1 -1
- package/docs/cli-program.md +5 -3
- package/docs/config-schema.md +36 -9
- package/docs/developing.md +7 -7
- package/docs/distribution-homebrew.md +103 -0
- package/docs/install.md +105 -189
- package/docs/mcp.md +2 -3
- package/docs/output-schema.md +1 -1
- package/examples/full-example/Formula/.gitkeep +0 -0
- package/examples/full-example/README.md +98 -0
- package/examples/full-example/biome.json +22 -0
- package/examples/{consumer-app → full-example}/bun.lock +2 -0
- package/examples/full-example/justfile +134 -0
- package/examples/{consumer-app → full-example}/package.json +10 -3
- package/examples/{consumer-app → full-example}/schemas/generated/app-config.json +1 -1
- package/examples/{consumer-app → full-example}/schemas/generated/status.json +1 -1
- package/examples/full-example/scripts/create-identity.ts +11 -0
- package/examples/full-example/scripts/formula-shared.ts +73 -0
- package/examples/full-example/scripts/gen-dev-formula.ts +26 -0
- package/examples/full-example/scripts/print-identity.ts +27 -0
- package/examples/full-example/src/commands/echo/command.ts +21 -0
- package/examples/full-example/src/commands/status/command.test.ts +10 -0
- package/examples/full-example/src/commands/status/command.ts +36 -0
- package/examples/{consumer-app → full-example}/src/commands/status/types.ts +1 -1
- package/examples/full-example/src/index.ts +10 -0
- package/examples/full-example/src/program.ts +57 -0
- package/examples/{consumer-app → full-example}/src/types.ts +1 -1
- package/examples/nested.ts +1 -3
- package/index.d.ts +27 -66
- package/package.json +2 -2
- package/src/builtins/builtins.test.ts +22 -23
- package/src/builtins/completion-group.ts +17 -15
- package/src/builtins/dispatch.ts +25 -1
- package/src/builtins/install.ts +22 -52
- package/src/builtins/registry.ts +2 -0
- package/src/builtins/uninstall.ts +80 -0
- package/src/capabilities.ts +1 -3
- package/src/cli-tool/cli-smoke.test.ts +19 -0
- package/src/cli-tool/create.test.ts +119 -0
- package/src/cli-tool/create.ts +380 -0
- package/{examples/consumer-app/capabilities.test.ts → src/cli-tool/full-example-capabilities.test.ts} +15 -14
- package/src/cli-tool/main.ts +8 -0
- package/src/cli-tool/post-create.ts +111 -0
- package/src/cli-tool/program.ts +82 -0
- package/src/cli-tool/prompt.ts +28 -0
- package/src/cli-tool/run-create.ts +149 -0
- package/src/cli.ts +0 -2
- package/src/config/bootstrap.ts +16 -9
- package/src/config/resolve.test.ts +167 -0
- package/src/config/resolve.ts +50 -6
- package/src/docs/api-guide.test.ts +4 -5
- package/src/docs/docs.test.ts +2 -1
- package/src/docs/mcp-guide.ts +7 -8
- package/src/index.ts +3 -10
- package/src/install/binary-placement.test.ts +101 -0
- package/src/install/binary-placement.ts +47 -0
- package/src/install/index.ts +117 -123
- package/src/install/install.test.ts +89 -105
- package/src/install/normalize-uninstall.ts +11 -0
- package/src/install/normalize.ts +4 -19
- package/src/install/paths.ts +0 -22
- package/src/install/plan.ts +14 -6
- package/src/install/shell.ts +0 -14
- package/src/install/status.test.ts +6 -6
- package/src/install/status.ts +0 -6
- package/src/install/target-effective.ts +0 -2
- package/src/install/target-scope.ts +15 -28
- package/src/install/target-types.ts +0 -16
- package/src/install/targets/app.ts +19 -28
- package/src/install/targets/configure.ts +5 -1
- package/src/install/targets/index.ts +0 -3
- package/src/install/targets.test.ts +24 -42
- package/src/mcp/env.test.ts +92 -0
- package/src/mcp/env.ts +15 -14
- package/src/parse.test.ts +3 -2
- package/src/prompt.ts +10 -0
- package/src/schema.ts +9 -1
- package/src/types.ts +27 -9
- package/src/validate.ts +5 -11
- package/docs/templates/cursor/rules/cli-program.mdc +0 -31
- package/examples/config-app/main.ts +0 -20
- package/examples/config-app/program.ts +0 -78
- package/examples/config-app/schema.ts +0 -37
- package/examples/config-app/types.ts +0 -19
- package/examples/consumer-app/README.md +0 -56
- package/examples/consumer-app/src/main.ts +0 -15
- package/examples/consumer-app/src/program.ts +0 -108
- package/src/install/app.ts +0 -94
- package/src/install/bootstrap.ts +0 -22
- package/src/install/completions.ts +0 -56
- package/src/install/targets/completions.ts +0 -133
- package/src/install/update.test.ts +0 -123
- package/src/install/update.ts +0 -54
- /package/examples/{consumer-app → full-example}/schemas/configSchemas.ts +0 -0
- /package/examples/{consumer-app → full-example}/schemas/outputSchemas.ts +0 -0
- /package/examples/{consumer-app → full-example}/scripts/schemagen/discover-schema-roots.test.ts +0 -0
- /package/examples/{consumer-app → full-example}/scripts/schemagen/discover-schema-roots.ts +0 -0
- /package/examples/{consumer-app → full-example}/scripts/schemagen/naming.ts +0 -0
- /package/examples/{consumer-app → full-example}/scripts/schemagen.ts +0 -0
- /package/examples/{consumer-app → full-example}/tsconfig.json +0 -0
|
@@ -6,6 +6,7 @@ import { completionBashScript, completionFishScript, completionZshScript } from
|
|
|
6
6
|
import { cliBuiltinInstallCommand, installBuiltinOptions } from "./install.ts";
|
|
7
7
|
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
8
8
|
import { cliParseRoot, cliPresentationRoot } from "./presentation.ts";
|
|
9
|
+
import { cliBuiltinUninstallCommand, uninstallBuiltinOptions } from "./uninstall.ts";
|
|
9
10
|
|
|
10
11
|
const fixture: CliProgram = {
|
|
11
12
|
key: "myapp",
|
|
@@ -22,22 +23,32 @@ const fixture: CliProgram = {
|
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
describe("builtins help copy", () => {
|
|
25
|
-
test("install command includes description
|
|
26
|
+
test("install command includes Homebrew-oriented description", () => {
|
|
26
27
|
const install = cliBuiltinInstallCommand(fixture);
|
|
27
|
-
expect(install.description).toContain("
|
|
28
|
-
expect(install.notes).toContain("install
|
|
28
|
+
expect(install.description).toContain("agent skills");
|
|
29
|
+
expect(install.notes).toContain("brew install");
|
|
29
30
|
const names = installBuiltinOptions(fixture).map((o) => o.name);
|
|
30
31
|
expect(names).toContain("all");
|
|
31
32
|
expect(names).toContain("mcp");
|
|
33
|
+
expect(names).not.toContain("app");
|
|
34
|
+
expect(names).not.toContain("completions");
|
|
35
|
+
expect(names).not.toContain("update");
|
|
32
36
|
expect(names.indexOf("all")).toBeLessThan(names.indexOf("mcp"));
|
|
33
37
|
expect(names.indexOf("mcp")).toBeLessThan(names.indexOf("status"));
|
|
34
|
-
expect(names
|
|
35
|
-
expect(names.indexOf("uninstall")).toBeLessThan(names.indexOf("from"));
|
|
36
|
-
expect(names.indexOf("from")).toBeLessThan(names.indexOf("yes"));
|
|
38
|
+
expect(names).not.toContain("uninstall");
|
|
37
39
|
const yesOpt = installBuiltinOptions(fixture).find((o) => o.name === "yes");
|
|
38
40
|
expect(yesOpt?.shortName).toBe("y");
|
|
39
41
|
});
|
|
40
42
|
|
|
43
|
+
test("uninstall command includes removal guidance", () => {
|
|
44
|
+
const uninstall = cliBuiltinUninstallCommand(fixture);
|
|
45
|
+
expect(uninstall.notes).toContain("brew uninstall");
|
|
46
|
+
const names = uninstallBuiltinOptions(fixture).map((o) => o.name);
|
|
47
|
+
expect(names).toContain("all");
|
|
48
|
+
expect(names).not.toContain("status");
|
|
49
|
+
expect(names).not.toContain("reinstall");
|
|
50
|
+
});
|
|
51
|
+
|
|
41
52
|
test("install -y parses as --yes", () => {
|
|
42
53
|
const root = cliParseRoot(fixture);
|
|
43
54
|
const pr = postParseValidate(root, parse(root, ["install", "-y"]));
|
|
@@ -53,23 +64,10 @@ describe("builtins help copy", () => {
|
|
|
53
64
|
expect(names).not.toContain("mcp");
|
|
54
65
|
});
|
|
55
66
|
|
|
56
|
-
test("install
|
|
67
|
+
test("install notes mention brew upgrade", () => {
|
|
57
68
|
const install = cliBuiltinInstallCommand(fixture);
|
|
58
|
-
expect(
|
|
59
|
-
expect(install.notes).
|
|
60
|
-
expect(install.notes).toContain("Refresh after upgrading");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("install notes include upgrade section when updateGetLatest is set", () => {
|
|
64
|
-
const withUpdate: CliProgram = {
|
|
65
|
-
...fixture,
|
|
66
|
-
install: { updateGetLatest: async () => ({ path: process.execPath }) },
|
|
67
|
-
};
|
|
68
|
-
const install = cliBuiltinInstallCommand(withUpdate);
|
|
69
|
-
const notes = install.notes ?? "";
|
|
70
|
-
expect(installBuiltinOptions(withUpdate).map((o) => o.name)).toContain("update");
|
|
71
|
-
expect(notes).toContain("Upgrade to latest release");
|
|
72
|
-
expect(notes.indexOf("install --reinstall")).toBeLessThan(notes.indexOf("install --update"));
|
|
69
|
+
expect(install.notes).toContain("brew upgrade");
|
|
70
|
+
expect(install.notes).toContain("install --configure");
|
|
73
71
|
});
|
|
74
72
|
|
|
75
73
|
test("mcp builtin description is user-facing", () => {
|
|
@@ -90,6 +88,7 @@ describe("presentation root", () => {
|
|
|
90
88
|
const keys = root.commands?.map((c) => c.key) ?? [];
|
|
91
89
|
expect(keys).toContain("mcp");
|
|
92
90
|
expect(keys).toContain("install");
|
|
91
|
+
expect(keys).toContain("uninstall");
|
|
93
92
|
});
|
|
94
93
|
|
|
95
94
|
test("omits install when install.enabled is false", () => {
|
|
@@ -159,6 +158,6 @@ describe("schema export builtins", () => {
|
|
|
159
158
|
test("exportPresentationBuiltins includes install options", () => {
|
|
160
159
|
const builtins = exportPresentationBuiltins(fixture);
|
|
161
160
|
const install = builtins.find((b) => b.key === "install");
|
|
162
|
-
expect(install?.options?.find((o) => o.name === "all")?.description).toContain("
|
|
161
|
+
expect(install?.options?.find((o) => o.name === "all")?.description).toContain("agent");
|
|
163
162
|
});
|
|
164
163
|
});
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { resolveCapabilities } from "../capabilities.ts";
|
|
2
|
-
import type { CliProgram
|
|
2
|
+
import type { CliProgram } from "../types.ts";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Builds the static `completion` / `bash` / `zsh` / `fish` command subtree (merged into the program root at runtime).
|
|
6
6
|
*/
|
|
7
|
-
export function cliBuiltinCompletionGroup(program: CliProgram): CliRouter {
|
|
7
|
+
export function cliBuiltinCompletionGroup(program: CliProgram): import("../types.ts").CliRouter {
|
|
8
8
|
const appName = program.key;
|
|
9
|
-
const
|
|
10
|
-
const router: CliRouter = {
|
|
9
|
+
const router: import("../types.ts").CliRouter = {
|
|
11
10
|
key: "completion",
|
|
12
11
|
description: "Generate the autocompletion script for shells.",
|
|
13
12
|
commands: [
|
|
@@ -15,9 +14,9 @@ export function cliBuiltinCompletionGroup(program: CliProgram): CliRouter {
|
|
|
15
14
|
key: "bash",
|
|
16
15
|
description: "Print a bash tab-completion script.",
|
|
17
16
|
notes:
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
"Homebrew installs completions during `brew install` via generate_completions_from_executable.\n\n" +
|
|
18
|
+
"Ensure your shell loads Homebrew completions:\n" +
|
|
19
|
+
" https://docs.brew.sh/Shell-Completion\n\n" +
|
|
21
20
|
"Try this session only:\n\n" +
|
|
22
21
|
` source <(${appName} completion bash)`,
|
|
23
22
|
handler: () => {},
|
|
@@ -26,9 +25,9 @@ export function cliBuiltinCompletionGroup(program: CliProgram): CliRouter {
|
|
|
26
25
|
key: "zsh",
|
|
27
26
|
description: "Print a zsh tab-completion script.",
|
|
28
27
|
notes:
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
"
|
|
28
|
+
"Homebrew installs completions to $(brew --prefix)/share/zsh/site-functions.\n\n" +
|
|
29
|
+
"Ensure brew shellenv + compinit are configured:\n" +
|
|
30
|
+
" https://docs.brew.sh/Shell-Completion\n\n" +
|
|
32
31
|
"Try this session only:\n\n" +
|
|
33
32
|
` eval "$(${appName} completion zsh)"`,
|
|
34
33
|
handler: () => {},
|
|
@@ -37,15 +36,18 @@ export function cliBuiltinCompletionGroup(program: CliProgram): CliRouter {
|
|
|
37
36
|
key: "fish",
|
|
38
37
|
description: "Print a fish tab-completion script.",
|
|
39
38
|
notes:
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
"
|
|
39
|
+
"Homebrew installs completions to $(brew --prefix)/share/fish/vendor_completions.d.\n\n" +
|
|
40
|
+
"See: https://docs.brew.sh/Shell-Completion\n\n" +
|
|
41
|
+
"Try this session only:\n\n" +
|
|
42
|
+
` ${appName} completion fish | source`,
|
|
43
43
|
handler: () => {},
|
|
44
44
|
},
|
|
45
45
|
],
|
|
46
46
|
};
|
|
47
|
-
if (
|
|
48
|
-
router.notes =
|
|
47
|
+
if (resolveCapabilities(program).install) {
|
|
48
|
+
router.notes =
|
|
49
|
+
"Completions are installed by Homebrew during formula install.\n\n" +
|
|
50
|
+
"See: https://docs.brew.sh/Shell-Completion";
|
|
49
51
|
}
|
|
50
52
|
return router;
|
|
51
53
|
}
|
package/src/builtins/dispatch.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { capabilityDeniedMessage, resolveCapabilities } from "../capabilities.ts";
|
|
2
2
|
import { Cli } from "../cli.ts";
|
|
3
3
|
import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
|
|
4
|
-
import { cliInstall } from "../install/index.ts";
|
|
4
|
+
import { cliInstall, cliUninstall } from "../install/index.ts";
|
|
5
5
|
import { runMcpBundle } from "../mcp/bundle.ts";
|
|
6
6
|
import type { ParseResult } from "../parse.ts";
|
|
7
7
|
import { ParseKind } from "../parse.ts";
|
|
@@ -15,6 +15,7 @@ import { cliBuiltinConfigGroupIfEnabled } from "./config.ts";
|
|
|
15
15
|
import { cliBuiltinInstallCommand } from "./install.ts";
|
|
16
16
|
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
17
17
|
import { cliPresentationRoot } from "./presentation.ts";
|
|
18
|
+
import { cliBuiltinUninstallCommand } from "./uninstall.ts";
|
|
18
19
|
import { cliBuiltinVersionCommand } from "./version.ts";
|
|
19
20
|
|
|
20
21
|
export interface DispatchBuiltinOpts {
|
|
@@ -103,6 +104,18 @@ export async function dispatchBuiltin(
|
|
|
103
104
|
}
|
|
104
105
|
await cliInstall(program, pr.opts);
|
|
105
106
|
}
|
|
107
|
+
|
|
108
|
+
if (pr.path[0] === "uninstall") {
|
|
109
|
+
if (!caps.install) {
|
|
110
|
+
process.stderr.write(capabilityDeniedMessage("install"));
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
if (pr.path.length !== 1) {
|
|
114
|
+
process.stderr.write(`Unknown subcommand: uninstall ${pr.path.slice(1).join(" ")}\n`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
await cliUninstall(program, pr.opts);
|
|
118
|
+
}
|
|
106
119
|
}
|
|
107
120
|
|
|
108
121
|
/** Built-in intercept roots for leaf programs. */
|
|
@@ -139,6 +152,17 @@ export function builtinInterceptRoot(
|
|
|
139
152
|
};
|
|
140
153
|
}
|
|
141
154
|
|
|
155
|
+
if (first === "uninstall" && caps.install) {
|
|
156
|
+
return {
|
|
157
|
+
parseRoot: {
|
|
158
|
+
key: program.key,
|
|
159
|
+
description: program.description,
|
|
160
|
+
commands: [cliBuiltinUninstallCommand(program)],
|
|
161
|
+
},
|
|
162
|
+
isLeafCompletionIntercept: false,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
142
166
|
if (first === "mcp" && caps.mcp) {
|
|
143
167
|
return {
|
|
144
168
|
parseRoot: {
|
package/src/builtins/install.ts
CHANGED
|
@@ -6,19 +6,7 @@ export function installBuiltinOptions(root: CliProgram): CliOption[] {
|
|
|
6
6
|
const opts: CliOption[] = [
|
|
7
7
|
{
|
|
8
8
|
name: "all",
|
|
9
|
-
description:
|
|
10
|
-
"Install the default set (app, shell completions, and configuration when supported).",
|
|
11
|
-
kind: CliOptionKind.Presence,
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
name: "app",
|
|
15
|
-
description: "Copy this app to your install directory (default ~/.local/bin).",
|
|
16
|
-
kind: CliOptionKind.Presence,
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
name: "completions",
|
|
20
|
-
description:
|
|
21
|
-
"Install bash, zsh, and fish tab-completion scripts (each skipped silently if that shell is not on PATH).",
|
|
9
|
+
description: "Install agent skills and MCP config (default artifact set for this app).",
|
|
22
10
|
kind: CliOptionKind.Presence,
|
|
23
11
|
},
|
|
24
12
|
{
|
|
@@ -41,8 +29,7 @@ export function installBuiltinOptions(root: CliProgram): CliOption[] {
|
|
|
41
29
|
if (root.appConfig) {
|
|
42
30
|
opts.push({
|
|
43
31
|
name: "configure",
|
|
44
|
-
description:
|
|
45
|
-
"Run the configuration wizard (install), or remove the config file (--uninstall).",
|
|
32
|
+
description: "Run the interactive configuration wizard.",
|
|
46
33
|
kind: CliOptionKind.Presence,
|
|
47
34
|
});
|
|
48
35
|
}
|
|
@@ -55,31 +42,10 @@ export function installBuiltinOptions(root: CliProgram): CliOption[] {
|
|
|
55
42
|
},
|
|
56
43
|
{
|
|
57
44
|
name: "reinstall",
|
|
58
|
-
description: "Refresh everything already installed for this app.",
|
|
59
|
-
kind: CliOptionKind.Presence,
|
|
60
|
-
},
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
if (resolveCapabilities(root).update) {
|
|
64
|
-
opts.push({
|
|
65
|
-
name: "update",
|
|
66
|
-
description: "Download the latest release and refresh installed files.",
|
|
67
|
-
kind: CliOptionKind.Presence,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
opts.push(
|
|
72
|
-
{
|
|
73
|
-
name: "uninstall",
|
|
74
45
|
description:
|
|
75
|
-
"
|
|
46
|
+
"Refresh installed agent artifacts (skills, MCP). Used by Homebrew post_install.",
|
|
76
47
|
kind: CliOptionKind.Presence,
|
|
77
48
|
},
|
|
78
|
-
{
|
|
79
|
-
name: "from",
|
|
80
|
-
description: "App executable to copy (default: running executable). Used with --reinstall.",
|
|
81
|
-
kind: CliOptionKind.String,
|
|
82
|
-
},
|
|
83
49
|
{
|
|
84
50
|
name: "yes",
|
|
85
51
|
description: "Skip the confirmation prompt.",
|
|
@@ -93,7 +59,7 @@ export function installBuiltinOptions(root: CliProgram): CliOption[] {
|
|
|
93
59
|
},
|
|
94
60
|
{
|
|
95
61
|
name: "json",
|
|
96
|
-
description: "Print changed paths (install/
|
|
62
|
+
description: "Print changed paths (install/reinstall) or status JSON on stdout.",
|
|
97
63
|
kind: CliOptionKind.Presence,
|
|
98
64
|
},
|
|
99
65
|
);
|
|
@@ -105,30 +71,34 @@ export function installBuiltinOptions(root: CliProgram): CliOption[] {
|
|
|
105
71
|
export function cliBuiltinInstallCommand(root: CliProgram): CliLeaf {
|
|
106
72
|
const app = root.key;
|
|
107
73
|
const notesLines = [
|
|
108
|
-
"
|
|
109
|
-
`
|
|
74
|
+
"Install the binary via Homebrew (tap-from-repo), then refresh agent artifacts:",
|
|
75
|
+
` brew tap <org>/<repo>`,
|
|
76
|
+
` brew install <tap>/${app}`,
|
|
110
77
|
"",
|
|
111
|
-
"
|
|
112
|
-
` ${app} install --reinstall`,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
78
|
+
"Homebrew post_install runs:",
|
|
79
|
+
` ${app} install --reinstall --yes`,
|
|
80
|
+
"",
|
|
81
|
+
"Configure separately (interactive):",
|
|
82
|
+
` ${app} install --configure`,
|
|
83
|
+
"",
|
|
84
|
+
"Upgrade:",
|
|
85
|
+
` brew upgrade ${app}`,
|
|
86
|
+
"",
|
|
87
|
+
"Shell completions are installed by Homebrew during brew install.",
|
|
88
|
+
"See: https://docs.brew.sh/Shell-Completion",
|
|
118
89
|
"",
|
|
119
90
|
"See what is installed:",
|
|
120
91
|
` ${app} install --status`,
|
|
121
92
|
"",
|
|
122
|
-
"Remove
|
|
123
|
-
` ${app}
|
|
93
|
+
"Remove agent artifacts:",
|
|
94
|
+
` ${app} uninstall --yes`,
|
|
124
95
|
"",
|
|
125
96
|
"Use --dry to preview changes without writing files.",
|
|
126
97
|
"Use --json for machine-readable output.",
|
|
127
|
-
|
|
98
|
+
];
|
|
128
99
|
return {
|
|
129
100
|
key: "install",
|
|
130
|
-
description:
|
|
131
|
-
"Install this app, shell completions, agent skills, and MCP config on your machine.",
|
|
101
|
+
description: "Install agent skills and MCP config for this app (binary via Homebrew).",
|
|
132
102
|
options: installBuiltinOptions(root),
|
|
133
103
|
notes: notesLines.join("\n"),
|
|
134
104
|
handler: () => {},
|
package/src/builtins/registry.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { cliBuiltinCompletionGroup } from "./completion-group.ts";
|
|
|
5
5
|
import { cliBuiltinConfigGroupIfEnabled } from "./config.ts";
|
|
6
6
|
import { cliBuiltinInstallCommand } from "./install.ts";
|
|
7
7
|
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
8
|
+
import { cliBuiltinUninstallCommand } from "./uninstall.ts";
|
|
8
9
|
import { cliBuiltinVersionCommand } from "./version.ts";
|
|
9
10
|
|
|
10
11
|
type BuiltinFactory = (program: CliProgram) => CliNode | null;
|
|
@@ -30,6 +31,7 @@ export function resolveBuiltins(program: CliProgram, caps: CliCapabilities): Cli
|
|
|
30
31
|
pushBuiltin(builtins, program, () => cliBuiltinVersionCommand());
|
|
31
32
|
if (caps.install) {
|
|
32
33
|
pushBuiltin(builtins, program, (p) => cliBuiltinInstallCommand(p));
|
|
34
|
+
pushBuiltin(builtins, program, (p) => cliBuiltinUninstallCommand(p));
|
|
33
35
|
}
|
|
34
36
|
pushBuiltin(builtins, program, (p) => cliBuiltinDocsGroupIfEnabled(p) ?? null);
|
|
35
37
|
if (caps.mcp) {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { resolveCapabilities } from "../capabilities.ts";
|
|
2
|
+
import { type CliLeaf, type CliOption, CliOptionKind, type CliProgram } from "../types.ts";
|
|
3
|
+
|
|
4
|
+
/** Uninstall command options (dynamic: `--mcp` only when MCP is enabled). */
|
|
5
|
+
export function uninstallBuiltinOptions(root: CliProgram): CliOption[] {
|
|
6
|
+
const opts: CliOption[] = [
|
|
7
|
+
{
|
|
8
|
+
name: "all",
|
|
9
|
+
description: "Remove all detected agent artifacts (default when no scope flags are given).",
|
|
10
|
+
kind: CliOptionKind.Presence,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "skill",
|
|
14
|
+
description: "Remove agent skills only.",
|
|
15
|
+
kind: CliOptionKind.Presence,
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
if (resolveCapabilities(root).mcp) {
|
|
20
|
+
opts.push({
|
|
21
|
+
name: "mcp",
|
|
22
|
+
description: "Remove MCP server configuration only.",
|
|
23
|
+
kind: CliOptionKind.Presence,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (root.appConfig) {
|
|
28
|
+
opts.push({
|
|
29
|
+
name: "configure",
|
|
30
|
+
description: "Remove the app config file only.",
|
|
31
|
+
kind: CliOptionKind.Presence,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
opts.push(
|
|
36
|
+
{
|
|
37
|
+
name: "yes",
|
|
38
|
+
description: "Skip the confirmation prompt.",
|
|
39
|
+
kind: CliOptionKind.Presence,
|
|
40
|
+
shortName: "y",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "dry",
|
|
44
|
+
description: "Show what would change without writing files.",
|
|
45
|
+
kind: CliOptionKind.Presence,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "json",
|
|
49
|
+
description: "Print changed paths on stdout.",
|
|
50
|
+
kind: CliOptionKind.Presence,
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return opts;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Builds the `uninstall` built-in command. */
|
|
58
|
+
export function cliBuiltinUninstallCommand(root: CliProgram): CliLeaf {
|
|
59
|
+
const app = root.key;
|
|
60
|
+
const notesLines = [
|
|
61
|
+
"Remove agent artifacts installed by this app (skills, MCP, config).",
|
|
62
|
+
"Run before `brew uninstall` while the CLI is still on PATH:",
|
|
63
|
+
"",
|
|
64
|
+
` ${app} uninstall --yes`,
|
|
65
|
+
` brew uninstall <tap>/${app}`,
|
|
66
|
+
"",
|
|
67
|
+
"Remove app config only:",
|
|
68
|
+
` ${app} uninstall --configure --yes`,
|
|
69
|
+
"",
|
|
70
|
+
"Use --dry to preview changes without writing files.",
|
|
71
|
+
"Use --json for machine-readable output.",
|
|
72
|
+
];
|
|
73
|
+
return {
|
|
74
|
+
key: "uninstall",
|
|
75
|
+
description: "Remove agent skills, MCP config, and app config for this app.",
|
|
76
|
+
options: uninstallBuiltinOptions(root),
|
|
77
|
+
notes: notesLines.join("\n"),
|
|
78
|
+
handler: () => {},
|
|
79
|
+
};
|
|
80
|
+
}
|
package/src/capabilities.ts
CHANGED
|
@@ -12,7 +12,6 @@ export interface CliCapabilities {
|
|
|
12
12
|
mcp: boolean;
|
|
13
13
|
install: boolean;
|
|
14
14
|
docs: boolean;
|
|
15
|
-
update: boolean;
|
|
16
15
|
configCommands: boolean;
|
|
17
16
|
}
|
|
18
17
|
|
|
@@ -24,7 +23,6 @@ export function resolveCapabilities(program: CliProgram): CliCapabilities {
|
|
|
24
23
|
mcp: program.mcpServer?.enabled === true,
|
|
25
24
|
install,
|
|
26
25
|
docs: program.docs?.enabled === true,
|
|
27
|
-
update: install && typeof program.install?.updateGetLatest === "function",
|
|
28
26
|
configCommands: configCommandsEnabled(program),
|
|
29
27
|
};
|
|
30
28
|
}
|
|
@@ -33,7 +31,7 @@ export function resolveCapabilities(program: CliProgram): CliCapabilities {
|
|
|
33
31
|
export function reservedCommandNames(caps: CliCapabilities): string[] {
|
|
34
32
|
const names = ["completion", "version"];
|
|
35
33
|
if (caps.install) {
|
|
36
|
-
names.push("install");
|
|
34
|
+
names.push("install", "uninstall");
|
|
37
35
|
}
|
|
38
36
|
if (caps.docs) {
|
|
39
37
|
names.push("docs");
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const main = join(import.meta.dir, "main.ts");
|
|
6
|
+
|
|
7
|
+
describe("argsbarg cli-tool", () => {
|
|
8
|
+
test("version subcommand prints version", () => {
|
|
9
|
+
const proc = spawnSync("bun", [main, "version"], { encoding: "utf8" });
|
|
10
|
+
expect(proc.status).toBe(0);
|
|
11
|
+
expect(proc.stdout.trim().length).toBeGreaterThan(0);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("completion subcommand emits bash script", () => {
|
|
15
|
+
const proc = spawnSync("bun", [main, "completion", "bash"], { encoding: "utf8" });
|
|
16
|
+
expect(proc.status).toBe(0);
|
|
17
|
+
expect(proc.stdout).toContain("complete");
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
applyCreate,
|
|
7
|
+
classNameFromKey,
|
|
8
|
+
diffCreate,
|
|
9
|
+
diffCreateDetails,
|
|
10
|
+
renderCreateTree,
|
|
11
|
+
substituteTemplateContent,
|
|
12
|
+
} from "./create.ts";
|
|
13
|
+
|
|
14
|
+
describe("argsbarg create", () => {
|
|
15
|
+
test("substitutes {key} tokens", () => {
|
|
16
|
+
const out = substituteTemplateContent(
|
|
17
|
+
"key={key} class={className} env={envPrefix}_API_TOKEN tap={tap} org={tapOrg}",
|
|
18
|
+
{
|
|
19
|
+
key: "my-cli",
|
|
20
|
+
className: "MyCli",
|
|
21
|
+
tap: "org/my-cli",
|
|
22
|
+
homepage: "https://github.com/org/my-cli",
|
|
23
|
+
releaseRepo: "org/my-cli",
|
|
24
|
+
desc: "My CLI",
|
|
25
|
+
force: false,
|
|
26
|
+
dryRun: false,
|
|
27
|
+
check: false,
|
|
28
|
+
diff: false,
|
|
29
|
+
yes: false,
|
|
30
|
+
devTemplate: false,
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
expect(out).toContain("my-cli");
|
|
34
|
+
expect(out).toContain("MyCli");
|
|
35
|
+
expect(out).toContain("MY_CLI_API_TOKEN");
|
|
36
|
+
expect(out).toContain("org/my-cli");
|
|
37
|
+
expect(out).not.toContain("{key}");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("classNameFromKey", () => {
|
|
41
|
+
expect(classNameFromKey("sqsp-i18n")).toBe("SqspI18n");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("renderCreateTree includes justfile and create-identity", () => {
|
|
45
|
+
const tree = renderCreateTree({
|
|
46
|
+
key: "testapp",
|
|
47
|
+
className: "Testapp",
|
|
48
|
+
tap: "local/testapp",
|
|
49
|
+
homepage: "https://example.com",
|
|
50
|
+
releaseRepo: "example/testapp",
|
|
51
|
+
desc: "Test",
|
|
52
|
+
force: false,
|
|
53
|
+
dryRun: false,
|
|
54
|
+
check: false,
|
|
55
|
+
diff: false,
|
|
56
|
+
yes: false,
|
|
57
|
+
devTemplate: false,
|
|
58
|
+
});
|
|
59
|
+
expect(tree.has("justfile")).toBe(true);
|
|
60
|
+
expect(tree.has("biome.json")).toBe(true);
|
|
61
|
+
expect(tree.has(".cursor/rules/code.mdc")).toBe(true);
|
|
62
|
+
expect(tree.has("scripts/create-identity.ts")).toBe(true);
|
|
63
|
+
const identity = tree.get("scripts/create-identity.ts");
|
|
64
|
+
expect(identity).toContain('key: "testapp"');
|
|
65
|
+
const formula = tree.get("scripts/formula-shared.ts");
|
|
66
|
+
expect(formula).toContain("create-identity.ts");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("--check detects drift", () => {
|
|
70
|
+
const dir = mkdtempSync(join(tmpdir(), "argsbarg-create-"));
|
|
71
|
+
try {
|
|
72
|
+
applyCreate(dir, {
|
|
73
|
+
key: "testapp",
|
|
74
|
+
className: "Testapp",
|
|
75
|
+
tap: "local/testapp",
|
|
76
|
+
homepage: "https://example.com",
|
|
77
|
+
releaseRepo: "example/testapp",
|
|
78
|
+
desc: "Test",
|
|
79
|
+
force: true,
|
|
80
|
+
dryRun: false,
|
|
81
|
+
check: false,
|
|
82
|
+
diff: false,
|
|
83
|
+
yes: false,
|
|
84
|
+
devTemplate: false,
|
|
85
|
+
});
|
|
86
|
+
expect(diffCreate(dir, { key: "testapp", className: "Testapp" })).toEqual([]);
|
|
87
|
+
} finally {
|
|
88
|
+
rmSync(dir, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("--check infers options from create-identity.ts", () => {
|
|
93
|
+
const dir = mkdtempSync(join(tmpdir(), "argsbarg-create-"));
|
|
94
|
+
try {
|
|
95
|
+
applyCreate(dir, {
|
|
96
|
+
key: "testapp",
|
|
97
|
+
className: "Testapp",
|
|
98
|
+
tap: "local/testapp",
|
|
99
|
+
homepage: "https://example.com",
|
|
100
|
+
releaseRepo: "example/testapp",
|
|
101
|
+
desc: "Test",
|
|
102
|
+
force: true,
|
|
103
|
+
dryRun: false,
|
|
104
|
+
check: false,
|
|
105
|
+
diff: false,
|
|
106
|
+
yes: false,
|
|
107
|
+
devTemplate: false,
|
|
108
|
+
});
|
|
109
|
+
expect(diffCreate(dir, { check: true })).toEqual([]);
|
|
110
|
+
} finally {
|
|
111
|
+
rmSync(dir, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("--diff captures drift details", () => {
|
|
116
|
+
const drifts = diffCreateDetails("/nonexistent", { key: "x" });
|
|
117
|
+
expect(drifts.length).toBeGreaterThan(0);
|
|
118
|
+
});
|
|
119
|
+
});
|