argsbarg 4.1.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CHANGELOG.md +74 -1
  2. package/README.md +91 -85
  3. package/docs/README.md +8 -8
  4. package/docs/ai-skills.md +9 -9
  5. package/docs/bundled-docs.md +5 -5
  6. package/docs/cli-program.md +13 -11
  7. package/docs/config-schema.md +36 -9
  8. package/docs/configure.md +177 -0
  9. package/docs/developing.md +7 -7
  10. package/docs/distribution-homebrew.md +104 -0
  11. package/docs/mcp.md +11 -12
  12. package/docs/output-schema.md +1 -1
  13. package/examples/full-example/Formula/.gitkeep +0 -0
  14. package/examples/full-example/README.md +98 -0
  15. package/examples/full-example/biome.json +22 -0
  16. package/examples/{consumer-app → full-example}/bun.lock +2 -0
  17. package/examples/full-example/justfile +134 -0
  18. package/examples/{consumer-app → full-example}/package.json +10 -3
  19. package/examples/{consumer-app → full-example}/schemas/generated/app-config.json +1 -1
  20. package/examples/{consumer-app → full-example}/schemas/generated/status.json +1 -1
  21. package/examples/full-example/scripts/create-identity.ts +11 -0
  22. package/examples/full-example/scripts/formula-shared.ts +73 -0
  23. package/examples/full-example/scripts/gen-dev-formula.ts +26 -0
  24. package/examples/full-example/scripts/print-identity.ts +27 -0
  25. package/examples/full-example/src/commands/echo/command.ts +21 -0
  26. package/examples/full-example/src/commands/status/command.test.ts +10 -0
  27. package/examples/full-example/src/commands/status/command.ts +36 -0
  28. package/examples/{consumer-app → full-example}/src/commands/status/types.ts +1 -1
  29. package/examples/full-example/src/index.ts +10 -0
  30. package/examples/full-example/src/program.ts +57 -0
  31. package/examples/{consumer-app → full-example}/src/types.ts +1 -1
  32. package/examples/nested.ts +1 -3
  33. package/index.d.ts +49 -81
  34. package/package.json +2 -2
  35. package/src/builtins/builtins.test.ts +84 -64
  36. package/src/builtins/completion-group.ts +17 -17
  37. package/src/builtins/configure-copy.ts +86 -0
  38. package/src/builtins/configure.ts +70 -0
  39. package/src/builtins/dispatch.ts +13 -9
  40. package/src/builtins/index.ts +1 -1
  41. package/src/builtins/mcp.ts +2 -2
  42. package/src/builtins/registry.ts +6 -4
  43. package/src/capabilities.ts +22 -15
  44. package/src/cli-tool/cli-smoke.test.ts +29 -0
  45. package/src/cli-tool/create.test.ts +141 -0
  46. package/src/cli-tool/create.ts +402 -0
  47. package/{examples/consumer-app/capabilities.test.ts → src/cli-tool/full-example-capabilities.test.ts} +16 -15
  48. package/src/cli-tool/main.ts +8 -0
  49. package/src/cli-tool/post-create.ts +111 -0
  50. package/src/cli-tool/program.ts +97 -0
  51. package/src/cli-tool/prompt.ts +28 -0
  52. package/src/cli-tool/run-create.ts +138 -0
  53. package/src/cli.ts +0 -2
  54. package/src/config/bootstrap.ts +27 -18
  55. package/src/config/file.test.ts +1 -1
  56. package/src/config/resolve.test.ts +167 -0
  57. package/src/config/resolve.ts +52 -8
  58. package/src/configure/configure.test.ts +148 -0
  59. package/src/configure/index.ts +284 -0
  60. package/src/configure/prompt.ts +40 -0
  61. package/src/docs/api-guide.test.ts +4 -5
  62. package/src/docs/builtin.ts +3 -5
  63. package/src/docs/docs.test.ts +6 -5
  64. package/src/docs/mcp-guide.ts +11 -12
  65. package/src/index.ts +5 -12
  66. package/src/install/binary-placement.test.ts +101 -0
  67. package/src/install/binary-placement.ts +47 -0
  68. package/src/install/install-validate.test.ts +5 -5
  69. package/src/install/normalize-uninstall.ts +11 -0
  70. package/src/install/normalize.ts +4 -19
  71. package/src/install/opts.ts +17 -0
  72. package/src/install/paths.ts +0 -22
  73. package/src/install/plan.ts +14 -6
  74. package/src/install/shell.ts +0 -14
  75. package/src/install/status.test.ts +6 -6
  76. package/src/install/status.ts +0 -6
  77. package/src/install/target-effective.ts +8 -10
  78. package/src/install/target-scope.ts +26 -36
  79. package/src/install/target-types.ts +0 -16
  80. package/src/install/targets/app.ts +19 -28
  81. package/src/install/targets/configure.ts +6 -2
  82. package/src/install/targets/index.ts +0 -3
  83. package/src/install/targets.test.ts +26 -44
  84. package/src/invoke.test.ts +1 -1
  85. package/src/mcp/env.test.ts +92 -0
  86. package/src/mcp/env.ts +15 -14
  87. package/src/mcp/tools.ts +1 -1
  88. package/src/mcp.integration.test.ts +4 -4
  89. package/src/parse.test.ts +13 -14
  90. package/src/prompt.ts +10 -0
  91. package/src/schema.ts +1 -1
  92. package/src/skill/hint.ts +2 -2
  93. package/src/types.ts +48 -22
  94. package/src/validate.ts +22 -28
  95. package/docs/install.md +0 -290
  96. package/docs/templates/cursor/rules/cli-program.mdc +0 -31
  97. package/examples/config-app/main.ts +0 -20
  98. package/examples/config-app/program.ts +0 -78
  99. package/examples/config-app/schema.ts +0 -37
  100. package/examples/config-app/types.ts +0 -19
  101. package/examples/consumer-app/README.md +0 -56
  102. package/examples/consumer-app/src/main.ts +0 -15
  103. package/examples/consumer-app/src/program.ts +0 -108
  104. package/src/builtins/install.ts +0 -136
  105. package/src/install/app.ts +0 -94
  106. package/src/install/bootstrap.ts +0 -22
  107. package/src/install/completions.ts +0 -56
  108. package/src/install/index.ts +0 -415
  109. package/src/install/install.test.ts +0 -333
  110. package/src/install/targets/completions.ts +0 -133
  111. package/src/install/update.test.ts +0 -123
  112. package/src/install/update.ts +0 -54
  113. /package/examples/{consumer-app → full-example}/schemas/configSchemas.ts +0 -0
  114. /package/examples/{consumer-app → full-example}/schemas/outputSchemas.ts +0 -0
  115. /package/examples/{consumer-app → full-example}/scripts/schemagen/discover-schema-roots.test.ts +0 -0
  116. /package/examples/{consumer-app → full-example}/scripts/schemagen/discover-schema-roots.ts +0 -0
  117. /package/examples/{consumer-app → full-example}/scripts/schemagen/naming.ts +0 -0
  118. /package/examples/{consumer-app → full-example}/scripts/schemagen.ts +0 -0
  119. /package/examples/{consumer-app → full-example}/tsconfig.json +0 -0
