harnessed 3.9.8 → 3.9.10

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
@@ -1222,7 +1222,7 @@ var init_auto_install = __esm({
1222
1222
 
1223
1223
  // package.json
1224
1224
  var package_default = {
1225
- version: "3.9.8"};
1225
+ version: "3.9.10"};
1226
1226
 
1227
1227
  // src/manifest/errors.ts
1228
1228
  function instancePathToKeyPath(instancePath) {
@@ -4379,6 +4379,9 @@ var installCcHookAdd = async (ctx) => {
4379
4379
  await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, "", "");
4380
4380
  return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath3] };
4381
4381
  };
4382
+
4383
+ // src/installers/lib/idempotent.ts
4384
+ init_readClaudeConfig();
4382
4385
  function expandTildeForWindows(cmd) {
4383
4386
  const home = homedir().replace(/\\/g, "/");
4384
4387
  return cmd.replace(/(^|[\s"'`(])~\//g, `$1${home}/`);
@@ -4466,18 +4469,92 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
4466
4469
 
4467
4470
  // src/installers/lib/idempotent.ts
4468
4471
  var IDEMPOTENT_CHECK_TIMEOUT_MS = 1e4;
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
+ for (const base of [".claude", ".agents"]) {
4509
+ const skillMd = join(homedir(), base, "skills", skillName, "SKILL.md");
4510
+ try {
4511
+ await access(skillMd);
4512
+ return true;
4513
+ } catch {
4514
+ }
4515
+ }
4516
+ }
4517
+ if (method === "git-clone-with-setup") {
4518
+ const target = extractGitCloneTarget(cmd);
4519
+ if (!target) return false;
4520
+ try {
4521
+ await access(target);
4522
+ return true;
4523
+ } catch {
4524
+ return false;
4525
+ }
4526
+ }
4527
+ if (method === "npm-cli") {
4528
+ const skillDir = join(homedir(), ".claude", "skills", name);
4529
+ try {
4530
+ await access(skillDir);
4531
+ return true;
4532
+ } catch {
4533
+ return false;
4534
+ }
4535
+ }
4536
+ const pluginNames = [name];
4537
+ if (name === "ctx7") pluginNames.push("context7");
4538
+ for (const pn of pluginNames) {
4539
+ try {
4540
+ if (await isPluginRegistered(pn)) return true;
4541
+ } catch {
4542
+ }
4543
+ }
4544
+ return false;
4545
+ }
4469
4546
  async function isAlreadyInstalled(ctx, opts = {}) {
4470
4547
  const honorUpdateFlag = opts.honorUpdateFlag !== false;
4471
4548
  if (honorUpdateFlag && ctx.opts.updateInstalled === true) return false;
4472
4549
  if (ctx.opts.dryRun) return false;
4550
+ const native = await detectNative(ctx);
4551
+ if (native) return true;
4473
4552
  const idempotentCmd = ctx.manifest.spec.install.idempotent_check;
4474
4553
  if (typeof idempotentCmd !== "string" || idempotentCmd.length === 0) {
4475
4554
  return false;
4476
4555
  }
4477
4556
  const r = await spawnCmd(ctx, idempotentCmd, [], IDEMPOTENT_CHECK_TIMEOUT_MS);
4478
- if (!("exitCode" in r)) {
4479
- return false;
4480
- }
4557
+ if (!("exitCode" in r)) return false;
4481
4558
  return r.exitCode === 0;
4482
4559
  }
4483
4560
 
@@ -4615,7 +4692,7 @@ ${newEntry}
4615
4692
  const r1 = await runArgs(["plugin", "marketplace", "add", parsed.marketplaceRef], spawnCwd);
4616
4693
  stepOneStderr = r1.stderr;
4617
4694
  }
4618
- const r2 = await runArgs(installArgs, spawnCwd);
4695
+ const r2 = await runArgs(installArgs, spawnCwd, 6e4);
4619
4696
  if (r2.exitCode !== 0) {
4620
4697
  return {
4621
4698
  ok: false,
@@ -5221,7 +5298,7 @@ var installNpmCli = async (ctx) => {
5221
5298
  await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, install.npm_version, "");
5222
5299
  return { ok: true, backupId: bk.backupId, appliedFiles: [] };
5223
5300
  };
5224
- function extractSkillName(cmd, fallback) {
5301
+ function extractSkillName2(cmd, fallback) {
5225
5302
  const m = cmd.match(/\bskills(?:@\S+)?\s+add\s+(\S+)/i);
5226
5303
  if (!m || m[1] === void 0) return fallback;
5227
5304
  const ref = m[1];
@@ -5252,21 +5329,6 @@ var installNpxSkillInstaller = async (ctx) => {
5252
5329
  if (await isAlreadyInstalled(ctx)) {
5253
5330
  return { ok: true, alreadyInstalled: true, backupId: "noop-idempotent" };
5254
5331
  }
5255
- if (!/\bskills@(?!latest\b)\S+/.test(install.cmd)) {
5256
- return {
5257
- ok: false,
5258
- phase: "preflight",
5259
- error: {
5260
- ...err(
5261
- ctx,
5262
- "/spec/install/cmd",
5263
- `npx-skill-installer cmd must reference a pinned skills@<version> (got: '${install.cmd.slice(0, 100)}'); @latest is forbidden for reproducibility (ADR 0001)`,
5264
- "skills-pin-required"
5265
- ),
5266
- suggest: "change `skills@latest` \u2192 `skills@1.5.7` (current research-pinned stable)"
5267
- }
5268
- };
5269
- }
5270
5332
  if (!/\B--copy\b/.test(install.cmd) || !/\B--global\b/.test(install.cmd)) {
5271
5333
  return {
5272
5334
  ok: false,
@@ -5283,7 +5345,7 @@ var installNpxSkillInstaller = async (ctx) => {
5283
5345
  };
5284
5346
  }
5285
5347
  const name = ctx.manifest.metadata.name;
5286
- const skillSegment = extractSkillName(install.cmd, name);
5348
+ const skillSegment = extractSkillName2(install.cmd, name);
5287
5349
  const skillDir = join(homedir(), ".claude", "skills", skillSegment);
5288
5350
  const skillMdPath = join(skillDir, "SKILL.md");
5289
5351
  const plan = {
@@ -6342,9 +6404,7 @@ function registerSetup(program2) {
6342
6404
  );
6343
6405
  for (const n of b.installed) console.log(` [B] installed ${n}`);
6344
6406
  for (const n of b.alreadyInstalled)
6345
- console.log(
6346
- ` [B] already-installed ${n} \u2014 run \`/mcp\` in Claude Code to verify connection`
6347
- );
6407
+ console.log(` [B] already-installed ${n}`);
6348
6408
  for (const s of b.skipped) console.log(` [B] skipped ${s.name} \u2014 ${s.reason}`);
6349
6409
  for (const n of b.failed) console.error(` [B] failed ${n}`);
6350
6410
  if (!forceFirstPass && !dryRun && raw.nonInteractive !== true && b.alreadyInstalled.length > 0) {
@@ -6647,7 +6707,7 @@ var uninstallNpmCli = async (ctx) => {
6647
6707
  }
6648
6708
  return { ok: true, removedPaths: [pkg] };
6649
6709
  };
6650
- function extractSkillName2(cmd, fallback) {
6710
+ function extractSkillName3(cmd, fallback) {
6651
6711
  const m = cmd.match(/\bskills(?:@\S+)?\s+add\s+(\S+)/i);
6652
6712
  if (!m || m[1] === void 0) return fallback;
6653
6713
  const ref = m[1];
@@ -6662,7 +6722,7 @@ var uninstallNpxSkillInstaller = async (ctx) => {
6662
6722
  const abort = dryRunGate(ctx);
6663
6723
  if (abort) return abort;
6664
6724
  const name = ctx.manifest.metadata.name;
6665
- const skillName = extractSkillName2(install.cmd, name);
6725
+ const skillName = extractSkillName3(install.cmd, name);
6666
6726
  const skillDir = join(homedir(), ".claude", "skills", skillName);
6667
6727
  await rm(skillDir, { recursive: true, force: true, maxRetries: 3 });
6668
6728
  return { ok: true, removedPaths: [skillDir] };