harnessed 3.9.17 → 3.9.19

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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync, spawn, execSync } from 'child_process';
3
3
  import { existsSync, mkdirSync, renameSync, writeFileSync, readFileSync, readdirSync } from 'fs';
4
- import { join, dirname, resolve, relative } from 'path';
4
+ import { join, dirname, resolve, sep, relative } from 'path';
5
5
  import { homedir } from 'os';
6
6
  import { readFile, readdir, unlink, writeFile, stat, rm, cp, mkdir, access, rename } from 'fs/promises';
7
7
  import { Type } from '@sinclair/typebox';
@@ -1218,7 +1218,7 @@ var init_auto_install = __esm({
1218
1218
 
1219
1219
  // package.json
1220
1220
  var package_default = {
1221
- version: "3.9.17"};
1221
+ version: "3.9.19"};
1222
1222
 
1223
1223
  // src/manifest/errors.ts
1224
1224
  function instancePathToKeyPath(instancePath) {
@@ -2057,9 +2057,9 @@ function redactRecord(r) {
2057
2057
  }
2058
2058
  function renderHumanTable(records) {
2059
2059
  const header = `${"ts".padEnd(19)} | ${"phase".padEnd(6)} | ${"category".padEnd(11)} | ${"matched_rule_id".padEnd(20)} | outcome`;
2060
- const sep = `${"-".repeat(19)}-+-${"-".repeat(6)}-+-${"-".repeat(11)}-+-${"-".repeat(20)}-+--------`;
2060
+ const sep2 = `${"-".repeat(19)}-+-${"-".repeat(6)}-+-${"-".repeat(11)}-+-${"-".repeat(20)}-+--------`;
2061
2061
  console.log(header);
2062
- console.log(sep);
2062
+ console.log(sep2);
2063
2063
  for (const r of records) {
2064
2064
  const ts = r.ts.slice(0, 19);
2065
2065
  const phase = r.phase.padEnd(6);
@@ -2069,7 +2069,7 @@ function renderHumanTable(records) {
2069
2069
  }
2070
2070
  }
2071
2071
  function pipeToJq(filterExpr, lines) {
2072
- return new Promise((resolve15, reject) => {
2072
+ return new Promise((resolve14, reject) => {
2073
2073
  const child = spawn("jq", [filterExpr], {
2074
2074
  stdio: ["pipe", "inherit", "inherit"],
2075
2075
  windowsHide: true
@@ -2078,12 +2078,12 @@ function pipeToJq(filterExpr, lines) {
2078
2078
  const e = err2;
2079
2079
  if (e.code === "ENOENT") {
2080
2080
  console.error(t("audit_log.jq_missing"));
2081
- resolve15(1);
2081
+ resolve14(1);
2082
2082
  } else {
2083
2083
  reject(err2);
2084
2084
  }
2085
2085
  });
2086
- child.on("close", (code) => resolve15(code ?? 0));
2086
+ child.on("close", (code) => resolve14(code ?? 0));
2087
2087
  child.stdin.write(lines.join("\n"));
2088
2088
  child.stdin.end();
2089
2089
  });
@@ -2740,10 +2740,10 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
2740
2740
  child.stderr?.setEncoding("utf8").on("data", (chunk) => {
2741
2741
  stderr += chunk;
2742
2742
  });
2743
- return await new Promise((resolve15) => {
2743
+ return await new Promise((resolve14) => {
2744
2744
  const timer = setTimeout(() => {
2745
2745
  child.kill("SIGKILL");
2746
- resolve15({
2746
+ resolve14({
2747
2747
  ok: false,
2748
2748
  phase: "spawn",
2749
2749
  error: {
@@ -2758,7 +2758,7 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
2758
2758
  }, effectiveTimeoutMs);
2759
2759
  child.on("error", (err2) => {
2760
2760
  clearTimeout(timer);
2761
- resolve15({
2761
+ resolve14({
2762
2762
  ok: false,
2763
2763
  phase: "spawn",
2764
2764
  error: {
@@ -2773,7 +2773,7 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
2773
2773
  });
2774
2774
  child.on("close", (code) => {
2775
2775
  clearTimeout(timer);
2776
- resolve15({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
2776
+ resolve14({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
2777
2777
  });
2778
2778
  });
2779
2779
  }
@@ -2801,10 +2801,25 @@ function extractGitCloneTarget(cmd) {
2801
2801
  if (dest.startsWith("/") || /^[A-Z]:[\\/]/i.test(dest)) return dest;
2802
2802
  return null;
2803
2803
  }
2804
+ var INSTALLED_INDICATORS = {
2805
+ gsd: ["gsd-progress", "gsd-plan-phase"],
2806
+ "mattpocock-skills": ["diagnose", "tdd", "zoom-out"]
2807
+ };
2804
2808
  async function detectNative(ctx) {
2805
2809
  const method = ctx.manifest.spec.install.method;
2806
2810
  const cmd = ctx.manifest.spec.install.cmd;
2807
2811
  const name = ctx.manifest.metadata.name;
2812
+ const indicators = INSTALLED_INDICATORS[name];
2813
+ if (indicators) {
2814
+ for (const ind of indicators) {
2815
+ const dir = join(homedir(), ".claude", "skills", ind);
2816
+ try {
2817
+ await access(dir);
2818
+ return true;
2819
+ } catch {
2820
+ }
2821
+ }
2822
+ }
2808
2823
  if (method === "cc-plugin-marketplace") {
2809
2824
  const m = cmd.match(/(?:claude\s+)?plugin\s+install\s+(\S+)/i);
2810
2825
  const pluginName = m?.[1]?.split("@")[0] ?? name;
@@ -2871,7 +2886,7 @@ async function isAlreadyInstalled(ctx, opts = {}) {
2871
2886
  // src/installers/ccPluginMarketplace.ts
2872
2887
  init_readClaudeConfig();
2873
2888
  function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
2874
- return new Promise((resolve15) => {
2889
+ return new Promise((resolve14) => {
2875
2890
  const isWin = process.platform === "win32";
2876
2891
  const child = isWin ? spawn("cmd.exe", ["/c", "claude", ...claudeArgs], { cwd, windowsHide: true }) : spawn("claude", claudeArgs, { cwd, shell: false });
2877
2892
  let stderr = "";
@@ -2880,15 +2895,15 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
2880
2895
  });
2881
2896
  const timer = setTimeout(() => {
2882
2897
  child.kill("SIGKILL");
2883
- resolve15({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
2898
+ resolve14({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
2884
2899
  }, timeoutMs);
2885
2900
  child.on("error", (e) => {
2886
2901
  clearTimeout(timer);
2887
- resolve15({ exitCode: -1, stderr: `${stderr}${e.message}` });
2902
+ resolve14({ exitCode: -1, stderr: `${stderr}${e.message}` });
2888
2903
  });
2889
2904
  child.on("close", (code) => {
2890
2905
  clearTimeout(timer);
2891
- resolve15({ exitCode: code ?? -1, stderr });
2906
+ resolve14({ exitCode: code ?? -1, stderr });
2892
2907
  });
2893
2908
  });
2894
2909
  }
@@ -3034,7 +3049,7 @@ ${newEntry}
3034
3049
  return { ok: true, backupId: bk.backupId, appliedFiles: [settingsFile] };
3035
3050
  };
3036
3051
  function gitRevParseHead(cwd, timeoutMs = 1e4) {
3037
- return new Promise((resolve15) => {
3052
+ return new Promise((resolve14) => {
3038
3053
  const isWin = process.platform === "win32";
3039
3054
  const child = isWin ? spawn("cmd.exe", ["/c", "git", "rev-parse", "HEAD"], { cwd, windowsHide: true }) : spawn("git", ["rev-parse", "HEAD"], { cwd, shell: false });
3040
3055
  let stdout2 = "";
@@ -3043,15 +3058,15 @@ function gitRevParseHead(cwd, timeoutMs = 1e4) {
3043
3058
  });
3044
3059
  const timer = setTimeout(() => {
3045
3060
  child.kill("SIGKILL");
3046
- resolve15({ sha: "", exit: -1 });
3061
+ resolve14({ sha: "", exit: -1 });
3047
3062
  }, timeoutMs);
3048
3063
  child.on("error", () => {
3049
3064
  clearTimeout(timer);
3050
- resolve15({ sha: "", exit: -1 });
3065
+ resolve14({ sha: "", exit: -1 });
3051
3066
  });
3052
3067
  child.on("close", (code) => {
3053
3068
  clearTimeout(timer);
3054
- resolve15({ sha: stdout2.trim(), exit: code ?? -1 });
3069
+ resolve14({ sha: stdout2.trim(), exit: code ?? -1 });
3055
3070
  });
3056
3071
  });
3057
3072
  }
@@ -6373,6 +6388,7 @@ function registerStatus(program2) {
6373
6388
  }
6374
6389
  });
6375
6390
  }
6391
+ init_harnessedRoot();
6376
6392
  init_path_guard();
6377
6393
 
6378
6394
  // src/uninstallers/lib/runOrPreview.ts
@@ -6558,7 +6574,7 @@ var uninstallNpmCli = async (ctx) => {
6558
6574
  const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
6559
6575
  const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
6560
6576
  const isWin = process.platform === "win32";
6561
- const result = await new Promise((resolve15) => {
6577
+ const result = await new Promise((resolve14) => {
6562
6578
  const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
6563
6579
  let stderr = "";
6564
6580
  child.stderr?.setEncoding("utf8").on("data", (c) => {
@@ -6566,15 +6582,15 @@ var uninstallNpmCli = async (ctx) => {
6566
6582
  });
6567
6583
  const timer = setTimeout(() => {
6568
6584
  child.kill("SIGKILL");
6569
- resolve15({ exitCode: -1, stderr: `${stderr}[timeout]` });
6585
+ resolve14({ exitCode: -1, stderr: `${stderr}[timeout]` });
6570
6586
  }, 3e4);
6571
6587
  child.on("error", (e) => {
6572
6588
  clearTimeout(timer);
6573
- resolve15({ exitCode: -1, stderr: e.message });
6589
+ resolve14({ exitCode: -1, stderr: e.message });
6574
6590
  });
6575
6591
  child.on("close", (code) => {
6576
6592
  clearTimeout(timer);
6577
- resolve15({ exitCode: code ?? -1, stderr });
6593
+ resolve14({ exitCode: code ?? -1, stderr });
6578
6594
  });
6579
6595
  });
6580
6596
  if (result.exitCode !== 0) {
@@ -6616,21 +6632,177 @@ async function runUninstall(manifest, opts) {
6616
6632
  }
6617
6633
 
6618
6634
  // src/cli/uninstall.ts
6635
+ async function discoverCommandFiles(commandsDir) {
6636
+ const owned = [];
6637
+ let entries;
6638
+ try {
6639
+ entries = await readdir(commandsDir);
6640
+ } catch {
6641
+ return owned;
6642
+ }
6643
+ for (const entry of entries) {
6644
+ if (!entry.endsWith(".md")) continue;
6645
+ const p5 = join(commandsDir, entry);
6646
+ try {
6647
+ const content = await readFile(p5, "utf8");
6648
+ if (shouldOverwriteFile(content)) owned.push(p5);
6649
+ } catch {
6650
+ }
6651
+ }
6652
+ return owned;
6653
+ }
6654
+ async function checkSettingsEnv(settingsPath3) {
6655
+ try {
6656
+ const raw = await readFile(settingsPath3, "utf8");
6657
+ const data = JSON.parse(raw);
6658
+ const env = data.env ?? {};
6659
+ return {
6660
+ hasAgentTeams: "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS" in env,
6661
+ hasUserLang: "HARNESSED_USER_LANG" in env
6662
+ };
6663
+ } catch {
6664
+ return { hasAgentTeams: false, hasUserLang: false };
6665
+ }
6666
+ }
6667
+ async function removeSettingsEnv(settingsPath3) {
6668
+ const raw = await readFile(settingsPath3, "utf8");
6669
+ const data = JSON.parse(raw);
6670
+ const env = data.env ?? {};
6671
+ let changed = false;
6672
+ if ("CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS" in env) {
6673
+ delete env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
6674
+ changed = true;
6675
+ }
6676
+ if ("HARNESSED_USER_LANG" in env) {
6677
+ delete env.HARNESSED_USER_LANG;
6678
+ changed = true;
6679
+ }
6680
+ if (!changed) return false;
6681
+ if (Object.keys(env).length === 0) delete data.env;
6682
+ else data.env = env;
6683
+ await writeFile(settingsPath3, JSON.stringify(data, null, 2) + "\n", "utf8");
6684
+ return true;
6685
+ }
6686
+ async function runUnifiedUninstall(home, dryRun) {
6687
+ const commandsDir = join(home, ".claude", "commands");
6688
+ const skillsDir = join(home, ".claude", "skills");
6689
+ const settingsPath3 = join(home, ".claude", "settings.json");
6690
+ const harnessedRoot = getHarnessedRoot();
6691
+ const pkgRoot = getPackageRoot();
6692
+ let workflowNames = [];
6693
+ try {
6694
+ const wfEntries = await readdir(resolve(pkgRoot, "workflows"));
6695
+ const scanResult = await scanWorkflowsNested(resolve(pkgRoot, "workflows"), wfEntries);
6696
+ workflowNames = scanResult.workflows.map((w) => w.name);
6697
+ } catch {
6698
+ }
6699
+ const commandFiles = await discoverCommandFiles(commandsDir);
6700
+ const settingsEnv = await checkSettingsEnv(settingsPath3);
6701
+ const hasSettingsChanges = settingsEnv.hasAgentTeams || settingsEnv.hasUserLang;
6702
+ const skillDirs = [];
6703
+ for (const name of workflowNames) {
6704
+ const dir = join(skillsDir, name);
6705
+ try {
6706
+ await stat(dir);
6707
+ skillDirs.push(dir);
6708
+ } catch {
6709
+ }
6710
+ }
6711
+ const discoverable = commandFiles.length + skillDirs.length + (hasSettingsChanges ? 1 : 0);
6712
+ if (discoverable === 0) {
6713
+ console.log(t("uninstall.unified.nothing"));
6714
+ process.exit(0);
6715
+ }
6716
+ console.log(t("uninstall.unified.header"));
6717
+ if (commandFiles.length > 0)
6718
+ console.log(t("uninstall.unified.commands", { count: commandFiles.length }));
6719
+ if (skillDirs.length > 0) console.log(t("uninstall.unified.skills", { count: skillDirs.length }));
6720
+ if (hasSettingsChanges) console.log(t("uninstall.unified.settings"));
6721
+ console.log(t("uninstall.unified.state_dir"));
6722
+ console.log(t("uninstall.unified.upstream_note"));
6723
+ if (dryRun) {
6724
+ console.log(t("uninstall.unified.dry_run_hint"));
6725
+ process.exit(2);
6726
+ }
6727
+ const answer = await p.confirm({
6728
+ message: t("uninstall.unified.confirm"),
6729
+ initialValue: false
6730
+ });
6731
+ if (p.isCancel(answer) || answer === false) {
6732
+ console.error(t("uninstall.cancelled"));
6733
+ process.exit(2);
6734
+ }
6735
+ console.log(t("uninstall.unified.removing"));
6736
+ const failures = [];
6737
+ let removedCommands = 0;
6738
+ for (const path of commandFiles) {
6739
+ try {
6740
+ await rm(path, { force: true });
6741
+ removedCommands++;
6742
+ } catch (e) {
6743
+ failures.push(`${path}: ${e.message}`);
6744
+ }
6745
+ }
6746
+ let removedSkills = 0;
6747
+ for (const dir of skillDirs) {
6748
+ try {
6749
+ await rm(dir, { recursive: true, force: true });
6750
+ removedSkills++;
6751
+ } catch (e) {
6752
+ failures.push(`${dir}: ${e.message}`);
6753
+ }
6754
+ }
6755
+ let removedSettings = false;
6756
+ if (hasSettingsChanges) {
6757
+ try {
6758
+ removedSettings = await removeSettingsEnv(settingsPath3);
6759
+ } catch (e) {
6760
+ failures.push(`${settingsPath3}: ${e.message}`);
6761
+ }
6762
+ }
6763
+ const normalizedRoot = resolve(harnessedRoot);
6764
+ const claudeDir = join(home, ".claude");
6765
+ if (!normalizedRoot.startsWith(resolve(claudeDir) + sep)) {
6766
+ console.error(`error: state dir ${harnessedRoot} is not under ${claudeDir} \u2014 refusing`);
6767
+ process.exit(1);
6768
+ }
6769
+ let removedStateDir = false;
6770
+ try {
6771
+ await rm(harnessedRoot, { recursive: true, force: true });
6772
+ removedStateDir = true;
6773
+ } catch (e) {
6774
+ failures.push(`${harnessedRoot}: ${e.message}`);
6775
+ }
6776
+ if (removedCommands > 0)
6777
+ console.log(t("uninstall.unified.removed_commands", { count: removedCommands }));
6778
+ if (removedSkills > 0)
6779
+ console.log(t("uninstall.unified.removed_skills", { count: removedSkills }));
6780
+ if (removedSettings) console.log(t("uninstall.unified.removed_settings"));
6781
+ if (removedStateDir) console.log(t("uninstall.unified.removed_state_dir"));
6782
+ if (failures.length > 0) {
6783
+ console.error(t("uninstall.unified.partial_failure", { count: failures.length }));
6784
+ for (const f of failures) console.error(` ${f}`);
6785
+ }
6786
+ console.log(t("uninstall.unified.complete"));
6787
+ process.exit(0);
6788
+ }
6619
6789
  function registerUninstall(program2) {
6620
- program2.command("uninstall <name>").description("Uninstall an upstream (immediate by default \u2014 use --dry-run for preview)").option("--dry-run", "preview only \u2014 do not delete files (opt-in for advanced users)").option("--yes", "skip interactive confirm (CI / scripts) \u2014 fatal with --dry-run").option("--non-interactive", "alias for --yes (CI compat)").action(async (name, raw) => {
6621
- const yes = raw.yes === true || raw.nonInteractive === true;
6622
- if (yes && raw.dryRun) {
6623
- console.error(
6624
- `${t("uninstall.yes_dryrun_conflict")}
6625
- ${t("uninstall.yes_dryrun_conflict.fix", { name })}`
6626
- );
6627
- process.exit(2);
6790
+ program2.command("uninstall [name]").description(
6791
+ "Uninstall an upstream by name, or remove all harnessed own files when no name given"
6792
+ ).option("--dry-run", "preview only \u2014 do not delete files").action(async (name, raw) => {
6793
+ const dryRun = raw.dryRun === true;
6794
+ if (!name) {
6795
+ await runUnifiedUninstall(homedir(), dryRun);
6796
+ return;
6628
6797
  }
6629
6798
  const { resolveAlias: resolveAlias2 } = await Promise.resolve().then(() => (init_aliases(), aliases_exports));
6630
6799
  const resolvedName = resolveAlias2(name) ?? name;
6631
6800
  checkPathSafe(resolvedName);
6632
6801
  const manifestPath = resolve(getPackageRoot(), `manifests/tools/${resolvedName}.yaml`);
6633
- const skillPackPath = resolve(getPackageRoot(), `manifests/skill-packs/${resolvedName}.yaml`);
6802
+ const skillPackPath = resolve(
6803
+ getPackageRoot(),
6804
+ `manifests/skill-packs/${resolvedName}.yaml`
6805
+ );
6634
6806
  let yamlSrc;
6635
6807
  let chosenPath = manifestPath;
6636
6808
  try {
@@ -6652,25 +6824,25 @@ ${t("install.manifest_not_found.fix", { name: resolvedName })}`
6652
6824
  for (const e of v.errors) console.error(`error: ${e.message} at ${e.path}`);
6653
6825
  process.exit(1);
6654
6826
  }
6655
- const method = v.manifest.spec.install.method;
6656
- const dryRun = raw.dryRun === true;
6657
6827
  if (dryRun) {
6658
- console.log(t("uninstall.dry_run.preview", { name: resolvedName, method }));
6828
+ console.log(
6829
+ t("uninstall.dry_run.preview", {
6830
+ name: resolvedName,
6831
+ method: v.manifest.spec.install.method
6832
+ })
6833
+ );
6659
6834
  console.log(t("uninstall.dry_run.run_hint"));
6660
6835
  process.exit(2);
6661
6836
  }
6662
- if (!yes) {
6663
- const answer = await p.confirm({
6664
- message: t("uninstall.confirm.prompt", { name: resolvedName }),
6665
- initialValue: false
6666
- });
6667
- if (p.isCancel(answer) || answer === false) {
6668
- console.error(t("uninstall.cancelled"));
6669
- process.exit(2);
6670
- }
6837
+ const answer = await p.confirm({
6838
+ message: t("uninstall.confirm.prompt", { name: resolvedName }),
6839
+ initialValue: false
6840
+ });
6841
+ if (p.isCancel(answer) || answer === false) {
6842
+ console.error(t("uninstall.cancelled"));
6843
+ process.exit(2);
6671
6844
  }
6672
- const opts = { apply: true, dryRun: false, yes };
6673
- const result = await runUninstall(v.manifest, opts);
6845
+ const result = await runUninstall(v.manifest, { apply: true, dryRun: false, yes: true });
6674
6846
  if ("aborted" in result) {
6675
6847
  console.error(t("install.aborted", { reason: result.reason }));
6676
6848
  process.exit(2);