harnessed 3.3.1 → 3.4.0

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
@@ -11,12 +11,12 @@ import lockfile from 'proper-lockfile';
11
11
  import { Command } from 'commander';
12
12
  import { Ajv } from 'ajv';
13
13
  import * as ajvFormatsNs from 'ajv-formats';
14
+ import { fileURLToPath } from 'url';
14
15
  import { createHash } from 'crypto';
15
16
  import { query } from '@anthropic-ai/claude-agent-sdk';
16
17
  import * as p from '@clack/prompts';
17
18
  import { createPatch } from 'diff';
18
19
  import pc from 'picocolors';
19
- import { fileURLToPath } from 'url';
20
20
  import { stdout, stdin } from 'process';
21
21
  import * as readline from 'readline/promises';
22
22
 
@@ -498,7 +498,7 @@ function checkTokenBudget() {
498
498
  const msg = `${items.length} skill(s) total ${total} tokens (under 1% / 2000 threshold)`;
499
499
  return { name: "token budget", status: "pass", message: msg };
500
500
  }
501
- const top = [...items].sort((a, b) => b.tokens - a.tokens).slice(0, 3).map((t) => `${t.name}:${t.tokens}`).join(", ");
501
+ const top = [...items].sort((a, b) => b.tokens - a.tokens).slice(0, 3).map((t2) => `${t2.name}:${t2.tokens}`).join(", ");
502
502
  const pct = (total / CONTEXT_WINDOW_TOKENS * 100).toFixed(2);
503
503
  const message = `${items.length} skill(s) total ${total} tokens (${pct}% of 200000) \u2014 top: ${top}`;
504
504
  return {
@@ -847,7 +847,7 @@ var init_resume = __esm({
847
847
 
848
848
  // package.json
849
849
  var package_default = {
850
- version: "3.3.1"};
850
+ version: "3.4.0"};
851
851
 
852
852
  // src/manifest/errors.ts
853
853
  function instancePathToKeyPath(instancePath) {
@@ -1600,6 +1600,71 @@ audited ${yamls.length} manifest${yamls.length === 1 ? "" : "s"} \u2014 ${findin
1600
1600
  process.exit(errorCount > 0 ? 1 : 0);
1601
1601
  });
1602
1602
  }
1603
+ var SUPPORTED = /* @__PURE__ */ new Set(["en", "zh-Hans"]);
1604
+ var currentLocale = null;
1605
+ var cache = {};
1606
+ function mapToSupported(raw) {
1607
+ if (!raw) return "en";
1608
+ if (/^zh([^a-z]|$)/i.test(raw)) return "zh-Hans";
1609
+ return "en";
1610
+ }
1611
+ function detectLocale() {
1612
+ const raw = process.env.HARNESSED_LANG || process.env.LC_ALL || process.env.LANG || process.env.LANGUAGE || safeIntlLocale();
1613
+ return mapToSupported(raw);
1614
+ }
1615
+ function safeIntlLocale() {
1616
+ try {
1617
+ return Intl.DateTimeFormat().resolvedOptions().locale;
1618
+ } catch {
1619
+ return void 0;
1620
+ }
1621
+ }
1622
+ function messagesDir() {
1623
+ const here = dirname(fileURLToPath(import.meta.url));
1624
+ const candidates = [resolve(here, "..", "..", "messages"), resolve(here, "..", "messages")];
1625
+ for (const c of candidates) {
1626
+ try {
1627
+ readFileSync(join(c, "en.json"), "utf8");
1628
+ return c;
1629
+ } catch {
1630
+ }
1631
+ }
1632
+ return resolve(process.cwd(), "messages");
1633
+ }
1634
+ function loadLocale(locale) {
1635
+ if (cache[locale]) return cache[locale];
1636
+ const path = join(messagesDir(), `${locale}.json`);
1637
+ try {
1638
+ const raw = readFileSync(path, "utf8");
1639
+ cache[locale] = JSON.parse(raw);
1640
+ } catch {
1641
+ cache[locale] = {};
1642
+ }
1643
+ return cache[locale];
1644
+ }
1645
+ function setLocale(locale) {
1646
+ if (!locale) return;
1647
+ const mapped = mapToSupported(locale);
1648
+ if (SUPPORTED.has(mapped)) currentLocale = mapped;
1649
+ }
1650
+ function getLocale() {
1651
+ if (currentLocale === null) currentLocale = detectLocale();
1652
+ return currentLocale;
1653
+ }
1654
+ function t(key, params) {
1655
+ const locale = getLocale();
1656
+ const primary = loadLocale(locale);
1657
+ let template = primary[key];
1658
+ if (template === void 0 && locale !== "en") {
1659
+ template = loadLocale("en")[key];
1660
+ }
1661
+ if (template === void 0) template = key;
1662
+ if (!params) return template;
1663
+ return template.replace(/\{\{(\w+)\}\}/g, (_match, name) => {
1664
+ const v = params[name];
1665
+ return v === void 0 ? `{{${name}}}` : String(v);
1666
+ });
1667
+ }
1603
1668
 
1604
1669
  // src/cli/audit-log.ts
1605
1670
  init_harnessedRoot();
@@ -1633,7 +1698,7 @@ function renderHumanTable(records) {
1633
1698
  }
1634
1699
  }
