harnessed 3.0.1 → 3.0.2
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/README.md +4 -0
- package/dist/cli.mjs +94 -125
- 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/workflows/capabilities.yaml +1 -1
- package/workflows/defaults.yaml +1 -1
package/README.md
CHANGED
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
npm install -g harnessed && harnessed setup
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
> Windows PowerShell 5.x 不支持 `&&` 链接,需改 `;` 或分两行 (`npm install -g harnessed; harnessed setup`)。bash / zsh / PowerShell 7+ / cmd.exe 都正常。
|
|
38
|
+
|
|
39
|
+
🤖 **或让 AI 帮你装** — 复制 [INSTALL-WITH-AI.md](./INSTALL-WITH-AI.md) 整段粘贴进 Claude Code (或任何 AI 助手),AI 自动处理 OS / 权限 / PATH / corepack 等 edge case。
|
|
40
|
+
|
|
37
41
|
---
|
|
38
42
|
|
|
39
43
|
## 📐 4-stage 流程图
|
package/dist/cli.mjs
CHANGED
|
@@ -793,7 +793,7 @@ var init_resume = __esm({
|
|
|
793
793
|
|
|
794
794
|
// package.json
|
|
795
795
|
var package_default = {
|
|
796
|
-
version: "3.0.
|
|
796
|
+
version: "3.0.2"};
|
|
797
797
|
|
|
798
798
|
// src/manifest/errors.ts
|
|
799
799
|
function instancePathToKeyPath(instancePath) {
|
|
@@ -3404,6 +3404,9 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
|
3404
3404
|
});
|
|
3405
3405
|
});
|
|
3406
3406
|
}
|
|
3407
|
+
function getMcpSpawnCwd() {
|
|
3408
|
+
return homedir();
|
|
3409
|
+
}
|
|
3407
3410
|
|
|
3408
3411
|
// src/installers/ccPluginMarketplace.ts
|
|
3409
3412
|
function parseCmd(cmd) {
|
|
@@ -3455,8 +3458,8 @@ var installCcPluginMarketplace = async (ctx) => {
|
|
|
3455
3458
|
}
|
|
3456
3459
|
};
|
|
3457
3460
|
}
|
|
3458
|
-
const pluginName = parsed.pluginAtMkt.split("@")[0];
|
|
3459
|
-
const installArgs = ["plugin", "install", parsed.pluginAtMkt, "--scope", "
|
|
3461
|
+
const pluginName = parsed.pluginAtMkt.split("@")[0] ?? parsed.pluginAtMkt;
|
|
3462
|
+
const installArgs = ["plugin", "install", parsed.pluginAtMkt, "--scope", "user"];
|
|
3460
3463
|
const allArgs = [];
|
|
3461
3464
|
if (parsed.marketplaceRef !== null) {
|
|
3462
3465
|
allArgs.push(["plugin", "marketplace", "add", parsed.marketplaceRef]);
|
|
@@ -3464,30 +3467,30 @@ var installCcPluginMarketplace = async (ctx) => {
|
|
|
3464
3467
|
allArgs.push(installArgs);
|
|
3465
3468
|
for (const argSet of allArgs) {
|
|
3466
3469
|
for (const a of argSet) {
|
|
3467
|
-
const
|
|
3468
|
-
if (
|
|
3470
|
+
const violation = checkCmdString(a);
|
|
3471
|
+
if (violation) {
|
|
3469
3472
|
return {
|
|
3470
3473
|
ok: false,
|
|
3471
3474
|
phase: "preflight",
|
|
3472
3475
|
error: err(
|
|
3473
3476
|
ctx,
|
|
3474
3477
|
"/spec/install/cmd",
|
|
3475
|
-
`shell escape detected in constructed cc-plugin arg '${a.slice(0, 60)}': ${
|
|
3478
|
+
`shell escape detected in constructed cc-plugin arg '${a.slice(0, 60)}': ${violation.label} (${violation.hint})`,
|
|
3476
3479
|
"security-gate-bypass"
|
|
3477
3480
|
)
|
|
3478
3481
|
};
|
|
3479
3482
|
}
|
|
3480
3483
|
}
|
|
3481
3484
|
}
|
|
3482
|
-
const settingsFile = `${
|
|
3485
|
+
const settingsFile = `${getMcpSpawnCwd()}/.claude.json`;
|
|
3483
3486
|
const newEntry = JSON.stringify({ enabledPlugins: { [parsed.pluginAtMkt]: true } }, null, 2);
|
|
3484
3487
|
const plan = {
|
|
3485
3488
|
files: [
|
|
3486
3489
|
{
|
|
3487
3490
|
target: settingsFile,
|
|
3488
|
-
scope: "
|
|
3491
|
+
scope: "HOME",
|
|
3489
3492
|
oldText: "",
|
|
3490
|
-
newText: `// will be merged into
|
|
3493
|
+
newText: `// will be merged into ~/.claude.json enabledPlugins map by \`claude plugin install --scope user\`:
|
|
3491
3494
|
${newEntry}
|
|
3492
3495
|
`
|
|
3493
3496
|
}
|
|
@@ -3502,15 +3505,13 @@ ${newEntry}
|
|
|
3502
3505
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
3503
3506
|
const bk = await backup(plan, ctx);
|
|
3504
3507
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
3508
|
+
const spawnCwd = install.cwd ?? getMcpSpawnCwd();
|
|
3505
3509
|
let stepOneStderr = "";
|
|
3506
3510
|
if (parsed.marketplaceRef !== null) {
|
|
3507
|
-
const r1 = await runArgs(
|
|
3508
|
-
["plugin", "marketplace", "add", parsed.marketplaceRef],
|
|
3509
|
-
install.cwd ?? ctx.cwd
|
|
3510
|
-
);
|
|
3511
|
+
const r1 = await runArgs(["plugin", "marketplace", "add", parsed.marketplaceRef], spawnCwd);
|
|
3511
3512
|
stepOneStderr = r1.stderr;
|
|
3512
3513
|
}
|
|
3513
|
-
const r2 = await runArgs(installArgs,
|
|
3514
|
+
const r2 = await runArgs(installArgs, spawnCwd);
|
|
3514
3515
|
if (r2.exitCode !== 0) {
|
|
3515
3516
|
return {
|
|
3516
3517
|
ok: false,
|
|
@@ -3524,43 +3525,34 @@ ${newEntry}
|
|
|
3524
3525
|
)
|
|
3525
3526
|
};
|
|
3526
3527
|
}
|
|
3527
|
-
const verifyShell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
|
|
3528
|
-
const verifyFlag = process.platform === "win32" ? "/c" : "-c";
|
|
3529
|
-
const verifyLine = `claude plugin list --json | grep -q ${pluginName}`;
|
|
3530
|
-
const violation = checkCmdString(verifyLine);
|
|
3531
|
-
if (violation) {
|
|
3532
|
-
return {
|
|
3533
|
-
ok: false,
|
|
3534
|
-
phase: "verify",
|
|
3535
|
-
backupId: bk.backupId,
|
|
3536
|
-
error: err(
|
|
3537
|
-
ctx,
|
|
3538
|
-
"/spec/verify/cmd",
|
|
3539
|
-
`verify shell escape: ${violation.label}`,
|
|
3540
|
-
"security-gate-bypass"
|
|
3541
|
-
)
|
|
3542
|
-
};
|
|
3543
|
-
}
|
|
3544
3528
|
const vr = await new Promise((resolve9) => {
|
|
3545
|
-
const child = spawn(
|
|
3529
|
+
const child = spawn("claude", ["plugin", "list", "--json"], {
|
|
3530
|
+
cwd: spawnCwd,
|
|
3531
|
+
shell: process.platform === "win32",
|
|
3532
|
+
windowsHide: true
|
|
3533
|
+
});
|
|
3534
|
+
let stdout2 = "";
|
|
3546
3535
|
let stderr = "";
|
|
3536
|
+
child.stdout?.setEncoding("utf8").on("data", (c) => {
|
|
3537
|
+
stdout2 += c;
|
|
3538
|
+
});
|
|
3547
3539
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
3548
3540
|
stderr += c;
|
|
3549
3541
|
});
|
|
3550
3542
|
const timer = setTimeout(() => {
|
|
3551
3543
|
child.kill("SIGKILL");
|
|
3552
|
-
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]
|
|
3544
|
+
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]`, stdout: stdout2 });
|
|
3553
3545
|
}, 15e3);
|
|
3554
3546
|
child.on("error", (e) => {
|
|
3555
3547
|
clearTimeout(timer);
|
|
3556
|
-
resolve9({ exitCode: -1, stderr: e.message });
|
|
3548
|
+
resolve9({ exitCode: -1, stderr: e.message, stdout: stdout2 });
|
|
3557
3549
|
});
|
|
3558
3550
|
child.on("close", (code) => {
|
|
3559
3551
|
clearTimeout(timer);
|
|
3560
|
-
resolve9({ exitCode: code ?? -1, stderr });
|
|
3552
|
+
resolve9({ exitCode: code ?? -1, stderr, stdout: stdout2 });
|
|
3561
3553
|
});
|
|
3562
3554
|
});
|
|
3563
|
-
if (vr.exitCode !== 0) {
|
|
3555
|
+
if (vr.exitCode !== 0 || !vr.stdout.includes(pluginName)) {
|
|
3564
3556
|
return {
|
|
3565
3557
|
ok: false,
|
|
3566
3558
|
phase: "verify",
|
|
@@ -3568,7 +3560,7 @@ ${newEntry}
|
|
|
3568
3560
|
error: err(
|
|
3569
3561
|
ctx,
|
|
3570
3562
|
"/spec/verify/cmd",
|
|
3571
|
-
`verify exit ${vr.exitCode}: ${vr.stderr.slice(0, 200)}`,
|
|
3563
|
+
`verify exit ${vr.exitCode} or '${pluginName}' not in plugin list stdout: ${vr.stderr.slice(0, 200)}`,
|
|
3572
3564
|
"verify-failed"
|
|
3573
3565
|
)
|
|
3574
3566
|
};
|
|
@@ -3576,8 +3568,9 @@ ${newEntry}
|
|
|
3576
3568
|
await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, install.git_ref, "");
|
|
3577
3569
|
return { ok: true, backupId: bk.backupId, appliedFiles: [settingsFile] };
|
|
3578
3570
|
};
|
|
3579
|
-
var
|
|
3580
|
-
|
|
3571
|
+
var DEFAULT_VERIFY_TIMEOUT_MS = 15e3;
|
|
3572
|
+
var DEFAULT_INSTALL_TIMEOUT_MS = 6e4;
|
|
3573
|
+
async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
3581
3574
|
const violation = checkCmdString(cmd);
|
|
3582
3575
|
if (violation) {
|
|
3583
3576
|
return {
|
|
@@ -3594,8 +3587,7 @@ async function spawnCmd(ctx, cmd, args) {
|
|
|
3594
3587
|
};
|
|
3595
3588
|
}
|
|
3596
3589
|
const installCfg = ctx.manifest.spec.install;
|
|
3597
|
-
const
|
|
3598
|
-
const timeoutMs = verifyCfg.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
3590
|
+
const effectiveTimeoutMs = timeoutMs ?? DEFAULT_INSTALL_TIMEOUT_MS;
|
|
3599
3591
|
const env = { ...process.env, ...installCfg.env ?? {} };
|
|
3600
3592
|
const cwd = installCfg.cwd ?? ctx.cwd;
|
|
3601
3593
|
let child;
|
|
@@ -3622,13 +3614,13 @@ async function spawnCmd(ctx, cmd, args) {
|
|
|
3622
3614
|
error: {
|
|
3623
3615
|
file: ctx.manifest.metadata.name,
|
|
3624
3616
|
path: "/spec/install/cmd",
|
|
3625
|
-
message: `spawn timed out after ${
|
|
3617
|
+
message: `spawn timed out after ${effectiveTimeoutMs}ms (cmd: ${cmd}); partial stderr: ${stderr.slice(0, 200)}`,
|
|
3626
3618
|
line: null,
|
|
3627
3619
|
column: null,
|
|
3628
3620
|
keyword: "spawn-timeout"
|
|
3629
3621
|
}
|
|
3630
3622
|
});
|
|
3631
|
-
},
|
|
3623
|
+
}, effectiveTimeoutMs);
|
|
3632
3624
|
child.on("error", (err2) => {
|
|
3633
3625
|
clearTimeout(timer);
|
|
3634
3626
|
resolve9({
|
|
@@ -3774,7 +3766,7 @@ var installGitCloneWithSetup = async (ctx) => {
|
|
|
3774
3766
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
3775
3767
|
const bk = await backup(plan, ctx);
|
|
3776
3768
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
3777
|
-
const sp = await spawnCmd(ctx, install.cmd, []);
|
|
3769
|
+
const sp = await spawnCmd(ctx, install.cmd, [], DEFAULT_INSTALL_TIMEOUT_MS);
|
|
3778
3770
|
if (!("exitCode" in sp)) return { ...sp, backupId: bk.backupId };
|
|
3779
3771
|
if (sp.exitCode !== 0) {
|
|
3780
3772
|
return {
|
|
@@ -3816,7 +3808,8 @@ var installGitCloneWithSetup = async (ctx) => {
|
|
|
3816
3808
|
)
|
|
3817
3809
|
};
|
|
3818
3810
|
}
|
|
3819
|
-
const
|
|
3811
|
+
const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
|
|
3812
|
+
const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
|
|
3820
3813
|
if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
|
|
3821
3814
|
const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
|
|
3822
3815
|
if (vr.exitCode !== expected) {
|
|
@@ -3919,33 +3912,23 @@ var installMcpHttpAdd = async (ctx) => {
|
|
|
3919
3912
|
}
|
|
3920
3913
|
};
|
|
3921
3914
|
}
|
|
3922
|
-
const addArgs = [
|
|
3923
|
-
"mcp",
|
|
3924
|
-
"add",
|
|
3925
|
-
"--scope",
|
|
3926
|
-
"project",
|
|
3927
|
-
"--transport",
|
|
3928
|
-
"http",
|
|
3929
|
-
...hdr.flat,
|
|
3930
|
-
name,
|
|
3931
|
-
url
|
|
3932
|
-
];
|
|
3915
|
+
const addArgs = ["mcp", "add", "--scope", "user", "--transport", "http", ...hdr.flat, name, url];
|
|
3933
3916
|
for (const a of addArgs) {
|
|
3934
|
-
const
|
|
3935
|
-
if (
|
|
3917
|
+
const violation = checkCmdString(a);
|
|
3918
|
+
if (violation) {
|
|
3936
3919
|
return {
|
|
3937
3920
|
ok: false,
|
|
3938
3921
|
phase: "preflight",
|
|
3939
3922
|
error: err(
|
|
3940
3923
|
ctx,
|
|
3941
3924
|
"/spec/install/cmd",
|
|
3942
|
-
`shell escape detected in constructed mcp-http-add arg '${a.slice(0, 60)}': ${
|
|
3925
|
+
`shell escape detected in constructed mcp-http-add arg '${a.slice(0, 60)}': ${violation.label} (${violation.hint})`,
|
|
3943
3926
|
"security-gate-bypass"
|
|
3944
3927
|
)
|
|
3945
3928
|
};
|
|
3946
3929
|
}
|
|
3947
3930
|
}
|
|
3948
|
-
const mcpFile = `${
|
|
3931
|
+
const mcpFile = `${getMcpSpawnCwd()}/.claude.json`;
|
|
3949
3932
|
const headersObj = {};
|
|
3950
3933
|
for (let i = 0; i < hdr.flat.length; i += 2) {
|
|
3951
3934
|
const kv = hdr.flat[i + 1];
|
|
@@ -3959,9 +3942,9 @@ var installMcpHttpAdd = async (ctx) => {
|
|
|
3959
3942
|
files: [
|
|
3960
3943
|
{
|
|
3961
3944
|
target: mcpFile,
|
|
3962
|
-
scope: "
|
|
3945
|
+
scope: "HOME",
|
|
3963
3946
|
oldText: "",
|
|
3964
|
-
newText: `// will be merged into .
|
|
3947
|
+
newText: `// will be merged into ~/.claude.json mcpServers map by \`claude mcp add --scope user\`:
|
|
3965
3948
|
${newEntry}
|
|
3966
3949
|
`
|
|
3967
3950
|
}
|
|
@@ -3976,9 +3959,10 @@ ${newEntry}
|
|
|
3976
3959
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
3977
3960
|
const bk = await backup(plan, ctx);
|
|
3978
3961
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
3979
|
-
const
|
|
3962
|
+
const spawnCwd = install.cwd ?? getMcpSpawnCwd();
|
|
3963
|
+
const r = await runArgs(addArgs, spawnCwd);
|
|
3980
3964
|
if (r.exitCode !== 0) {
|
|
3981
|
-
if (r.stderr.includes("already exists
|
|
3965
|
+
if (r.stderr.includes("already exists")) {
|
|
3982
3966
|
return { ok: true, alreadyInstalled: true, backupId: bk.backupId };
|
|
3983
3967
|
}
|
|
3984
3968
|
return {
|
|
@@ -3993,43 +3977,34 @@ ${newEntry}
|
|
|
3993
3977
|
)
|
|
3994
3978
|
};
|
|
3995
3979
|
}
|
|
3996
|
-
const verifyShell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
|
|
3997
|
-
const verifyFlag = process.platform === "win32" ? "/c" : "-c";
|
|
3998
|
-
const verifyLine = `claude mcp list | grep -q ${name}`;
|
|
3999
|
-
const violation = checkCmdString(verifyLine);
|
|
4000
|
-
if (violation) {
|
|
4001
|
-
return {
|
|
4002
|
-
ok: false,
|
|
4003
|
-
phase: "verify",
|
|
4004
|
-
backupId: bk.backupId,
|
|
4005
|
-
error: err(
|
|
4006
|
-
ctx,
|
|
4007
|
-
"/spec/verify/cmd",
|
|
4008
|
-
`verify shell escape: ${violation.label}`,
|
|
4009
|
-
"security-gate-bypass"
|
|
4010
|
-
)
|
|
4011
|
-
};
|
|
4012
|
-
}
|
|
4013
3980
|
const vr = await new Promise((resolve9) => {
|
|
4014
|
-
const child = spawn(
|
|
3981
|
+
const child = spawn("claude", ["mcp", "list"], {
|
|
3982
|
+
cwd: spawnCwd,
|
|
3983
|
+
shell: process.platform === "win32",
|
|
3984
|
+
windowsHide: true
|
|
3985
|
+
});
|
|
3986
|
+
let stdout2 = "";
|
|
4015
3987
|
let stderr = "";
|
|
3988
|
+
child.stdout?.setEncoding("utf8").on("data", (c) => {
|
|
3989
|
+
stdout2 += c;
|
|
3990
|
+
});
|
|
4016
3991
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
4017
3992
|
stderr += c;
|
|
4018
3993
|
});
|
|
4019
3994
|
const timer = setTimeout(() => {
|
|
4020
3995
|
child.kill("SIGKILL");
|
|
4021
|
-
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]
|
|
3996
|
+
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]`, stdout: stdout2 });
|
|
4022
3997
|
}, 15e3);
|
|
4023
3998
|
child.on("error", (e) => {
|
|
4024
3999
|
clearTimeout(timer);
|
|
4025
|
-
resolve9({ exitCode: -1, stderr: e.message });
|
|
4000
|
+
resolve9({ exitCode: -1, stderr: e.message, stdout: stdout2 });
|
|
4026
4001
|
});
|
|
4027
4002
|
child.on("close", (code) => {
|
|
4028
4003
|
clearTimeout(timer);
|
|
4029
|
-
resolve9({ exitCode: code ?? -1, stderr });
|
|
4004
|
+
resolve9({ exitCode: code ?? -1, stderr, stdout: stdout2 });
|
|
4030
4005
|
});
|
|
4031
4006
|
});
|
|
4032
|
-
if (vr.exitCode !== 0) {
|
|
4007
|
+
if (vr.exitCode !== 0 || !vr.stdout.includes(name)) {
|
|
4033
4008
|
return {
|
|
4034
4009
|
ok: false,
|
|
4035
4010
|
phase: "verify",
|
|
@@ -4037,7 +4012,7 @@ ${newEntry}
|
|
|
4037
4012
|
error: err(
|
|
4038
4013
|
ctx,
|
|
4039
4014
|
"/spec/verify/cmd",
|
|
4040
|
-
`verify exit ${vr.exitCode}: ${vr.stderr.slice(0, 200)}`,
|
|
4015
|
+
`verify exit ${vr.exitCode} or '${name}' not in mcp list stdout: ${vr.stderr.slice(0, 200)}`,
|
|
4041
4016
|
"verify-failed"
|
|
4042
4017
|
)
|
|
4043
4018
|
};
|
|
@@ -4073,7 +4048,7 @@ var installMcpStdioAdd = async (ctx) => {
|
|
|
4073
4048
|
"mcp",
|
|
4074
4049
|
"add",
|
|
4075
4050
|
"--scope",
|
|
4076
|
-
"
|
|
4051
|
+
"user",
|
|
4077
4052
|
"--transport",
|
|
4078
4053
|
"stdio",
|
|
4079
4054
|
name,
|
|
@@ -4083,21 +4058,21 @@ var installMcpStdioAdd = async (ctx) => {
|
|
|
4083
4058
|
`${pkg}@${ver}`
|
|
4084
4059
|
];
|
|
4085
4060
|
for (const a of addArgs) {
|
|
4086
|
-
const
|
|
4087
|
-
if (
|
|
4061
|
+
const violation = checkCmdString(a);
|
|
4062
|
+
if (violation) {
|
|
4088
4063
|
return {
|
|
4089
4064
|
ok: false,
|
|
4090
4065
|
phase: "preflight",
|
|
4091
4066
|
error: err(
|
|
4092
4067
|
ctx,
|
|
4093
4068
|
"/spec/install/cmd",
|
|
4094
|
-
`shell escape detected in constructed mcp-add arg '${a.slice(0, 60)}': ${
|
|
4069
|
+
`shell escape detected in constructed mcp-add arg '${a.slice(0, 60)}': ${violation.label} (${violation.hint})`,
|
|
4095
4070
|
"security-gate-bypass"
|
|
4096
4071
|
)
|
|
4097
4072
|
};
|
|
4098
4073
|
}
|
|
4099
4074
|
}
|
|
4100
|
-
const mcpFile = `${
|
|
4075
|
+
const mcpFile = `${getMcpSpawnCwd()}/.claude.json`;
|
|
4101
4076
|
const newEntry = JSON.stringify(
|
|
4102
4077
|
{ [name]: { type: "stdio", command: "npx", args: ["--yes", `${pkg}@${ver}`] } },
|
|
4103
4078
|
null,
|
|
@@ -4107,9 +4082,9 @@ var installMcpStdioAdd = async (ctx) => {
|
|
|
4107
4082
|
files: [
|
|
4108
4083
|
{
|
|
4109
4084
|
target: mcpFile,
|
|
4110
|
-
scope: "
|
|
4085
|
+
scope: "HOME",
|
|
4111
4086
|
oldText: "",
|
|
4112
|
-
newText: `// will be merged into .
|
|
4087
|
+
newText: `// will be merged into ~/.claude.json mcpServers map by \`claude mcp add --scope user\`:
|
|
4113
4088
|
${newEntry}
|
|
4114
4089
|
`
|
|
4115
4090
|
}
|
|
@@ -4124,9 +4099,10 @@ ${newEntry}
|
|
|
4124
4099
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
4125
4100
|
const bk = await backup(plan, ctx);
|
|
4126
4101
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
4127
|
-
const
|
|
4102
|
+
const spawnCwd = install.cwd ?? getMcpSpawnCwd();
|
|
4103
|
+
const r = await runArgs(addArgs, spawnCwd);
|
|
4128
4104
|
if (r.exitCode !== 0) {
|
|
4129
|
-
if (r.stderr.includes("already exists
|
|
4105
|
+
if (r.stderr.includes("already exists")) {
|
|
4130
4106
|
return { ok: true, alreadyInstalled: true, backupId: bk.backupId };
|
|
4131
4107
|
}
|
|
4132
4108
|
return {
|
|
@@ -4141,43 +4117,34 @@ ${newEntry}
|
|
|
4141
4117
|
)
|
|
4142
4118
|
};
|
|
4143
4119
|
}
|
|
4144
|
-
const verifyShell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
|
|
4145
|
-
const verifyFlag = process.platform === "win32" ? "/c" : "-c";
|
|
4146
|
-
const verifyLine = `claude mcp list | grep -q ${name}`;
|
|
4147
|
-
const violation = checkCmdString(verifyLine);
|
|
4148
|
-
if (violation) {
|
|
4149
|
-
return {
|
|
4150
|
-
ok: false,
|
|
4151
|
-
phase: "verify",
|
|
4152
|
-
backupId: bk.backupId,
|
|
4153
|
-
error: err(
|
|
4154
|
-
ctx,
|
|
4155
|
-
"/spec/verify/cmd",
|
|
4156
|
-
`verify shell escape: ${violation.label}`,
|
|
4157
|
-
"security-gate-bypass"
|
|
4158
|
-
)
|
|
4159
|
-
};
|
|
4160
|
-
}
|
|
4161
4120
|
const vr = await new Promise((resolve9) => {
|
|
4162
|
-
const child = spawn(
|
|
4121
|
+
const child = spawn("claude", ["mcp", "list"], {
|
|
4122
|
+
cwd: spawnCwd,
|
|
4123
|
+
shell: process.platform === "win32",
|
|
4124
|
+
windowsHide: true
|
|
4125
|
+
});
|
|
4126
|
+
let stdout2 = "";
|
|
4163
4127
|
let stderr = "";
|
|
4128
|
+
child.stdout?.setEncoding("utf8").on("data", (c) => {
|
|
4129
|
+
stdout2 += c;
|
|
4130
|
+
});
|
|
4164
4131
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
4165
4132
|
stderr += c;
|
|
4166
4133
|
});
|
|
4167
4134
|
const timer = setTimeout(() => {
|
|
4168
4135
|
child.kill("SIGKILL");
|
|
4169
|
-
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]
|
|
4136
|
+
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]`, stdout: stdout2 });
|
|
4170
4137
|
}, 15e3);
|
|
4171
4138
|
child.on("error", (e) => {
|
|
4172
4139
|
clearTimeout(timer);
|
|
4173
|
-
resolve9({ exitCode: -1, stderr: e.message });
|
|
4140
|
+
resolve9({ exitCode: -1, stderr: e.message, stdout: stdout2 });
|
|
4174
4141
|
});
|
|
4175
4142
|
child.on("close", (code) => {
|
|
4176
4143
|
clearTimeout(timer);
|
|
4177
|
-
resolve9({ exitCode: code ?? -1, stderr });
|
|
4144
|
+
resolve9({ exitCode: code ?? -1, stderr, stdout: stdout2 });
|
|
4178
4145
|
});
|
|
4179
4146
|
});
|
|
4180
|
-
if (vr.exitCode !== 0) {
|
|
4147
|
+
if (vr.exitCode !== 0 || !vr.stdout.includes(name)) {
|
|
4181
4148
|
return {
|
|
4182
4149
|
ok: false,
|
|
4183
4150
|
phase: "verify",
|
|
@@ -4185,7 +4152,7 @@ ${newEntry}
|
|
|
4185
4152
|
error: err(
|
|
4186
4153
|
ctx,
|
|
4187
4154
|
"/spec/verify/cmd",
|
|
4188
|
-
`verify exit ${vr.exitCode}: ${vr.stderr.slice(0, 200)}`,
|
|
4155
|
+
`verify exit ${vr.exitCode} or '${name}' not in mcp list stdout: ${vr.stderr.slice(0, 200)}`,
|
|
4189
4156
|
"verify-failed"
|
|
4190
4157
|
)
|
|
4191
4158
|
};
|
|
@@ -4251,7 +4218,7 @@ var installNpmCli = async (ctx) => {
|
|
|
4251
4218
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
4252
4219
|
const bk = await backup(plan, ctx);
|
|
4253
4220
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
4254
|
-
const sp = await spawnCmd(ctx, cmd, []);
|
|
4221
|
+
const sp = await spawnCmd(ctx, cmd, [], DEFAULT_INSTALL_TIMEOUT_MS);
|
|
4255
4222
|
if (!("exitCode" in sp)) return { ...sp, backupId: bk.backupId };
|
|
4256
4223
|
if (sp.exitCode !== 0) {
|
|
4257
4224
|
return {
|
|
@@ -4266,7 +4233,8 @@ var installNpmCli = async (ctx) => {
|
|
|
4266
4233
|
)
|
|
4267
4234
|
};
|
|
4268
4235
|
}
|
|
4269
|
-
const
|
|
4236
|
+
const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
|
|
4237
|
+
const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
|
|
4270
4238
|
if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
|
|
4271
4239
|
const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
|
|
4272
4240
|
if (vr.exitCode !== expected) {
|
|
@@ -4367,7 +4335,7 @@ var installNpxSkillInstaller = async (ctx) => {
|
|
|
4367
4335
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
4368
4336
|
const bk = await backup(plan, ctx);
|
|
4369
4337
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
4370
|
-
const sp = await spawnCmd(ctx, install.cmd, []);
|
|
4338
|
+
const sp = await spawnCmd(ctx, install.cmd, [], DEFAULT_INSTALL_TIMEOUT_MS);
|
|
4371
4339
|
if (!("exitCode" in sp)) return { ...sp, backupId: bk.backupId };
|
|
4372
4340
|
if (sp.exitCode !== 0) {
|
|
4373
4341
|
return {
|
|
@@ -4400,7 +4368,8 @@ var installNpxSkillInstaller = async (ctx) => {
|
|
|
4400
4368
|
}
|
|
4401
4369
|
};
|
|
4402
4370
|
}
|
|
4403
|
-
const
|
|
4371
|
+
const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
|
|
4372
|
+
const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
|
|
4404
4373
|
if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
|
|
4405
4374
|
const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
|
|
4406
4375
|
if (vr.exitCode !== expected) {
|
|
@@ -4896,7 +4865,7 @@ async function warnIfAgentTeamsMissing() {
|
|
|
4896
4865
|
console.warn("\n\u26A0\uFE0F Agent Teams \u672A\u542F\u7528 \u2014 parallelism-gate \u5347\u7EA7\u8DEF\u5F84\u4E0D\u53EF\u7528");
|
|
4897
4866
|
console.warn(" \u4FEE\u590D: claude config set env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS 1");
|
|
4898
4867
|
console.warn(
|
|
4899
|
-
" \u8BF4\u660E: harnessed
|
|
4868
|
+
" \u8BF4\u660E: harnessed v3.0 \u4E09\u5C42\u6808\u65B9\u6CD5\u8BBA parallelism-gate \u5347\u7EA7\u8DEF\u5F84\u9700 CC 2.1.133+ Agent Teams enable"
|
|
4900
4869
|
);
|
|
4901
4870
|
console.warn(
|
|
4902
4871
|
" \u4E0D\u963B\u585E setup,\u540E\u7EED parallelism-gate workflow phase \u89E6\u53D1\u65F6\u81EA\u52A8\u964D\u7EA7 subagent fan-out\n"
|
|
@@ -5052,7 +5021,7 @@ MCP servers configured. Run \`/mcp\` in Claude Code to verify each server's conn
|
|
|
5052
5021
|
);
|
|
5053
5022
|
}
|
|
5054
5023
|
console.log(
|
|
5055
|
-
"\n\u2713 harnessed
|
|
5024
|
+
"\n\u2713 harnessed v3.0 \u4E09\u5C42\u6808\u65B9\u6CD5\u8BBA bundled \u2014 23 workflows (4 master + 18 sub + 1 standalone) + 6 disciplines + 10 judgments + ~83 capabilities ready"
|
|
5056
5025
|
);
|
|
5057
5026
|
console.log(
|
|
5058
5027
|
" workflows in <packageRoot>/workflows/ (Pure bundled, NOT user-dir override per D-01)"
|