harnessed 3.9.6 → 3.9.8

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
- 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);
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.6"};
1225
+ version: "3.9.8"};
1204
1226
 
1205
1227
  // src/manifest/errors.ts
1206
1228
  function instancePathToKeyPath(instancePath) {
@@ -4357,6 +4379,10 @@ 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
+ function expandTildeForWindows(cmd) {
4383
+ const home = homedir().replace(/\\/g, "/");
4384
+ return cmd.replace(/(^|[\s"'`(])~\//g, `$1${home}/`);
4385
+ }
4360
4386
  var DEFAULT_VERIFY_TIMEOUT_MS = 15e3;
4361
4387
  var DEFAULT_INSTALL_TIMEOUT_MS = 6e4;
4362
4388
  async function spawnCmd(ctx, cmd, args, timeoutMs) {
@@ -4381,7 +4407,13 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
4381
4407
  const cwd = installCfg.cwd ?? ctx.cwd;
4382
4408
  let child;
4383
4409
  if (process.platform === "win32") {
4384
- child = spawn("cmd.exe", ["/c", cmd, ...args], { cwd, env, windowsHide: true });
4410
+ const expandedCmd = expandTildeForWindows(cmd);
4411
+ const expandedArgs = args.map(expandTildeForWindows);
4412
+ child = spawn("cmd.exe", ["/c", expandedCmd, ...expandedArgs], {
4413
+ cwd,
4414
+ env,
4415
+ windowsHide: true
4416
+ });
4385
4417
  } else {
4386
4418
  const joined = args.length > 0 ? `${cmd} ${args.join(" ")}` : cmd;
4387
4419
  child = spawn("/bin/sh", ["-c", joined], { cwd, env });
@@ -4434,8 +4466,10 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
4434
4466
 
4435
4467
  // src/installers/lib/idempotent.ts
4436
4468
  var IDEMPOTENT_CHECK_TIMEOUT_MS = 1e4;
4437
- async function isAlreadyInstalled(ctx) {
4438
- if (ctx.opts.updateInstalled === true) return false;
4469
+ async function isAlreadyInstalled(ctx, opts = {}) {
4470
+ const honorUpdateFlag = opts.honorUpdateFlag !== false;
4471
+ if (honorUpdateFlag && ctx.opts.updateInstalled === true) return false;
4472
+ if (ctx.opts.dryRun) return false;
4439
4473
  const idempotentCmd = ctx.manifest.spec.install.idempotent_check;
4440
4474
  if (typeof idempotentCmd !== "string" || idempotentCmd.length === 0) {
4441
4475
  return false;
@@ -4856,6 +4890,9 @@ var installMcpHttpAdd = async (ctx) => {
4856
4890
  const e = pre.errors[0] ?? err(ctx, "/", "preflight failed (no detail)", "preflight");
4857
4891
  return { ok: false, phase: "preflight", error: e };
4858
4892
  }
4893
+ if (await isAlreadyInstalled(ctx, { honorUpdateFlag: false })) {
4894
+ return { ok: true, alreadyInstalled: true, backupId: "noop-idempotent" };
4895
+ }
4859
4896
  const name = ctx.manifest.metadata.name;
4860
4897
  const url = extractUrl(install.cmd);
4861
4898
  if (!url) {
@@ -4992,6 +5029,9 @@ var installMcpStdioAdd = async (ctx) => {
4992
5029
  const e = pre.errors[0] ?? err(ctx, "/", "preflight failed (no detail)", "preflight");
4993
5030
  return { ok: false, phase: "preflight", error: e };
4994
5031
  }
5032
+ if (await isAlreadyInstalled(ctx, { honorUpdateFlag: false })) {
5033
+ return { ok: true, alreadyInstalled: true, backupId: "noop-idempotent" };
5034
+ }
4995
5035
  const name = ctx.manifest.metadata.name;
4996
5036
  const pkg = ctx.manifest.metadata.upstream.source;
4997
5037
  const ver = install.npm_version;
@@ -6110,7 +6150,7 @@ async function runStepBInstall(manifestPaths, runOpts = {}) {
6110
6150
  }
6111
6151
  const name = v.manifest.metadata.name;
6112
6152
  const r = await runInstall(v.manifest, opts);
6113
- if ("aborted" in r) return { status: "skipped", name };
6153
+ if ("aborted" in r) return { status: "skipped", name, reason: r.reason };
6114
6154
  if (r.ok && "alreadyInstalled" in r && r.alreadyInstalled)
6115
6155
  return { status: "already-installed", name };
6116
6156
  if (r.ok) return { status: "installed", name };
@@ -6129,8 +6169,10 @@ async function runStepBInstall(manifestPaths, runOpts = {}) {
6129
6169
  };
6130
6170
  if (v.status === "installed") installed.push(v.name);
6131
6171
  else if (v.status === "already-installed") alreadyInstalled.push(v.name);
6132
- else if (v.status === "skipped") skipped.push(v.name);
6133
- else
6172
+ else if (v.status === "skipped") {
6173
+ const skipReason = v.reason ?? "unknown";
6174
+ skipped.push({ name: v.name, reason: skipReason });
6175
+ } else
6134
6176
  failed.push(`${v.name}: ${v.reason}`);
6135
6177
  }
6136
6178
  return { installed, alreadyInstalled, skipped, failed, elapsedMs: Date.now() - start };
@@ -6285,20 +6327,9 @@ function registerSetup(program2) {
6285
6327
  } else {
6286
6328
  console.warn(t("setup.step_d.skipped", { message: dResult.message }));
6287
6329
  }
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
- }
6300
6330
  const manifestPaths = await listBaseManifests2(pkgRoot);
6301
- const b = await runStepBInstall(manifestPaths, { updateInstalled: updateInstalled2 });
6331
+ const forceFirstPass = raw.updateInstalled === true;
6332
+ const b = await runStepBInstall(manifestPaths, { updateInstalled: forceFirstPass });
6302
6333
  const stepBMs = (b.elapsedMs / 1e3).toFixed(1);
6303
6334
  console.log(
6304
6335
  t("setup.step_b_complete", {
@@ -6314,8 +6345,32 @@ function registerSetup(program2) {
6314
6345
  console.log(
6315
6346
  ` [B] already-installed ${n} \u2014 run \`/mcp\` in Claude Code to verify connection`
6316
6347
  );
6317
- for (const n of b.skipped) console.log(` [B] skipped ${n}`);
6348
+ for (const s of b.skipped) console.log(` [B] skipped ${s.name} \u2014 ${s.reason}`);
6318
6349
  for (const n of b.failed) console.error(` [B] failed ${n}`);
6350
+ if (!forceFirstPass && !dryRun && raw.nonInteractive !== true && b.alreadyInstalled.length > 0) {
6351
+ const isTty = process.stdin.isTTY === true && process.stdout.isTTY === true;
6352
+ if (isTty) {
6353
+ const { confirm: confirm4, isCancel: isCancel5 } = await import('@clack/prompts');
6354
+ const ans = await confirm4({
6355
+ message: `Update ${b.alreadyInstalled.length} already-installed plugin(s) listed above? (MCP servers excluded \u2014 they ignore force-update)`,
6356
+ initialValue: false
6357
+ });
6358
+ if (!isCancel5(ans) && ans === true) {
6359
+ const b2 = await runStepBInstall(manifestPaths, { updateInstalled: true });
6360
+ const stepB2Ms = (b2.elapsedMs / 1e3).toFixed(1);
6361
+ console.log(
6362
+ `
6363
+ Force-update pass complete: ${b2.installed.length} installed / ${b2.alreadyInstalled.length} still-already-installed (MCP) / ${b2.skipped.length} skipped / ${b2.failed.length} failed [parallel ${stepB2Ms}s]`
6364
+ );
6365
+ for (const n of b2.installed) console.log(` [B*] installed ${n}`);
6366
+ for (const n of b2.alreadyInstalled)
6367
+ console.log(` [B*] already-installed ${n} (MCP / no force-update)`);
6368
+ for (const s of b2.skipped)
6369
+ console.log(` [B*] skipped ${s.name} \u2014 ${s.reason}`);
6370
+ for (const n of b2.failed) console.error(` [B*] failed ${n}`);
6371
+ }
6372
+ }
6373
+ }
6319
6374
  console.log(
6320
6375
  t("setup.complete", {
6321
6376
  skills: skillsInstalled,