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
|
@@ -12,8 +12,6 @@ export type InstallPlanMode =
|
|
|
12
12
|
|
|
13
13
|
export interface InstallScope {
|
|
14
14
|
all?: boolean;
|
|
15
|
-
app?: boolean;
|
|
16
|
-
completions?: boolean;
|
|
17
15
|
skill?: boolean;
|
|
18
16
|
mcp?: boolean;
|
|
19
17
|
configure?: boolean;
|
|
@@ -29,7 +27,6 @@ export type CliInstallArtifactKey =
|
|
|
29
27
|
| "claudeSkill"
|
|
30
28
|
| "codexMcp"
|
|
31
29
|
| "codexSkill"
|
|
32
|
-
| "completions"
|
|
33
30
|
| "configure"
|
|
34
31
|
| "cursorMcp"
|
|
35
32
|
| "cursorSkill"
|
|
@@ -40,7 +37,6 @@ export type CliInstallArtifactKey =
|
|
|
40
37
|
|
|
41
38
|
export type InstallActionKind =
|
|
42
39
|
| "app"
|
|
43
|
-
| "completions"
|
|
44
40
|
| "cursor-skill"
|
|
45
41
|
| "claude-skill"
|
|
46
42
|
| "codex-skill"
|
|
@@ -73,9 +69,6 @@ export interface UninstallAction {
|
|
|
73
69
|
|
|
74
70
|
export interface InstalledArtifacts {
|
|
75
71
|
app: boolean;
|
|
76
|
-
bashCompletion: boolean;
|
|
77
|
-
zshCompletion: boolean;
|
|
78
|
-
fishCompletion: boolean;
|
|
79
72
|
cursorSkill: boolean;
|
|
80
73
|
claudeSkill: boolean;
|
|
81
74
|
codexSkill: boolean;
|
|
@@ -88,15 +81,10 @@ export interface InstalledArtifacts {
|
|
|
88
81
|
codexMcp: boolean;
|
|
89
82
|
openclawMcp: boolean;
|
|
90
83
|
chatGptMcp: boolean;
|
|
91
|
-
bashRcPath: boolean;
|
|
92
|
-
zshRcFpath: boolean;
|
|
93
84
|
}
|
|
94
85
|
|
|
95
86
|
export interface InstallStatus {
|
|
96
87
|
app?: string;
|
|
97
|
-
bashCompletion?: string;
|
|
98
|
-
zshCompletion?: string;
|
|
99
|
-
fishCompletion?: string;
|
|
100
88
|
cursorSkill?: string;
|
|
101
89
|
claudeSkill?: string;
|
|
102
90
|
codexSkill?: string;
|
|
@@ -117,13 +105,9 @@ export interface DetectedSnapshot extends InstalledArtifacts {
|
|
|
117
105
|
|
|
118
106
|
export interface InstallOpts {
|
|
119
107
|
all?: boolean;
|
|
120
|
-
app?: boolean;
|
|
121
|
-
completions?: boolean;
|
|
122
108
|
skill?: boolean;
|
|
123
109
|
mcp?: boolean;
|
|
124
110
|
reinstall?: boolean;
|
|
125
|
-
update?: boolean;
|
|
126
|
-
from?: string;
|
|
127
111
|
status?: boolean;
|
|
128
112
|
uninstall?: boolean;
|
|
129
113
|
configure?: boolean;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
1
|
import type { CliProgram } from "../../types.ts";
|
|
3
|
-
import {
|
|
2
|
+
import { isExternallyManagedBinary } from "../binary-placement.ts";
|
|
4
3
|
import { displayInstallPath, type InstallPaths } from "../paths.ts";
|
|
5
4
|
import { InstallTarget } from "../target-base.ts";
|
|
6
5
|
import type {
|
|
@@ -12,57 +11,49 @@ import type {
|
|
|
12
11
|
UninstallAction,
|
|
13
12
|
} from "../target-types.ts";
|
|
14
13
|
|
|
15
|
-
/**
|
|
14
|
+
/** Reports app install location (Homebrew PATH or legacy ~/.local/bin). No self-install actions. */
|
|
16
15
|
class AppInstallTarget extends InstallTarget {
|
|
17
16
|
readonly key = "app" as const;
|
|
18
17
|
readonly actionKind = "app" as const;
|
|
19
18
|
readonly category = "core" as const;
|
|
20
19
|
|
|
20
|
+
defaultIncludedInAll(): boolean {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
21
24
|
isAvailable(_root: CliProgram, _paths: InstallPaths): boolean {
|
|
22
25
|
return true;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
isDetected(paths: InstallPaths,
|
|
26
|
-
return
|
|
28
|
+
isDetected(paths: InstallPaths, root: CliProgram): boolean {
|
|
29
|
+
return isExternallyManagedBinary(root.key) || false;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
applyDetected(
|
|
30
|
-
out.app =
|
|
32
|
+
applyDetected(_paths: InstallPaths, root: CliProgram, out: InstalledArtifacts): void {
|
|
33
|
+
out.app = isExternallyManagedBinary(root.key);
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
protected isDetectedFromSnapshot(detected: DetectedSnapshot): boolean {
|
|
34
37
|
return detected.app;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
protected formatStatusLine(
|
|
38
|
-
|
|
40
|
+
protected formatStatusLine(_paths: InstallPaths, root: CliProgram): string {
|
|
41
|
+
if (isExternallyManagedBinary(root.key)) {
|
|
42
|
+
return "system (PATH)";
|
|
43
|
+
}
|
|
44
|
+
return "not installed (use Homebrew)";
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
protected assignStatusLine(status: InstallStatus, line: string): void {
|
|
42
48
|
status.app = line;
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
protected buildInstallActions(
|
|
46
|
-
|
|
47
|
-
return [
|
|
48
|
-
{
|
|
49
|
-
kind: this.actionKind,
|
|
50
|
-
summary: `app: ${displayInstallPath(ctx.paths.appPath)}`,
|
|
51
|
-
message: `Installing app to ${displayInstallPath(ctx.paths.appPath)}`,
|
|
52
|
-
run: () => installApp(ctx.root, ctx.paths, ctx.dry, sourcePath).changedFiles,
|
|
53
|
-
},
|
|
54
|
-
];
|
|
51
|
+
protected buildInstallActions(_ctx: TargetPlanContext): InstallAction[] {
|
|
52
|
+
return [];
|
|
55
53
|
}
|
|
56
54
|
|
|
57
|
-
protected buildUninstallActions(
|
|
58
|
-
return [
|
|
59
|
-
{
|
|
60
|
-
kind: this.actionKind,
|
|
61
|
-
summary: `app: ${displayInstallPath(ctx.paths.appPath)}`,
|
|
62
|
-
message: `Removing app ${displayInstallPath(ctx.paths.appPath)}`,
|
|
63
|
-
run: () => uninstallApp(ctx.root, ctx.paths, ctx.dry),
|
|
64
|
-
},
|
|
65
|
-
];
|
|
55
|
+
protected buildUninstallActions(_ctx: TargetPlanContext): UninstallAction[] {
|
|
56
|
+
return [];
|
|
66
57
|
}
|
|
67
58
|
}
|
|
68
59
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { appConfigInstalled, displayAppConfigPath, uninstallAppConfig } from "../../config/file.ts";
|
|
2
|
-
import type { CliProgram } from "../../types.ts";
|
|
2
|
+
import type { CliProgram, InstallAgentIntegration } from "../../types.ts";
|
|
3
3
|
import type { InstallPaths } from "../paths.ts";
|
|
4
4
|
import { InstallTarget } from "../target-base.ts";
|
|
5
5
|
import type {
|
|
@@ -17,6 +17,10 @@ class ConfigureInstallTarget extends InstallTarget {
|
|
|
17
17
|
readonly actionKind = "configure" as const;
|
|
18
18
|
readonly category = "core" as const;
|
|
19
19
|
|
|
20
|
+
defaultIncludedInAll(_integration: InstallAgentIntegration): boolean {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
isAvailable(root: CliProgram, _paths: InstallPaths): boolean {
|
|
21
25
|
return root.appConfig !== undefined;
|
|
22
26
|
}
|
|
@@ -6,7 +6,6 @@ import { claudeDesktopMcpTarget } from "./claude-desktop-mcp.ts";
|
|
|
6
6
|
import { claudeSkillTarget } from "./claude-skill.ts";
|
|
7
7
|
import { codexMcpTarget } from "./codex-mcp.ts";
|
|
8
8
|
import { codexSkillTarget } from "./codex-skill.ts";
|
|
9
|
-
import { completionsTarget } from "./completions.ts";
|
|
10
9
|
import { configureTarget } from "./configure.ts";
|
|
11
10
|
import { cursorMcpTarget } from "./cursor-mcp.ts";
|
|
12
11
|
import { cursorSkillTarget } from "./cursor-skill.ts";
|
|
@@ -18,7 +17,6 @@ import { opencodeSkillTarget } from "./opencode-skill.ts";
|
|
|
18
17
|
/** Ordered install targets (plan iteration order). */
|
|
19
18
|
export const INSTALL_TARGETS: InstallTarget[] = [
|
|
20
19
|
appTarget,
|
|
21
|
-
completionsTarget,
|
|
22
20
|
cursorSkillTarget,
|
|
23
21
|
claudeSkillTarget,
|
|
24
22
|
codexSkillTarget,
|
|
@@ -42,7 +40,6 @@ export {
|
|
|
42
40
|
claudeSkillTarget,
|
|
43
41
|
codexMcpTarget,
|
|
44
42
|
codexSkillTarget,
|
|
45
|
-
completionsTarget,
|
|
46
43
|
configureTarget,
|
|
47
44
|
cursorMcpTarget,
|
|
48
45
|
cursorSkillTarget,
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
2
|
import type { CliProgram } from "../types.ts";
|
|
6
|
-
import { maybeBootstrapInstallArgv } from "./bootstrap.ts";
|
|
7
3
|
import { normalizeInstallRawOpts } from "./normalize.ts";
|
|
8
|
-
import {
|
|
4
|
+
import { normalizeUninstallRawOpts } from "./normalize-uninstall.ts";
|
|
9
5
|
import { resolveAgentIntegration, resolveEffectiveInstallTargets } from "./target-effective.ts";
|
|
10
6
|
import { isArtifactInScope } from "./target-scope.ts";
|
|
11
7
|
|
|
@@ -15,7 +11,7 @@ describe("normalizeInstallRawOpts", () => {
|
|
|
15
11
|
});
|
|
16
12
|
|
|
17
13
|
test("bare uninstall sets all", () => {
|
|
18
|
-
expect(
|
|
14
|
+
expect(normalizeUninstallRawOpts({})).toEqual({
|
|
19
15
|
uninstall: "1",
|
|
20
16
|
all: "1",
|
|
21
17
|
});
|
|
@@ -37,11 +33,10 @@ describe("resolveAgentIntegration", () => {
|
|
|
37
33
|
});
|
|
38
34
|
|
|
39
35
|
describe("resolveEffectiveInstallTargets", () => {
|
|
40
|
-
test("defaults app
|
|
36
|
+
test("defaults app and configure not in --all", () => {
|
|
41
37
|
const t = resolveEffectiveInstallTargets(undefined);
|
|
42
|
-
expect(t.app.includedInAll).toBe(
|
|
43
|
-
expect(t.
|
|
44
|
-
expect(t.configure.includedInAll).toBe(true);
|
|
38
|
+
expect(t.app.includedInAll).toBe(false);
|
|
39
|
+
expect(t.configure.includedInAll).toBe(false);
|
|
45
40
|
});
|
|
46
41
|
|
|
47
42
|
test("skill mode includes skills in --all not MCP pairs", () => {
|
|
@@ -99,38 +94,25 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
99
94
|
),
|
|
100
95
|
).toBe(true);
|
|
101
96
|
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
describe("maybeBootstrapInstallArgv", () => {
|
|
105
|
-
const program: CliProgram = {
|
|
106
|
-
key: "bootapp",
|
|
107
|
-
version: "1.0.0",
|
|
108
|
-
description: "Boot",
|
|
109
|
-
handler: () => {},
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
test("rewrites empty argv when app missing and TTY", () => {
|
|
113
|
-
const prev = process.stdin.isTTY;
|
|
114
|
-
Object.defineProperty(process.stdin, "isTTY", { value: true, configurable: true });
|
|
115
|
-
try {
|
|
116
|
-
expect(maybeBootstrapInstallArgv([], program)).toEqual(["install"]);
|
|
117
|
-
} finally {
|
|
118
|
-
Object.defineProperty(process.stdin, "isTTY", { value: prev, configurable: true });
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
97
|
|
|
122
|
-
test("
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
98
|
+
test("scoped --mcp --skill --configure includes skill and mcp not only configure", () => {
|
|
99
|
+
const program: CliProgram = {
|
|
100
|
+
key: "app",
|
|
101
|
+
version: "1",
|
|
102
|
+
description: "x",
|
|
103
|
+
mcpServer: { enabled: true },
|
|
104
|
+
install: { agentIntegration: "both" },
|
|
105
|
+
handler: () => {},
|
|
106
|
+
};
|
|
107
|
+
const effective = resolveEffectiveInstallTargets(program.install, program);
|
|
108
|
+
const scope = { mcp: true, skill: true, configure: true };
|
|
109
|
+
expect(isArtifactInScope("cursorSkill", scope, effective, "install-scoped", program)).toBe(
|
|
110
|
+
true,
|
|
111
|
+
);
|
|
112
|
+
expect(isArtifactInScope("cursorMcp", scope, effective, "install-scoped", program)).toBe(true);
|
|
113
|
+
expect(isArtifactInScope("configure", scope, effective, "install-scoped", program)).toBe(true);
|
|
114
|
+
expect(isArtifactInScope("claudeSkill", scope, effective, "install-scoped", program)).toBe(
|
|
115
|
+
true,
|
|
116
|
+
);
|
|
135
117
|
});
|
|
136
118
|
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { chmodSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { applyShellEnv, bootstrapMcpEnv } from "./env.ts";
|
|
6
|
+
|
|
7
|
+
const TEST_VAR = "ARGS_BARG_SHELL_ENV_TEST";
|
|
8
|
+
|
|
9
|
+
describe("mcp/env", () => {
|
|
10
|
+
test("applyShellEnv merges PATH and fills missing vars", () => {
|
|
11
|
+
const prevPath = process.env.PATH;
|
|
12
|
+
const prevTest = process.env[TEST_VAR];
|
|
13
|
+
delete process.env[TEST_VAR];
|
|
14
|
+
try {
|
|
15
|
+
process.env.PATH = "/host/bin";
|
|
16
|
+
applyShellEnv({
|
|
17
|
+
PATH: "/shell/bin:/host/bin",
|
|
18
|
+
[TEST_VAR]: "from-shell",
|
|
19
|
+
});
|
|
20
|
+
expect(process.env.PATH).toBe("/shell/bin:/host/bin");
|
|
21
|
+
expect(process.env[TEST_VAR]).toBe("from-shell");
|
|
22
|
+
} finally {
|
|
23
|
+
if (prevPath === undefined) delete process.env.PATH;
|
|
24
|
+
else process.env.PATH = prevPath;
|
|
25
|
+
if (prevTest === undefined) delete process.env[TEST_VAR];
|
|
26
|
+
else process.env[TEST_VAR] = prevTest;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("applyShellEnv does not overwrite host vars except PATH merge", () => {
|
|
31
|
+
const prev = process.env.HOME;
|
|
32
|
+
process.env.HOME = "/host/home";
|
|
33
|
+
try {
|
|
34
|
+
applyShellEnv({ HOME: "/shell/home" });
|
|
35
|
+
expect(process.env.HOME).toBe("/host/home");
|
|
36
|
+
} finally {
|
|
37
|
+
if (prev === undefined) delete process.env.HOME;
|
|
38
|
+
else process.env.HOME = prev;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("bootstrapMcpEnv", () => {
|
|
44
|
+
let fakeShell: string;
|
|
45
|
+
let prevShell: string | undefined;
|
|
46
|
+
let prevTest: string | undefined;
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
const dir = mkdtempSync(join(tmpdir(), "argsbarg-shell-env-"));
|
|
50
|
+
fakeShell = join(dir, "fake-shell.sh");
|
|
51
|
+
writeFileSync(
|
|
52
|
+
fakeShell,
|
|
53
|
+
`#!/bin/sh
|
|
54
|
+
if [ "$1" = "-l" ] && [ "$2" = "-c" ] && [ "$3" = "env" ]; then
|
|
55
|
+
echo "${TEST_VAR}=from_fake_shell"
|
|
56
|
+
fi
|
|
57
|
+
`,
|
|
58
|
+
);
|
|
59
|
+
chmodSync(fakeShell, 0o755);
|
|
60
|
+
prevShell = process.env.SHELL;
|
|
61
|
+
prevTest = process.env[TEST_VAR];
|
|
62
|
+
delete process.env[TEST_VAR];
|
|
63
|
+
process.env.SHELL = fakeShell;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
if (prevShell === undefined) delete process.env.SHELL;
|
|
68
|
+
else process.env.SHELL = prevShell;
|
|
69
|
+
if (prevTest === undefined) delete process.env[TEST_VAR];
|
|
70
|
+
else process.env[TEST_VAR] = prevTest;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("defaults on when shellEnv is undefined", () => {
|
|
74
|
+
bootstrapMcpEnv({});
|
|
75
|
+
expect(process.env[TEST_VAR]).toBe("from_fake_shell");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("runs when shellEnv is true", () => {
|
|
79
|
+
bootstrapMcpEnv({ shellEnv: true });
|
|
80
|
+
expect(process.env[TEST_VAR]).toBe("from_fake_shell");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("uses explicit shell path when shellEnv is a string", () => {
|
|
84
|
+
bootstrapMcpEnv({ shellEnv: fakeShell });
|
|
85
|
+
expect(process.env[TEST_VAR]).toBe("from_fake_shell");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("skips capture when shellEnv is false", () => {
|
|
89
|
+
bootstrapMcpEnv({ shellEnv: false });
|
|
90
|
+
expect(process.env[TEST_VAR]).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
});
|
package/src/mcp/env.ts
CHANGED
|
@@ -39,21 +39,22 @@ export function applyShellEnv(env: Record<string, string>): void {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
/** Applies mcpServer shellEnv bootstrap. */
|
|
42
|
+
/** Applies mcpServer shellEnv bootstrap (default on; opt out with `shellEnv: false`). */
|
|
43
43
|
export function bootstrapMcpEnv(config: { shellEnv?: boolean | string }): void {
|
|
44
|
+
if (config.shellEnv === false) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
44
47
|
const shellEnvCfg = config.shellEnv;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
applyShellEnv(captured);
|
|
57
|
-
}
|
|
48
|
+
const shell =
|
|
49
|
+
typeof shellEnvCfg === "string"
|
|
50
|
+
? shellEnvCfg
|
|
51
|
+
: (process.env.SHELL ?? (process.platform === "darwin" ? "/bin/zsh" : "/bin/bash"));
|
|
52
|
+
const captured = captureShellEnv(shell);
|
|
53
|
+
if (Object.keys(captured).length === 0) {
|
|
54
|
+
process.stderr.write(
|
|
55
|
+
`[argsbarg] shellEnv: failed to capture shell environment from ${shell}\n`,
|
|
56
|
+
);
|
|
57
|
+
} else {
|
|
58
|
+
applyShellEnv(captured);
|
|
58
59
|
}
|
|
59
60
|
}
|
package/src/parse.test.ts
CHANGED
|
@@ -495,7 +495,7 @@ test("leaf completion help prints correctly", async () => {
|
|
|
495
495
|
const out = stdout.toString();
|
|
496
496
|
expect(exitCode).toBe(0);
|
|
497
497
|
expect(out).toContain("Show help for this command.");
|
|
498
|
-
expect(out).toContain("
|
|
498
|
+
expect(out).toContain("Homebrew");
|
|
499
499
|
expect(stderr.toString()).toBe("");
|
|
500
500
|
});
|
|
501
501
|
|
|
@@ -529,6 +529,7 @@ test("docs schema exports JSON for leaf roots", async () => {
|
|
|
529
529
|
"completion",
|
|
530
530
|
"version",
|
|
531
531
|
"install",
|
|
532
|
+
"uninstall",
|
|
532
533
|
"docs",
|
|
533
534
|
]);
|
|
534
535
|
});
|
|
@@ -614,7 +615,7 @@ test("cliSchemaExport resolves program key in install notes", () => {
|
|
|
614
615
|
|
|
615
616
|
const json = cliSchemaJson(root);
|
|
616
617
|
expect(json).not.toContain("{argsbarg:program}");
|
|
617
|
-
expect(json).toContain("
|
|
618
|
+
expect(json).toContain("brew install");
|
|
618
619
|
});
|
|
619
620
|
|
|
620
621
|
test("cliSchemaExport resolves {argsbarg:program} in consumer notes", () => {
|
package/src/prompt.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Shared terminal prompt helpers. */
|
|
2
|
+
|
|
3
|
+
import { readSync } from "node:fs";
|
|
4
|
+
|
|
5
|
+
/** Read one line from stdin (no masking). */
|
|
6
|
+
export function readPromptLine(): string {
|
|
7
|
+
const buf = Buffer.alloc(4096);
|
|
8
|
+
const n = readSync(0, buf, { length: 4096 });
|
|
9
|
+
return buf.toString("utf8", 0, n).replace(/\r?\n$/, "");
|
|
10
|
+
}
|
package/src/schema.ts
CHANGED
|
@@ -13,7 +13,15 @@ import {
|
|
|
13
13
|
leafOutputSchema,
|
|
14
14
|
} from "./types.ts";
|
|
15
15
|
|
|
16
|
-
const RESERVED = new Set([
|
|
16
|
+
const RESERVED = new Set([
|
|
17
|
+
"completion",
|
|
18
|
+
"install",
|
|
19
|
+
"uninstall",
|
|
20
|
+
"docs",
|
|
21
|
+
"mcp",
|
|
22
|
+
"version",
|
|
23
|
+
"config",
|
|
24
|
+
]);
|
|
17
25
|
|
|
18
26
|
function exportCommand(cmd: CliNode, root: CliProgram): CliSchemaExport | null {
|
|
19
27
|
if (cmd.hidden) {
|
package/src/types.ts
CHANGED
|
@@ -202,6 +202,26 @@ export interface CliUpdateArtifact {
|
|
|
202
202
|
/** Fetches the latest release binary for `install --update`. */
|
|
203
203
|
export type CliUpdateGetLatest = (ctx: { version: string }) => Promise<CliUpdateArtifact>;
|
|
204
204
|
|
|
205
|
+
/** Context passed to {@link CliAppConfigEntry.resolve} for one config key. */
|
|
206
|
+
export interface CliAppConfigResolveContext {
|
|
207
|
+
/** Schema key being resolved. */
|
|
208
|
+
key: string;
|
|
209
|
+
/** Entry metadata for this key. */
|
|
210
|
+
entry: CliAppConfigEntry;
|
|
211
|
+
/** Program root (read-only). */
|
|
212
|
+
program: CliProgram;
|
|
213
|
+
/** Raw value from the config file, if any. */
|
|
214
|
+
fileValue: unknown;
|
|
215
|
+
/** Non-empty host env string when `entry.env` is set; otherwise `undefined`. */
|
|
216
|
+
envValue: string | undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Optional fallback resolver for one config key (e.g. `gh auth token` when `GH_TOKEN` is unset).
|
|
221
|
+
* Return `undefined` to continue resolution (env, then default).
|
|
222
|
+
*/
|
|
223
|
+
export type CliAppConfigResolveFn = (ctx: CliAppConfigResolveContext) => unknown;
|
|
224
|
+
|
|
205
225
|
/**
|
|
206
226
|
* Metadata overlay for one key in {@link CliAppConfig.entries}.
|
|
207
227
|
* Types and validation come from {@link CliAppConfig.jsonSchema} when set; otherwise all values are strings.
|
|
@@ -222,6 +242,11 @@ export interface CliAppConfigEntry {
|
|
|
222
242
|
sensitive?: boolean;
|
|
223
243
|
/** When set: non-empty `process.env[env]` overrides file; value exported after resolve. */
|
|
224
244
|
env?: string;
|
|
245
|
+
/**
|
|
246
|
+
* Optional fallback after file when env is empty.
|
|
247
|
+
* Return `undefined` to fall back to `env` (if set) and schema defaults.
|
|
248
|
+
*/
|
|
249
|
+
resolve?: CliAppConfigResolveFn;
|
|
225
250
|
}
|
|
226
251
|
|
|
227
252
|
/**
|
|
@@ -248,11 +273,6 @@ export interface CliInstallConfig {
|
|
|
248
273
|
agentIntegration?: InstallAgentIntegration;
|
|
249
274
|
/** Per-artifact gates for full install/uninstall. See {@link resolveEffectiveInstallTargets}. */
|
|
250
275
|
targets?: CliInstallTargets;
|
|
251
|
-
/**
|
|
252
|
-
* When set, enables `install --update` on the program root.
|
|
253
|
-
* Should download or locate the latest release binary and return its path.
|
|
254
|
-
*/
|
|
255
|
-
updateGetLatest?: CliUpdateGetLatest;
|
|
256
276
|
}
|
|
257
277
|
|
|
258
278
|
/** Agent integration mode for install — MCP vs shell skill per host. */
|
|
@@ -275,7 +295,7 @@ export interface ResolvedInstallTarget {
|
|
|
275
295
|
|
|
276
296
|
/** Per-artifact gates for full install/uninstall. See {@link resolveEffectiveInstallTargets}. */
|
|
277
297
|
export interface CliInstallTargets {
|
|
278
|
-
/**
|
|
298
|
+
/** App binary status only (Homebrew PATH); no self-install. */
|
|
279
299
|
app?: InstallTargetSpec;
|
|
280
300
|
/** ChatGPT desktop MCP. Default false. */
|
|
281
301
|
chatgptMcp?: InstallTargetSpec;
|
|
@@ -289,9 +309,7 @@ export interface CliInstallTargets {
|
|
|
289
309
|
codexMcp?: InstallTargetSpec;
|
|
290
310
|
/** Codex skill. Default false. */
|
|
291
311
|
codexSkill?: InstallTargetSpec;
|
|
292
|
-
/**
|
|
293
|
-
completions?: InstallTargetSpec;
|
|
294
|
-
/** App config: wizard on install, file removal on uninstall. Default includedInAll true. */
|
|
312
|
+
/** App config: wizard via install --configure only. Default not in --all. */
|
|
295
313
|
configure?: InstallTargetSpec;
|
|
296
314
|
/** Cursor MCP. Default false. */
|
|
297
315
|
cursorMcp?: InstallTargetSpec;
|
package/src/validate.ts
CHANGED
|
@@ -81,6 +81,11 @@ function validateConfigBlock(appConfigBlock: import("./types.ts").CliAppConfig):
|
|
|
81
81
|
}
|
|
82
82
|
envNames.add(entry.env);
|
|
83
83
|
}
|
|
84
|
+
if (entry.resolve !== undefined && typeof entry.resolve !== "function") {
|
|
85
|
+
throw new CliSchemaValidationError(
|
|
86
|
+
`program.appConfig.entries['${key}'].resolve must be a function when set`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
const jsonSchema = appConfigBlock.jsonSchema;
|
|
@@ -197,17 +202,6 @@ export function cliValidateProgram(program: CliProgram): void {
|
|
|
197
202
|
validateConfigBlock(program.appConfig);
|
|
198
203
|
}
|
|
199
204
|
|
|
200
|
-
if (program.install?.updateGetLatest !== undefined) {
|
|
201
|
-
if (program.install.enabled === false) {
|
|
202
|
-
throw new CliSchemaValidationError(
|
|
203
|
-
"install.updateGetLatest requires install to be enabled (omit install.enabled: false)",
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
if (typeof program.install.updateGetLatest !== "function") {
|
|
207
|
-
throw new CliSchemaValidationError("install.updateGetLatest must be a function");
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
205
|
if (program.install !== undefined) {
|
|
212
206
|
validateInstallConfig(program);
|
|
213
207
|
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Argsbarg schema — read framework docs before editing CLI commands
|
|
3
|
-
globs: "src/**/commands/**/*.{ts,tsx},src/index.{ts,tsx}"
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
When adding or changing argsbarg schema, leaf handlers, or MCP exposure:
|
|
8
|
-
|
|
9
|
-
1. **Read** `node_modules/argsbarg/docs/cli-program.md` (required — authoritative guide).
|
|
10
|
-
2. MCP tools, varargs, `inputSchema` → also `node_modules/argsbarg/docs/mcp.md`.
|
|
11
|
-
3. JSON stdout / `outputSchema` guide and codegen → `node_modules/argsbarg/docs/output-schema.md`.
|
|
12
|
-
4. App config / `program.appConfig` guide and codegen → `node_modules/argsbarg/docs/config-schema.md`.
|
|
13
|
-
5. `install`, `install.targets`, completions, skills → `node_modules/argsbarg/docs/install.md`.
|
|
14
|
-
6. Bundled `docs` built-in → `node_modules/argsbarg/docs/bundled-docs.md`.
|
|
15
|
-
7. **Examples** (shipped under `node_modules/argsbarg/examples/`):
|
|
16
|
-
- Concepts / minimal config → `examples/config-app/`
|
|
17
|
-
- **Copy template** (all builtins, schemagen, `outputSchema`) → `examples/consumer-app/`
|
|
18
|
-
|
|
19
|
-
**Hard rules** (details and examples are in the docs above — do not contradict them):
|
|
20
|
-
|
|
21
|
-
- Reserved root commands: `completion`, `install`, `mcp`, `version`, `docs`, `update`, `config`.
|
|
22
|
-
- `satisfies CliProgram` / `CliLeaf`; action-oriented `description` on root, commands, options, and positionals.
|
|
23
|
-
- Omit `mcpTool` unless genuinely CLI-only (`enabled: false`) or an irreducible wire limit — fix schema and headless handlers first.
|
|
24
|
-
- Interactive leaves: one headless path for MCP, non-TTY CLI, and `--yes` / `--dry-run` / `--json` (`shouldRunHeadless*`, `requireYesInNonTty`); not raw `isTTY`.
|
|
25
|
-
- String options: `format` / `default` / `pattern` per `cli-program.md`; `readLeafInputs()` for multi-flag leaves.
|
|
26
|
-
- Varargs (`argMax: 0`): CLI space-separated; MCP JSON array only — no comma-splitting positionals.
|
|
27
|
-
- Multi-surface leaves (Ink + headless + MCP): one **`read*Flags(ctx)`** per command (or shared family helper + extensions); one **`resolve*Input(flags)`** for cross-field rules — handler reads ctx once, all paths share the struct.
|
|
28
|
-
- JSON stdout: `outputSchema` on the leaf from generated constants in `outputSchemas.ts` — see `output-schema.md`; mark roots with **`JSON payload`** JSDoc in `src/**/types.ts`; do not hand-edit `src/schemas/generated/` or `outputSchemas.ts`.
|
|
29
|
-
- App config: `program.appConfig.jsonSchema` from generated `configSchemas.ts` — see `config-schema.md`; mark roots with **`Config schema`** JSDoc; prefer the layout in `node_modules/argsbarg/examples/consumer-app/` when adding new schema roots.
|
|
30
|
-
|
|
31
|
-
**App-specific conventions:** replace this line with a `**<your-app> conventions:**` section (bullets only). Keep it at the bottom of this file — `just consumer-dev` / `just consumers-sync` in the argsbarg repo refresh the template above and preserve this block. Do not duplicate `cli-program.md` here; link paths and patterns only. For a second rule file (e.g. `.cursor/argsbarg.mdc`), that is fine too.
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/*
|
|
3
|
-
Multi-file consumer example for program.appConfig.
|
|
4
|
-
|
|
5
|
-
Files:
|
|
6
|
-
types.ts — AppConfig interface (Config schema JSDoc marker for schemagen)
|
|
7
|
-
schema.ts — APP_CONFIG_JSON_SCHEMA (inline; production apps generate this)
|
|
8
|
-
program.ts — CliProgram with appConfig block and commands using ctx.appConfig
|
|
9
|
-
|
|
10
|
-
Try:
|
|
11
|
-
CONFIG_APP_API_TOKEN=dev bun ./examples/config-app/main.ts show --json
|
|
12
|
-
CONFIG_APP_API_TOKEN=dev bun ./examples/config-app/main.ts config get
|
|
13
|
-
CONFIG_APP_API_TOKEN=dev bun ./examples/config-app/main.ts ping
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { Cli } from "../../src/index.ts";
|
|
17
|
-
import { program } from "./program.ts";
|
|
18
|
-
|
|
19
|
-
const cli = new Cli(program);
|
|
20
|
-
await cli.run();
|