harnessed 3.9.7 → 3.9.9
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
CHANGED
|
@@ -789,11 +789,33 @@ async function isMcpServerRegistered(name) {
|
|
|
789
789
|
return Object.hasOwn(servers, name);
|
|
790
790
|
}
|
|
791
791
|
async function isPluginRegistered(pluginName) {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
792
|
+
try {
|
|
793
|
+
const path = join(homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
794
|
+
const raw = await readFile(path, "utf8");
|
|
795
|
+
const parsed = JSON.parse(raw);
|
|
796
|
+
const plugins = parsed.plugins;
|
|
797
|
+
if (plugins && typeof plugins === "object") {
|
|
798
|
+
if (Object.hasOwn(plugins, pluginName)) return true;
|
|
799
|
+
if (Object.keys(plugins).some((k) => k.split("@")[0] === pluginName)) return true;
|
|
800
|
+
}
|
|
801
|
+
} catch {
|
|
802
|
+
}
|
|
803
|
+
for (const path of [
|
|
804
|
+
join(homedir(), ".claude", "settings.json"),
|
|
805
|
+
join(homedir(), ".claude.json")
|
|
806
|
+
]) {
|
|
807
|
+
try {
|
|
808
|
+
const raw = await readFile(path, "utf8");
|
|
809
|
+
const parsed = JSON.parse(raw);
|
|
810
|
+
const plugins = parsed.enabledPlugins;
|
|
811
|
+
if (plugins && typeof plugins === "object") {
|
|
812
|
+
if (Object.hasOwn(plugins, pluginName)) return true;
|
|
813
|
+
if (Object.keys(plugins).some((k) => k.split("@")[0] === pluginName)) return true;
|
|
814
|
+
}
|
|
815
|
+
} catch {
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return false;
|
|
797
819
|
}
|
|
798
820
|
var init_readClaudeConfig = __esm({
|
|
799
821
|
"src/installers/lib/readClaudeConfig.ts"() {
|
|
@@ -1200,7 +1222,7 @@ var init_auto_install = __esm({
|
|
|
1200
1222
|
|
|
1201
1223
|
// package.json
|
|
1202
1224
|
var package_default = {
|
|
1203
|
-
version: "3.9.
|
|
1225
|
+
version: "3.9.9"};
|
|
1204
1226
|
|
|
1205
1227
|
// src/manifest/errors.ts
|
|
1206
1228
|
function instancePathToKeyPath(instancePath) {
|
|
@@ -4357,6 +4379,13 @@ var installCcHookAdd = async (ctx) => {
|
|
|
4357
4379
|
await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, "", "");
|
|
4358
4380
|
return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath3] };
|
|
4359
4381
|
};
|
|
4382
|
+
|
|
4383
|
+
// src/installers/lib/idempotent.ts
|
|
4384
|
+
init_readClaudeConfig();
|
|
4385
|
+
function expandTildeForWindows(cmd) {
|
|
4386
|
+
const home = homedir().replace(/\\/g, "/");
|
|
4387
|
+
return cmd.replace(/(^|[\s"'`(])~\//g, `$1${home}/`);
|
|
4388
|
+
}
|
|
4360
4389
|
var DEFAULT_VERIFY_TIMEOUT_MS = 15e3;
|
|
4361
4390
|
var DEFAULT_INSTALL_TIMEOUT_MS = 6e4;
|
|
4362
4391
|
async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
@@ -4381,7 +4410,13 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
|
4381
4410
|
const cwd = installCfg.cwd ?? ctx.cwd;
|
|
4382
4411
|
let child;
|
|
4383
4412
|
if (process.platform === "win32") {
|
|
4384
|
-
|
|
4413
|
+
const expandedCmd = expandTildeForWindows(cmd);
|
|
4414
|
+
const expandedArgs = args.map(expandTildeForWindows);
|
|
4415
|
+
child = spawn("cmd.exe", ["/c", expandedCmd, ...expandedArgs], {
|
|
4416
|
+
cwd,
|
|
4417
|
+
env,
|
|
4418
|
+
windowsHide: true
|
|
4419
|
+
});
|
|
4385
4420
|
} else {
|
|
4386
4421
|
const joined = args.length > 0 ? `${cmd} ${args.join(" ")}` : cmd;
|
|
4387
4422
|
child = spawn("/bin/sh", ["-c", joined], { cwd, env });
|
|
@@ -4434,16 +4469,83 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
|
4434
4469
|
|
|
4435
4470
|
// src/installers/lib/idempotent.ts
|
|
4436
4471
|
var IDEMPOTENT_CHECK_TIMEOUT_MS = 1e4;
|
|
4437
|
-
|
|
4438
|
-
|
|
4472
|
+
function extractSkillName(cmd, fallback) {
|
|
4473
|
+
const m = cmd.match(/\bskills(?:@\S+)?\s+add\s+(\S+)/i);
|
|
4474
|
+
if (!m?.[1]) return fallback;
|
|
4475
|
+
const seg = m[1].split("/");
|
|
4476
|
+
return seg[seg.length - 1] ?? fallback;
|
|
4477
|
+
}
|
|
4478
|
+
function extractGitCloneTarget(cmd) {
|
|
4479
|
+
const idx = cmd.indexOf("git clone");
|
|
4480
|
+
if (idx < 0) return null;
|
|
4481
|
+
const tail = cmd.slice(idx + "git clone".length).trim();
|
|
4482
|
+
const tokens = tail.split(/\s+/);
|
|
4483
|
+
let i = 0;
|
|
4484
|
+
while (i < tokens.length && tokens[i]?.startsWith("-")) {
|
|
4485
|
+
i += tokens[i]?.includes("=") ? 1 : 2;
|
|
4486
|
+
}
|
|
4487
|
+
const dest = tokens[i + 1];
|
|
4488
|
+
if (!dest || dest === "&&" || dest === ";" || dest === "|") return null;
|
|
4489
|
+
if (dest.startsWith("~/")) return join(homedir(), dest.slice(2));
|
|
4490
|
+
if (dest.startsWith("/") || /^[A-Z]:[\\/]/i.test(dest)) return dest;
|
|
4491
|
+
return null;
|
|
4492
|
+
}
|
|
4493
|
+
async function detectNative(ctx) {
|
|
4494
|
+
const method = ctx.manifest.spec.install.method;
|
|
4495
|
+
const cmd = ctx.manifest.spec.install.cmd;
|
|
4496
|
+
const name = ctx.manifest.metadata.name;
|
|
4497
|
+
if (method === "cc-plugin-marketplace") {
|
|
4498
|
+
const m = cmd.match(/(?:claude\s+)?plugin\s+install\s+(\S+)/i);
|
|
4499
|
+
const pluginName = m?.[1]?.split("@")[0] ?? name;
|
|
4500
|
+
try {
|
|
4501
|
+
return await isPluginRegistered(pluginName);
|
|
4502
|
+
} catch {
|
|
4503
|
+
return false;
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
if (method === "npx-skill-installer") {
|
|
4507
|
+
const skillName = extractSkillName(cmd, name);
|
|
4508
|
+
const skillMd = join(homedir(), ".claude", "skills", skillName, "SKILL.md");
|
|
4509
|
+
try {
|
|
4510
|
+
await access(skillMd);
|
|
4511
|
+
return true;
|
|
4512
|
+
} catch {
|
|
4513
|
+
return false;
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
if (method === "git-clone-with-setup") {
|
|
4517
|
+
const target = extractGitCloneTarget(cmd);
|
|
4518
|
+
if (!target) return false;
|
|
4519
|
+
try {
|
|
4520
|
+
await access(target);
|
|
4521
|
+
return true;
|
|
4522
|
+
} catch {
|
|
4523
|
+
return false;
|
|
4524
|
+
}
|
|
4525
|
+
}
|
|
4526
|
+
if (method === "npm-cli") {
|
|
4527
|
+
const skillDir = join(homedir(), ".claude", "skills", name);
|
|
4528
|
+
try {
|
|
4529
|
+
await access(skillDir);
|
|
4530
|
+
return true;
|
|
4531
|
+
} catch {
|
|
4532
|
+
return false;
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
return false;
|
|
4536
|
+
}
|
|
4537
|
+
async function isAlreadyInstalled(ctx, opts = {}) {
|
|
4538
|
+
const honorUpdateFlag = opts.honorUpdateFlag !== false;
|
|
4539
|
+
if (honorUpdateFlag && ctx.opts.updateInstalled === true) return false;
|
|
4540
|
+
if (ctx.opts.dryRun) return false;
|
|
4541
|
+
const native = await detectNative(ctx);
|
|
4542
|
+
if (native) return true;
|
|
4439
4543
|
const idempotentCmd = ctx.manifest.spec.install.idempotent_check;
|
|
4440
4544
|
if (typeof idempotentCmd !== "string" || idempotentCmd.length === 0) {
|
|
4441
4545
|
return false;
|
|
4442
4546
|
}
|
|
4443
4547
|
const r = await spawnCmd(ctx, idempotentCmd, [], IDEMPOTENT_CHECK_TIMEOUT_MS);
|
|
4444
|
-
if (!("exitCode" in r))
|
|
4445
|
-
return false;
|
|
4446
|
-
}
|
|
4548
|
+
if (!("exitCode" in r)) return false;
|
|
4447
4549
|
return r.exitCode === 0;
|
|
4448
4550
|
}
|
|
4449
4551
|
|
|
@@ -4581,7 +4683,7 @@ ${newEntry}
|
|
|
4581
4683
|
const r1 = await runArgs(["plugin", "marketplace", "add", parsed.marketplaceRef], spawnCwd);
|
|
4582
4684
|
stepOneStderr = r1.stderr;
|
|
4583
4685
|
}
|
|
4584
|
-
const r2 = await runArgs(installArgs, spawnCwd);
|
|
4686
|
+
const r2 = await runArgs(installArgs, spawnCwd, 6e4);
|
|
4585
4687
|
if (r2.exitCode !== 0) {
|
|
4586
4688
|
return {
|
|
4587
4689
|
ok: false,
|
|
@@ -4856,6 +4958,9 @@ var installMcpHttpAdd = async (ctx) => {
|
|
|
4856
4958
|
const e = pre.errors[0] ?? err(ctx, "/", "preflight failed (no detail)", "preflight");
|
|
4857
4959
|
return { ok: false, phase: "preflight", error: e };
|
|
4858
4960
|
}
|
|
4961
|
+
if (await isAlreadyInstalled(ctx, { honorUpdateFlag: false })) {
|
|
4962
|
+
return { ok: true, alreadyInstalled: true, backupId: "noop-idempotent" };
|
|
4963
|
+
}
|
|
4859
4964
|
const name = ctx.manifest.metadata.name;
|
|
4860
4965
|
const url = extractUrl(install.cmd);
|
|
4861
4966
|
if (!url) {
|
|
@@ -4992,6 +5097,9 @@ var installMcpStdioAdd = async (ctx) => {
|
|
|
4992
5097
|
const e = pre.errors[0] ?? err(ctx, "/", "preflight failed (no detail)", "preflight");
|
|
4993
5098
|
return { ok: false, phase: "preflight", error: e };
|
|
4994
5099
|
}
|
|
5100
|
+
if (await isAlreadyInstalled(ctx, { honorUpdateFlag: false })) {
|
|
5101
|
+
return { ok: true, alreadyInstalled: true, backupId: "noop-idempotent" };
|
|
5102
|
+
}
|
|
4995
5103
|
const name = ctx.manifest.metadata.name;
|
|
4996
5104
|
const pkg = ctx.manifest.metadata.upstream.source;
|
|
4997
5105
|
const ver = install.npm_version;
|
|
@@ -5181,7 +5289,7 @@ var installNpmCli = async (ctx) => {
|
|
|
5181
5289
|
await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, install.npm_version, "");
|
|
5182
5290
|
return { ok: true, backupId: bk.backupId, appliedFiles: [] };
|
|
5183
5291
|
};
|
|
5184
|
-
function
|
|
5292
|
+
function extractSkillName2(cmd, fallback) {
|
|
5185
5293
|
const m = cmd.match(/\bskills(?:@\S+)?\s+add\s+(\S+)/i);
|
|
5186
5294
|
if (!m || m[1] === void 0) return fallback;
|
|
5187
5295
|
const ref = m[1];
|
|
@@ -5212,21 +5320,6 @@ var installNpxSkillInstaller = async (ctx) => {
|
|
|
5212
5320
|
if (await isAlreadyInstalled(ctx)) {
|
|
5213
5321
|
return { ok: true, alreadyInstalled: true, backupId: "noop-idempotent" };
|
|
5214
5322
|
}
|
|
5215
|
-
if (!/\bskills@(?!latest\b)\S+/.test(install.cmd)) {
|
|
5216
|
-
return {
|
|
5217
|
-
ok: false,
|
|
5218
|
-
phase: "preflight",
|
|
5219
|
-
error: {
|
|
5220
|
-
...err(
|
|
5221
|
-
ctx,
|
|
5222
|
-
"/spec/install/cmd",
|
|
5223
|
-
`npx-skill-installer cmd must reference a pinned skills@<version> (got: '${install.cmd.slice(0, 100)}'); @latest is forbidden for reproducibility (ADR 0001)`,
|
|
5224
|
-
"skills-pin-required"
|
|
5225
|
-
),
|
|
5226
|
-
suggest: "change `skills@latest` \u2192 `skills@1.5.7` (current research-pinned stable)"
|
|
5227
|
-
}
|
|
5228
|
-
};
|
|
5229
|
-
}
|
|
5230
5323
|
if (!/\B--copy\b/.test(install.cmd) || !/\B--global\b/.test(install.cmd)) {
|
|
5231
5324
|
return {
|
|
5232
5325
|
ok: false,
|
|
@@ -5243,7 +5336,7 @@ var installNpxSkillInstaller = async (ctx) => {
|
|
|
5243
5336
|
};
|
|
5244
5337
|
}
|
|
5245
5338
|
const name = ctx.manifest.metadata.name;
|
|
5246
|
-
const skillSegment =
|
|
5339
|
+
const skillSegment = extractSkillName2(install.cmd, name);
|
|
5247
5340
|
const skillDir = join(homedir(), ".claude", "skills", skillSegment);
|
|
5248
5341
|
const skillMdPath = join(skillDir, "SKILL.md");
|
|
5249
5342
|
const plan = {
|
|
@@ -6110,7 +6203,7 @@ async function runStepBInstall(manifestPaths, runOpts = {}) {
|
|
|
6110
6203
|
}
|
|
6111
6204
|
const name = v.manifest.metadata.name;
|
|
6112
6205
|
const r = await runInstall(v.manifest, opts);
|
|
6113
|
-
if ("aborted" in r) return { status: "skipped", name };
|
|
6206
|
+
if ("aborted" in r) return { status: "skipped", name, reason: r.reason };
|
|
6114
6207
|
if (r.ok && "alreadyInstalled" in r && r.alreadyInstalled)
|
|
6115
6208
|
return { status: "already-installed", name };
|
|
6116
6209
|
if (r.ok) return { status: "installed", name };
|
|
@@ -6129,8 +6222,10 @@ async function runStepBInstall(manifestPaths, runOpts = {}) {
|
|
|
6129
6222
|
};
|
|
6130
6223
|
if (v.status === "installed") installed.push(v.name);
|
|
6131
6224
|
else if (v.status === "already-installed") alreadyInstalled.push(v.name);
|
|
6132
|
-
else if (v.status === "skipped")
|
|
6133
|
-
|
|
6225
|
+
else if (v.status === "skipped") {
|
|
6226
|
+
const skipReason = v.reason ?? "unknown";
|
|
6227
|
+
skipped.push({ name: v.name, reason: skipReason });
|
|
6228
|
+
} else
|
|
6134
6229
|
failed.push(`${v.name}: ${v.reason}`);
|
|
6135
6230
|
}
|
|
6136
6231
|
return { installed, alreadyInstalled, skipped, failed, elapsedMs: Date.now() - start };
|
|
@@ -6303,7 +6398,7 @@ function registerSetup(program2) {
|
|
|
6303
6398
|
console.log(
|
|
6304
6399
|
` [B] already-installed ${n} \u2014 run \`/mcp\` in Claude Code to verify connection`
|
|
6305
6400
|
);
|
|
6306
|
-
for (const
|
|
6401
|
+
for (const s of b.skipped) console.log(` [B] skipped ${s.name} \u2014 ${s.reason}`);
|
|
6307
6402
|
for (const n of b.failed) console.error(` [B] failed ${n}`);
|
|
6308
6403
|
if (!forceFirstPass && !dryRun && raw.nonInteractive !== true && b.alreadyInstalled.length > 0) {
|
|
6309
6404
|
const isTty = process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
@@ -6323,7 +6418,8 @@ Force-update pass complete: ${b2.installed.length} installed / ${b2.alreadyInsta
|
|
|
6323
6418
|
for (const n of b2.installed) console.log(` [B*] installed ${n}`);
|
|
6324
6419
|
for (const n of b2.alreadyInstalled)
|
|
6325
6420
|
console.log(` [B*] already-installed ${n} (MCP / no force-update)`);
|
|
6326
|
-
for (const
|
|
6421
|
+
for (const s of b2.skipped)
|
|
6422
|
+
console.log(` [B*] skipped ${s.name} \u2014 ${s.reason}`);
|
|
6327
6423
|
for (const n of b2.failed) console.error(` [B*] failed ${n}`);
|
|
6328
6424
|
}
|
|
6329
6425
|
}
|
|
@@ -6604,7 +6700,7 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
6604
6700
|
}
|
|
6605
6701
|
return { ok: true, removedPaths: [pkg] };
|
|
6606
6702
|
};
|
|
6607
|
-
function
|
|
6703
|
+
function extractSkillName3(cmd, fallback) {
|
|
6608
6704
|
const m = cmd.match(/\bskills(?:@\S+)?\s+add\s+(\S+)/i);
|
|
6609
6705
|
if (!m || m[1] === void 0) return fallback;
|
|
6610
6706
|
const ref = m[1];
|
|
@@ -6619,7 +6715,7 @@ var uninstallNpxSkillInstaller = async (ctx) => {
|
|
|
6619
6715
|
const abort = dryRunGate(ctx);
|
|
6620
6716
|
if (abort) return abort;
|
|
6621
6717
|
const name = ctx.manifest.metadata.name;
|
|
6622
|
-
const skillName =
|
|
6718
|
+
const skillName = extractSkillName3(install.cmd, name);
|
|
6623
6719
|
const skillDir = join(homedir(), ".claude", "skills", skillName);
|
|
6624
6720
|
await rm(skillDir, { recursive: true, force: true, maxRetries: 3 });
|
|
6625
6721
|
return { ok: true, removedPaths: [skillDir] };
|