argsbarg 4.1.1 → 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 +27 -1
- package/README.md +5 -5
- package/docs/README.md +3 -3
- package/docs/ai-skills.md +9 -9
- package/docs/bundled-docs.md +4 -4
- package/docs/cli-program.md +8 -8
- package/docs/config-schema.md +2 -2
- package/docs/configure.md +177 -0
- package/docs/developing.md +1 -1
- package/docs/distribution-homebrew.md +10 -9
- package/docs/mcp.md +9 -9
- package/examples/full-example/README.md +3 -3
- package/examples/full-example/justfile +6 -6
- package/examples/full-example/scripts/formula-shared.ts +2 -2
- package/examples/full-example/src/program.ts +3 -3
- package/examples/nested.ts +1 -1
- package/index.d.ts +23 -16
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +82 -61
- package/src/builtins/completion-group.ts +4 -6
- package/src/builtins/configure-copy.ts +86 -0
- package/src/builtins/configure.ts +70 -0
- package/src/builtins/dispatch.ts +13 -33
- package/src/builtins/index.ts +1 -1
- package/src/builtins/mcp.ts +2 -2
- package/src/builtins/registry.ts +6 -6
- package/src/capabilities.ts +22 -13
- package/src/cli-tool/cli-smoke.test.ts +13 -3
- package/src/cli-tool/create.test.ts +23 -1
- package/src/cli-tool/create.ts +26 -4
- package/src/cli-tool/full-example-capabilities.test.ts +2 -2
- package/src/cli-tool/program.ts +20 -5
- package/src/cli-tool/run-create.ts +8 -19
- package/src/config/bootstrap.ts +11 -9
- package/src/config/file.test.ts +1 -1
- package/src/config/resolve.ts +2 -2
- 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/builtin.ts +3 -5
- package/src/docs/docs.test.ts +5 -5
- package/src/docs/mcp-guide.ts +4 -4
- package/src/index.ts +2 -2
- package/src/install/install-validate.test.ts +5 -5
- package/src/install/opts.ts +17 -0
- package/src/install/target-effective.ts +8 -8
- package/src/install/target-scope.ts +11 -8
- package/src/install/targets/configure.ts +1 -1
- package/src/install/targets.test.ts +4 -4
- package/src/invoke.test.ts +1 -1
- package/src/mcp/tools.ts +1 -1
- package/src/mcp.integration.test.ts +4 -4
- package/src/parse.test.ts +11 -13
- package/src/schema.ts +1 -9
- package/src/skill/hint.ts +2 -2
- package/src/types.ts +22 -14
- package/src/validate.ts +17 -17
- package/docs/install.md +0 -206
- package/src/builtins/install.ts +0 -106
- package/src/builtins/uninstall.ts +0 -80
- package/src/install/index.ts +0 -409
- package/src/install/install.test.ts +0 -317
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { resolveCapabilities } from "../capabilities.ts";
|
|
2
|
+
import { displayAppConfigPath, runConfigure } from "../config/bootstrap.ts";
|
|
3
|
+
import { resolveInstallPaths } from "../install/paths.ts";
|
|
4
|
+
import { buildInstallPlan, buildUpdatePlan } from "../install/plan.ts";
|
|
5
|
+
import {
|
|
6
|
+
installErr,
|
|
7
|
+
installInfo,
|
|
8
|
+
installOut,
|
|
9
|
+
printInstallStatus,
|
|
10
|
+
writeInteractiveInstallIntro,
|
|
11
|
+
} from "../install/status.ts";
|
|
12
|
+
import { resolveEffectiveInstallTargets } from "../install/target-effective.ts";
|
|
13
|
+
import { runTargetPreflight } from "../install/target-plan-build.ts";
|
|
14
|
+
import { INSTALL_TARGETS, installTargetForKey } from "../install/target-registry.ts";
|
|
15
|
+
import { buildDetectedSnapshot, buildTargetPlanContext } from "../install/target-scope.ts";
|
|
16
|
+
import type {
|
|
17
|
+
CliInstallArtifactKey,
|
|
18
|
+
InstallAction,
|
|
19
|
+
InstallActionKind,
|
|
20
|
+
InstallOpts,
|
|
21
|
+
TargetPlanContext,
|
|
22
|
+
UninstallAction,
|
|
23
|
+
} from "../install/target-types.ts";
|
|
24
|
+
import {
|
|
25
|
+
buildUninstallPlan,
|
|
26
|
+
skillDirFromUninstallSummary,
|
|
27
|
+
uninstallSkillDir,
|
|
28
|
+
} from "../install/uninstall.ts";
|
|
29
|
+
import { cliSkillInstall, skillTargetFromActionKind } from "../skill/install.ts";
|
|
30
|
+
import type { CliProgram } from "../types.ts";
|
|
31
|
+
import { artifactPromptLabel, promptTargetAction } from "./prompt.ts";
|
|
32
|
+
|
|
33
|
+
/** True when interactive `configure` should auto-run the app config wizard. */
|
|
34
|
+
export function appConfigHasEntries(program: CliProgram): boolean {
|
|
35
|
+
const entries = program.appConfig?.entries;
|
|
36
|
+
return !!entries && Object.keys(entries).length > 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ConfigureOpts {
|
|
40
|
+
sync?: boolean;
|
|
41
|
+
removeAll?: boolean;
|
|
42
|
+
removeConfig?: boolean;
|
|
43
|
+
status?: boolean;
|
|
44
|
+
yes?: boolean;
|
|
45
|
+
dry?: boolean;
|
|
46
|
+
json?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function parseConfigureOpts(raw: Record<string, string>): ConfigureOpts {
|
|
50
|
+
const flag = (name: string) => raw[name] === "1";
|
|
51
|
+
return {
|
|
52
|
+
sync: flag("sync"),
|
|
53
|
+
removeAll: flag("remove-all"),
|
|
54
|
+
removeConfig: flag("remove-config"),
|
|
55
|
+
status: flag("status"),
|
|
56
|
+
yes: flag("yes"),
|
|
57
|
+
dry: flag("dry"),
|
|
58
|
+
json: flag("json"),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function validateConfigureOpts(opts: ConfigureOpts): string | null {
|
|
63
|
+
const flags = [opts.sync, opts.removeAll, opts.removeConfig, opts.status].filter(Boolean);
|
|
64
|
+
if (flags.length > 1) {
|
|
65
|
+
return "Use only one of --sync, --remove-all, --remove-config, or --status.";
|
|
66
|
+
}
|
|
67
|
+
if (opts.json) {
|
|
68
|
+
opts.yes = true;
|
|
69
|
+
}
|
|
70
|
+
if ((opts.sync || opts.removeAll || opts.removeConfig) && !opts.yes) {
|
|
71
|
+
return "--yes is required with --sync, --remove-all, or --remove-config.";
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function configureToInstallOpts(opts: ConfigureOpts): InstallOpts {
|
|
77
|
+
if (opts.status) {
|
|
78
|
+
return { status: true, yes: opts.yes, dry: opts.dry, json: opts.json };
|
|
79
|
+
}
|
|
80
|
+
if (opts.sync) {
|
|
81
|
+
return { reinstall: true, yes: true, dry: opts.dry, json: opts.json };
|
|
82
|
+
}
|
|
83
|
+
if (opts.removeAll) {
|
|
84
|
+
return { uninstall: true, all: true, yes: true, dry: opts.dry, json: opts.json };
|
|
85
|
+
}
|
|
86
|
+
if (opts.removeConfig) {
|
|
87
|
+
return { uninstall: true, configure: true, yes: true, dry: opts.dry, json: opts.json };
|
|
88
|
+
}
|
|
89
|
+
return { dry: opts.dry, json: opts.json };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function runSkillAction(root: CliProgram, kind: InstallActionKind, opts: InstallOpts): string[] {
|
|
93
|
+
const target = skillTargetFromActionKind(kind);
|
|
94
|
+
if (!target) return [];
|
|
95
|
+
return cliSkillInstall(root, target, {
|
|
96
|
+
global: true,
|
|
97
|
+
rimraf: true,
|
|
98
|
+
dry: opts.dry,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function executePlan(
|
|
103
|
+
root: CliProgram,
|
|
104
|
+
actions: Array<InstallAction | UninstallAction>,
|
|
105
|
+
opts: InstallOpts,
|
|
106
|
+
showProgress: boolean,
|
|
107
|
+
): string[] {
|
|
108
|
+
const changed: string[] = [];
|
|
109
|
+
const paths = resolveInstallPaths(root);
|
|
110
|
+
for (const action of actions) {
|
|
111
|
+
if (showProgress) {
|
|
112
|
+
installInfo(action.message, opts);
|
|
113
|
+
}
|
|
114
|
+
if ("kind" in action && action.kind) {
|
|
115
|
+
const skillTarget = skillTargetFromActionKind(action.kind);
|
|
116
|
+
if (skillTarget) {
|
|
117
|
+
changed.push(...runSkillAction(root, action.kind as InstallActionKind, opts));
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!("kind" in action) || !action.kind) {
|
|
122
|
+
const skillDir = skillDirFromUninstallSummary(action.summary, paths);
|
|
123
|
+
if (skillDir) {
|
|
124
|
+
changed.push(...uninstallSkillDir(skillDir, !!opts.dry));
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
changed.push(...action.run());
|
|
129
|
+
}
|
|
130
|
+
return changed;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function buildSingleTargetContext(
|
|
134
|
+
root: CliProgram,
|
|
135
|
+
paths: ReturnType<typeof resolveInstallPaths>,
|
|
136
|
+
detected: ReturnType<typeof buildDetectedSnapshot>,
|
|
137
|
+
key: CliInstallArtifactKey,
|
|
138
|
+
mode: "install" | "uninstall",
|
|
139
|
+
opts: InstallOpts,
|
|
140
|
+
): TargetPlanContext {
|
|
141
|
+
const effective = resolveEffectiveInstallTargets(root.configure, root);
|
|
142
|
+
const base = buildTargetPlanContext(root, paths, opts, detected);
|
|
143
|
+
return {
|
|
144
|
+
...base,
|
|
145
|
+
mode: mode === "install" ? "install-scoped" : "uninstall-scoped",
|
|
146
|
+
include: (k) => k === key && effective[k].enabled,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function actionsForTarget(
|
|
151
|
+
root: CliProgram,
|
|
152
|
+
paths: ReturnType<typeof resolveInstallPaths>,
|
|
153
|
+
key: CliInstallArtifactKey,
|
|
154
|
+
action: "install" | "uninstall",
|
|
155
|
+
opts: InstallOpts,
|
|
156
|
+
): Array<InstallAction | UninstallAction> {
|
|
157
|
+
const detected = buildDetectedSnapshot(root, paths);
|
|
158
|
+
const target = installTargetForKey(key);
|
|
159
|
+
if (!target) return [];
|
|
160
|
+
const ctx = buildSingleTargetContext(root, paths, detected, key, action, opts);
|
|
161
|
+
if (action === "install") {
|
|
162
|
+
return target.planInstall(ctx);
|
|
163
|
+
}
|
|
164
|
+
return target.planUninstall(ctx);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function runInteractiveConfigure(root: CliProgram, opts: ConfigureOpts): Promise<string[]> {
|
|
168
|
+
if (!process.stdin.isTTY) {
|
|
169
|
+
throw new Error("Interactive configure requires a TTY. Use flags such as --sync --yes.");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
writeInteractiveInstallIntro(root);
|
|
173
|
+
const paths = resolveInstallPaths(root);
|
|
174
|
+
const detected = buildDetectedSnapshot(root, paths);
|
|
175
|
+
const effective = resolveEffectiveInstallTargets(root.configure, root);
|
|
176
|
+
const mutationOpts: InstallOpts = { dry: opts.dry, json: opts.json };
|
|
177
|
+
const changed: string[] = [];
|
|
178
|
+
|
|
179
|
+
for (const target of INSTALL_TARGETS) {
|
|
180
|
+
if (target.key === "app") continue;
|
|
181
|
+
if (!effective[target.key].enabled) continue;
|
|
182
|
+
if (target.key !== "configure" && !target.isAvailable(root, paths)) continue;
|
|
183
|
+
if (target.key === "configure" && !root.appConfig) continue;
|
|
184
|
+
|
|
185
|
+
if (target.key === "configure") {
|
|
186
|
+
if (!appConfigHasEntries(root)) continue;
|
|
187
|
+
|
|
188
|
+
const result = runConfigure(root, { context: "standalone", showHeading: false });
|
|
189
|
+
if (result.changed) {
|
|
190
|
+
installOut(`Wrote config: ${displayAppConfigPath(root)}`, mutationOpts);
|
|
191
|
+
changed.push(displayAppConfigPath(root));
|
|
192
|
+
}
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const label = artifactPromptLabel(target.key);
|
|
197
|
+
const installed = target.detectedForSnapshot(detected);
|
|
198
|
+
|
|
199
|
+
const statusHint = installed ? "installed" : "not installed";
|
|
200
|
+
process.stderr.write(`\n${label} (${statusHint})\n`);
|
|
201
|
+
const choice = promptTargetAction(label, installed);
|
|
202
|
+
if (!choice || choice === "skip") continue;
|
|
203
|
+
|
|
204
|
+
const actions = actionsForTarget(root, paths, target.key, choice, mutationOpts);
|
|
205
|
+
if (actions.length === 0) continue;
|
|
206
|
+
|
|
207
|
+
const installActions = actions.filter(
|
|
208
|
+
(a): a is InstallAction =>
|
|
209
|
+
"kind" in a && typeof a.kind === "string" && a.kind.includes("-mcp"),
|
|
210
|
+
);
|
|
211
|
+
if (choice === "install" && resolveCapabilities(root).mcp && installActions.length > 0) {
|
|
212
|
+
runTargetPreflight(root, paths, mutationOpts, installActions);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
changed.push(...executePlan(root, actions, mutationOpts, true));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return changed;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function runAutomatedConfigure(root: CliProgram, opts: ConfigureOpts): Promise<string[]> {
|
|
222
|
+
const installOpts = configureToInstallOpts(opts);
|
|
223
|
+
const paths = resolveInstallPaths(root);
|
|
224
|
+
|
|
225
|
+
if (installOpts.status) {
|
|
226
|
+
printInstallStatus(root, installOpts);
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let actions: Array<InstallAction | UninstallAction>;
|
|
231
|
+
if (installOpts.uninstall) {
|
|
232
|
+
actions = buildUninstallPlan(root, paths, installOpts);
|
|
233
|
+
} else if (installOpts.reinstall) {
|
|
234
|
+
actions = buildUpdatePlan(root, paths, installOpts);
|
|
235
|
+
} else {
|
|
236
|
+
actions = buildInstallPlan(root, paths, installOpts);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const installActions = actions.filter(
|
|
240
|
+
(a): a is InstallAction => "kind" in a && typeof a.kind === "string" && a.kind.includes("-mcp"),
|
|
241
|
+
);
|
|
242
|
+
if (!installOpts.uninstall && resolveCapabilities(root).mcp && installActions.length > 0) {
|
|
243
|
+
runTargetPreflight(root, paths, installOpts, installActions);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return executePlan(root, actions, installOpts, true);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Main configure command orchestrator. */
|
|
250
|
+
export async function cliConfigure(
|
|
251
|
+
root: CliProgram,
|
|
252
|
+
rawOpts: Record<string, string>,
|
|
253
|
+
): Promise<never> {
|
|
254
|
+
const opts = parseConfigureOpts(rawOpts);
|
|
255
|
+
const err = validateConfigureOpts(opts);
|
|
256
|
+
if (err) {
|
|
257
|
+
installErr(err);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const isInteractive = !opts.sync && !opts.removeAll && !opts.removeConfig && !opts.status;
|
|
262
|
+
|
|
263
|
+
let changed: string[] = [];
|
|
264
|
+
try {
|
|
265
|
+
changed = isInteractive
|
|
266
|
+
? await runInteractiveConfigure(root, opts)
|
|
267
|
+
: await runAutomatedConfigure(root, opts);
|
|
268
|
+
} catch (mutationErr) {
|
|
269
|
+
installErr(mutationErr instanceof Error ? mutationErr.message : String(mutationErr));
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (opts.json && !opts.status) {
|
|
274
|
+
process.stdout.write(`${JSON.stringify(changed, null, 2)}\n`);
|
|
275
|
+
process.exit(0);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!opts.status && changed.length > 0) {
|
|
279
|
+
const verb = opts.removeAll || opts.removeConfig ? "Removed" : opts.sync ? "Synced" : "Updated";
|
|
280
|
+
installOut(`${verb} ${changed.length} file(s).`, configureToInstallOpts(opts));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
process.exit(0);
|
|
284
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readSync } from "node:fs";
|
|
2
|
+
import type { CliInstallArtifactKey } from "../install/target-types.ts";
|
|
3
|
+
|
|
4
|
+
const LABELS: Record<CliInstallArtifactKey, string> = {
|
|
5
|
+
app: "App binary",
|
|
6
|
+
cursorSkill: "Cursor skill",
|
|
7
|
+
claudeSkill: "Claude skill",
|
|
8
|
+
codexSkill: "Codex skill",
|
|
9
|
+
opencodeSkill: "OpenCode skill",
|
|
10
|
+
openclawSkill: "OpenClaw skill",
|
|
11
|
+
cursorMcp: "Cursor MCP",
|
|
12
|
+
claudeCodeMcp: "Claude Code MCP",
|
|
13
|
+
claudeDesktopMcp: "Claude Desktop MCP",
|
|
14
|
+
opencodeMcp: "OpenCode MCP",
|
|
15
|
+
codexMcp: "Codex MCP",
|
|
16
|
+
openclawMcp: "OpenClaw MCP",
|
|
17
|
+
chatgptMcp: "ChatGPT desktop MCP",
|
|
18
|
+
configure: "App config",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function artifactPromptLabel(key: CliInstallArtifactKey): string {
|
|
22
|
+
return LABELS[key];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type TargetPromptAction = "install" | "skip" | "uninstall";
|
|
26
|
+
|
|
27
|
+
/** Prompt per target: Y/n when not installed, y/N when installed. */
|
|
28
|
+
export function promptTargetAction(label: string, installed: boolean): TargetPromptAction | null {
|
|
29
|
+
const hint = installed ? `${label} [y/N]: ` : `${label} [Y/n]: `;
|
|
30
|
+
process.stderr.write(hint);
|
|
31
|
+
const buf = Buffer.alloc(256);
|
|
32
|
+
const n = readSync(0, buf, { length: 256 });
|
|
33
|
+
const ans = buf.toString("utf8", 0, n).trim().toLowerCase();
|
|
34
|
+
if (installed) {
|
|
35
|
+
if (ans === "n") return "uninstall";
|
|
36
|
+
return "skip";
|
|
37
|
+
}
|
|
38
|
+
if (ans === "n") return "skip";
|
|
39
|
+
return "install";
|
|
40
|
+
}
|
package/src/docs/builtin.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { docsSkillTopicDescription } from "../builtins/configure-copy.ts";
|
|
2
|
+
import { resolveCapabilities } from "../capabilities.ts";
|
|
1
3
|
import {
|
|
2
4
|
CliFallbackMode,
|
|
3
5
|
type CliLeaf,
|
|
@@ -75,11 +77,7 @@ export function cliBuiltinDocsGroup(program: CliProgram): CliRouter {
|
|
|
75
77
|
leaves.push(
|
|
76
78
|
docsLeaf(program, "schema", "Print the full command tree as JSON."),
|
|
77
79
|
docsLeaf(program, "api", "Print the full command reference as markdown."),
|
|
78
|
-
docsLeaf(
|
|
79
|
-
program,
|
|
80
|
-
"skill",
|
|
81
|
-
"Print a reference agent SKILL, use `install --skill` for optimized.",
|
|
82
|
-
),
|
|
80
|
+
docsLeaf(program, "skill", docsSkillTopicDescription(program, resolveCapabilities(program))),
|
|
83
81
|
);
|
|
84
82
|
|
|
85
83
|
return {
|
package/src/docs/docs.test.ts
CHANGED
|
@@ -106,7 +106,7 @@ test("docs mcp when MCP enabled", async () => {
|
|
|
106
106
|
expect(result.stdout).toContain("MCP server (myapp)");
|
|
107
107
|
expect(result.stdout).toContain("myapp mcp");
|
|
108
108
|
expect(result.stdout).toContain("claude_desktop_config.json");
|
|
109
|
-
expect(result.stdout).toContain("
|
|
109
|
+
expect(result.stdout).toContain("configure --sync --yes");
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
test("docs rejects unknown subcommand", async () => {
|
|
@@ -176,14 +176,14 @@ test("docs skill prints Cursor SKILL.md", async () => {
|
|
|
176
176
|
expect(result.stdout).not.toContain("mcp.json");
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
-
test("docs skill help recommends
|
|
179
|
+
test("docs skill help recommends configure", async () => {
|
|
180
180
|
const presentation = cliPresentationRoot(docsFixture());
|
|
181
181
|
const docsNode = presentation.commands.find((c) => c.key === "docs");
|
|
182
182
|
expect(docsNode && "commands" in docsNode).toBe(true);
|
|
183
183
|
if (docsNode && "commands" in docsNode) {
|
|
184
184
|
const skill = docsNode.commands.find((c) => c.key === "skill");
|
|
185
185
|
expect(skill?.description).toContain("reference agent SKILL");
|
|
186
|
-
expect(skill?.description).toContain("
|
|
186
|
+
expect(skill?.description).toContain("configure");
|
|
187
187
|
expect(skill?.notes).toBeUndefined();
|
|
188
188
|
expect(docsNode.notes).toContain("--save");
|
|
189
189
|
expect(docsNode.notes).not.toContain("install --skill");
|
|
@@ -210,14 +210,14 @@ test("completions offer docs subcommands", () => {
|
|
|
210
210
|
expect(bash).toContain("skill) echo");
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
-
test("generateMcpGuide includes schema URI and
|
|
213
|
+
test("generateMcpGuide includes schema URI and configure sync", () => {
|
|
214
214
|
const guide = generateMcpGuide(docsFixture(true));
|
|
215
215
|
expect(guide).toContain("myapp://schema");
|
|
216
216
|
expect(guide).toContain("~/.cursor/mcp.json");
|
|
217
217
|
expect(guide).toContain("claude_desktop_config.json");
|
|
218
218
|
expect(guide).toContain("## Installation");
|
|
219
219
|
expect(guide).toContain("## Running directly");
|
|
220
|
-
expect(guide).toContain("
|
|
220
|
+
expect(guide).toContain("configure --sync");
|
|
221
221
|
expect(guide).toContain("brew install myapp");
|
|
222
222
|
expect(guide).toContain("OpenAI Codex");
|
|
223
223
|
expect(guide).toContain("ChatGPT");
|
package/src/docs/mcp-guide.ts
CHANGED
|
@@ -105,11 +105,11 @@ export function generateMcpGuide(root: CliProgram): string {
|
|
|
105
105
|
"",
|
|
106
106
|
"## Installation",
|
|
107
107
|
"",
|
|
108
|
-
"### `
|
|
108
|
+
"### `configure`",
|
|
109
109
|
"",
|
|
110
110
|
];
|
|
111
111
|
|
|
112
|
-
if (caps.
|
|
112
|
+
if (caps.configure) {
|
|
113
113
|
lines.push(
|
|
114
114
|
`Install the CLI first so \`${root.key}\` is on your PATH (e.g. \`brew install ${root.key}\`). Host configs reference the app by name.`,
|
|
115
115
|
"",
|
|
@@ -123,7 +123,7 @@ export function generateMcpGuide(root: CliProgram): string {
|
|
|
123
123
|
|
|
124
124
|
lines.push(
|
|
125
125
|
"```bash",
|
|
126
|
-
`${root.key}
|
|
126
|
+
`${root.key} configure --sync --yes`,
|
|
127
127
|
"```",
|
|
128
128
|
"",
|
|
129
129
|
"Merges the server entry below into host config when each host is present:",
|
|
@@ -177,7 +177,7 @@ export function generateMcpGuide(root: CliProgram): string {
|
|
|
177
177
|
if (root.appConfig?.entries && Object.keys(root.appConfig.entries).length > 0) {
|
|
178
178
|
lines.push("## Configuration", "");
|
|
179
179
|
lines.push(
|
|
180
|
-
`Configure before first use in Cursor or Claude Desktop (MCP hosts are non-interactive): \`${root.key}
|
|
180
|
+
`Configure before first use in Cursor or Claude Desktop (MCP hosts are non-interactive): \`${root.key} configure\`.`,
|
|
181
181
|
"",
|
|
182
182
|
`Default config file: \`${displayAppConfigPath(root)}\` (flat JSON keys).`,
|
|
183
183
|
"",
|
package/src/index.ts
CHANGED
|
@@ -34,11 +34,11 @@ export type {
|
|
|
34
34
|
CliAppConfigEntry,
|
|
35
35
|
CliAppConfigResolveContext,
|
|
36
36
|
CliAppConfigResolveFn,
|
|
37
|
+
CliConfigureConfig,
|
|
38
|
+
CliConfigureTargets,
|
|
37
39
|
CliDocsConfig,
|
|
38
40
|
CliDocsTopic,
|
|
39
41
|
CliHandler,
|
|
40
|
-
CliInstallConfig,
|
|
41
|
-
CliInstallTargets,
|
|
42
42
|
CliInvocation,
|
|
43
43
|
CliMcpBundleConfig,
|
|
44
44
|
CliMcpResource,
|
|
@@ -10,7 +10,7 @@ const base: CliProgram = {
|
|
|
10
10
|
handler: () => {},
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
describe("
|
|
13
|
+
describe("validateConfigureConfig", () => {
|
|
14
14
|
test("accepts empty install config", () => {
|
|
15
15
|
expect(() => cliValidateProgram(base)).not.toThrow();
|
|
16
16
|
});
|
|
@@ -18,7 +18,7 @@ describe("validateInstallConfig", () => {
|
|
|
18
18
|
test("rejects removed allSkills shorthand", () => {
|
|
19
19
|
const program = {
|
|
20
20
|
...base,
|
|
21
|
-
|
|
21
|
+
configure: { targets: { allSkills: true } },
|
|
22
22
|
} as CliProgram;
|
|
23
23
|
expect(() => cliValidateProgram(program)).toThrow(CliSchemaValidationError);
|
|
24
24
|
expect(() => cliValidateProgram(program)).toThrow(/allSkills/);
|
|
@@ -28,7 +28,7 @@ describe("validateInstallConfig", () => {
|
|
|
28
28
|
const program: CliProgram = {
|
|
29
29
|
...base,
|
|
30
30
|
mcpServer: { enabled: true },
|
|
31
|
-
|
|
31
|
+
configure: {
|
|
32
32
|
agentIntegration: "mcp",
|
|
33
33
|
targets: { cursorMcp: true, cursorSkill: true },
|
|
34
34
|
},
|
|
@@ -39,7 +39,7 @@ describe("validateInstallConfig", () => {
|
|
|
39
39
|
test("rejects explicit MCP target in skill integration mode", () => {
|
|
40
40
|
const program: CliProgram = {
|
|
41
41
|
...base,
|
|
42
|
-
|
|
42
|
+
configure: {
|
|
43
43
|
agentIntegration: "skill",
|
|
44
44
|
targets: { cursorMcp: true },
|
|
45
45
|
},
|
|
@@ -51,7 +51,7 @@ describe("validateInstallConfig", () => {
|
|
|
51
51
|
const program: CliProgram = {
|
|
52
52
|
...base,
|
|
53
53
|
mcpServer: { enabled: true },
|
|
54
|
-
|
|
54
|
+
configure: {
|
|
55
55
|
agentIntegration: "both",
|
|
56
56
|
targets: { cursorMcp: true, cursorSkill: true },
|
|
57
57
|
},
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { InstallOpts } from "./target-types.ts";
|
|
2
|
+
|
|
3
|
+
export function parseInstallOpts(raw: Record<string, string>): InstallOpts {
|
|
4
|
+
const flag = (name: string) => raw[name] === "1";
|
|
5
|
+
return {
|
|
6
|
+
all: flag("all"),
|
|
7
|
+
skill: flag("skill"),
|
|
8
|
+
mcp: flag("mcp"),
|
|
9
|
+
reinstall: flag("reinstall"),
|
|
10
|
+
status: flag("status"),
|
|
11
|
+
uninstall: flag("uninstall"),
|
|
12
|
+
configure: flag("configure"),
|
|
13
|
+
yes: flag("yes"),
|
|
14
|
+
dry: flag("dry"),
|
|
15
|
+
json: flag("json"),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolveCapabilities } from "../capabilities.ts";
|
|
2
2
|
import type {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
CliConfigureConfig,
|
|
4
|
+
CliConfigureTargets,
|
|
5
5
|
CliProgram,
|
|
6
6
|
InstallAgentIntegration,
|
|
7
7
|
InstallTargetSpec,
|
|
@@ -15,10 +15,10 @@ export type { InstallTargetSpec, ResolvedInstallTarget } from "../types.ts";
|
|
|
15
15
|
export type { InstallPlanMode, InstallScope } from "./target-types.ts";
|
|
16
16
|
|
|
17
17
|
export function resolveAgentIntegration(
|
|
18
|
-
|
|
18
|
+
configure?: CliConfigureConfig,
|
|
19
19
|
mcpEnabled = false,
|
|
20
20
|
): InstallAgentIntegration {
|
|
21
|
-
return
|
|
21
|
+
return configure?.agentIntegration ?? (mcpEnabled ? "mcp" : "skill");
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/** Resolves a boolean or object target spec against category defaults. */
|
|
@@ -52,7 +52,7 @@ function artifactDefaults(
|
|
|
52
52
|
|
|
53
53
|
function applyAgentPairDedupe(
|
|
54
54
|
out: Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>,
|
|
55
|
-
user:
|
|
55
|
+
user: CliConfigureTargets | undefined,
|
|
56
56
|
integration: InstallAgentIntegration,
|
|
57
57
|
): void {
|
|
58
58
|
if (integration === "both") return;
|
|
@@ -78,12 +78,12 @@ function applyAgentPairDedupe(
|
|
|
78
78
|
|
|
79
79
|
/** Effective per-artifact gates for install.targets. */
|
|
80
80
|
export function resolveEffectiveInstallTargets(
|
|
81
|
-
|
|
81
|
+
configure?: CliConfigureConfig,
|
|
82
82
|
program?: Pick<CliProgram, "mcpServer">,
|
|
83
83
|
): Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }> {
|
|
84
84
|
const mcpEnabled = program?.mcpServer?.enabled === true;
|
|
85
|
-
const integration = resolveAgentIntegration(
|
|
86
|
-
const user =
|
|
85
|
+
const integration = resolveAgentIntegration(configure, mcpEnabled);
|
|
86
|
+
const user = configure?.targets;
|
|
87
87
|
const out = {} as Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>;
|
|
88
88
|
for (const key of INSTALL_ARTIFACT_KEYS) {
|
|
89
89
|
out[key] = resolveInstallTargetSpec(user?.[key], artifactDefaults(key, integration));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveCapabilities } from "../capabilities.ts";
|
|
2
2
|
import { appConfigInstalled } from "../config/file.ts";
|
|
3
|
-
import type {
|
|
3
|
+
import type { CliConfigureTargets, CliProgram } from "../types.ts";
|
|
4
4
|
import type { InstallPaths } from "./paths.ts";
|
|
5
5
|
import {
|
|
6
6
|
type InstallPlanMode,
|
|
@@ -78,7 +78,7 @@ export function buildDetectedSnapshot(root: CliProgram, paths: InstallPaths): De
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
function targetExplicitlyConfigured(
|
|
81
|
-
user:
|
|
81
|
+
user: CliConfigureTargets | undefined,
|
|
82
82
|
key: CliInstallArtifactKey,
|
|
83
83
|
): boolean {
|
|
84
84
|
return user?.[key] !== undefined;
|
|
@@ -89,7 +89,7 @@ function agentCategoryInScope(
|
|
|
89
89
|
categoryKeys: readonly CliInstallArtifactKey[],
|
|
90
90
|
category: "skill" | "mcp",
|
|
91
91
|
effective: Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>,
|
|
92
|
-
targets:
|
|
92
|
+
targets: CliConfigureTargets | undefined,
|
|
93
93
|
root: CliProgram,
|
|
94
94
|
): boolean {
|
|
95
95
|
if (!categoryKeys.includes(key)) return false;
|
|
@@ -105,7 +105,7 @@ export function isArtifactInScope(
|
|
|
105
105
|
effective: Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>,
|
|
106
106
|
mode: InstallPlanMode,
|
|
107
107
|
root: CliProgram,
|
|
108
|
-
targets?:
|
|
108
|
+
targets?: CliConfigureTargets,
|
|
109
109
|
): boolean {
|
|
110
110
|
if (mode === "uninstall-all") {
|
|
111
111
|
return true;
|
|
@@ -150,7 +150,7 @@ export function shouldIncludeArtifact(
|
|
|
150
150
|
detected?: Partial<Record<CliInstallArtifactKey, boolean>>,
|
|
151
151
|
): boolean {
|
|
152
152
|
const target = installTargetForKey(key);
|
|
153
|
-
const targets = root.
|
|
153
|
+
const targets = root.configure?.targets;
|
|
154
154
|
|
|
155
155
|
if (key !== "configure" && target && !target.isAvailable(root, paths)) {
|
|
156
156
|
return false;
|
|
@@ -196,7 +196,7 @@ export function buildTargetPlanContext(
|
|
|
196
196
|
opts: InstallOpts,
|
|
197
197
|
detected: DetectedSnapshot,
|
|
198
198
|
): TargetPlanContext {
|
|
199
|
-
const effective = resolveEffectiveInstallTargets(root.
|
|
199
|
+
const effective = resolveEffectiveInstallTargets(root.configure, root);
|
|
200
200
|
const mode = resolveInstallPlanMode(opts);
|
|
201
201
|
const scope = resolveInstallScope(opts);
|
|
202
202
|
const detPartial = Object.fromEntries(
|
|
@@ -236,8 +236,11 @@ export function resolveInstallTargetPreview(
|
|
|
236
236
|
program: CliProgram,
|
|
237
237
|
paths: InstallPaths,
|
|
238
238
|
): InstallTargetPreview {
|
|
239
|
-
const effective = resolveEffectiveInstallTargets(program.
|
|
240
|
-
const integration = resolveAgentIntegration(
|
|
239
|
+
const effective = resolveEffectiveInstallTargets(program.configure, program);
|
|
240
|
+
const integration = resolveAgentIntegration(
|
|
241
|
+
program.configure,
|
|
242
|
+
program.mcpServer?.enabled === true,
|
|
243
|
+
);
|
|
241
244
|
|
|
242
245
|
const keysForScope = (scope: InstallScope, mode: InstallPlanMode): CliInstallArtifactKey[] =>
|
|
243
246
|
INSTALL_ARTIFACT_KEYS.filter((key) =>
|
|
@@ -11,7 +11,7 @@ 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;
|
|
@@ -75,10 +75,10 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
75
75
|
version: "1",
|
|
76
76
|
description: "x",
|
|
77
77
|
mcpServer: { enabled: true },
|
|
78
|
-
|
|
78
|
+
configure: { agentIntegration: "skill" },
|
|
79
79
|
handler: () => {},
|
|
80
80
|
};
|
|
81
|
-
const effective = resolveEffectiveInstallTargets(program.
|
|
81
|
+
const effective = resolveEffectiveInstallTargets(program.configure, program);
|
|
82
82
|
const scope = { mcp: true };
|
|
83
83
|
expect(isArtifactInScope("cursorMcp", scope, effective, "install-scoped", program)).toBe(false);
|
|
84
84
|
expect(
|
|
@@ -101,10 +101,10 @@ describe("resolveEffectiveInstallTargets", () => {
|
|
|
101
101
|
version: "1",
|
|
102
102
|
description: "x",
|
|
103
103
|
mcpServer: { enabled: true },
|
|
104
|
-
|
|
104
|
+
configure: { agentIntegration: "both" },
|
|
105
105
|
handler: () => {},
|
|
106
106
|
};
|
|
107
|
-
const effective = resolveEffectiveInstallTargets(program.
|
|
107
|
+
const effective = resolveEffectiveInstallTargets(program.configure, program);
|
|
108
108
|
const scope = { mcp: true, skill: true, configure: true };
|
|
109
109
|
expect(isArtifactInScope("cursorSkill", scope, effective, "install-scoped", program)).toBe(
|
|
110
110
|
true,
|
package/src/invoke.test.ts
CHANGED
package/src/mcp/tools.ts
CHANGED