harnessed 3.9.8 → 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
@@ -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.9"};
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,83 @@ 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
+ 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
+ }
4469
4537
  async function isAlreadyInstalled(ctx, opts = {}) {
4470
4538
  const honorUpdateFlag = opts.honorUpdateFlag !== false;
4471
4539
  if (honorUpdateFlag && ctx.opts.updateInstalled === true) return false;
4472
4540
  if (ctx.opts.dryRun) return false;
4541
+ const native = await detectNative(ctx);
4542
+ if (native) return true;
4473
4543
  const idempotentCmd = ctx.manifest.spec.install.idempotent_check;
4474
4544
  if (typeof idempotentCmd !== "string" || idempotentCmd.length === 0) {
4475
4545
  return false;
4476
4546
  }
4477
4547
  const r = await spawnCmd(ctx, idempotentCmd, [], IDEMPOTENT_CHECK_TIMEOUT_MS);
4478
- if (!("exitCode" in r)) {
4479
- return false;
4480
- }
4548
+ if (!("exitCode" in r)) return false;
4481
4549
  return r.exitCode === 0;
4482
4550
  }
4483
4551
 
@@ -4615,7 +4683,7 @@ ${newEntry}
4615
4683
  const r1 = await runArgs(["plugin", "marketplace", "add", parsed.marketplaceRef], spawnCwd);
4616
4684
  stepOneStderr = r1.stderr;
4617
4685
  }
4618
- const r2 = await runArgs(installArgs, spawnCwd);
4686
+ const r2 = await runArgs(installArgs, spawnCwd, 6e4);
4619
4687
  if (r2.exitCode !== 0) {
4620
4688
  return {
4621
4689
  ok: false,
@@ -5221,7 +5289,7 @@ var installNpmCli = async (ctx) => {
5221
5289
  await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, install.npm_version, "");
5222
5290
  return { ok: true, backupId: bk.backupId, appliedFiles: [] };
5223
5291
  };
5224
- function extractSkillName(cmd, fallback) {
5292
+ function extractSkillName2(cmd, fallback) {
5225
5293
  const m = cmd.match(/\bskills(?:@\S+)?\s+add\s+(\S+)/i);
5226
5294
  if (!m || m[1] === void 0) return fallback;
5227
5295
  const ref = m[1];
@@ -5252,21 +5320,6 @@ var installNpxSkillInstaller = async (ctx) => {
5252
5320
  if (await isAlreadyInstalled(ctx)) {
5253
5321
  return { ok: true, alreadyInstalled: true, backupId: "noop-idempotent" };
5254
5322
  }
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
5323
  if (!/\B--copy\b/.test(install.cmd) || !/\B--global\b/.test(install.cmd)) {
5271
5324
  return {
5272
5325
  ok: false,
@@ -5283,7 +5336,7 @@ var installNpxSkillInstaller = async (ctx) => {
5283
5336
  };
5284
5337
  }
5285
5338
  const name = ctx.manifest.metadata.name;
5286
- const skillSegment = extractSkillName(install.cmd, name);
5339
+ const skillSegment = extractSkillName2(install.cmd, name);
5287
5340
  const skillDir = join(homedir(), ".claude", "skills", skillSegment);
5288
5341
  const skillMdPath = join(skillDir, "SKILL.md");
5289
5342
  const plan = {
@@ -6647,7 +6700,7 @@ var uninstallNpmCli = async (ctx) => {
6647
6700
  }
6648
6701
  return { ok: true, removedPaths: [pkg] };
6649
6702
  };
6650
- function extractSkillName2(cmd, fallback) {
6703
+ function extractSkillName3(cmd, fallback) {
6651
6704
  const m = cmd.match(/\bskills(?:@\S+)?\s+add\s+(\S+)/i);
6652
6705
  if (!m || m[1] === void 0) return fallback;
6653
6706
  const ref = m[1];
@@ -6662,7 +6715,7 @@ var uninstallNpxSkillInstaller = async (ctx) => {
6662
6715
  const abort = dryRunGate(ctx);
6663
6716
  if (abort) return abort;
6664
6717
  const name = ctx.manifest.metadata.name;
6665
- const skillName = extractSkillName2(install.cmd, name);
6718
+ const skillName = extractSkillName3(install.cmd, name);
6666
6719
  const skillDir = join(homedir(), ".claude", "skills", skillName);
6667
6720
  await rm(skillDir, { recursive: true, force: true, maxRetries: 3 });
6668
6721
  return { ok: true, removedPaths: [skillDir] };