harnessed 3.3.0 → 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.0"};
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((resolve9, 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
- resolve9(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) => resolve9(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 settingsPath = join(homedir(), ".claude", "settings.json");
3434
+ const settingsPath3 = join(homedir(), ".claude", "settings.json");
3368
3435
  let existing;
3369
3436
  try {
3370
- existing = await readFile(settingsPath, "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: settingsPath, 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(settingsPath, newText);
3479
+ await writeFile(settingsPath3, newText);
3413
3480
  let verify;
3414
3481
  try {
3415
- verify = JSON.parse(await readFile(settingsPath, "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: [settingsPath] };
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((resolve9) => {
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
- resolve9({ 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
- resolve9({ 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
- resolve9({ 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((resolve9) => {
3742
+ return await new Promise((resolve12) => {
3676
3743
  const timer = setTimeout(() => {
3677
3744
  child.kill("SIGKILL");
3678
- resolve9({
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
- resolve9({
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
- resolve9({ 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((resolve9) => {
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
- resolve9({ sha: "", exit: -1 });
3791
+ resolve12({ sha: "", exit: -1 });
3725
3792
  }, timeoutMs);
3726
3793
  child.on("error", () => {
3727
3794
  clearTimeout(timer);
3728
- resolve9({ sha: "", exit: -1 });
3795
+ resolve12({ sha: "", exit: -1 });
3729
3796
  });
3730
3797
  child.on("close", (code) => {
3731
3798
  clearTimeout(timer);
3732
- resolve9({ 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,16 +4840,186 @@ 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
  }
4857
+
4858
+ // src/cli/lib/enableAgentTeamsInSettings.ts
4859
+ init_harnessedRoot();
4860
+ function settingsPath() {
4861
+ return resolve(homedir(), ".claude", "settings.json");
4862
+ }
4863
+ async function enableAgentTeamsInSettings() {
4864
+ const path = settingsPath();
4865
+ let raw;
4866
+ try {
4867
+ raw = await readFile(path, "utf8");
4868
+ } catch (err2) {
4869
+ const code = err2.code;
4870
+ if (code !== "ENOENT") {
4871
+ return { status: "warn", message: `read ${path} failed: ${err2.message}` };
4872
+ }
4873
+ return createFreshSettings(path);
4874
+ }
4875
+ let data;
4876
+ try {
4877
+ const parsed = JSON.parse(raw);
4878
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
4879
+ return { status: "warn", message: `${path} is not a JSON object` };
4880
+ }
4881
+ data = parsed;
4882
+ } catch (err2) {
4883
+ return { status: "warn", message: `${path} malformed JSON: ${err2.message}` };
4884
+ }
4885
+ const env = data.env ?? {};
4886
+ if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1") {
4887
+ return { status: "already-enabled", path };
4888
+ }
4889
+ const backupPath = await backupOriginal(raw);
4890
+ if (backupPath.status === "warn") return backupPath;
4891
+ data.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
4892
+ const writeErr = await atomicWrite(path, `${JSON.stringify(data, null, 2)}
4893
+ `);
4894
+ if (writeErr) return { status: "warn", message: writeErr };
4895
+ return { status: "enabled", path, backupPath: backupPath.path };
4896
+ }
4897
+ async function createFreshSettings(path) {
4898
+ const data = { env: { CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" } };
4899
+ try {
4900
+ await mkdir(join(homedir(), ".claude"), { recursive: true });
4901
+ } catch (err2) {
4902
+ return { status: "warn", message: `mkdir ~/.claude failed: ${err2.message}` };
4903
+ }
4904
+ const writeErr = await atomicWrite(path, `${JSON.stringify(data, null, 2)}
4905
+ `);
4906
+ if (writeErr) return { status: "warn", message: writeErr };
4907
+ return { status: "created", path };
4908
+ }
4909
+ async function backupOriginal(raw) {
4910
+ const backupRoot = harnessedSubdir("backups");
4911
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
4912
+ const backupPath = join(backupRoot, `settings.json.${ts}.bak`);
4913
+ try {
4914
+ await mkdir(backupRoot, { recursive: true });
4915
+ await writeFile(backupPath, raw, "utf8");
4916
+ return { status: "ok", path: backupPath };
4917
+ } catch (err2) {
4918
+ return { status: "warn", message: `backup ${backupPath} failed: ${err2.message}` };
4919
+ }
4920
+ }
4921
+ async function atomicWrite(path, content) {
4922
+ const tmpPath = `${path}.tmp-${process.pid}-${Date.now()}`;
4923
+ try {
4924
+ await writeFile(tmpPath, content, "utf8");
4925
+ await rename(tmpPath, path);
4926
+ return void 0;
4927
+ } catch (err2) {
4928
+ return `write ${path} failed: ${err2.message}`;
4929
+ }
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
+ }
4785
5023
  init_checkAgentTeams();
4786
5024
  var FLAT_LEGACY_DEPRECATED = /* @__PURE__ */ new Set(["plan-feature", "execute-task", "verify-work"]);
4787
5025
  var FLAT_LEGACY_KEEP = /* @__PURE__ */ new Set(["research", "retro", "auto"]);
@@ -4952,7 +5190,10 @@ async function listBaseManifests2(pkgRoot) {
4952
5190
  function registerSetup(program2) {
4953
5191
  program2.command("setup").description(
4954
5192
  "One-shot onboarding: install workflow skills + base manifests to ~/.claude/ (immediate by default \u2014 use --dry-run for preview)"
4955
- ).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) => {
4956
5197
  const dryRun = raw.dryRun === true;
4957
5198
  const pkgRoot = getPackageRoot();
4958
5199
  const workflowsDir = resolve(pkgRoot, "workflows");
@@ -4962,7 +5203,7 @@ function registerSetup(program2) {
4962
5203
  try {
4963
5204
  entries = await readdir(workflowsDir);
4964
5205
  } catch {
4965
- console.error(`error: workflows directory not found at ${workflowsDir}`);
5206
+ console.error(t("setup.workflows_not_found", { path: workflowsDir }));
4966
5207
  process.exit(1);
4967
5208
  }
4968
5209
  const { workflows: toInstall, deprecated } = await scanWorkflowsWithSkill(
@@ -4972,18 +5213,16 @@ function registerSetup(program2) {
4972
5213
  const depBlock = renderDeprecationBlock(deprecated);
4973
5214
  if (depBlock) console.log(depBlock);
4974
5215
  if (toInstall.length === 0) {
4975
- console.log("setup: no workflow directories with SKILL.md found \u2014 nothing to install");
5216
+ console.log(t("setup.nothing_to_install"));
4976
5217
  process.exit(2);
4977
5218
  }
4978
5219
  if (dryRun) {
4979
- console.log(
4980
- `[dry-run] setup would install ${toInstall.length} workflow(s) to ${skillsBase}:`
4981
- );
5220
+ console.log(t("setup.dry_run.header", { count: toInstall.length, path: skillsBase }));
4982
5221
  for (const wf of toInstall) {
4983
5222
  const masterTag = wf.isMaster ? " (master)" : "";
4984
5223
  console.log(` ${wf.name} \u2192 ${join(skillsBase, wf.name)}${masterTag}`);
4985
5224
  }
4986
- console.log(` run without --dry-run to execute`);
5225
+ console.log(t("setup.dry_run.run_hint"));
4987
5226
  process.exit(0);
4988
5227
  }
4989
5228
  let skillsInstalled = 0;
@@ -4996,19 +5235,53 @@ function registerSetup(program2) {
4996
5235
  console.log(` [A] installed ${wf.name} \u2192 ${dst}${masterTag}`);
4997
5236
  skillsInstalled++;
4998
5237
  } catch (e) {
4999
- console.error(` error: failed to copy ${wf.name}: ${e.message}`);
5238
+ console.error(t("setup.copy_failed", { name: wf.name, message: e.message }));
5000
5239
  process.exit(1);
5001
5240
  }
5002
5241
  }
5003
- console.log(
5004
- `
5005
- Step A complete: ${skillsInstalled} workflow skill(s) installed to ${skillsBase}`
5006
- );
5242
+ console.log(t("setup.step_a_complete", { count: skillsInstalled, path: skillsBase }));
5243
+ const cResult = await enableAgentTeamsInSettings();
5244
+ if (cResult.status === "created") {
5245
+ console.log(t("setup.step_c.created", { path: cResult.path }));
5246
+ } else if (cResult.status === "already-enabled") {
5247
+ console.log(t("setup.step_c.already_enabled", { path: cResult.path }));
5248
+ } else if (cResult.status === "enabled") {
5249
+ console.log(
5250
+ t("setup.step_c.enabled_backup", {
5251
+ path: cResult.path,
5252
+ backupPath: cResult.backupPath
5253
+ })
5254
+ );
5255
+ } else {
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 }));
5273
+ }
5007
5274
  const manifestPaths = await listBaseManifests2(pkgRoot);
5008
5275
  const b = await runStepBInstall(manifestPaths);
5009
5276
  const stepBMs = (b.elapsedMs / 1e3).toFixed(1);
5010
5277
  console.log(
5011
- `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
+ })
5012
5285
  );
5013
5286
  for (const n of b.installed) console.log(` [B] installed ${n}`);
5014
5287
  for (const n of b.alreadyInstalled)
@@ -5018,41 +5291,33 @@ Step A complete: ${skillsInstalled} workflow skill(s) installed to ${skillsBase}
5018
5291
  for (const n of b.skipped) console.log(` [B] skipped ${n}`);
5019
5292
  for (const n of b.failed) console.error(` [B] failed ${n}`);
5020
5293
  console.log(
5021
- `
5022
- 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
+ })
5023
5298
  );
5024
5299
  if (b.alreadyInstalled.length > 0 || b.installed.length > 0) {
5025
- console.log(
5026
- `
5027
- 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.`
5028
- );
5300
+ console.log(t("setup.mcp_hint"));
5029
5301
  }
5030
- console.log(
5031
- "\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"
5032
- );
5033
- console.log(
5034
- " workflows in <packageRoot>/workflows/ (Pure bundled, NOT user-dir override per D-01)"
5035
- );
5302
+ console.log(t("setup.bundled_summary"));
5303
+ console.log(t("setup.bundled_location"));
5036
5304
  process.exit(0);
5037
5305
  });
5038
5306
  }
5039
-
5040
- // src/cli/status.ts
5041
5307
  init_harnessedRoot();
5042
5308
  function registerStatus(program2) {
5043
5309
  program2.command("status").description("Show installed upstreams (from <harnessed-root>/state.json)").action(async () => {
5044
5310
  const state = await readState(process.cwd());
5045
5311
  const names = Object.keys(state.installed).sort();
5046
5312
  if (names.length === 0) {
5047
- console.log(`no installs recorded (${harnessedFile("state.json")} absent or empty)`);
5313
+ console.log(t("status.no_installs", { path: harnessedFile("state.json") }));
5048
5314
  } else {
5049
5315
  for (const n of names) {
5050
5316
  const e = state.installed[n];
5051
5317
  if (!e) continue;
5052
5318
  console.log(`${n} @ ${e.version} (installed ${e.installedAt})`);
5053
5319
  }
5054
- console.log(`
5055
- ${names.length} install${names.length === 1 ? "" : "s"} recorded`);
5320
+ console.log(t("status.summary_installs", { count: names.length }));
5056
5321
  }
5057
5322
  const lockPath = harnessedFile(".lock");
5058
5323
  try {
@@ -5064,18 +5329,20 @@ ${names.length} install${names.length === 1 ? "" : "s"} recorded`);
5064
5329
  const s = await stat(lockPath);
5065
5330
  const ageMs = Date.now() - s.mtime.getTime();
5066
5331
  const stale = ageMs > 1e4;
5067
- console.log(`
5068
- lock: held (since ${s.mtime.toISOString()})${stale ? " \u2014 STALE" : ""}`);
5069
- 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 }));
5070
5339
  } else {
5071
- console.log("\nlock: free");
5340
+ console.log(t("status.lock_free"));
5072
5341
  }
5073
5342
  } catch {
5074
5343
  }
5075
5344
  });
5076
5345
  }
5077
-
5078
- // src/cli/uninstall.ts
5079
5346
  init_path_guard();
5080
5347
 
5081
5348
  // src/uninstallers/lib/runOrPreview.ts
@@ -5092,10 +5359,10 @@ var uninstallCcHookAdd = async (ctx) => {
5092
5359
  }
5093
5360
  const abort = dryRunGate(ctx);
5094
5361
  if (abort) return abort;
5095
- const settingsPath = join(homedir(), ".claude", "settings.json");
5362
+ const settingsPath3 = join(homedir(), ".claude", "settings.json");
5096
5363
  let existing;
5097
5364
  try {
5098
- existing = await readFile(settingsPath, "utf8");
5365
+ existing = await readFile(settingsPath3, "utf8");
5099
5366
  } catch {
5100
5367
  return { ok: true, removedPaths: [] };
5101
5368
  }
@@ -5124,8 +5391,8 @@ var uninstallCcHookAdd = async (ctx) => {
5124
5391
  if (settings.hooks?.[ev]?.length === before || before === settings.hooks?.[ev]?.length) ;
5125
5392
  const newText = `${JSON.stringify(settings, null, 2)}
5126
5393
  `;
5127
- await writeFile(settingsPath, newText);
5128
- return { ok: true, removedPaths: [settingsPath] };
5394
+ await writeFile(settingsPath3, newText);
5395
+ return { ok: true, removedPaths: [settingsPath3] };
5129
5396
  };
5130
5397
 
5131
5398
  // src/uninstallers/ccPluginMarketplace.ts
@@ -5167,11 +5434,11 @@ function extractCloneTarget2(cmd) {
5167
5434
  const tokens = tail.split(/\s+/);
5168
5435
  let i = 0;
5169
5436
  while (i < tokens.length) {
5170
- const t = tokens[i];
5171
- if (t === void 0 || !t.startsWith("-")) break;
5172
- 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") {
5173
5440
  i += 2;
5174
- } else if (t.includes("=")) {
5441
+ } else if (t2.includes("=")) {
5175
5442
  i += 1;
5176
5443
  } else {
5177
5444
  i += 2;
@@ -5261,7 +5528,7 @@ var uninstallNpmCli = async (ctx) => {
5261
5528
  const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
5262
5529
  const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
5263
5530
  const isWin = process.platform === "win32";
5264
- const result = await new Promise((resolve9) => {
5531
+ const result = await new Promise((resolve12) => {
5265
5532
  const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
5266
5533
  let stderr = "";
5267
5534
  child.stderr?.setEncoding("utf8").on("data", (c) => {
@@ -5269,15 +5536,15 @@ var uninstallNpmCli = async (ctx) => {
5269
5536
  });
5270
5537
  const timer = setTimeout(() => {
5271
5538
  child.kill("SIGKILL");
5272
- resolve9({ exitCode: -1, stderr: `${stderr}[timeout]` });
5539
+ resolve12({ exitCode: -1, stderr: `${stderr}[timeout]` });
5273
5540
  }, 3e4);
5274
5541
  child.on("error", (e) => {
5275
5542
  clearTimeout(timer);
5276
- resolve9({ exitCode: -1, stderr: e.message });
5543
+ resolve12({ exitCode: -1, stderr: e.message });
5277
5544
  });
5278
5545
  child.on("close", (code) => {
5279
5546
  clearTimeout(timer);
5280
- resolve9({ exitCode: code ?? -1, stderr });
5547
+ resolve12({ exitCode: code ?? -1, stderr });
5281
5548
  });
5282
5549
  });
5283
5550
  if (result.exitCode !== 0) {
@@ -5331,8 +5598,8 @@ function registerUninstall(program2) {
5331
5598
  const yes = raw.yes === true || raw.nonInteractive === true;
5332
5599
  if (yes && raw.dryRun) {
5333
5600
  console.error(
5334
- `error: --yes is incompatible with --dry-run (dry-run does not mutate)
5335
- 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 })}`
5336
5603
  );
5337
5604
  process.exit(2);
5338
5605
  }
@@ -5351,8 +5618,8 @@ function registerUninstall(program2) {
5351
5618
  chosenPath = skillPackPath;
5352
5619
  } catch {
5353
5620
  console.error(
5354
- `error: manifest '${resolvedName}' not found
5355
- 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 })}`
5356
5623
  );
5357
5624
  process.exit(1);
5358
5625
  }
@@ -5365,28 +5632,28 @@ function registerUninstall(program2) {
5365
5632
  const method = v.manifest.spec.install.method;
5366
5633
  const dryRun = raw.dryRun === true;
5367
5634
  if (dryRun) {
5368
- console.log(`[dry-run] would uninstall '${resolvedName}' via method '${method}'`);
5369
- 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"));
5370
5637
  process.exit(2);
5371
5638
  }
5372
5639
  if (!yes) {
5373
5640
  const answer = await p.confirm({
5374
- message: `Uninstall '${resolvedName}'? This cannot be undone.`,
5641
+ message: t("uninstall.confirm.prompt", { name: resolvedName }),
5375
5642
  initialValue: false
5376
5643
  });
5377
5644
  if (p.isCancel(answer) || answer === false) {
5378
- console.error(`aborted: user cancelled`);
5645
+ console.error(t("uninstall.cancelled"));
5379
5646
  process.exit(2);
5380
5647
  }
5381
5648
  }
5382
5649
  const opts = { apply: true, dryRun: false, yes };
5383
5650
  const result = await runUninstall(v.manifest, opts);
5384
5651
  if ("aborted" in result) {
5385
- console.error(`aborted: ${result.reason}`);
5652
+ console.error(t("install.aborted", { reason: result.reason }));
5386
5653
  process.exit(2);
5387
5654
  }
5388
5655
  if (result.ok) {
5389
- console.log(`uninstalled ${resolvedName}`);
5656
+ console.log(t("uninstall.completed", { name: resolvedName }));
5390
5657
  process.exit(0);
5391
5658
  }
5392
5659
  console.error(`error: ${result.error}`);
@@ -5397,8 +5664,20 @@ function registerUninstall(program2) {
5397
5664
  // src/cli.ts
5398
5665
  init_harnessedRoot();
5399
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
+ }
5400
5679
  var program = new Command();
5401
- 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)");
5402
5681
  registerInstall(program);
5403
5682
  registerInstallBase(program);
5404
5683
  registerResearch(program);