harnessed 3.9.3 → 3.9.6
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/dist/cli.mjs +197 -166
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -762,6 +762,43 @@ var init_check_mattpocock_skills = __esm({
|
|
|
762
762
|
INSTALL_COMMANDS2 = ["npx skills@latest add mattpocock/skills"];
|
|
763
763
|
}
|
|
764
764
|
});
|
|
765
|
+
function getUserClaudeJsonPath() {
|
|
766
|
+
return join(homedir(), ".claude.json");
|
|
767
|
+
}
|
|
768
|
+
async function readUserClaudeJson() {
|
|
769
|
+
const path = getUserClaudeJsonPath();
|
|
770
|
+
let raw;
|
|
771
|
+
try {
|
|
772
|
+
raw = await readFile(path, "utf8");
|
|
773
|
+
} catch (err2) {
|
|
774
|
+
if (err2.code === "ENOENT") return {};
|
|
775
|
+
throw err2;
|
|
776
|
+
}
|
|
777
|
+
try {
|
|
778
|
+
const parsed = JSON.parse(raw);
|
|
779
|
+
if (parsed === null || typeof parsed !== "object") return {};
|
|
780
|
+
return parsed;
|
|
781
|
+
} catch {
|
|
782
|
+
return {};
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
async function isMcpServerRegistered(name) {
|
|
786
|
+
const config = await readUserClaudeJson();
|
|
787
|
+
const servers = config.mcpServers;
|
|
788
|
+
if (!servers || typeof servers !== "object") return false;
|
|
789
|
+
return Object.hasOwn(servers, name);
|
|
790
|
+
}
|
|
791
|
+
async function isPluginRegistered(pluginName) {
|
|
792
|
+
const config = await readUserClaudeJson();
|
|
793
|
+
const plugins = config.enabledPlugins;
|
|
794
|
+
if (!plugins || typeof plugins !== "object") return false;
|
|
795
|
+
if (Object.hasOwn(plugins, pluginName)) return true;
|
|
796
|
+
return Object.keys(plugins).some((k) => k.split("@")[0] === pluginName);
|
|
797
|
+
}
|
|
798
|
+
var init_readClaudeConfig = __esm({
|
|
799
|
+
"src/installers/lib/readClaudeConfig.ts"() {
|
|
800
|
+
}
|
|
801
|
+
});
|
|
765
802
|
|
|
766
803
|
// src/cli/lib/check-mcp-availability.ts
|
|
767
804
|
var check_mcp_availability_exports = {};
|
|
@@ -769,17 +806,15 @@ __export(check_mcp_availability_exports, {
|
|
|
769
806
|
checkMcpAvailability: () => checkMcpAvailability
|
|
770
807
|
});
|
|
771
808
|
async function checkMcpAvailability() {
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
missing = TARGET_SERVERS.filter((s) => !installed.includes(s));
|
|
782
|
-
} catch {
|
|
809
|
+
const installed = [];
|
|
810
|
+
const missing = [];
|
|
811
|
+
for (const s of TARGET_SERVERS) {
|
|
812
|
+
const present = await isMcpServerRegistered(s);
|
|
813
|
+
if (present) {
|
|
814
|
+
installed.push(s);
|
|
815
|
+
} else {
|
|
816
|
+
missing.push(s);
|
|
817
|
+
}
|
|
783
818
|
}
|
|
784
819
|
if (missing.length === 0) {
|
|
785
820
|
return {
|
|
@@ -788,35 +823,26 @@ async function checkMcpAvailability() {
|
|
|
788
823
|
message: `all 3 installed: ${installed.join(", ")}`
|
|
789
824
|
};
|
|
790
825
|
}
|
|
791
|
-
const installCommands = missing.map((s) => SERVER_INSTALL_COMMANDS[s]);
|
|
792
826
|
if (installed.length === 0) {
|
|
793
827
|
return {
|
|
794
828
|
name: "MCP servers (tavily/exa/chrome-devtools)",
|
|
795
829
|
status: "warn",
|
|
796
|
-
message: "none of 3 target MCP servers
|
|
797
|
-
fix: "
|
|
798
|
-
install_commands: installCommands
|
|
830
|
+
message: "none of 3 target MCP servers registered in ~/.claude.json",
|
|
831
|
+
fix: "run `harnessed setup` to install via Step B (manifests/tools/{tavily,exa,chrome-devtools}-mcp.yaml)"
|
|
799
832
|
};
|
|
800
833
|
}
|
|
801
834
|
return {
|
|
802
835
|
name: "MCP servers (tavily/exa/chrome-devtools)",
|
|
803
836
|
status: "warn",
|
|
804
837
|
message: `${installed.length}/3 installed: ${installed.join(", ")}; missing: ${missing.join(", ")}`,
|
|
805
|
-
fix: `
|
|
806
|
-
install_commands: installCommands
|
|
838
|
+
fix: "run `harnessed setup` to install missing MCPs via Step B"
|
|
807
839
|
};
|
|
808
840
|
}
|
|
809
|
-
var TARGET_SERVERS
|
|
841
|
+
var TARGET_SERVERS;
|
|
810
842
|
var init_check_mcp_availability = __esm({
|
|
811
843
|
"src/cli/lib/check-mcp-availability.ts"() {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
"tavily-remote-mcp": "claude mcp add tavily-remote-mcp --transport http https://mcp.tavily.com/mcp/",
|
|
815
|
-
exa: "claude mcp add --transport http exa https://mcp.exa.ai/mcp",
|
|
816
|
-
// chrome-devtools: official Claude marketplace direct install (v3.9.2 dogfood
|
|
817
|
-
// confirmed — was assumed npx in v3.9.1 SPEC, corrected to official marketplace).
|
|
818
|
-
"chrome-devtools": "claude plugin install chrome-devtools-mcp"
|
|
819
|
-
};
|
|
844
|
+
init_readClaudeConfig();
|
|
845
|
+
TARGET_SERVERS = ["tavily-mcp", "exa-mcp", "chrome-devtools-mcp"];
|
|
820
846
|
}
|
|
821
847
|
});
|
|
822
848
|
|
|
@@ -1118,15 +1144,14 @@ async function runAutoInstall(opts) {
|
|
|
1118
1144
|
out.skipped.push(check.name);
|
|
1119
1145
|
continue;
|
|
1120
1146
|
}
|
|
1121
|
-
|
|
1147
|
+
const failureReasons = [];
|
|
1122
1148
|
for (const cmd of commands) {
|
|
1123
1149
|
const tokens = cmd.split(/\s+/).filter((t2) => t2.length > 0);
|
|
1124
1150
|
const exe = tokens[0];
|
|
1125
1151
|
const args = tokens.slice(1);
|
|
1126
1152
|
if (exe === void 0) {
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
break;
|
|
1153
|
+
failureReasons.push(`empty command in install_commands`);
|
|
1154
|
+
continue;
|
|
1130
1155
|
}
|
|
1131
1156
|
const r = spawnSync(exe, args, {
|
|
1132
1157
|
encoding: "utf8",
|
|
@@ -1137,16 +1162,28 @@ async function runAutoInstall(opts) {
|
|
|
1137
1162
|
shell: true
|
|
1138
1163
|
});
|
|
1139
1164
|
if (r.status !== 0) {
|
|
1140
|
-
const reason = r.error !== void 0 ? `spawn error
|
|
1141
|
-
|
|
1142
|
-
console.error(`
|
|
1143
|
-
chainOk = false;
|
|
1144
|
-
break;
|
|
1165
|
+
const reason = r.error !== void 0 ? `spawn error on \`${cmd}\`: ${r.error.message}` : `exit ${r.status ?? "<unknown>"} on \`${cmd}\``;
|
|
1166
|
+
failureReasons.push(reason);
|
|
1167
|
+
console.error(` \u2717 ${cmd} \u2014 ${reason}`);
|
|
1145
1168
|
}
|
|
1146
1169
|
}
|
|
1147
|
-
if (
|
|
1170
|
+
if (failureReasons.length === 0) {
|
|
1148
1171
|
out.installed.push(check.name);
|
|
1149
1172
|
console.log(` \u2713 installed ${check.name}`);
|
|
1173
|
+
} else if (failureReasons.length === commands.length) {
|
|
1174
|
+
out.failed.push({
|
|
1175
|
+
name: check.name,
|
|
1176
|
+
reason: `all commands failed (${failureReasons.length})`
|
|
1177
|
+
});
|
|
1178
|
+
console.error(` \u2717 failed ${check.name} \u2014 all ${commands.length} commands failed`);
|
|
1179
|
+
} else {
|
|
1180
|
+
out.failed.push({
|
|
1181
|
+
name: check.name,
|
|
1182
|
+
reason: `partial: ${failureReasons.length}/${commands.length} commands failed`
|
|
1183
|
+
});
|
|
1184
|
+
console.error(
|
|
1185
|
+
` \u26A0 ${check.name} \u2014 partial: ${failureReasons.length}/${commands.length} commands failed (others succeeded)`
|
|
1186
|
+
);
|
|
1150
1187
|
}
|
|
1151
1188
|
}
|
|
1152
1189
|
console.log(
|
|
@@ -1163,7 +1200,7 @@ var init_auto_install = __esm({
|
|
|
1163
1200
|
|
|
1164
1201
|
// package.json
|
|
1165
1202
|
var package_default = {
|
|
1166
|
-
version: "3.9.
|
|
1203
|
+
version: "3.9.6"};
|
|
1167
1204
|
|
|
1168
1205
|
// src/manifest/errors.ts
|
|
1169
1206
|
function instancePathToKeyPath(instancePath) {
|
|
@@ -4320,39 +4357,98 @@ var installCcHookAdd = async (ctx) => {
|
|
|
4320
4357
|
await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, "", "");
|
|
4321
4358
|
return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath3] };
|
|
4322
4359
|
};
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4360
|
+
var DEFAULT_VERIFY_TIMEOUT_MS = 15e3;
|
|
4361
|
+
var DEFAULT_INSTALL_TIMEOUT_MS = 6e4;
|
|
4362
|
+
async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
4363
|
+
const violation = checkCmdString(cmd);
|
|
4364
|
+
if (violation) {
|
|
4365
|
+
return {
|
|
4366
|
+
ok: false,
|
|
4367
|
+
phase: "preflight",
|
|
4368
|
+
error: {
|
|
4369
|
+
file: ctx.manifest.metadata.name,
|
|
4370
|
+
path: "/spec/install/cmd",
|
|
4371
|
+
message: `shell escape detected at spawn boundary: '${violation.label}' (${violation.hint}) \u2014 refusing to execute. v0.1 forbids dynamic shell evaluation; this is a defense-in-depth gate after schema validation.`,
|
|
4372
|
+
line: null,
|
|
4373
|
+
column: null,
|
|
4374
|
+
keyword: "security-gate-bypass"
|
|
4375
|
+
}
|
|
4376
|
+
};
|
|
4334
4377
|
}
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4378
|
+
const installCfg = ctx.manifest.spec.install;
|
|
4379
|
+
const effectiveTimeoutMs = timeoutMs ?? DEFAULT_INSTALL_TIMEOUT_MS;
|
|
4380
|
+
const env = { ...process.env, ...installCfg.env ?? {} };
|
|
4381
|
+
const cwd = installCfg.cwd ?? ctx.cwd;
|
|
4382
|
+
let child;
|
|
4383
|
+
if (process.platform === "win32") {
|
|
4384
|
+
child = spawn("cmd.exe", ["/c", cmd, ...args], { cwd, env, windowsHide: true });
|
|
4385
|
+
} else {
|
|
4386
|
+
const joined = args.length > 0 ? `${cmd} ${args.join(" ")}` : cmd;
|
|
4387
|
+
child = spawn("/bin/sh", ["-c", joined], { cwd, env });
|
|
4341
4388
|
}
|
|
4389
|
+
let stdout2 = "";
|
|
4390
|
+
let stderr = "";
|
|
4391
|
+
child.stdout?.setEncoding("utf8").on("data", (chunk) => {
|
|
4392
|
+
stdout2 += chunk;
|
|
4393
|
+
});
|
|
4394
|
+
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
4395
|
+
stderr += chunk;
|
|
4396
|
+
});
|
|
4397
|
+
return await new Promise((resolve15) => {
|
|
4398
|
+
const timer = setTimeout(() => {
|
|
4399
|
+
child.kill("SIGKILL");
|
|
4400
|
+
resolve15({
|
|
4401
|
+
ok: false,
|
|
4402
|
+
phase: "spawn",
|
|
4403
|
+
error: {
|
|
4404
|
+
file: ctx.manifest.metadata.name,
|
|
4405
|
+
path: "/spec/install/cmd",
|
|
4406
|
+
message: `spawn timed out after ${effectiveTimeoutMs}ms (cmd: ${cmd}); partial stderr: ${stderr.slice(0, 200)}`,
|
|
4407
|
+
line: null,
|
|
4408
|
+
column: null,
|
|
4409
|
+
keyword: "spawn-timeout"
|
|
4410
|
+
}
|
|
4411
|
+
});
|
|
4412
|
+
}, effectiveTimeoutMs);
|
|
4413
|
+
child.on("error", (err2) => {
|
|
4414
|
+
clearTimeout(timer);
|
|
4415
|
+
resolve15({
|
|
4416
|
+
ok: false,
|
|
4417
|
+
phase: "spawn",
|
|
4418
|
+
error: {
|
|
4419
|
+
file: ctx.manifest.metadata.name,
|
|
4420
|
+
path: "/spec/install/cmd",
|
|
4421
|
+
message: `spawn failed: ${err2.message}`,
|
|
4422
|
+
line: null,
|
|
4423
|
+
column: null,
|
|
4424
|
+
keyword: "spawn-error"
|
|
4425
|
+
}
|
|
4426
|
+
});
|
|
4427
|
+
});
|
|
4428
|
+
child.on("close", (code) => {
|
|
4429
|
+
clearTimeout(timer);
|
|
4430
|
+
resolve15({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
|
|
4431
|
+
});
|
|
4432
|
+
});
|
|
4342
4433
|
}
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
if (
|
|
4354
|
-
|
|
4434
|
+
|
|
4435
|
+
// src/installers/lib/idempotent.ts
|
|
4436
|
+
var IDEMPOTENT_CHECK_TIMEOUT_MS = 1e4;
|
|
4437
|
+
async function isAlreadyInstalled(ctx) {
|
|
4438
|
+
if (ctx.opts.updateInstalled === true) return false;
|
|
4439
|
+
const idempotentCmd = ctx.manifest.spec.install.idempotent_check;
|
|
4440
|
+
if (typeof idempotentCmd !== "string" || idempotentCmd.length === 0) {
|
|
4441
|
+
return false;
|
|
4442
|
+
}
|
|
4443
|
+
const r = await spawnCmd(ctx, idempotentCmd, [], IDEMPOTENT_CHECK_TIMEOUT_MS);
|
|
4444
|
+
if (!("exitCode" in r)) {
|
|
4445
|
+
return false;
|
|
4446
|
+
}
|
|
4447
|
+
return r.exitCode === 0;
|
|
4355
4448
|
}
|
|
4449
|
+
|
|
4450
|
+
// src/installers/ccPluginMarketplace.ts
|
|
4451
|
+
init_readClaudeConfig();
|
|
4356
4452
|
function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
4357
4453
|
return new Promise((resolve15) => {
|
|
4358
4454
|
const isWin = process.platform === "win32";
|
|
@@ -4413,6 +4509,9 @@ var installCcPluginMarketplace = async (ctx) => {
|
|
|
4413
4509
|
const e = pre.errors[0] ?? err(ctx, "/", "preflight failed (no detail)", "preflight");
|
|
4414
4510
|
return { ok: false, phase: "preflight", error: e };
|
|
4415
4511
|
}
|
|
4512
|
+
if (await isAlreadyInstalled(ctx)) {
|
|
4513
|
+
return { ok: true, alreadyInstalled: true, backupId: "noop-idempotent" };
|
|
4514
|
+
}
|
|
4416
4515
|
const parsed = parseCmd(install.cmd);
|
|
4417
4516
|
if (!parsed) {
|
|
4418
4517
|
return {
|
|
@@ -4513,82 +4612,6 @@ ${newEntry}
|
|
|
4513
4612
|
await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, install.git_ref, "");
|
|
4514
4613
|
return { ok: true, backupId: bk.backupId, appliedFiles: [settingsFile] };
|
|
4515
4614
|
};
|
|
4516
|
-
var DEFAULT_VERIFY_TIMEOUT_MS = 15e3;
|
|
4517
|
-
var DEFAULT_INSTALL_TIMEOUT_MS = 6e4;
|
|
4518
|
-
async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
4519
|
-
const violation = checkCmdString(cmd);
|
|
4520
|
-
if (violation) {
|
|
4521
|
-
return {
|
|
4522
|
-
ok: false,
|
|
4523
|
-
phase: "preflight",
|
|
4524
|
-
error: {
|
|
4525
|
-
file: ctx.manifest.metadata.name,
|
|
4526
|
-
path: "/spec/install/cmd",
|
|
4527
|
-
message: `shell escape detected at spawn boundary: '${violation.label}' (${violation.hint}) \u2014 refusing to execute. v0.1 forbids dynamic shell evaluation; this is a defense-in-depth gate after schema validation.`,
|
|
4528
|
-
line: null,
|
|
4529
|
-
column: null,
|
|
4530
|
-
keyword: "security-gate-bypass"
|
|
4531
|
-
}
|
|
4532
|
-
};
|
|
4533
|
-
}
|
|
4534
|
-
const installCfg = ctx.manifest.spec.install;
|
|
4535
|
-
const effectiveTimeoutMs = timeoutMs ?? DEFAULT_INSTALL_TIMEOUT_MS;
|
|
4536
|
-
const env = { ...process.env, ...installCfg.env ?? {} };
|
|
4537
|
-
const cwd = installCfg.cwd ?? ctx.cwd;
|
|
4538
|
-
let child;
|
|
4539
|
-
if (process.platform === "win32") {
|
|
4540
|
-
child = spawn("cmd.exe", ["/c", cmd, ...args], { cwd, env, windowsHide: true });
|
|
4541
|
-
} else {
|
|
4542
|
-
const joined = args.length > 0 ? `${cmd} ${args.join(" ")}` : cmd;
|
|
4543
|
-
child = spawn("/bin/sh", ["-c", joined], { cwd, env });
|
|
4544
|
-
}
|
|
4545
|
-
let stdout2 = "";
|
|
4546
|
-
let stderr = "";
|
|
4547
|
-
child.stdout?.setEncoding("utf8").on("data", (chunk) => {
|
|
4548
|
-
stdout2 += chunk;
|
|
4549
|
-
});
|
|
4550
|
-
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
4551
|
-
stderr += chunk;
|
|
4552
|
-
});
|
|
4553
|
-
return await new Promise((resolve15) => {
|
|
4554
|
-
const timer = setTimeout(() => {
|
|
4555
|
-
child.kill("SIGKILL");
|
|
4556
|
-
resolve15({
|
|
4557
|
-
ok: false,
|
|
4558
|
-
phase: "spawn",
|
|
4559
|
-
error: {
|
|
4560
|
-
file: ctx.manifest.metadata.name,
|
|
4561
|
-
path: "/spec/install/cmd",
|
|
4562
|
-
message: `spawn timed out after ${effectiveTimeoutMs}ms (cmd: ${cmd}); partial stderr: ${stderr.slice(0, 200)}`,
|
|
4563
|
-
line: null,
|
|
4564
|
-
column: null,
|
|
4565
|
-
keyword: "spawn-timeout"
|
|
4566
|
-
}
|
|
4567
|
-
});
|
|
4568
|
-
}, effectiveTimeoutMs);
|
|
4569
|
-
child.on("error", (err2) => {
|
|
4570
|
-
clearTimeout(timer);
|
|
4571
|
-
resolve15({
|
|
4572
|
-
ok: false,
|
|
4573
|
-
phase: "spawn",
|
|
4574
|
-
error: {
|
|
4575
|
-
file: ctx.manifest.metadata.name,
|
|
4576
|
-
path: "/spec/install/cmd",
|
|
4577
|
-
message: `spawn failed: ${err2.message}`,
|
|
4578
|
-
line: null,
|
|
4579
|
-
column: null,
|
|
4580
|
-
keyword: "spawn-error"
|
|
4581
|
-
}
|
|
4582
|
-
});
|
|
4583
|
-
});
|
|
4584
|
-
child.on("close", (code) => {
|
|
4585
|
-
clearTimeout(timer);
|
|
4586
|
-
resolve15({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
|
|
4587
|
-
});
|
|
4588
|
-
});
|
|
4589
|
-
}
|
|
4590
|
-
|
|
4591
|
-
// src/installers/gitCloneWithSetup.ts
|
|
4592
4615
|
function gitRevParseHead(cwd, timeoutMs = 1e4) {
|
|
4593
4616
|
return new Promise((resolve15) => {
|
|
4594
4617
|
const isWin = process.platform === "win32";
|
|
@@ -4658,6 +4681,9 @@ var installGitCloneWithSetup = async (ctx) => {
|
|
|
4658
4681
|
const e = pre.errors[0] ?? err(ctx, "/", "preflight failed (no detail)", "preflight");
|
|
4659
4682
|
return { ok: false, phase: "preflight", error: e };
|
|
4660
4683
|
}
|
|
4684
|
+
if (await isAlreadyInstalled(ctx)) {
|
|
4685
|
+
return { ok: true, alreadyInstalled: true, backupId: "noop-idempotent" };
|
|
4686
|
+
}
|
|
4661
4687
|
if (!/^[a-f0-9]{7,40}$/.test(install.git_ref)) {
|
|
4662
4688
|
return {
|
|
4663
4689
|
ok: false,
|
|
@@ -4775,6 +4801,7 @@ var installGitCloneWithSetup = async (ctx) => {
|
|
|
4775
4801
|
};
|
|
4776
4802
|
|
|
4777
4803
|
// src/installers/mcpHttpAdd.ts
|
|
4804
|
+
init_readClaudeConfig();
|
|
4778
4805
|
function resolveEnvVars(value) {
|
|
4779
4806
|
const pattern = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
4780
4807
|
let resolved = value;
|
|
@@ -4943,6 +4970,7 @@ ${newEntry}
|
|
|
4943
4970
|
};
|
|
4944
4971
|
|
|
4945
4972
|
// src/installers/mcpStdioAdd.ts
|
|
4973
|
+
init_readClaudeConfig();
|
|
4946
4974
|
var installMcpStdioAdd = async (ctx) => {
|
|
4947
4975
|
const install = ctx.manifest.spec.install;
|
|
4948
4976
|
if (install.method !== "mcp-stdio-add") {
|
|
@@ -5083,6 +5111,9 @@ var installNpmCli = async (ctx) => {
|
|
|
5083
5111
|
const e = pre.errors[0] ?? err(ctx, "/", "preflight failed (no detail)", "preflight");
|
|
5084
5112
|
return { ok: false, phase: "preflight", error: e };
|
|
5085
5113
|
}
|
|
5114
|
+
if (await isAlreadyInstalled(ctx)) {
|
|
5115
|
+
return { ok: true, alreadyInstalled: true, backupId: "noop-idempotent" };
|
|
5116
|
+
}
|
|
5086
5117
|
let level = detectLevel(install.cmd);
|
|
5087
5118
|
let cmd = install.cmd;
|
|
5088
5119
|
const plan = { files: [] };
|
|
@@ -5178,6 +5209,9 @@ var installNpxSkillInstaller = async (ctx) => {
|
|
|
5178
5209
|
const e = pre.errors[0] ?? err(ctx, "/", "preflight failed (no detail)", "preflight");
|
|
5179
5210
|
return { ok: false, phase: "preflight", error: e };
|
|
5180
5211
|
}
|
|
5212
|
+
if (await isAlreadyInstalled(ctx)) {
|
|
5213
|
+
return { ok: true, alreadyInstalled: true, backupId: "noop-idempotent" };
|
|
5214
|
+
}
|
|
5181
5215
|
if (!/\bskills@(?!latest\b)\S+/.test(install.cmd)) {
|
|
5182
5216
|
return {
|
|
5183
5217
|
ok: false,
|
|
@@ -5392,12 +5426,6 @@ ${t("install.manifest_not_found.fix", { name: resolvedName })}`
|
|
|
5392
5426
|
process.exit(1);
|
|
5393
5427
|
});
|
|
5394
5428
|
}
|
|
5395
|
-
var PHASE_21 = /* @__PURE__ */ new Set([
|
|
5396
|
-
"cc-plugin-marketplace",
|
|
5397
|
-
"git-clone-with-setup",
|
|
5398
|
-
"npx-skill-installer",
|
|
5399
|
-
"mcp-http-add"
|
|
5400
|
-
]);
|
|
5401
5429
|
async function listBaseManifests(cwd) {
|
|
5402
5430
|
const out = [];
|
|
5403
5431
|
for (const d of ["manifests/tools", "manifests/skill-packs"]) {
|
|
@@ -5440,11 +5468,6 @@ function registerInstallBase(program2) {
|
|
|
5440
5468
|
continue;
|
|
5441
5469
|
}
|
|
5442
5470
|
const name = v.manifest.metadata.name;
|
|
5443
|
-
const method = v.manifest.spec.install.method;
|
|
5444
|
-
if (PHASE_21.has(method)) {
|
|
5445
|
-
skipped.push({ name, reason: `deferred phase 2.1 (${method})` });
|
|
5446
|
-
continue;
|
|
5447
|
-
}
|
|
5448
5471
|
const r = await runInstall(v.manifest, opts);
|
|
5449
5472
|
if ("aborted" in r) skipped.push({ name, reason: `aborted: ${r.reason}` });
|
|
5450
5473
|
else if (r.ok && "alreadyInstalled" in r && r.alreadyInstalled) alreadyInstalled.push(name);
|
|
@@ -5453,7 +5476,7 @@ function registerInstallBase(program2) {
|
|
|
5453
5476
|
}
|
|
5454
5477
|
console.log(
|
|
5455
5478
|
`
|
|
5456
|
-
installed: ${installed.length} / already-installed: ${alreadyInstalled.length} / skipped (
|
|
5479
|
+
installed: ${installed.length} / already-installed: ${alreadyInstalled.length} / skipped (user-aborted): ${skipped.length} / failed: ${failed.length}`
|
|
5457
5480
|
);
|
|
5458
5481
|
for (const i of installed) console.log(` installed ${i}`);
|
|
5459
5482
|
for (const a of alreadyInstalled)
|
|
@@ -6043,12 +6066,6 @@ async function scanWorkflowsNested(workflowsDir, entries) {
|
|
|
6043
6066
|
}
|
|
6044
6067
|
|
|
6045
6068
|
// src/cli/lib/setup-helpers.ts
|
|
6046
|
-
var PHASE_212 = /* @__PURE__ */ new Set([
|
|
6047
|
-
"cc-plugin-marketplace",
|
|
6048
|
-
"git-clone-with-setup",
|
|
6049
|
-
"npx-skill-installer",
|
|
6050
|
-
"mcp-http-add"
|
|
6051
|
-
]);
|
|
6052
6069
|
async function warnIfAgentTeamsMissing() {
|
|
6053
6070
|
const r = await checkAgentTeams();
|
|
6054
6071
|
if (r.status !== "missing") return;
|
|
@@ -6064,14 +6081,15 @@ async function warnIfAgentTeamsMissing() {
|
|
|
6064
6081
|
async function scanWorkflowsWithSkill(workflowsDir, entries) {
|
|
6065
6082
|
return scanWorkflowsNested(workflowsDir, entries);
|
|
6066
6083
|
}
|
|
6067
|
-
async function runStepBInstall(manifestPaths) {
|
|
6084
|
+
async function runStepBInstall(manifestPaths, runOpts = {}) {
|
|
6068
6085
|
const opts = {
|
|
6069
6086
|
apply: true,
|
|
6070
6087
|
dryRun: false,
|
|
6071
6088
|
system: false,
|
|
6072
6089
|
nonInteractive: true,
|
|
6073
6090
|
fullDiff: false,
|
|
6074
|
-
color: "auto"
|
|
6091
|
+
color: "auto",
|
|
6092
|
+
updateInstalled: runOpts.updateInstalled === true
|
|
6075
6093
|
};
|
|
6076
6094
|
const start = Date.now();
|
|
6077
6095
|
const settled = await Promise.allSettled(
|
|
@@ -6091,8 +6109,6 @@ async function runStepBInstall(manifestPaths) {
|
|
|
6091
6109
|
};
|
|
6092
6110
|
}
|
|
6093
6111
|
const name = v.manifest.metadata.name;
|
|
6094
|
-
const method = v.manifest.spec.install.method;
|
|
6095
|
-
if (PHASE_212.has(method)) return { status: "skipped", name };
|
|
6096
6112
|
const r = await runInstall(v.manifest, opts);
|
|
6097
6113
|
if ("aborted" in r) return { status: "skipped", name };
|
|
6098
6114
|
if (r.ok && "alreadyInstalled" in r && r.alreadyInstalled)
|
|
@@ -6138,7 +6154,10 @@ function registerSetup(program2) {
|
|
|
6138
6154
|
).option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option(
|
|
6139
6155
|
"--user-lang <code>",
|
|
6140
6156
|
"override detected OS locale for env.HARNESSED_USER_LANG (en | zh-Hans / zh-CN / zh-TW)"
|
|
6141
|
-
).option("--non-interactive", "skip all confirm prompts (CI / scripted setup)").option("--no-auto-install", "do not prompt to auto-install missing plugins (advisory only)").
|
|
6157
|
+
).option("--non-interactive", "skip all confirm prompts (CI / scripted setup)").option("--no-auto-install", "do not prompt to auto-install missing plugins (advisory only)").option(
|
|
6158
|
+
"--update-installed",
|
|
6159
|
+
"force re-install already-installed plugins (excludes MCP servers); default: skip if installed"
|
|
6160
|
+
).action(async (raw) => {
|
|
6142
6161
|
const dryRun = raw.dryRun === true;
|
|
6143
6162
|
const pkgRoot = getPackageRoot();
|
|
6144
6163
|
const workflowsDir = resolve(pkgRoot, "workflows");
|
|
@@ -6266,8 +6285,20 @@ function registerSetup(program2) {
|
|
|
6266
6285
|
} else {
|
|
6267
6286
|
console.warn(t("setup.step_d.skipped", { message: dResult.message }));
|
|
6268
6287
|
}
|
|
6288
|
+
let updateInstalled2 = raw.updateInstalled === true;
|
|
6289
|
+
if (!updateInstalled2 && !dryRun && raw.nonInteractive !== true) {
|
|
6290
|
+
const isTty = process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
6291
|
+
if (isTty) {
|
|
6292
|
+
const { confirm: confirm4, isCancel: isCancel5 } = await import('@clack/prompts');
|
|
6293
|
+
const ans = await confirm4({
|
|
6294
|
+
message: "Update already-installed third-party plugins? (excludes MCP servers)",
|
|
6295
|
+
initialValue: false
|
|
6296
|
+
});
|
|
6297
|
+
if (!isCancel5(ans) && ans === true) updateInstalled2 = true;
|
|
6298
|
+
}
|
|
6299
|
+
}
|
|
6269
6300
|
const manifestPaths = await listBaseManifests2(pkgRoot);
|
|
6270
|
-
const b = await runStepBInstall(manifestPaths);
|
|
6301
|
+
const b = await runStepBInstall(manifestPaths, { updateInstalled: updateInstalled2 });
|
|
6271
6302
|
const stepBMs = (b.elapsedMs / 1e3).toFixed(1);
|
|
6272
6303
|
console.log(
|
|
6273
6304
|
t("setup.step_b_complete", {
|