@@ -1,7 +1,7 @@
1
1
  import { capabilityDeniedMessage, resolveCapabilities } from "../capabilities.ts";
2
2
  import { Cli } from "../cli.ts";
3
+ import { cliConfigure } from "../configure/index.ts";
3
4
  import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
4
- import { cliInstall } 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";
@@ -12,7 +12,7 @@ import { completionFishScript } from "./completion-fish.ts";
12
12
  import { cliBuiltinCompletionGroup as completionGroup } from "./completion-group.ts";
13
13
  import { completionZshScript } from "./completion-zsh.ts";
14
14
  import { cliBuiltinConfigGroupIfEnabled } from "./config.ts";
15
- import { cliBuiltinInstallCommand } from "./install.ts";
15
+ import { cliBuiltinConfigureCommand } from "./configure.ts";
16
16
  import { cliBuiltinMcpCommand } from "./mcp.ts";
17
17
  import { cliPresentationRoot } from "./presentation.ts";
18
18
  import { cliBuiltinVersionCommand } from "./version.ts";
@@ -44,6 +44,10 @@ export async function dispatchBuiltin(
44
44
  const caps = resolveCapabilities(program);
45
45
 
46
46
  if (pr.path[0] === "completion") {
47
+ if (!caps.completion) {
48
+ process.stderr.write(capabilityDeniedMessage("completion"));
49
+ process.exit(1);
50
+ }
47
51
  const schemaForCompletion = completionSchema(program, opts);
48
52
  if (pr.path[1] === "bash") {
49
53
  process.stdout.write(completionBashScript(schemaForCompletion));
@@ -92,16 +96,16 @@ export async function dispatchBuiltin(
92
96
  process.exit(1);
93
97
  }
94
98
 
95
- if (pr.path[0] === "install") {
96
- if (!caps.install) {
97
- process.stderr.write(capabilityDeniedMessage("install"));
99
+ if (pr.path[0] === "configure") {
100
+ if (!caps.configure) {
101
+ process.stderr.write(capabilityDeniedMessage("configure"));
98
102
  process.exit(1);
99
103
  }
100
104
  if (pr.path.length !== 1) {
101
- process.stderr.write(`Unknown subcommand: install ${pr.path.slice(1).join(" ")}\n`);
105
+ process.stderr.write(`Unknown subcommand: configure ${pr.path.slice(1).join(" ")}\n`);
102
106
  process.exit(1);
103
107
  }
104
- await cliInstall(program, pr.opts);
108
+ await cliConfigure(program, pr.opts);
105
109
  }
106
110
  }
107
111
 
@@ -128,12 +132,12 @@ export function builtinInterceptRoot(
128
132
  };
129
133
  }
130
134
 
131
- if (first === "install" && caps.install) {
135
+ if (first === "configure" && caps.configure) {
132
136
  return {
133
137
  parseRoot: {
134
138
  key: program.key,
135
139
  description: program.description,
136
- commands: [cliBuiltinInstallCommand(program)],
140
+ commands: [cliBuiltinConfigureCommand(program)],
137
141
  },
138
142
  isLeafCompletionIntercept: false,
139
143
  };
@@ -2,9 +2,9 @@ export { completionBashScript } from "./completion-bash.ts";
2
2
  export { completionFishScript } from "./completion-fish.ts";
3
3
  export { cliBuiltinCompletionGroup } from "./completion-group.ts";
4
4
  export { completionZshScript } from "./completion-zsh.ts";
5
+ export { cliBuiltinConfigureCommand, configureBuiltinOptions } from "./configure.ts";
5
6
  export { builtinInterceptRoot, dispatchBuiltin } from "./dispatch.ts";
6
7
  export { type CliSchemaExport, exportPresentationBuiltins } from "./export.ts";
7
- export { cliBuiltinInstallCommand, installBuiltinOptions } from "./install.ts";
8
8
  export { cliBuiltinMcpCommand } from "./mcp.ts";
9
9
  export {
10
10
  cliParseRoot,
@@ -12,8 +12,8 @@ export function cliBuiltinMcpCommand(program: CliProgram): CliRouter {
12
12
  " args: mcp",
13
13
  "",
14
14
  ];
15
- if (caps.install) {
16
- lines.push("Or:", "", " {argsbarg:program} install --mcp --yes", "");
15
+ if (caps.configure) {
16
+ lines.push("Or:", "", " {argsbarg:program} configure", "");
17
17
  }
18
18
  if (docsEnabled(program)) {
19
19
  lines.push("Full setup guide: {argsbarg:program} docs mcp");
@@ -3,7 +3,7 @@ import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
3
3
  import type { CliNode, CliProgram } from "../types.ts";
4
4
  import { cliBuiltinCompletionGroup } from "./completion-group.ts";
5
5
  import { cliBuiltinConfigGroupIfEnabled } from "./config.ts";
6
- import { cliBuiltinInstallCommand } from "./install.ts";
6
+ import { cliBuiltinConfigureCommand } from "./configure.ts";
7
7
  import { cliBuiltinMcpCommand } from "./mcp.ts";
8
8
  import { cliBuiltinVersionCommand } from "./version.ts";
9
9
 
@@ -26,10 +26,12 @@ function pushBuiltin(
26
26
  /** Capability-gated built-in command nodes in stable order (parse, help, export). */
27
27
  export function resolveBuiltins(program: CliProgram, caps: CliCapabilities): CliNode[] {
28
28
  const builtins: CliNode[] = [];
29
- pushBuiltin(builtins, program, (p) => cliBuiltinCompletionGroup(p));
29
+ if (caps.completion) {
30
+ pushBuiltin(builtins, program, (p) => cliBuiltinCompletionGroup(p));
31
+ }
30
32
  pushBuiltin(builtins, program, () => cliBuiltinVersionCommand());
31
- if (caps.install) {
32
- pushBuiltin(builtins, program, (p) => cliBuiltinInstallCommand(p));
33
+ if (caps.configure) {
34
+ pushBuiltin(builtins, program, (p) => cliBuiltinConfigureCommand(p));
33
35
  }
34
36
  pushBuiltin(builtins, program, (p) => cliBuiltinDocsGroupIfEnabled(p) ?? null);
35
37
  if (caps.mcp) {
@@ -8,32 +8,33 @@ import type { CliProgram } from "./types.ts";
8
8
 
9
9
  /** Platform builtins derived from program config and runtime. */
10
10
  export interface CliCapabilities {
11
- completion: true;
11
+ completion: boolean;
12
12
  mcp: boolean;
13
- install: boolean;
13
+ configure: boolean;
14
14
  docs: boolean;
15
- update: boolean;
16
15
  configCommands: boolean;
17
16
  }
18
17
 
19
18
  /** Resolves which capabilities are enabled for a program. */
20
19
  export function resolveCapabilities(program: CliProgram): CliCapabilities {
21
- const install = program.install?.enabled !== false;
20
+ const configure = program.configure?.enabled !== false;
22
21
  return {
23
- completion: true,
22
+ completion: program.completion?.enabled !== false,
24
23
  mcp: program.mcpServer?.enabled === true,
25
- install,
24
+ configure,
26
25
  docs: program.docs?.enabled === true,
27
- update: install && typeof program.install?.updateGetLatest === "function",
28
26
  configCommands: configCommandsEnabled(program),
29
27
  };
30
28
  }
31
29
 
32
30
  /** Reserved top-level command names for the given capabilities. */
33
31
  export function reservedCommandNames(caps: CliCapabilities): string[] {
34
- const names = ["completion", "version"];
35
- if (caps.install) {
36
- names.push("install");
32
+ const names = ["version"];
33
+ if (caps.completion) {
34
+ names.unshift("completion");
35
+ }
36
+ if (caps.configure) {
37
+ names.push("configure");
37
38
  }
38
39
  if (caps.docs) {
39
40
  names.push("docs");
@@ -59,15 +60,17 @@ export function skipsRequiredAppConfigExit(path: string[], caps: CliCapabilities
59
60
  return false;
60
61
  }
61
62
 
62
- export type CapabilityFeature = "mcp" | "install" | "docs" | "config";
63
+ export type CapabilityFeature = "mcp" | "configure" | "docs" | "config" | "completion";
63
64
 
64
65
  /** Stderr message when a disabled built-in is invoked from the CLI. */
65
66
  export function capabilityDeniedMessage(feature: CapabilityFeature): string {
66
67
  switch (feature) {
68
+ case "completion":
69
+ return "Shell completion is not available for this app.\n";
67
70
  case "mcp":
68
71
  return "MCP is not available for this app.\n";
69
- case "install":
70
- return "Install is not available for this app.\n";
72
+ case "configure":
73
+ return "Configure is not available for this app.\n";
71
74
  case "docs":
72
75
  return "Documentation commands are not available for this app.\n";
73
76
  case "config":
@@ -81,12 +84,16 @@ export function assertBuiltinAllowed(argv: string[], caps: CliCapabilities): voi
81
84
  return;
82
85
  }
83
86
  const first = argv[0];
87
+ if (first === "completion" && !caps.completion) {
88
+ process.stderr.write(capabilityDeniedMessage("completion"));
89
+ process.exit(1);
90
+ }
84
91
  if (first === "mcp" && !caps.mcp) {
85
92
  process.stderr.write(capabilityDeniedMessage("mcp"));
86
93
  process.exit(1);
87
94
  }
88
- if (first === "install" && !caps.install) {
89
- process.stderr.write(capabilityDeniedMessage("install"));
95
+ if (first === "configure" && !caps.configure) {
96
+ process.stderr.write(capabilityDeniedMessage("configure"));
90
97
  process.exit(1);
91
98
  }
92
99
  if (first === "docs" && !caps.docs) {
@@ -0,0 +1,29 @@
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("help lists create and version only (no install, completion, mcp)", () => {
15
+ const proc = spawnSync("bun", [main, "--help"], { encoding: "utf8" });
16
+ expect(proc.status).toBe(0);
17
+ expect(proc.stdout).toContain("create");
18
+ expect(proc.stdout).toContain("version");
19
+ expect(proc.stdout).not.toContain("configure");
20
+ expect(proc.stdout).not.toContain("completion");
21
+ expect(proc.stdout).not.toContain("mcp");
22
+ });
23
+
24
+ test("completion subcommand is disabled", () => {
25
+ const proc = spawnSync("bun", [main, "completion", "bash"], { encoding: "utf8" });
26
+ expect(proc.status).toBe(1);
27
+ expect(proc.stderr).toContain("Shell completion is not available");
28
+ });
29
+ });
@@ -0,0 +1,141 @@
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
+ resolveCreateOptions,
12
+ substituteTemplateContent,
13
+ } from "./create.ts";
14
+
15
+ describe("argsbarg create", () => {
16
+ test("substitutes {key} tokens", () => {
17
+ const out = substituteTemplateContent(
18
+ "key={key} class={className} env={envPrefix}_API_TOKEN tap={tap} org={tapOrg}",
19
+ {
20
+ key: "my-cli",
21
+ className: "MyCli",
22
+ tap: "org/my-cli",
23
+ homepage: "https://github.com/org/my-cli",
24
+ releaseRepo: "org/my-cli",
25
+ desc: "My CLI",
26
+ force: false,
27
+ dryRun: false,
28
+ check: false,
29
+ diff: false,
30
+ yes: false,
31
+ devTemplate: false,
32
+ },
33
+ );
34
+ expect(out).toContain("my-cli");
35
+ expect(out).toContain("MyCli");
36
+ expect(out).toContain("MY_CLI_API_TOKEN");
37
+ expect(out).toContain("org/my-cli");
38
+ expect(out).not.toContain("{key}");
39
+ });
40
+
41
+ test("classNameFromKey", () => {
42
+ expect(classNameFromKey("sqsp-i18n")).toBe("SqspI18n");
43
+ expect(classNameFromKey("at1")).toBe("At1");
44
+ expect(classNameFromKey("1password")).toBe("App1password");
45
+ });
46
+
47
+ test("resolveCreateOptions derives identity defaults from key", () => {
48
+ expect(resolveCreateOptions({ key: "1password", releaseRepo: "org/1password" }).className).toBe(
49
+ "App1password",
50
+ );
51
+ expect(
52
+ resolveCreateOptions({ key: "my-cli", className: "Custom", releaseRepo: "org/my-cli" })
53
+ .className,
54
+ ).toBe("Custom");
55
+ const opts = resolveCreateOptions({ key: "at1", releaseRepo: "bdombro/at1" });
56
+ expect(opts.tap).toBe("bdombro/at1");
57
+ expect(opts.releaseRepo).toBe("bdombro/at1");
58
+ expect(opts.homepage).toBe("https://github.com/bdombro/at1");
59
+ expect(opts.desc).toBe("At1 CLI");
60
+ });
61
+
62
+ test("resolveCreateOptions requires release repo", () => {
63
+ expect(() => resolveCreateOptions({ key: "at1" })).toThrow(/release repo/i);
64
+ });
65
+
66
+ test("renderCreateTree includes justfile and create-identity", () => {
67
+ const tree = renderCreateTree({
68
+ key: "testapp",
69
+ className: "Testapp",
70
+ tap: "local/testapp",
71
+ homepage: "https://example.com",
72
+ releaseRepo: "example/testapp",
73
+ desc: "Test",
74
+ force: false,
75
+ dryRun: false,
76
+ check: false,
77
+ diff: false,
78
+ yes: false,
79
+ devTemplate: false,
80
+ });
81
+ expect(tree.has("justfile")).toBe(true);
82
+ expect(tree.has("biome.json")).toBe(true);
83
+ expect(tree.has(".cursor/rules/code.mdc")).toBe(true);
84
+ expect(tree.has("scripts/create-identity.ts")).toBe(true);
85
+ const identity = tree.get("scripts/create-identity.ts");
86
+ expect(identity).toContain('key: "testapp"');
87
+ const formula = tree.get("scripts/formula-shared.ts");
88
+ expect(formula).toContain("create-identity.ts");
89
+ });
90
+
91
+ test("--check detects drift", () => {
92
+ const dir = mkdtempSync(join(tmpdir(), "argsbarg-create-"));
93
+ try {
94
+ applyCreate(dir, {
95
+ key: "testapp",
96
+ className: "Testapp",
97
+ tap: "local/testapp",
98
+ homepage: "https://example.com",
99
+ releaseRepo: "example/testapp",
100
+ desc: "Test",
101
+ force: true,
102
+ dryRun: false,
103
+ check: false,
104
+ diff: false,
105
+ yes: false,
106
+ devTemplate: false,
107
+ });
108
+ expect(diffCreate(dir, { key: "testapp", className: "Testapp" })).toEqual([]);
109
+ } finally {
110
+ rmSync(dir, { recursive: true, force: true });
111
+ }
112
+ });
113
+
114
+ test("--check infers options from create-identity.ts", () => {
115
+ const dir = mkdtempSync(join(tmpdir(), "argsbarg-create-"));
116
+ try {
117
+ applyCreate(dir, {
118
+ key: "testapp",
119
+ className: "Testapp",
120
+ tap: "local/testapp",
121
+ homepage: "https://example.com",
122
+ releaseRepo: "example/testapp",
123
+ desc: "Test",
124
+ force: true,
125
+ dryRun: false,
126
+ check: false,
127
+ diff: false,
128
+ yes: false,
129
+ devTemplate: false,
130
+ });
131
+ expect(diffCreate(dir, { check: true })).toEqual([]);
132
+ } finally {
133
+ rmSync(dir, { recursive: true, force: true });
134
+ }
135
+ });
136
+
137
+ test("--diff captures drift details", () => {
138
+ const drifts = diffCreateDetails("/nonexistent", { key: "x", releaseRepo: "org/x" });
139
+ expect(drifts.length).toBeGreaterThan(0);
140
+ });
141
+ });