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.
- package/CHANGELOG.md +74 -1
- package/README.md +91 -85
- package/docs/README.md +8 -8
- package/docs/ai-skills.md +9 -9
- package/docs/bundled-docs.md +5 -5
- package/docs/cli-program.md +13 -11
- package/docs/config-schema.md +36 -9
- package/docs/configure.md +177 -0
- package/docs/developing.md +7 -7
- package/docs/distribution-homebrew.md +104 -0
- package/docs/mcp.md +11 -12
- 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 +49 -81
- package/package.json +2 -2
- package/src/builtins/builtins.test.ts +84 -64
- package/src/builtins/completion-group.ts +17 -17
- package/src/builtins/configure-copy.ts +86 -0
- package/src/builtins/configure.ts +70 -0
- package/src/builtins/dispatch.ts +13 -9
- package/src/builtins/index.ts +1 -1
- package/src/builtins/mcp.ts +2 -2
- package/src/builtins/registry.ts +6 -4
- package/src/capabilities.ts +22 -15
- package/src/cli-tool/cli-smoke.test.ts +29 -0
- package/src/cli-tool/create.test.ts +141 -0
- package/src/cli-tool/create.ts +402 -0
- package/{examples/consumer-app/capabilities.test.ts → src/cli-tool/full-example-capabilities.test.ts} +16 -15
- package/src/cli-tool/main.ts +8 -0
- package/src/cli-tool/post-create.ts +111 -0
- package/src/cli-tool/program.ts +97 -0
- package/src/cli-tool/prompt.ts +28 -0
- package/src/cli-tool/run-create.ts +138 -0
- package/src/cli.ts +0 -2
- package/src/config/bootstrap.ts +27 -18
- package/src/config/file.test.ts +1 -1
- package/src/config/resolve.test.ts +167 -0
- package/src/config/resolve.ts +52 -8
- package/src/configure/configure.test.ts +148 -0
- package/src/configure/index.ts +284 -0
- package/src/configure/prompt.ts +40 -0
- package/src/docs/api-guide.test.ts +4 -5
- package/src/docs/builtin.ts +3 -5
- package/src/docs/docs.test.ts +6 -5
- package/src/docs/mcp-guide.ts +11 -12
- package/src/index.ts +5 -12
- package/src/install/binary-placement.test.ts +101 -0
- package/src/install/binary-placement.ts +47 -0
- package/src/install/install-validate.test.ts +5 -5
- package/src/install/normalize-uninstall.ts +11 -0
- package/src/install/normalize.ts +4 -19
- package/src/install/opts.ts +17 -0
- 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 +8 -10
- package/src/install/target-scope.ts +26 -36
- package/src/install/target-types.ts +0 -16
- package/src/install/targets/app.ts +19 -28
- package/src/install/targets/configure.ts +6 -2
- package/src/install/targets/index.ts +0 -3
- package/src/install/targets.test.ts +26 -44
- package/src/invoke.test.ts +1 -1
- package/src/mcp/env.test.ts +92 -0
- package/src/mcp/env.ts +15 -14
- package/src/mcp/tools.ts +1 -1
- package/src/mcp.integration.test.ts +4 -4
- package/src/parse.test.ts +13 -14
- package/src/prompt.ts +10 -0
- package/src/schema.ts +1 -1
- package/src/skill/hint.ts +2 -2
- package/src/types.ts +48 -22
- package/src/validate.ts +22 -28
- package/docs/install.md +0 -290
- 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/builtins/install.ts +0 -136
- package/src/install/app.ts +0 -94
- package/src/install/bootstrap.ts +0 -22
- package/src/install/completions.ts +0 -56
- package/src/install/index.ts +0 -415
- package/src/install/install.test.ts +0 -333
- 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 {
|
|
@@ -11,12 +11,16 @@ import type {
|
|
|
11
11
|
UninstallAction,
|
|
12
12
|
} from "../target-types.ts";
|
|
13
13
|
|
|
14
|
-
/** App config file (
|
|
14
|
+
/** App config file (interactive configure / wizard). */
|
|
15
15
|
class ConfigureInstallTarget extends InstallTarget {
|
|
16
16
|
readonly key = "configure" as const;
|
|
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", () => {
|
|
@@ -80,10 +75,10 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
80
75
|
version: "1",
|
|
81
76
|
description: "x",
|
|
82
77
|
mcpServer: { enabled: true },
|
|
83
|
-
|
|
78
|
+
configure: { agentIntegration: "skill" },
|
|
84
79
|
handler: () => {},
|
|
85
80
|
};
|
|
86
|
-
const effective = resolveEffectiveInstallTargets(program.
|
|
81
|
+
const effective = resolveEffectiveInstallTargets(program.configure, program);
|
|
87
82
|
const scope = { mcp: true };
|
|
88
83
|
expect(isArtifactInScope("cursorMcp", scope, effective, "install-scoped", program)).toBe(false);
|
|
89
84
|
expect(
|
|
@@ -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
|
+
configure: { agentIntegration: "both" },
|
|
105
|
+
handler: () => {},
|
|
106
|
+
};
|
|
107
|
+
const effective = resolveEffectiveInstallTargets(program.configure, 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
|
});
|
package/src/invoke.test.ts
CHANGED
|
@@ -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/mcp/tools.ts
CHANGED
|
@@ -35,7 +35,7 @@ test("collectMcpTools lists user leaf commands only", () => {
|
|
|
35
35
|
expect(names).toContain("stat_owner_lookup");
|
|
36
36
|
expect(names).toContain("read");
|
|
37
37
|
expect(names).not.toContain("hidden");
|
|
38
|
-
expect(names).not.toContain("
|
|
38
|
+
expect(names).not.toContain("configure");
|
|
39
39
|
expect(names).not.toContain("mcp");
|
|
40
40
|
expect(names).not.toContain("completion");
|
|
41
41
|
const lookup = tools.find((t) => t.name === "stat_owner_lookup")!;
|
|
@@ -242,19 +242,19 @@ test("mcpToolCallToArgv expands varargs positionals", () => {
|
|
|
242
242
|
expect(argv).toEqual(["read", "a", "b"]);
|
|
243
243
|
});
|
|
244
244
|
|
|
245
|
-
test("reserved command name
|
|
245
|
+
test("reserved command name configure is rejected", () => {
|
|
246
246
|
const root = testProgram({
|
|
247
247
|
key: "app",
|
|
248
248
|
description: "",
|
|
249
249
|
commands: [
|
|
250
250
|
{
|
|
251
|
-
key: "
|
|
251
|
+
key: "configure",
|
|
252
252
|
description: "bad",
|
|
253
253
|
handler: () => {},
|
|
254
254
|
},
|
|
255
255
|
],
|
|
256
256
|
});
|
|
257
|
-
expect(() => cliValidateProgram(root)).toThrow(/Reserved command name:
|
|
257
|
+
expect(() => cliValidateProgram(root)).toThrow(/Reserved command name: configure/);
|
|
258
258
|
});
|
|
259
259
|
|
|
260
260
|
test("top-level command name mcp is allowed without mcpServer", () => {
|
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
|
|
|
@@ -526,9 +526,8 @@ test("docs schema exports JSON for leaf roots", async () => {
|
|
|
526
526
|
expect(schema.positionals[0].name).toBe("name");
|
|
527
527
|
expect(schema.options[0].name).toBe("verbose");
|
|
528
528
|
expect(schema.commands.map((c: { key: string }) => c.key)).toEqual([
|
|
529
|
-
"completion",
|
|
530
529
|
"version",
|
|
531
|
-
"
|
|
530
|
+
"configure",
|
|
532
531
|
"docs",
|
|
533
532
|
]);
|
|
534
533
|
});
|
|
@@ -539,11 +538,11 @@ test("version builtin prints program version", async () => {
|
|
|
539
538
|
expect(stdout.toString().trim()).toMatch(/^\d+\.\d+\.\d+/);
|
|
540
539
|
});
|
|
541
540
|
|
|
542
|
-
test("leaf root help
|
|
541
|
+
test("leaf root help omits hidden completion built-in", async () => {
|
|
543
542
|
const { stdout, exitCode } = await $`bun run examples/minimal.ts -h`.nothrow().quiet();
|
|
544
543
|
expect(exitCode).toBe(0);
|
|
545
|
-
expect(stdout.toString()).toContain("completion");
|
|
546
|
-
expect(stdout.toString()).toContain("
|
|
544
|
+
expect(stdout.toString()).not.toContain("completion");
|
|
545
|
+
expect(stdout.toString()).toContain("configure");
|
|
547
546
|
});
|
|
548
547
|
|
|
549
548
|
test("root --schema is no longer a flag", () => {
|
|
@@ -614,7 +613,7 @@ test("cliSchemaExport resolves program key in install notes", () => {
|
|
|
614
613
|
|
|
615
614
|
const json = cliSchemaJson(root);
|
|
616
615
|
expect(json).not.toContain("{argsbarg:program}");
|
|
617
|
-
expect(json).toContain("
|
|
616
|
+
expect(json).toContain("brew install");
|
|
618
617
|
});
|
|
619
618
|
|
|
620
619
|
test("cliSchemaExport resolves {argsbarg:program} in consumer notes", () => {
|
|
@@ -1107,7 +1106,7 @@ test("mcpToolCallToArgv empty array varargs errors when required", () => {
|
|
|
1107
1106
|
|
|
1108
1107
|
// ── Skills ────────────────────────────────────────────────────────────────────
|
|
1109
1108
|
|
|
1110
|
-
test("
|
|
1109
|
+
test("configure config on non-root node is rejected", () => {
|
|
1111
1110
|
const root = {
|
|
1112
1111
|
key: "app",
|
|
1113
1112
|
version: "0.0.0",
|
|
@@ -1116,23 +1115,23 @@ test("install config on non-root node is rejected", () => {
|
|
|
1116
1115
|
{
|
|
1117
1116
|
key: "x",
|
|
1118
1117
|
description: "",
|
|
1119
|
-
|
|
1118
|
+
configure: { enabled: false },
|
|
1120
1119
|
handler: () => {},
|
|
1121
1120
|
},
|
|
1122
1121
|
],
|
|
1123
1122
|
} as unknown as CliProgram;
|
|
1124
|
-
expect(() => cliValidateProgram(root)).toThrow(/
|
|
1123
|
+
expect(() => cliValidateProgram(root)).toThrow(/configure is only supported on the program root/);
|
|
1125
1124
|
});
|
|
1126
1125
|
|
|
1127
|
-
test("
|
|
1126
|
+
test("configure.prefix is rejected", () => {
|
|
1128
1127
|
const root = {
|
|
1129
1128
|
key: "app",
|
|
1130
1129
|
version: "0.0.0",
|
|
1131
1130
|
description: "",
|
|
1132
|
-
|
|
1131
|
+
configure: { prefix: "/opt/bin" },
|
|
1133
1132
|
handler: () => {},
|
|
1134
1133
|
} as unknown as CliProgram;
|
|
1135
|
-
expect(() => cliValidateProgram(root)).toThrow(/
|
|
1134
|
+
expect(() => cliValidateProgram(root)).toThrow(/configure\.prefix removed/);
|
|
1136
1135
|
});
|
|
1137
1136
|
|
|
1138
1137
|
test("generateSkillBundle includes frontmatter and compact command index", () => {
|
|
@@ -1183,7 +1182,7 @@ test("cliSkillInstall writes project Cursor skill files", () => {
|
|
|
1183
1182
|
expect(readFileSync(join(skillDir, "reference.md"), "utf8")).toContain("CLI API reference");
|
|
1184
1183
|
const skillText = readFileSync(join(skillDir, "SKILL.md"), "utf8");
|
|
1185
1184
|
expect(skillText.startsWith("---\n")).toBe(true);
|
|
1186
|
-
const hint = "<!-- Generated by nested.ts
|
|
1185
|
+
const hint = "<!-- Generated by nested.ts configure; do not edit. -->";
|
|
1187
1186
|
expect(skillText.indexOf(hint)).toBeGreaterThan(skillText.indexOf("---\n", 4));
|
|
1188
1187
|
const refText = readFileSync(join(skillDir, "reference.md"), "utf8");
|
|
1189
1188
|
expect(refText.startsWith(hint)).toBe(true);
|
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,7 @@ import {
|
|
|
13
13
|
leafOutputSchema,
|
|
14
14
|
} from "./types.ts";
|
|
15
15
|
|
|
16
|
-
const RESERVED = new Set(["completion", "
|
|
16
|
+
const RESERVED = new Set(["completion", "configure", "docs", "mcp", "version", "config"]);
|
|
17
17
|
|
|
18
18
|
function exportCommand(cmd: CliNode, root: CliProgram): CliSchemaExport | null {
|
|
19
19
|
if (cmd.hidden) {
|
package/src/skill/hint.ts
CHANGED
|
@@ -23,9 +23,9 @@ export function insertGeneratedHint(
|
|
|
23
23
|
return `${hint}${content}`;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/** Hint for `
|
|
26
|
+
/** Hint for `configure` skill output files. */
|
|
27
27
|
export function skillInstallHint(program: CliProgram): string {
|
|
28
|
-
return generatedFileHtmlComment(`${program.key}
|
|
28
|
+
return generatedFileHtmlComment(`${program.key} configure`);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Applies install hints to SKILL.md (after frontmatter) and reference.md. */
|