1635
1700
  function pipeToJq(filterExpr, lines) {
1636
- return new Promise((resolve10, reject) => {
1701
+ return new Promise((resolve12, reject) => {
1637
1702
  const child = spawn("jq", [filterExpr], {
1638
1703
  stdio: ["pipe", "inherit", "inherit"],
1639
1704
  windowsHide: true
@@ -1641,13 +1706,13 @@ function pipeToJq(filterExpr, lines) {
1641
1706
  child.on("error", (err2) => {
1642
1707
  const e = err2;
1643
1708
  if (e.code === "ENOENT") {
1644
- console.error("\u2717 jq not found in PATH \u2014 run: harnessed doctor");
1645
- resolve10(1);
1709
+ console.error(t("audit_log.jq_missing"));
1710
+ resolve12(1);
1646
1711
  } else {
1647
1712
  reject(err2);
1648
1713
  }
1649
1714
  });
1650
- child.on("close", (code) => resolve10(code ?? 0));
1715
+ child.on("close", (code) => resolve12(code ?? 0));
1651
1716
  child.stdin.write(lines.join("\n"));
1652
1717
  child.stdin.end();
1653
1718
  });
@@ -1659,23 +1724,23 @@ function registerAuditLog(program2) {
1659
1724
  async (opts) => {
1660
1725
  const tailN = opts.tail !== void 0 ? Number(opts.tail) : 50;
1661
1726
  if (Number.isNaN(tailN) || tailN < 1) {
1662
- console.error("\u2717 --tail must be a positive integer");
1727
+ console.error(t("audit_log.tail_invalid"));
1663
1728
  process.exit(2);
1664
1729
  }
1665
1730
  const headN = opts.head !== void 0 ? Number(opts.head) : void 0;
1666
1731
  if (headN !== void 0 && (Number.isNaN(headN) || headN < 1)) {
1667
- console.error("\u2717 --head must be a positive integer");
1732
+ console.error(t("audit_log.head_invalid"));
1668
1733
  process.exit(2);
1669
1734
  }
1670
1735
  const path = auditPath();
1671
1736
  if (!existsSync(path)) {
1672
- console.log(`no audit records found (${path} does not exist)`);
1737
+ console.log(t("audit_log.no_records_file", { path }));
1673
1738
  process.exit(0);
1674
1739
  }
1675
1740
  const raw = readFileSync(path, "utf8");
1676
1741
  const lines = raw.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
1677
1742
  if (lines.length === 0) {
1678
- console.log("no audit records found (audit.log is empty)");
1743
+ console.log(t("audit_log.no_records_empty"));
1679
1744
  process.exit(0);
1680
1745
  }
1681
1746
  let records = [];
@@ -1832,11 +1897,11 @@ function registerBackupList(program2) {
1832
1897
  try {
1833
1898
  dirs = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort();
1834
1899
  } catch {
1835
- console.log(`no backups found (${root} absent)`);
1900
+ console.log(t("backup.no_backups", { root }));
1836
1901
  return;
1837
1902
  }
1838
1903
  if (dirs.length === 0) {
1839
- console.log(`no backups found (${root} empty)`);
1904
+ console.log(t("backup.no_backups_empty", { root }));
1840
1905
  return;
1841
1906
  }
1842
1907
  for (const ts of dirs) {
@@ -1851,8 +1916,7 @@ function registerBackupList(program2) {
1851
1916
  console.log(`${ts} (metadata.json missing or unreadable)`);
1852
1917
  }
1853
1918
  }
1854
- console.log(`
1855
- ${dirs.length} snapshot${dirs.length === 1 ? "" : "s"} total`);
1919
+ console.log(t("backup.total_snapshots", { count: dirs.length }));
1856
1920
  });
1857
1921
  }
1858
1922
  function checkNodeVersion() {
@@ -1998,7 +2062,7 @@ function registerDoctor(program2) {
1998
2062
  if (r.status !== "pass" && r.fix) console.log(` fix: ${r.fix}`);
1999
2063
  }
2000
2064
  console.log(
2001
- hasFail ? "\nsome checks failed (see fix hints above)" : hasWarn ? "\nall checks ok (with warnings \u2014 see hints above)" : "\nall checks passed"
2065
+ hasFail ? t("doctor.summary.fail") : hasWarn ? t("doctor.summary.warn") : t("doctor.summary.pass")
2002
2066
  );
2003
2067
  }
2004
2068
  process.exit(hasFail ? 1 : 0);
@@ -3005,7 +3069,7 @@ function registerExecuteTask(program2) {
3005
3069
  "Run execute-task workflow (4-phase chain \u2192 ralph-loop COMPLETE; immediate by default \u2014 use --dry-run for preview)"
3006
3070
  ).requiredOption("--task <text>", "task description (required)").option("--workflow <name>", "workflow name", "execute-task").option("--dry-run", "preview only \u2014 do not spawn subagent (opt-in for advanced users)").option("--non-interactive", "CI / scripts").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").option("--model-tier <tier>", "override: 'inherit' bypasses per-phase phase.model (B-10)").option("--max-iterations <n>", "ralph-loop max iter (default 20)", (v) => parseInt(v, 10)).action(async (raw) => {
3007
3071
  if (!raw.task) {
3008
- console.error("error: --task <text> is required");
3072
+ console.error(t("execute_task.require_task"));
3009
3073
  process.exit(2);
3010
3074
  }
3011
3075
  const workflowName = raw.workflow ?? "execute-task";
@@ -3014,7 +3078,10 @@ function registerExecuteTask(program2) {
3014
3078
  phases = loadPhases(`workflows/${workflowName}/phases.yaml`);
3015
3079
  } catch (error) {
3016
3080
  console.error(
3017
- `error: failed to load workflows/${workflowName}/phases.yaml \u2014 ${error.message}`
3081
+ t("execute_task.load_phases_failed", {
3082
+ workflow: workflowName,
3083
+ message: error.message
3084
+ })
3018
3085
  );
3019
3086
  process.exit(2);
3020
3087
  }
@@ -3053,9 +3120,7 @@ function registerExecuteTask(program2) {
3053
3120
  // apply-immediate default
3054
3121
  });
3055
3122
  } catch (err2) {
3056
- console.warn(
3057
- `\u26A0\uFE0F before-commit pre-flight skipped (${err2.message}); subagent will biome-check at commit time.`
3058
- );
3123
+ console.warn(t("execute_task.precommit_skipped", { message: err2.message }));
3059
3124
  }
3060
3125
  const result = await runRouting(taskCtx, {
3061
3126
  maxIterations: raw.maxIterations ?? 20,
@@ -3064,7 +3129,7 @@ function registerExecuteTask(program2) {
3064
3129
  ...fallbackPhaseId ? { fallbackPhaseId } : {}
3065
3130
  });
3066
3131
  if ("aborted" in result) {
3067
- console.error(`aborted: ${result.reason}`);
3132
+ console.error(t("install.aborted", { reason: result.reason }));
3068
3133
  process.exit(2);
3069
3134
  }
3070
3135
  if ("ok" in result && result.ok === false) {
@@ -3110,8 +3175,8 @@ function registerGc(program2) {
3110
3175
  const olderMs = parseDuration(opts.olderThan ?? "30d");
3111
3176
  if (olderMs == null) {
3112
3177
  console.error(
3113
- `error: invalid --older-than '${opts.olderThan}'
3114
- fix: use format <N>{d|h|m|w} e.g. 30d / 24h / 60m / 4w`
3178
+ `${t("gc.invalid_duration", { value: opts.olderThan ?? "" })}
3179
+ ${t("gc.invalid_duration.fix")}`
3115
3180
  );
3116
3181
  process.exit(1);
3117
3182
  return;
@@ -3122,7 +3187,7 @@ function registerGc(program2) {
3122
3187
  try {
3123
3188
  dirs = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort();
3124
3189
  } catch {
3125
- console.log(`no backups found (${root} absent) \u2014 nothing to gc`);
3190
+ console.log(t("gc.no_backups", { root }));
3126
3191
  return;
3127
3192
  }
3128
3193
  const cutoff = Date.now() - olderMs;
@@ -3132,8 +3197,8 @@ function registerGc(program2) {
3132
3197
  if (kept.has(ts)) continue;
3133
3198
  const path = join(root, ts);
3134
3199
  const iso = ts.replace(/T(\d{2})-(\d{2})-(\d{2})/, "T$1:$2:$3");
3135
- const t = Date.parse(iso);
3136
- if (Number.isNaN(t) || t > cutoff) continue;
3200
+ const t2 = Date.parse(iso);
3201
+ if (Number.isNaN(t2) || t2 > cutoff) continue;
3137
3202
  let manifest = "(unknown)";
3138
3203
  try {
3139
3204
  const meta = JSON.parse(
@@ -3146,17 +3211,19 @@ function registerGc(program2) {
3146
3211
  candidates.push({ ts, path, manifest, sizeKb });
3147
3212
  }
3148
3213
  if (candidates.length === 0) {
3149
- console.log(`no snapshots older than ${opts.olderThan} (kept ${kept.size} most-recent)`);
3214
+ console.log(
3215
+ t("gc.no_old_snapshots", { cutoff: opts.olderThan ?? "", keptCount: kept.size })
3216
+ );
3150
3217
  return;
3151
3218
  }
3152
3219
  const totalKb = candidates.reduce((a, c) => a + c.sizeKb, 0);
3153
- const verb = dryRun ? "would delete" : "deleting";
3154
- console.log(`${verb} ${candidates.length} snapshot(s), ~${totalKb} KB total:`);
3220
+ const key = dryRun ? "gc.summary_will_delete" : "gc.summary_deleting";
3221
+ console.log(t(key, { count: candidates.length, sizeKb: totalKb }));
3155
3222
  for (const c of candidates) {
3156
3223
  console.log(` ${c.ts} ${c.manifest} (${c.sizeKb} KB)`);
3157
3224
  if (!dryRun) await rm(c.path, { recursive: true, force: true });
3158
3225
  }
3159
- if (dryRun) console.log("\n(dry-run \u2014 re-run without --dry-run to actually delete)");
3226
+ if (dryRun) console.log(t("gc.dry_run_rerun_hint"));
3160
3227
  });
3161
3228
  }
3162
3229
  async function confirmAt(level, ctx) {
@@ -3364,10 +3431,10 @@ var installCcHookAdd = async (ctx) => {
3364
3431
  const e = pre.errors[0] ?? err(ctx, "/", "preflight failed", "preflight");
3365
3432
  return { ok: false, phase: "preflight", error: e };
3366
3433
  }
3367
- const settingsPath2 = join(homedir(), ".claude", "settings.json");
3434
+ const settingsPath3 = join(homedir(), ".claude", "settings.json");
3368
3435
  let existing;
3369
3436
  try {
3370
- existing = await readFile(settingsPath2, "utf8");
3437
+ existing = await readFile(settingsPath3, "utf8");
3371
3438
  } catch {
3372
3439
  existing = null;
3373
3440
  }
@@ -3398,7 +3465,7 @@ var installCcHookAdd = async (ctx) => {
3398
3465
  const newText = `${JSON.stringify(settings, null, 2)}
3399
3466
  `;
3400
3467
  const plan = {
3401
- files: [{ target: settingsPath2, scope: "HOME", oldText: existing ?? "", newText }]
3468
+ files: [{ target: settingsPath3, scope: "HOME", oldText: existing ?? "", newText }]
3402
3469
  };
3403
3470
  process.stdout.write(renderDiff(plan, ctx));
3404
3471
  const conf = await confirmAt("L3", { ...ctx});
@@ -3409,10 +3476,10 @@ var installCcHookAdd = async (ctx) => {
3409
3476
  if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
3410
3477
  const bk = await backup(plan, ctx);
3411
3478
  if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
3412
- await writeFile(settingsPath2, newText);
3479
+ await writeFile(settingsPath3, newText);
3413
3480
  let verify;
3414
3481
  try {
3415
- verify = JSON.parse(await readFile(settingsPath2, "utf8"));
3482
+ verify = JSON.parse(await readFile(settingsPath3, "utf8"));
3416
3483
  } catch (e) {
3417
3484
  return {
3418
3485
  ok: false,
@@ -3440,7 +3507,7 @@ var installCcHookAdd = async (ctx) => {
3440
3507
  };
3441
3508
  }
3442
3509
  await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, "", "");
3443
- return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath2] };
3510
+ return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath3] };
3444
3511
  };
3445
3512
  function getUserClaudeJsonPath() {
3446
3513
  return join(homedir(), ".claude.json");
@@ -3476,7 +3543,7 @@ async function isPluginRegistered(pluginName) {
3476
3543
  return Object.keys(plugins).some((k) => k.split("@")[0] === pluginName);
3477
3544
  }
3478
3545
  function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
3479
- return new Promise((resolve10) => {
3546
+ return new Promise((resolve12) => {
3480
3547
  const isWin = process.platform === "win32";
3481
3548
  const child = isWin ? spawn("cmd.exe", ["/c", "claude", ...claudeArgs], { cwd, windowsHide: true }) : spawn("claude", claudeArgs, { cwd, shell: false });
3482
3549
  let stderr = "";
@@ -3485,15 +3552,15 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
3485
3552
  });
3486
3553
  const timer = setTimeout(() => {
3487
3554
  child.kill("SIGKILL");
3488
- resolve10({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
3555
+ resolve12({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
3489
3556
  }, timeoutMs);
3490
3557
  child.on("error", (e) => {
3491
3558
  clearTimeout(timer);
3492
- resolve10({ exitCode: -1, stderr: `${stderr}${e.message}` });
3559
+ resolve12({ exitCode: -1, stderr: `${stderr}${e.message}` });
3493
3560
  });
3494
3561
  child.on("close", (code) => {
3495
3562
  clearTimeout(timer);
3496
- resolve10({ exitCode: code ?? -1, stderr });
3563
+ resolve12({ exitCode: code ?? -1, stderr });
3497
3564
  });
3498
3565
  });
3499
3566
  }
@@ -3672,10 +3739,10 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
3672
3739
  child.stderr?.setEncoding("utf8").on("data", (chunk) => {
3673
3740
  stderr += chunk;
3674
3741
  });
3675
- return await new Promise((resolve10) => {
3742
+ return await new Promise((resolve12) => {
3676
3743
  const timer = setTimeout(() => {
3677
3744
  child.kill("SIGKILL");
3678
- resolve10({
3745
+ resolve12({
3679
3746
  ok: false,
3680
3747
  phase: "spawn",
3681
3748
  error: {
@@ -3690,7 +3757,7 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
3690
3757
  }, effectiveTimeoutMs);
3691
3758
  child.on("error", (err2) => {
3692
3759
  clearTimeout(timer);
3693
- resolve10({
3760
+ resolve12({
3694
3761
  ok: false,
3695
3762
  phase: "spawn",
3696
3763
  error: {
@@ -3705,14 +3772,14 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
3705
3772
  });
3706
3773
  child.on("close", (code) => {
3707
3774
  clearTimeout(timer);
3708
- resolve10({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
3775
+ resolve12({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
3709
3776
  });
3710
3777
  });
3711
3778
  }
3712
3779
 
3713
3780
  // src/installers/gitCloneWithSetup.ts
3714
3781
  function gitRevParseHead(cwd, timeoutMs = 1e4) {
3715
- return new Promise((resolve10) => {
3782
+ return new Promise((resolve12) => {
3716
3783
  const isWin = process.platform === "win32";
3717
3784
  const child = isWin ? spawn("cmd.exe", ["/c", "git", "rev-parse", "HEAD"], { cwd, windowsHide: true }) : spawn("git", ["rev-parse", "HEAD"], { cwd, shell: false });
3718
3785
  let stdout2 = "";
@@ -3721,15 +3788,15 @@ function gitRevParseHead(cwd, timeoutMs = 1e4) {
3721
3788
  });
3722
3789
  const timer = setTimeout(() => {
3723
3790
  child.kill("SIGKILL");
3724
- resolve10({ sha: "", exit: -1 });
3791
+ resolve12({ sha: "", exit: -1 });
3725
3792
  }, timeoutMs);
3726
3793
  child.on("error", () => {
3727
3794
  clearTimeout(timer);
3728
- resolve10({ sha: "", exit: -1 });
3795
+ resolve12({ sha: "", exit: -1 });
3729
3796
  });
3730
3797
  child.on("close", (code) => {
3731
3798
  clearTimeout(timer);
3732
- resolve10({ sha: stdout2.trim(), exit: code ?? -1 });
3799
+ resolve12({ sha: stdout2.trim(), exit: code ?? -1 });
3733
3800
  });
3734
3801
  });
3735
3802
  }
@@ -3740,11 +3807,11 @@ function extractCloneTarget(cmd) {
3740
3807
  const tokens = tail.split(/\s+/);
3741
3808
  let i = 0;
3742
3809
  while (i < tokens.length) {
3743
- const t = tokens[i];
3744
- if (t === void 0 || !t.startsWith("-")) break;
3745
- if (t === "--depth" || t === "--branch" || t === "-b") {
3810
+ const t2 = tokens[i];
3811
+ if (t2 === void 0 || !t2.startsWith("-")) break;
3812
+ if (t2 === "--depth" || t2 === "--branch" || t2 === "-b") {
3746
3813
  i += 2;
3747
- } else if (t.includes("=")) {
3814
+ } else if (t2.includes("=")) {
3748
3815
  i += 1;
3749
3816
  } else {
3750
3817
  i += 2;
@@ -4479,8 +4546,8 @@ function registerInstall(program2) {
4479
4546
  chosenPath = skillPackPath;
4480
4547
  } catch {
4481
4548
  console.error(
4482
- `error: manifest '${resolvedName}' not found
4483
- fix: ensure manifests/tools/${resolvedName}.yaml or manifests/skill-packs/${resolvedName}.yaml exists`
4549
+ `${t("install.manifest_not_found", { name: resolvedName })}
4550
+ ${t("install.manifest_not_found.fix", { name: resolvedName })}`
4484
4551
  );
4485
4552
  process.exit(1);
4486
4553
  }
@@ -4488,7 +4555,7 @@ function registerInstall(program2) {
4488
4555
  const v = validateManifestFile(yamlSrc, chosenPath);
4489
4556
  if (!v.ok) {
4490
4557
  for (const e of v.errors) console.error(`error: ${e.message} at ${e.path}`);
4491
- console.error(` fix: run 'harnessed audit' to inspect manifest issues`);
4558
+ console.error(t("install.audit_hint"));
4492
4559
  process.exit(1);
4493
4560
  }
4494
4561
  const dryRun = raw.dryRun === true;
@@ -4510,12 +4577,14 @@ function registerInstall(program2) {
4510
4577
  }
4511
4578
  const result = await runInstall(v.manifest, opts);
4512
4579
  if ("aborted" in result) {
4513
- console.error(`aborted: ${result.reason}`);
4580
+ console.error(t("install.aborted", { reason: result.reason }));
4514
4581
  process.exit(2);
4515
4582
  }
4516
4583
  if (result.ok) {
4517
4584
  const version = v.manifest.spec.install.method === "npm-cli" && "npm_version" in v.manifest.spec.install ? v.manifest.spec.install.npm_version : "";
4518
- console.log(`installed ${v.manifest.metadata.name}${version ? `@${version}` : ""}`);
4585
+ console.log(
4586
+ version ? t("install.success_with_version", { name: v.manifest.metadata.name, version }) : t("install.success", { name: v.manifest.metadata.name })
4587
+ );
4519
4588
  process.exit(0);
4520
4589
  }
4521
4590
  console.error(formatError(result.error));
@@ -4613,10 +4682,8 @@ function registerManifestAdd(program2) {
4613
4682
  const category = raw.category ?? "skill-packs";
4614
4683
  const outPath = `manifests/${category}/${name}.ee5-answers.json`;
4615
4684
  if (raw.nonInteractive) {
4616
- console.warn(
4617
- "[ee-5-gate] WARN: --non-interactive skips 5-question prompt (D-03 dry-run-only). plan-phase hard reject still applies."
4618
- );
4619
- console.log(`[manifest-add] dry-run preview for upstream: ${upstream} \u2192 ${outPath}`);
4685
+ console.warn(t("manifest_add.non_interactive_warn"));
4686
+ console.log(t("manifest_add.dry_run_preview", { upstream, path: outPath }));
4620
4687
  process.exit(0);
4621
4688
  }
4622
4689
  const rl = readline.createInterface({ input: stdin, output: stdout });
@@ -4629,7 +4696,7 @@ function registerManifestAdd(program2) {
4629
4696
  const a = (await rl.question(`${q}
4630
4697
  > `)).trim();
4631
4698
  if (!a) {
4632
- console.error("error: EE-5 gate requires non-empty answer");
4699
+ console.error(t("manifest_add.empty_answer"));
4633
4700
  rl.close();
4634
4701
  process.exit(1);
4635
4702
  }
@@ -4640,9 +4707,9 @@ function registerManifestAdd(program2) {
4640
4707
  if (!dryRun) {
4641
4708
  writeFileSync(outPath, `${JSON.stringify(payload, null, 2)}
4642
4709
  `, "utf8");
4643
- console.log(`[manifest-add] EE-5 gate passed; wrote ${outPath}`);
4710
+ console.log(t("manifest_add.gate_passed_wrote", { path: outPath }));
4644
4711
  } else {
4645
- console.log(`[manifest-add] EE-5 gate passed (dry-run); would write ${outPath}`);
4712
+ console.log(t("manifest_add.gate_passed_dry_run", { path: outPath }));
4646
4713
  console.log(JSON.stringify(payload, null, 2));
4647
4714
  }
4648
4715
  process.exit(0);
@@ -4655,7 +4722,7 @@ function registerResearch(program2) {
4655
4722
  "Run research workflow (search category sub-routing \u2192 spawn \u2192 verbatim COMPLETE; immediate by default \u2014 use --dry-run for preview)"
4656
4723
  ).requiredOption("--query <text>", "research prompt (required)").option("--dry-run", "preview only \u2014 do not spawn subagent (opt-in for advanced users)").option("--non-interactive", "skip all prompts (CI / scripts)").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").action(async (raw) => {
4657
4724
  if (!raw.query) {
4658
- console.error("error: --query <text> is required");
4725
+ console.error(t("research.require_query"));
4659
4726
  process.exit(2);
4660
4727
  }
4661
4728
  const taskCtx = { task: raw.query, task_type: "search" };
@@ -4668,31 +4735,33 @@ function registerResearch(program2) {
4668
4735
  ...raw.model ? { agentOpts: { modelOverride: raw.model } } : {}
4669
4736
  });
4670
4737
  if ("aborted" in preview) {
4671
- console.error(`aborted: ${preview.reason}`);
4738
+ console.error(t("install.aborted", { reason: preview.reason }));
4672
4739
  process.exit(2);
4673
4740
  }
4674
4741
  if ("ok" in preview && preview.ok === false) {
4675
4742
  console.error(`error: ${preview.phase} \u2014 ${preview.error.message}`);
4676
4743
  process.exit(1);
4677
4744
  }
4678
- console.log(`[dry-run] matched_rule: ${preview.matchedRule?.id ?? "(fallback supervisor)"}`);
4679
- console.log(`[dry-run] query: ${raw.query}`);
4680
4745
  console.log(
4681
- " (run without --dry-run to spawn the subagent and emit verbatim COMPLETE round-trip)"
4746
+ t("research.dry_run.matched_rule", {
4747
+ rule: preview.matchedRule?.id ?? "(fallback supervisor)"
4748
+ })
4682
4749
  );
4750
+ console.log(t("research.dry_run.query", { query: raw.query }));
4751
+ console.log(t("research.dry_run.run_hint"));
4683
4752
  process.exit(0);
4684
4753
  }
4685
4754
  const result = await runRouting(taskCtx, {
4686
4755
  ...raw.model ? { agentOpts: { modelOverride: raw.model } } : {}
4687
4756
  });
4688
4757
  if ("aborted" in result) {
4689
- console.error(`aborted: ${result.reason}`);
4758
+ console.error(t("install.aborted", { reason: result.reason }));
4690
4759
  process.exit(2);
4691
4760
  }
4692
4761
  if ("ok" in result && result.ok === false) {
4693
4762
  console.error(`error: ${result.phase} \u2014 ${result.error.message}`);
4694
4763
  if (result.phase === "install") {
4695
- console.error(` fix: 'harnessed install <skill>' (see error above)`);
4764
+ console.error(t("research.install_fix_hint"));
4696
4765
  }
4697
4766
  process.exit(1);
4698
4767
  }
@@ -4714,12 +4783,11 @@ function registerResume(program2) {
4714
4783
  return;
4715
4784
  }
4716
4785
  if (r.status === "no-paused-phase") {
4717
- console.error(`\u2717 ${r.error}`);
4786
+ console.error(t("resume.fail", { error: r.error }));
4718
4787
  process.exit(1);
4719
4788
  }
4720
4789
  if (r.status === "corrupt") {
4721
- console.error(`\u2717 ${r.error}
4722
- path: ${r.path}`);
4790
+ console.error(t("resume.corrupt", { error: r.error, path: r.path }));
4723
4791
  process.exit(1);
4724
4792
  }
4725
4793
  if (r.cwdWarn) console.error(r.cwdWarn);
@@ -4748,8 +4816,8 @@ function registerRollback(program2) {
4748
4816
  meta = JSON.parse(await readFile(metaPath, "utf8"));
4749
4817
  } catch (err2) {
4750
4818
  console.error(
4751
- `error: cannot read ${metaPath}: ${err2.message}
4752
- fix: run 'harnessed backup list' to see available timestamps`
4819
+ `${t("rollback.metadata_unreadable", { path: metaPath, message: err2.message })}
4820
+ ${t("rollback.metadata_unreadable.fix")}`
4753
4821
  );
4754
4822
  process.exit(1);
4755
4823
  return;
@@ -4772,14 +4840,18 @@ function registerRollback(program2) {
4772
4840
  const sha1 = createHash("sha1").update(buf).digest("hex");
4773
4841
  if (sha1 !== entry.sha1) {
4774
4842
  console.error(
4775
- `error: backup checksum mismatch for ${entry.target} (expected ${entry.sha1.slice(0, 12)}, got ${sha1.slice(0, 12)})`
4843
+ t("rollback.checksum_mismatch", {
4844
+ target: entry.target,
4845
+ expected: entry.sha1.slice(0, 12),
4846
+ actual: sha1.slice(0, 12)
4847
+ })
4776
4848
  );
4777
4849
  process.exit(1);
4778
4850
  return;
4779
4851
  }
4780
4852
  await writeFile(entry.target, normalizeEol(buf, entry.eol));
4781
4853
  }
4782
- console.log(`restored ${meta.files.length} file(s) from ${timestamp}`);
4854
+ console.log(t("rollback.restored", { count: meta.files.length, timestamp }));
4783
4855
  });
4784
4856
  }
4785
4857
 
@@ -4856,6 +4928,98 @@ async function atomicWrite(path, content) {
4856
4928
  return `write ${path} failed: ${err2.message}`;
4857
4929
  }
4858
4930
  }
4931
+
4932
+ // src/cli/lib/enableUserLangInSettings.ts
4933
+ init_harnessedRoot();
4934
+ function settingsPath2() {
4935
+ return resolve(homedir(), ".claude", "settings.json");
4936
+ }
4937
+ function detectUserLang(override) {
4938
+ if (override) {
4939
+ if (/^zh([^a-z]|$)/i.test(override)) return "zh-Hans";
4940
+ return "en";
4941
+ }
4942
+ const raw = process.env.HARNESSED_LANG || process.env.LC_ALL || process.env.LANG || process.env.LANGUAGE || safeIntlLocale2() || "";
4943
+ if (/^zh([^a-z]|$)/i.test(raw)) return "zh-Hans";
4944
+ return "en";
4945
+ }
4946
+ function safeIntlLocale2() {
4947
+ try {
4948
+ return Intl.DateTimeFormat().resolvedOptions().locale;
4949
+ } catch {
4950
+ return void 0;
4951
+ }
4952
+ }
4953
+ async function enableUserLangInSettings(override) {
4954
+ const path = settingsPath2();
4955
+ const detected = detectUserLang(override);
4956
+ let raw;
4957
+ try {
4958
+ raw = await readFile(path, "utf8");
4959
+ } catch (err2) {
4960
+ const code = err2.code;
4961
+ if (code !== "ENOENT") {
4962
+ return { status: "warn", message: `read ${path} failed: ${err2.message}` };
4963
+ }
4964
+ return createFreshSettings2(path, detected);
4965
+ }
4966
+ let data;
4967
+ try {
4968
+ const parsed = JSON.parse(raw);
4969
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
4970
+ return { status: "warn", message: `${path} is not a JSON object` };
4971
+ }
4972
+ data = parsed;
4973
+ } catch (err2) {
4974
+ return { status: "warn", message: `${path} malformed JSON: ${err2.message}` };
4975
+ }
4976
+ const env = data.env ?? {};
4977
+ const existing = env.HARNESSED_USER_LANG;
4978
+ if (typeof existing === "string" && existing.length > 0 && override === void 0) {
4979
+ return { status: "already-set", path, existing };
4980
+ }
4981
+ const backupPath = await backupOriginal2(raw);
4982
+ if (backupPath.status === "warn") return backupPath;
4983
+ data.env = { ...env, HARNESSED_USER_LANG: detected };
4984
+ const writeErr = await atomicWrite2(path, `${JSON.stringify(data, null, 2)}
4985
+ `);
4986
+ if (writeErr) return { status: "warn", message: writeErr };
4987
+ return { status: "enabled", path, backupPath: backupPath.path, detected };
4988
+ }
4989
+ async function createFreshSettings2(path, detected) {
4990
+ const data = { env: { HARNESSED_USER_LANG: detected } };
4991
+ try {
4992
+ await mkdir(join(homedir(), ".claude"), { recursive: true });
4993
+ } catch (err2) {
4994
+ return { status: "warn", message: `mkdir ~/.claude failed: ${err2.message}` };
4995
+ }
4996
+ const writeErr = await atomicWrite2(path, `${JSON.stringify(data, null, 2)}
4997
+ `);
4998
+ if (writeErr) return { status: "warn", message: writeErr };
4999
+ return { status: "created", path, detected };
5000
+ }
5001
+ async function backupOriginal2(raw) {
5002
+ const backupRoot = harnessedSubdir("backups");
5003
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
5004
+ const backupPath = join(backupRoot, `settings.json.${ts}.bak`);
5005
+ try {
5006
+ await mkdir(backupRoot, { recursive: true });
5007
+ await writeFile(backupPath, raw, "utf8");
5008
+ return { status: "ok", path: backupPath };
5009
+ } catch (err2) {
5010
+ return { status: "warn", message: `backup ${backupPath} failed: ${err2.message}` };
5011
+ }
5012
+ }
5013
+ async function atomicWrite2(path, content) {
5014
+ const tmpPath = `${path}.tmp-${process.pid}-${Date.now()}`;
5015
+ try {
5016
+ await writeFile(tmpPath, content, "utf8");
5017
+ await rename(tmpPath, path);
5018
+ return void 0;
5019
+ } catch (err2) {
5020
+ return `write ${path} failed: ${err2.message}`;
5021
+ }
5022
+ }
4859
5023
  init_checkAgentTeams();
4860
5024
  var FLAT_LEGACY_DEPRECATED = /* @__PURE__ */ new Set(["plan-feature", "execute-task", "verify-work"]);
4861
5025
  var FLAT_LEGACY_KEEP = /* @__PURE__ */ new Set(["research", "retro", "auto"]);
@@ -5026,7 +5190,10 @@ async function listBaseManifests2(pkgRoot) {
5026
5190
  function registerSetup(program2) {
5027
5191
  program2.command("setup").description(
5028
5192
  "One-shot onboarding: install workflow skills + base manifests to ~/.claude/ (immediate by default \u2014 use --dry-run for preview)"
5029
- ).option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").action(async (raw) => {
5193
+ ).option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option(
5194
+ "--user-lang <code>",
5195
+ "override detected OS locale for env.HARNESSED_USER_LANG (en | zh-Hans / zh-CN / zh-TW)"
5196
+ ).action(async (raw) => {
5030
5197
  const dryRun = raw.dryRun === true;
5031
5198
  const pkgRoot = getPackageRoot();
5032
5199
  const workflowsDir = resolve(pkgRoot, "workflows");
@@ -5036,7 +5203,7 @@ function registerSetup(program2) {
5036
5203
  try {
5037
5204
  entries = await readdir(workflowsDir);
5038
5205
  } catch {
5039
- console.error(`error: workflows directory not found at ${workflowsDir}`);
5206
+ console.error(t("setup.workflows_not_found", { path: workflowsDir }));
5040
5207
  process.exit(1);
5041
5208
  }
5042
5209
  const { workflows: toInstall, deprecated } = await scanWorkflowsWithSkill(
@@ -5046,18 +5213,16 @@ function registerSetup(program2) {
5046
5213
  const depBlock = renderDeprecationBlock(deprecated);
5047
5214
  if (depBlock) console.log(depBlock);
5048
5215
  if (toInstall.length === 0) {
5049
- console.log("setup: no workflow directories with SKILL.md found \u2014 nothing to install");
5216
+ console.log(t("setup.nothing_to_install"));
5050
5217
  process.exit(2);
5051
5218
  }
5052
5219
  if (dryRun) {
5053
- console.log(
5054
- `[dry-run] setup would install ${toInstall.length} workflow(s) to ${skillsBase}:`
5055
- );
5220
+ console.log(t("setup.dry_run.header", { count: toInstall.length, path: skillsBase }));
5056
5221
  for (const wf of toInstall) {
5057
5222
  const masterTag = wf.isMaster ? " (master)" : "";
5058
5223
  console.log(` ${wf.name} \u2192 ${join(skillsBase, wf.name)}${masterTag}`);
5059
5224
  }
5060
- console.log(` run without --dry-run to execute`);
5225
+ console.log(t("setup.dry_run.run_hint"));
5061
5226
  process.exit(0);
5062
5227
  }
5063
5228
  let skillsInstalled = 0;
@@ -5070,31 +5235,53 @@ function registerSetup(program2) {
5070
5235
  console.log(` [A] installed ${wf.name} \u2192 ${dst}${masterTag}`);
5071
5236
  skillsInstalled++;
5072
5237
  } catch (e) {
5073
- console.error(` error: failed to copy ${wf.name}: ${e.message}`);
5238
+ console.error(t("setup.copy_failed", { name: wf.name, message: e.message }));
5074
5239
  process.exit(1);
5075
5240
  }
5076
5241
  }
5077
- console.log(
5078
- `
5079
- Step A complete: ${skillsInstalled} workflow skill(s) installed to ${skillsBase}`
5080
- );
5242
+ console.log(t("setup.step_a_complete", { count: skillsInstalled, path: skillsBase }));
5081
5243
  const cResult = await enableAgentTeamsInSettings();
5082
5244
  if (cResult.status === "created") {
5083
- console.log(` [C] created ${cResult.path} + enabled Agent Teams`);
5245
+ console.log(t("setup.step_c.created", { path: cResult.path }));
5084
5246
  } else if (cResult.status === "already-enabled") {
5085
- console.log(` [C] Agent Teams already enabled (${cResult.path})`);
5247
+ console.log(t("setup.step_c.already_enabled", { path: cResult.path }));
5086
5248
  } else if (cResult.status === "enabled") {
5087
5249
  console.log(
5088
- ` [C] enabled Agent Teams in ${cResult.path} (backup saved \u2192 ${cResult.backupPath})`
5250
+ t("setup.step_c.enabled_backup", {
5251
+ path: cResult.path,
5252
+ backupPath: cResult.backupPath
5253
+ })
5089
5254
  );
5090
5255
  } else {
5091
- console.warn(` [C] Agent Teams enable skipped: ${cResult.message}`);
5256
+ console.warn(t("setup.step_c.skipped", { message: cResult.message }));
5257
+ }
5258
+ const dResult = await enableUserLangInSettings(raw.userLang);
5259
+ if (dResult.status === "created") {
5260
+ console.log(t("setup.step_d.created", { path: dResult.path, lang: dResult.detected }));
5261
+ } else if (dResult.status === "already-set") {
5262
+ console.log(t("setup.step_d.already_set", { path: dResult.path, lang: dResult.existing }));
5263
+ } else if (dResult.status === "enabled") {
5264
+ console.log(
5265
+ t("setup.step_d.enabled_backup", {
5266
+ path: dResult.path,
5267
+ lang: dResult.detected,
5268
+ backupPath: dResult.backupPath
5269
+ })
5270
+ );
5271
+ } else {
5272
+ console.warn(t("setup.step_d.skipped", { message: dResult.message }));
5092
5273
  }
5093
5274
  const manifestPaths = await listBaseManifests2(pkgRoot);
5094
5275
  const b = await runStepBInstall(manifestPaths);
5095
5276
  const stepBMs = (b.elapsedMs / 1e3).toFixed(1);
5096
5277
  console.log(
5097
- `Step B complete: ${b.installed.length} manifest(s) installed / ${b.alreadyInstalled.length} already-installed / ${b.skipped.length} skipped / ${b.failed.length} failed [parallel ${stepBMs}s]`
5278
+ t("setup.step_b_complete", {
5279
+ installed: b.installed.length,
5280
+ already: b.alreadyInstalled.length,
5281
+ skipped: b.skipped.length,
5282
+ failed: b.failed.length,
5283
+ seconds: stepBMs
5284
+ })
5098
5285
  );
5099
5286
  for (const n of b.installed) console.log(` [B] installed ${n}`);
5100
5287
  for (const n of b.alreadyInstalled)
@@ -5104,41 +5291,33 @@ Step A complete: ${skillsInstalled} workflow skill(s) installed to ${skillsBase}
5104
5291
  for (const n of b.skipped) console.log(` [B] skipped ${n}`);
5105
5292
  for (const n of b.failed) console.error(` [B] failed ${n}`);
5106
5293
  console.log(
5107
- `
5108
- setup complete: ${skillsInstalled} workflow skill(s) + ${b.installed.length + b.alreadyInstalled.length} base manifest(s) configured`
5294
+ t("setup.complete", {
5295
+ skills: skillsInstalled,
5296
+ manifests: b.installed.length + b.alreadyInstalled.length
5297
+ })
5109
5298
  );
5110
5299
  if (b.alreadyInstalled.length > 0 || b.installed.length > 0) {
5111
- console.log(
5112
- `
5113
- MCP servers configured. Run \`/mcp\` in Claude Code to verify each server's connection status. If a server shows disconnected, restart Claude Code or check the MCP command spec.`
5114
- );
5300
+ console.log(t("setup.mcp_hint"));
5115
5301
  }
5116
- console.log(
5117
- "\n\u2713 harnessed v3.0 \u4E09\u5C42\u6808\u65B9\u6CD5\u8BBA bundled \u2014 23 workflows (4 master + 18 sub + 1 standalone) + 6 disciplines + 10 judgments + ~83 capabilities ready"
5118
- );
5119
- console.log(
5120
- " workflows in <packageRoot>/workflows/ (Pure bundled, NOT user-dir override per D-01)"
5121
- );
5302
+ console.log(t("setup.bundled_summary"));
5303
+ console.log(t("setup.bundled_location"));
5122
5304
  process.exit(0);
5123
5305
  });
5124
5306
  }
5125
-
5126
- // src/cli/status.ts
5127
5307
  init_harnessedRoot();
5128
5308
  function registerStatus(program2) {
5129
5309
  program2.command("status").description("Show installed upstreams (from <harnessed-root>/state.json)").action(async () => {
5130
5310
  const state = await readState(process.cwd());
5131
5311
  const names = Object.keys(state.installed).sort();
5132
5312
  if (names.length === 0) {
5133
- console.log(`no installs recorded (${harnessedFile("state.json")} absent or empty)`);
5313
+ console.log(t("status.no_installs", { path: harnessedFile("state.json") }));
5134
5314
  } else {
5135
5315
  for (const n of names) {
5136
5316
  const e = state.installed[n];
5137
5317
  if (!e) continue;
5138
5318
  console.log(`${n} @ ${e.version} (installed ${e.installedAt})`);
5139
5319
  }
5140
- console.log(`
5141
- ${names.length} install${names.length === 1 ? "" : "s"} recorded`);
5320
+ console.log(t("status.summary_installs", { count: names.length }));
5142
5321
  }
5143
5322
  const lockPath = harnessedFile(".lock");
5144
5323
  try {
@@ -5150,18 +5329,20 @@ ${names.length} install${names.length === 1 ? "" : "s"} recorded`);
5150
5329
  const s = await stat(lockPath);
5151
5330
  const ageMs = Date.now() - s.mtime.getTime();
5152
5331
  const stale = ageMs > 1e4;
5153
- console.log(`
5154
- lock: held (since ${s.mtime.toISOString()})${stale ? " \u2014 STALE" : ""}`);
5155
- console.log(` to release: wait for process to finish or delete ${lockPath}`);
5332
+ console.log(
5333
+ t("status.lock_held", {
5334
+ since: s.mtime.toISOString(),
5335
+ staleSuffix: stale ? t("status.lock_held.stale_suffix") : ""
5336
+ })
5337
+ );
5338
+ console.log(t("status.lock_release_hint", { path: lockPath }));
5156
5339
  } else {
5157
- console.log("\nlock: free");
5340
+ console.log(t("status.lock_free"));
5158
5341
  }
5159
5342
  } catch {
5160
5343
  }
5161
5344
  });
5162
5345
  }
5163
-
5164
- // src/cli/uninstall.ts
5165
5346
  init_path_guard();
5166
5347
 
5167
5348
  // src/uninstallers/lib/runOrPreview.ts
@@ -5178,10 +5359,10 @@ var uninstallCcHookAdd = async (ctx) => {
5178
5359
  }
5179
5360
  const abort = dryRunGate(ctx);
5180
5361
  if (abort) return abort;
5181
- const settingsPath2 = join(homedir(), ".claude", "settings.json");
5362
+ const settingsPath3 = join(homedir(), ".claude", "settings.json");
5182
5363
  let existing;
5183
5364
  try {
5184
- existing = await readFile(settingsPath2, "utf8");
5365
+ existing = await readFile(settingsPath3, "utf8");
5185
5366
  } catch {
5186
5367
  return { ok: true, removedPaths: [] };
5187
5368
  }
@@ -5210,8 +5391,8 @@ var uninstallCcHookAdd = async (ctx) => {
5210
5391
  if (settings.hooks?.[ev]?.length === before || before === settings.hooks?.[ev]?.length) ;
5211
5392
  const newText = `${JSON.stringify(settings, null, 2)}
5212
5393
  `;
5213
- await writeFile(settingsPath2, newText);
5214
- return { ok: true, removedPaths: [settingsPath2] };
5394
+ await writeFile(settingsPath3, newText);
5395
+ return { ok: true, removedPaths: [settingsPath3] };
5215
5396
  };
5216
5397
 
5217
5398
  // src/uninstallers/ccPluginMarketplace.ts
@@ -5253,11 +5434,11 @@ function extractCloneTarget2(cmd) {
5253
5434
  const tokens = tail.split(/\s+/);
5254
5435
  let i = 0;
5255
5436
  while (i < tokens.length) {
5256
- const t = tokens[i];
5257
- if (t === void 0 || !t.startsWith("-")) break;
5258
- if (t === "--depth" || t === "--branch" || t === "-b") {
5437
+ const t2 = tokens[i];
5438
+ if (t2 === void 0 || !t2.startsWith("-")) break;
5439
+ if (t2 === "--depth" || t2 === "--branch" || t2 === "-b") {
5259
5440
  i += 2;
5260
- } else if (t.includes("=")) {
5441
+ } else if (t2.includes("=")) {
5261
5442
  i += 1;
5262
5443
  } else {
5263
5444
  i += 2;
@@ -5347,7 +5528,7 @@ var uninstallNpmCli = async (ctx) => {
5347
5528
  const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
5348
5529
  const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
5349
5530
  const isWin = process.platform === "win32";
5350
- const result = await new Promise((resolve10) => {
5531
+ const result = await new Promise((resolve12) => {
5351
5532
  const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
5352
5533
  let stderr = "";
5353
5534
  child.stderr?.setEncoding("utf8").on("data", (c) => {
@@ -5355,15 +5536,15 @@ var uninstallNpmCli = async (ctx) => {
5355
5536
  });
5356
5537
  const timer = setTimeout(() => {
5357
5538
  child.kill("SIGKILL");
5358
- resolve10({ exitCode: -1, stderr: `${stderr}[timeout]` });
5539
+ resolve12({ exitCode: -1, stderr: `${stderr}[timeout]` });
5359
5540
  }, 3e4);
5360
5541
  child.on("error", (e) => {
5361
5542
  clearTimeout(timer);
5362
- resolve10({ exitCode: -1, stderr: e.message });
5543
+ resolve12({ exitCode: -1, stderr: e.message });
5363
5544
  });
5364
5545
  child.on("close", (code) => {
5365
5546
  clearTimeout(timer);
5366
- resolve10({ exitCode: code ?? -1, stderr });
5547
+ resolve12({ exitCode: code ?? -1, stderr });
5367
5548
  });
5368
5549
  });
5369
5550
  if (result.exitCode !== 0) {
@@ -5417,8 +5598,8 @@ function registerUninstall(program2) {
5417
5598
  const yes = raw.yes === true || raw.nonInteractive === true;
5418
5599
  if (yes && raw.dryRun) {
5419
5600
  console.error(
5420
- `error: --yes is incompatible with --dry-run (dry-run does not mutate)
5421
- fix: harnessed uninstall ${name} --yes (immediate) OR harnessed uninstall ${name} --dry-run (preview)`
5601
+ `${t("uninstall.yes_dryrun_conflict")}
5602
+ ${t("uninstall.yes_dryrun_conflict.fix", { name })}`
5422
5603
  );
5423
5604
  process.exit(2);
5424
5605
  }
@@ -5437,8 +5618,8 @@ function registerUninstall(program2) {
5437
5618
  chosenPath = skillPackPath;
5438
5619
  } catch {
5439
5620
  console.error(
5440
- `error: manifest '${resolvedName}' not found
5441
- fix: ensure manifests/tools/${resolvedName}.yaml or manifests/skill-packs/${resolvedName}.yaml exists`
5621
+ `${t("install.manifest_not_found", { name: resolvedName })}
5622
+ ${t("install.manifest_not_found.fix", { name: resolvedName })}`
5442
5623
  );
5443
5624
  process.exit(1);
5444
5625
  }
@@ -5451,28 +5632,28 @@ function registerUninstall(program2) {
5451
5632
  const method = v.manifest.spec.install.method;
5452
5633
  const dryRun = raw.dryRun === true;
5453
5634
  if (dryRun) {
5454
- console.log(`[dry-run] would uninstall '${resolvedName}' via method '${method}'`);
5455
- console.log(` run without --dry-run to execute`);
5635
+ console.log(t("uninstall.dry_run.preview", { name: resolvedName, method }));
5636
+ console.log(t("uninstall.dry_run.run_hint"));
5456
5637
  process.exit(2);
5457
5638
  }
5458
5639
  if (!yes) {
5459
5640
  const answer = await p.confirm({
5460
- message: `Uninstall '${resolvedName}'? This cannot be undone.`,
5641
+ message: t("uninstall.confirm.prompt", { name: resolvedName }),
5461
5642
  initialValue: false
5462
5643
  });
5463
5644
  if (p.isCancel(answer) || answer === false) {
5464
- console.error(`aborted: user cancelled`);
5645
+ console.error(t("uninstall.cancelled"));
5465
5646
  process.exit(2);
5466
5647
  }
5467
5648
  }
5468
5649
  const opts = { apply: true, dryRun: false, yes };
5469
5650
  const result = await runUninstall(v.manifest, opts);
5470
5651
  if ("aborted" in result) {
5471
- console.error(`aborted: ${result.reason}`);
5652
+ console.error(t("install.aborted", { reason: result.reason }));
5472
5653
  process.exit(2);
5473
5654
  }
5474
5655
  if (result.ok) {
5475
- console.log(`uninstalled ${resolvedName}`);
5656
+ console.log(t("uninstall.completed", { name: resolvedName }));
5476
5657
  process.exit(0);
5477
5658
  }
5478
5659
  console.error(`error: ${result.error}`);
@@ -5483,8 +5664,20 @@ function registerUninstall(program2) {
5483
5664
  // src/cli.ts
5484
5665
  init_harnessedRoot();
5485
5666
  migrateLegacyHarnessedRoot();
5667
+ var argv = process.argv;
5668
+ for (let i = 2; i < argv.length; i++) {
5669
+ const a = argv[i];
5670
+ if (a === "--lang" && i + 1 < argv.length) {
5671
+ setLocale(argv[i + 1]);
5672
+ break;
5673
+ }
5674
+ if (a?.startsWith("--lang=")) {
5675
+ setLocale(a.slice("--lang=".length));
5676
+ break;
5677
+ }
5678
+ }
5486
5679
  var program = new Command();
5487
- program.name("harnessed").description("AI coding harness package manager + composition orchestrator").version(package_default.version);
5680
+ program.name("harnessed").description("AI coding harness package manager + composition orchestrator").version(package_default.version).option("--lang <code>", "output language: en | zh (auto-detect from $LANG / Intl if absent)");
5488
5681
  registerInstall(program);
5489
5682
  registerInstallBase(program);
5490
5683
  registerResearch(program);