clawt 2.13.0 → 2.14.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/index.js CHANGED
@@ -251,7 +251,26 @@ var RESET_MESSAGES = {
251
251
  // src/constants/messages/config.ts
252
252
  var CONFIG_CMD_MESSAGES = {
253
253
  /** 配置已恢复为默认值 */
254
- CONFIG_RESET_SUCCESS: "\u2713 \u914D\u7F6E\u5DF2\u6062\u590D\u4E3A\u9ED8\u8BA4\u503C"
254
+ CONFIG_RESET_SUCCESS: "\u2713 \u914D\u7F6E\u5DF2\u6062\u590D\u4E3A\u9ED8\u8BA4\u503C",
255
+ /** 配置项设置成功 */
256
+ CONFIG_SET_SUCCESS: (key, value) => `\u2713 ${key} \u5DF2\u8BBE\u7F6E\u4E3A ${value}`,
257
+ /** 获取配置值显示 */
258
+ CONFIG_GET_VALUE: (key, value) => `${key} = ${value}`,
259
+ /** 无效配置项名称 */
260
+ CONFIG_INVALID_KEY: (key, validKeys) => `\u65E0\u6548\u7684\u914D\u7F6E\u9879: ${key}
261
+ \u53EF\u7528\u7684\u914D\u7F6E\u9879: ${validKeys.join(", ")}`,
262
+ /** 布尔类型值无效 */
263
+ CONFIG_INVALID_BOOLEAN: (key) => `\u914D\u7F6E\u9879 ${key} \u4E3A\u5E03\u5C14\u7C7B\u578B\uFF0C\u4EC5\u63A5\u53D7 true \u6216 false`,
264
+ /** 数字类型值无效 */
265
+ CONFIG_INVALID_NUMBER: (key) => `\u914D\u7F6E\u9879 ${key} \u4E3A\u6570\u5B57\u7C7B\u578B\uFF0C\u8BF7\u8F93\u5165\u6709\u6548\u7684\u6570\u5B57`,
266
+ /** 枚举类型配置项值无效(通用版) */
267
+ CONFIG_INVALID_ENUM: (key, validValues) => `\u914D\u7F6E\u9879 ${key} \u4EC5\u63A5\u53D7\u4EE5\u4E0B\u503C: ${validValues.join(", ")}`,
268
+ /** 交互式选择配置项提示 */
269
+ CONFIG_SELECT_PROMPT: "\u9009\u62E9\u8981\u4FEE\u6539\u7684\u914D\u7F6E\u9879",
270
+ /** 交互式输入新值提示 */
271
+ CONFIG_INPUT_PROMPT: (key) => `\u8F93\u5165 ${key} \u7684\u65B0\u503C`,
272
+ /** 缺少 value 参数提示 */
273
+ CONFIG_MISSING_VALUE: (key) => `\u7F3A\u5C11\u914D\u7F6E\u503C\uFF0C\u7528\u6CD5: clawt config set ${key} <value>`
255
274
  };
256
275
 
257
276
  // src/constants/messages/status.ts
@@ -353,7 +372,8 @@ var CONFIG_DEFINITIONS = {
353
372
  },
354
373
  terminalApp: {
355
374
  defaultValue: "auto",
356
- description: "\u6279\u91CF resume \u4F7F\u7528\u7684\u7EC8\u7AEF\u5E94\u7528\uFF1Aauto\uFF08\u81EA\u52A8\u68C0\u6D4B\uFF09\u3001iterm2\u3001terminal\uFF08macOS\uFF09"
375
+ description: "\u6279\u91CF resume \u4F7F\u7528\u7684\u7EC8\u7AEF\u5E94\u7528\uFF1Aauto\uFF08\u81EA\u52A8\u68C0\u6D4B\uFF09\u3001iterm2\u3001terminal\uFF08macOS\uFF09",
376
+ allowedValues: VALID_TERMINAL_APPS
357
377
  },
358
378
  aliases: {
359
379
  defaultValue: {},
@@ -936,6 +956,9 @@ function writeConfig(config2) {
936
956
  function writeDefaultConfig() {
937
957
  writeConfig(DEFAULT_CONFIG);
938
958
  }
959
+ function saveConfig(config2) {
960
+ writeFileSync(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
961
+ }
939
962
  function getConfigValue(key) {
940
963
  const config2 = loadConfig();
941
964
  return config2[key];
@@ -1731,8 +1754,98 @@ function applyAliases(program2, aliases) {
1731
1754
  }
1732
1755
  }
1733
1756
 
1734
- // src/commands/list.ts
1757
+ // src/utils/config-strategy.ts
1735
1758
  import chalk4 from "chalk";
1759
+ import Enquirer3 from "enquirer";
1760
+ function isValidConfigKey(key) {
1761
+ return key in DEFAULT_CONFIG;
1762
+ }
1763
+ function getValidConfigKeys() {
1764
+ return Object.keys(DEFAULT_CONFIG);
1765
+ }
1766
+ function parseConfigValue(key, rawValue) {
1767
+ const expectedType = typeof DEFAULT_CONFIG[key];
1768
+ if (expectedType === "boolean") {
1769
+ if (rawValue === "true") return { success: true, value: true };
1770
+ if (rawValue === "false") return { success: true, value: false };
1771
+ return { success: false, error: MESSAGES.CONFIG_INVALID_BOOLEAN(key) };
1772
+ }
1773
+ if (expectedType === "number") {
1774
+ const num = Number(rawValue);
1775
+ if (Number.isNaN(num)) {
1776
+ return { success: false, error: MESSAGES.CONFIG_INVALID_NUMBER(key) };
1777
+ }
1778
+ return { success: true, value: num };
1779
+ }
1780
+ const definition = CONFIG_DEFINITIONS[key];
1781
+ if (definition.allowedValues && !definition.allowedValues.includes(rawValue)) {
1782
+ return { success: false, error: MESSAGES.CONFIG_INVALID_ENUM(key, definition.allowedValues) };
1783
+ }
1784
+ return { success: true, value: rawValue };
1785
+ }
1786
+ async function promptConfigValue(key, currentValue) {
1787
+ const expectedType = typeof currentValue;
1788
+ if (expectedType === "boolean") {
1789
+ return promptBooleanValue(key, currentValue);
1790
+ }
1791
+ if (expectedType === "number") {
1792
+ return promptNumberValue(key, currentValue);
1793
+ }
1794
+ const definition = CONFIG_DEFINITIONS[key];
1795
+ if (definition.allowedValues) {
1796
+ return promptEnumValue(key, currentValue, definition.allowedValues);
1797
+ }
1798
+ return promptStringValue(key, currentValue);
1799
+ }
1800
+ function formatConfigValue(value) {
1801
+ if (typeof value === "boolean") {
1802
+ return value ? chalk4.green("true") : chalk4.yellow("false");
1803
+ }
1804
+ return chalk4.cyan(String(value));
1805
+ }
1806
+ async function promptBooleanValue(key, currentValue) {
1807
+ const choices = [
1808
+ { name: "true", message: "true" },
1809
+ { name: "false", message: "false" }
1810
+ ];
1811
+ const selected = await new Enquirer3.Select({
1812
+ message: MESSAGES.CONFIG_INPUT_PROMPT(key),
1813
+ choices,
1814
+ initial: currentValue ? 0 : 1
1815
+ }).run();
1816
+ return selected === "true";
1817
+ }
1818
+ async function promptNumberValue(key, currentValue) {
1819
+ const input = await new Enquirer3.Input({
1820
+ message: MESSAGES.CONFIG_INPUT_PROMPT(key),
1821
+ initial: String(currentValue),
1822
+ validate: (val) => {
1823
+ if (Number.isNaN(Number(val))) return "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u6570\u5B57";
1824
+ return true;
1825
+ }
1826
+ }).run();
1827
+ return Number(input);
1828
+ }
1829
+ async function promptEnumValue(key, currentValue, allowedValues) {
1830
+ const choices = allowedValues.map((v) => ({
1831
+ name: v,
1832
+ message: v
1833
+ }));
1834
+ return await new Enquirer3.Select({
1835
+ message: MESSAGES.CONFIG_INPUT_PROMPT(key),
1836
+ choices,
1837
+ initial: allowedValues.indexOf(currentValue)
1838
+ }).run();
1839
+ }
1840
+ async function promptStringValue(key, currentValue) {
1841
+ return await new Enquirer3.Input({
1842
+ message: MESSAGES.CONFIG_INPUT_PROMPT(key),
1843
+ initial: currentValue
1844
+ }).run();
1845
+ }
1846
+
1847
+ // src/commands/list.ts
1848
+ import chalk5 from "chalk";
1736
1849
  function registerListCommand(program2) {
1737
1850
  program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
1738
1851
  handleList(options);
@@ -1769,12 +1882,12 @@ function printListAsText(projectName, worktrees) {
1769
1882
  for (const wt of worktrees) {
1770
1883
  const status = getWorktreeStatus(wt);
1771
1884
  const isIdle = status ? isWorktreeIdle(status) : false;
1772
- const pathDisplay = isIdle ? chalk4.hex("#FF8C00")(wt.path) : wt.path;
1885
+ const pathDisplay = isIdle ? chalk5.hex("#FF8C00")(wt.path) : wt.path;
1773
1886
  printInfo(` ${pathDisplay} [${wt.branch}]`);
1774
1887
  if (status) {
1775
1888
  printInfo(` ${formatWorktreeStatus(status)}`);
1776
1889
  } else {
1777
- printInfo(` ${chalk4.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
1890
+ printInfo(` ${chalk5.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
1778
1891
  }
1779
1892
  printInfo("");
1780
1893
  }
@@ -1997,7 +2110,7 @@ async function handleBatchResume(worktrees) {
1997
2110
  }
1998
2111
 
1999
2112
  // src/commands/validate.ts
2000
- import Enquirer3 from "enquirer";
2113
+ import Enquirer4 from "enquirer";
2001
2114
  var VALIDATE_RESOLVE_MESSAGES = {
2002
2115
  noWorktrees: MESSAGES.VALIDATE_NO_WORKTREES,
2003
2116
  selectBranch: MESSAGES.VALIDATE_SELECT_BRANCH,
@@ -2011,7 +2124,7 @@ function registerValidateCommand(program2) {
2011
2124
  }
2012
2125
  async function handleDirtyMainWorktree(mainWorktreePath) {
2013
2126
  printWarning("\u4E3B worktree \u5F53\u524D\u5206\u652F\u6709\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF0C\u8BF7\u9009\u62E9\u5904\u7406\u65B9\u5F0F\uFF1A\n");
2014
- const choice = await new Enquirer3.Select({
2127
+ const choice = await new Enquirer4.Select({
2015
2128
  message: "\u9009\u62E9\u5904\u7406\u65B9\u5F0F",
2016
2129
  choices: [
2017
2130
  {
@@ -2299,34 +2412,21 @@ async function handleMerge(options) {
2299
2412
  }
2300
2413
 
2301
2414
  // src/commands/config.ts
2302
- import chalk5 from "chalk";
2415
+ import chalk6 from "chalk";
2416
+ import Enquirer5 from "enquirer";
2303
2417
  function registerConfigCommand(program2) {
2304
- const configCmd = program2.command("config").description("\u67E5\u770B\u548C\u7BA1\u7406\u5168\u5C40\u914D\u7F6E").action(() => {
2305
- handleConfig();
2418
+ const configCmd = program2.command("config").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u5168\u5C40\u914D\u7F6E").action(async () => {
2419
+ await handleConfigSet();
2306
2420
  });
2307
2421
  configCmd.command("reset").description("\u5C06\u914D\u7F6E\u6062\u590D\u4E3A\u9ED8\u8BA4\u503C").action(async () => {
2308
2422
  await handleConfigReset();
2309
2423
  });
2310
- }
2311
- function handleConfig() {
2312
- const config2 = loadConfig();
2313
- logger.info("config \u547D\u4EE4\u6267\u884C\uFF0C\u5C55\u793A\u5168\u5C40\u914D\u7F6E");
2314
- printInfo(`
2315
- ${chalk5.dim("\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84:")} ${CONFIG_PATH}
2316
- `);
2317
- printSeparator();
2318
- const keys = Object.keys(DEFAULT_CONFIG);
2319
- for (let i = 0; i < keys.length; i++) {
2320
- const key = keys[i];
2321
- const value = config2[key];
2322
- const description = CONFIG_DESCRIPTIONS[key];
2323
- const formattedValue = formatConfigValue(value);
2324
- if (i === 0) printInfo("");
2325
- printInfo(` ${chalk5.bold(key)}: ${formattedValue}`);
2326
- printInfo(` ${chalk5.dim(description)}`);
2327
- printInfo("");
2328
- }
2329
- printSeparator();
2424
+ configCmd.command("set [key] [value]").description("\u4FEE\u6539\u914D\u7F6E\u9879\uFF08\u65E0\u53C2\u6570\u8FDB\u5165\u4EA4\u4E92\u5F0F\u914D\u7F6E\uFF09").action(async (key, value) => {
2425
+ await handleConfigSet(key, value);
2426
+ });
2427
+ configCmd.command("get <key>").description("\u83B7\u53D6\u5355\u4E2A\u914D\u7F6E\u9879\u7684\u503C").action((key) => {
2428
+ handleConfigGet(key);
2429
+ });
2330
2430
  }
2331
2431
  async function handleConfigReset() {
2332
2432
  logger.info("config reset \u547D\u4EE4\u6267\u884C\uFF0C\u6062\u590D\u9ED8\u8BA4\u914D\u7F6E");
@@ -2341,19 +2441,57 @@ async function handleConfigReset() {
2341
2441
  writeDefaultConfig();
2342
2442
  printSuccess(MESSAGES.CONFIG_RESET_SUCCESS);
2343
2443
  }
2344
- function formatConfigValue(value) {
2345
- if (typeof value === "boolean") {
2346
- return value ? chalk5.green("true") : chalk5.yellow("false");
2444
+ async function handleConfigSet(key, value) {
2445
+ if (!key) {
2446
+ await handleInteractiveConfigSet();
2447
+ return;
2347
2448
  }
2348
- if (typeof value === "object" && value !== null) {
2349
- const entries = Object.entries(value);
2350
- if (entries.length === 0) {
2351
- return chalk5.dim("(\u65E0)");
2352
- }
2353
- const lines = entries.map(([k, v]) => ` ${chalk5.cyan(k)} \u2192 ${chalk5.cyan(String(v))}`);
2354
- return "\n" + lines.join("\n");
2449
+ if (!isValidConfigKey(key)) {
2450
+ printError(MESSAGES.CONFIG_INVALID_KEY(key, getValidConfigKeys()));
2451
+ return;
2452
+ }
2453
+ if (value === void 0) {
2454
+ printError(MESSAGES.CONFIG_MISSING_VALUE(key));
2455
+ return;
2456
+ }
2457
+ const result = parseConfigValue(key, value);
2458
+ if (!result.success) {
2459
+ printError(result.error);
2460
+ return;
2355
2461
  }
2356
- return chalk5.cyan(String(value));
2462
+ const config2 = loadConfig();
2463
+ config2[key] = result.value;
2464
+ saveConfig(config2);
2465
+ logger.info(`config set \u547D\u4EE4\u6267\u884C\uFF0C\u8BBE\u7F6E ${key} = ${String(result.value)}`);
2466
+ printSuccess(MESSAGES.CONFIG_SET_SUCCESS(key, String(result.value)));
2467
+ }
2468
+ async function handleInteractiveConfigSet() {
2469
+ const config2 = loadConfig();
2470
+ const keys = Object.keys(DEFAULT_CONFIG);
2471
+ logger.info("config set \u547D\u4EE4\u6267\u884C\uFF0C\u8FDB\u5165\u4EA4\u4E92\u5F0F\u914D\u7F6E");
2472
+ const choices = keys.map((k) => ({
2473
+ name: k,
2474
+ message: `${k}: ${formatConfigValue(config2[k])} ${chalk6.dim(`\u2014 ${CONFIG_DESCRIPTIONS[k]}`)}`
2475
+ }));
2476
+ const selectedKey = await new Enquirer5.Select({
2477
+ message: MESSAGES.CONFIG_SELECT_PROMPT,
2478
+ choices
2479
+ }).run();
2480
+ const currentValue = config2[selectedKey];
2481
+ const newValue = await promptConfigValue(selectedKey, currentValue);
2482
+ config2[selectedKey] = newValue;
2483
+ saveConfig(config2);
2484
+ printSuccess(MESSAGES.CONFIG_SET_SUCCESS(selectedKey, String(newValue)));
2485
+ }
2486
+ function handleConfigGet(key) {
2487
+ if (!isValidConfigKey(key)) {
2488
+ printError(MESSAGES.CONFIG_INVALID_KEY(key, getValidConfigKeys()));
2489
+ return;
2490
+ }
2491
+ const config2 = loadConfig();
2492
+ const value = config2[key];
2493
+ logger.info(`config get \u547D\u4EE4\u6267\u884C\uFF0C\u83B7\u53D6 ${key} = ${String(value)}`);
2494
+ printInfo(MESSAGES.CONFIG_GET_VALUE(key, String(value)));
2357
2495
  }
2358
2496
 
2359
2497
  // src/commands/sync.ts
@@ -2440,7 +2578,7 @@ async function handleReset() {
2440
2578
  }
2441
2579
 
2442
2580
  // src/commands/status.ts
2443
- import chalk6 from "chalk";
2581
+ import chalk7 from "chalk";
2444
2582
  function registerStatusCommand(program2) {
2445
2583
  program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
2446
2584
  handleStatus(options);
@@ -2536,7 +2674,7 @@ function printStatusAsJson(result) {
2536
2674
  }
2537
2675
  function printStatusAsText(result) {
2538
2676
  printDoubleSeparator();
2539
- printInfo(` ${chalk6.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
2677
+ printInfo(` ${chalk7.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
2540
2678
  printDoubleSeparator();
2541
2679
  printInfo("");
2542
2680
  printMainSection(result.main);
@@ -2549,17 +2687,17 @@ function printStatusAsText(result) {
2549
2687
  printDoubleSeparator();
2550
2688
  }
2551
2689
  function printMainSection(main) {
2552
- printInfo(` ${chalk6.bold("\u25C6")} ${chalk6.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
2553
- printInfo(` \u5206\u652F: ${chalk6.bold(main.branch)}`);
2690
+ printInfo(` ${chalk7.bold("\u25C6")} ${chalk7.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
2691
+ printInfo(` \u5206\u652F: ${chalk7.bold(main.branch)}`);
2554
2692
  if (main.isClean) {
2555
- printInfo(` \u72B6\u6001: ${chalk6.green("\u2713 \u5E72\u51C0")}`);
2693
+ printInfo(` \u72B6\u6001: ${chalk7.green("\u2713 \u5E72\u51C0")}`);
2556
2694
  } else {
2557
- printInfo(` \u72B6\u6001: ${chalk6.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
2695
+ printInfo(` \u72B6\u6001: ${chalk7.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
2558
2696
  }
2559
2697
  printInfo("");
2560
2698
  }
2561
2699
  function printWorktreesSection(worktrees, total) {
2562
- printInfo(` ${chalk6.bold("\u25C6")} ${chalk6.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
2700
+ printInfo(` ${chalk7.bold("\u25C6")} ${chalk7.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
2563
2701
  printInfo("");
2564
2702
  if (worktrees.length === 0) {
2565
2703
  printInfo(` ${MESSAGES.STATUS_NO_WORKTREES}`);
@@ -2571,39 +2709,39 @@ function printWorktreesSection(worktrees, total) {
2571
2709
  }
2572
2710
  function printWorktreeItem(wt) {
2573
2711
  const statusLabel = formatChangeStatusLabel(wt.changeStatus);
2574
- printInfo(` ${chalk6.bold("\u25CF")} ${chalk6.bold(wt.branch)} [${statusLabel}]`);
2712
+ printInfo(` ${chalk7.bold("\u25CF")} ${chalk7.bold(wt.branch)} [${statusLabel}]`);
2575
2713
  const parts = [];
2576
2714
  if (wt.insertions > 0 || wt.deletions > 0) {
2577
- parts.push(`${chalk6.green(`+${wt.insertions}`)} ${chalk6.red(`-${wt.deletions}`)}`);
2715
+ parts.push(`${chalk7.green(`+${wt.insertions}`)} ${chalk7.red(`-${wt.deletions}`)}`);
2578
2716
  }
2579
2717
  if (wt.commitsAhead > 0) {
2580
- parts.push(chalk6.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`));
2718
+ parts.push(chalk7.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`));
2581
2719
  }
2582
2720
  if (wt.commitsBehind > 0) {
2583
- parts.push(chalk6.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`));
2721
+ parts.push(chalk7.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`));
2584
2722
  } else {
2585
- parts.push(chalk6.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65"));
2723
+ parts.push(chalk7.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65"));
2586
2724
  }
2587
2725
  printInfo(` ${parts.join(" ")}`);
2588
2726
  if (wt.hasSnapshot) {
2589
- printInfo(` ${chalk6.blue("\u6709 validate \u5FEB\u7167")}`);
2727
+ printInfo(` ${chalk7.blue("\u6709 validate \u5FEB\u7167")}`);
2590
2728
  }
2591
2729
  printInfo("");
2592
2730
  }
2593
2731
  function formatChangeStatusLabel(status) {
2594
2732
  switch (status) {
2595
2733
  case "committed":
2596
- return chalk6.green(MESSAGES.STATUS_CHANGE_COMMITTED);
2734
+ return chalk7.green(MESSAGES.STATUS_CHANGE_COMMITTED);
2597
2735
  case "uncommitted":
2598
- return chalk6.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
2736
+ return chalk7.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
2599
2737
  case "conflict":
2600
- return chalk6.red(MESSAGES.STATUS_CHANGE_CONFLICT);
2738
+ return chalk7.red(MESSAGES.STATUS_CHANGE_CONFLICT);
2601
2739
  case "clean":
2602
- return chalk6.gray(MESSAGES.STATUS_CHANGE_CLEAN);
2740
+ return chalk7.gray(MESSAGES.STATUS_CHANGE_CLEAN);
2603
2741
  }
2604
2742
  }
2605
2743
  function printSnapshotsSection(snapshots) {
2606
- printInfo(` ${chalk6.bold("\u25C6")} ${chalk6.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.length} \u4E2A)`);
2744
+ printInfo(` ${chalk7.bold("\u25C6")} ${chalk7.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.length} \u4E2A)`);
2607
2745
  printInfo("");
2608
2746
  if (snapshots.length === 0) {
2609
2747
  printInfo(` ${MESSAGES.STATUS_NO_SNAPSHOTS}`);
@@ -2611,15 +2749,15 @@ function printSnapshotsSection(snapshots) {
2611
2749
  return;
2612
2750
  }
2613
2751
  for (const snap of snapshots) {
2614
- const orphanLabel = snap.worktreeExists ? "" : ` ${chalk6.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED)}`;
2615
- const icon = snap.worktreeExists ? chalk6.blue("\u25CF") : chalk6.yellow("\u26A0");
2752
+ const orphanLabel = snap.worktreeExists ? "" : ` ${chalk7.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED)}`;
2753
+ const icon = snap.worktreeExists ? chalk7.blue("\u25CF") : chalk7.yellow("\u26A0");
2616
2754
  printInfo(` ${icon} ${snap.branch}${orphanLabel}`);
2617
2755
  }
2618
2756
  printInfo("");
2619
2757
  }
2620
2758
 
2621
2759
  // src/commands/alias.ts
2622
- import chalk7 from "chalk";
2760
+ import chalk8 from "chalk";
2623
2761
  function getRegisteredCommandNames(program2) {
2624
2762
  return program2.commands.map((cmd) => cmd.name());
2625
2763
  }
@@ -2640,7 +2778,7 @@ ${MESSAGES.ALIAS_LIST_TITLE}
2640
2778
  `);
2641
2779
  printSeparator();
2642
2780
  for (const [alias, command] of entries) {
2643
- printInfo(` ${chalk7.bold(alias)} \u2192 ${chalk7.cyan(command)}`);
2781
+ printInfo(` ${chalk8.bold(alias)} \u2192 ${chalk8.cyan(command)}`);
2644
2782
  }
2645
2783
  printInfo("");
2646
2784
  printSeparator();
@@ -243,7 +243,26 @@ var RESET_MESSAGES = {
243
243
  // src/constants/messages/config.ts
244
244
  var CONFIG_CMD_MESSAGES = {
245
245
  /** 配置已恢复为默认值 */
246
- CONFIG_RESET_SUCCESS: "\u2713 \u914D\u7F6E\u5DF2\u6062\u590D\u4E3A\u9ED8\u8BA4\u503C"
246
+ CONFIG_RESET_SUCCESS: "\u2713 \u914D\u7F6E\u5DF2\u6062\u590D\u4E3A\u9ED8\u8BA4\u503C",
247
+ /** 配置项设置成功 */
248
+ CONFIG_SET_SUCCESS: (key, value) => `\u2713 ${key} \u5DF2\u8BBE\u7F6E\u4E3A ${value}`,
249
+ /** 获取配置值显示 */
250
+ CONFIG_GET_VALUE: (key, value) => `${key} = ${value}`,
251
+ /** 无效配置项名称 */
252
+ CONFIG_INVALID_KEY: (key, validKeys) => `\u65E0\u6548\u7684\u914D\u7F6E\u9879: ${key}
253
+ \u53EF\u7528\u7684\u914D\u7F6E\u9879: ${validKeys.join(", ")}`,
254
+ /** 布尔类型值无效 */
255
+ CONFIG_INVALID_BOOLEAN: (key) => `\u914D\u7F6E\u9879 ${key} \u4E3A\u5E03\u5C14\u7C7B\u578B\uFF0C\u4EC5\u63A5\u53D7 true \u6216 false`,
256
+ /** 数字类型值无效 */
257
+ CONFIG_INVALID_NUMBER: (key) => `\u914D\u7F6E\u9879 ${key} \u4E3A\u6570\u5B57\u7C7B\u578B\uFF0C\u8BF7\u8F93\u5165\u6709\u6548\u7684\u6570\u5B57`,
258
+ /** 枚举类型配置项值无效(通用版) */
259
+ CONFIG_INVALID_ENUM: (key, validValues) => `\u914D\u7F6E\u9879 ${key} \u4EC5\u63A5\u53D7\u4EE5\u4E0B\u503C: ${validValues.join(", ")}`,
260
+ /** 交互式选择配置项提示 */
261
+ CONFIG_SELECT_PROMPT: "\u9009\u62E9\u8981\u4FEE\u6539\u7684\u914D\u7F6E\u9879",
262
+ /** 交互式输入新值提示 */
263
+ CONFIG_INPUT_PROMPT: (key) => `\u8F93\u5165 ${key} \u7684\u65B0\u503C`,
264
+ /** 缺少 value 参数提示 */
265
+ CONFIG_MISSING_VALUE: (key) => `\u7F3A\u5C11\u914D\u7F6E\u503C\uFF0C\u7528\u6CD5: clawt config set ${key} <value>`
247
266
  };
248
267
 
249
268
  // src/constants/messages/status.ts
@@ -306,6 +325,9 @@ var MESSAGES = {
306
325
  ...ALIAS_MESSAGES
307
326
  };
308
327
 
328
+ // src/constants/terminal.ts
329
+ var VALID_TERMINAL_APPS = ["auto", "iterm2", "terminal"];
330
+
309
331
  // src/constants/config.ts
310
332
  var CONFIG_DEFINITIONS = {
311
333
  autoDeleteBranch: {
@@ -330,7 +352,8 @@ var CONFIG_DEFINITIONS = {
330
352
  },
331
353
  terminalApp: {
332
354
  defaultValue: "auto",
333
- description: "\u6279\u91CF resume \u4F7F\u7528\u7684\u7EC8\u7AEF\u5E94\u7528\uFF1Aauto\uFF08\u81EA\u52A8\u68C0\u6D4B\uFF09\u3001iterm2\u3001terminal\uFF08macOS\uFF09"
355
+ description: "\u6279\u91CF resume \u4F7F\u7528\u7684\u7EC8\u7AEF\u5E94\u7528\uFF1Aauto\uFF08\u81EA\u52A8\u68C0\u6D4B\uFF09\u3001iterm2\u3001terminal\uFF08macOS\uFF09",
356
+ allowedValues: VALID_TERMINAL_APPS
334
357
  },
335
358
  aliases: {
336
359
  defaultValue: {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,22 @@
1
1
  import type { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import { CONFIG_PATH, DEFAULT_CONFIG, CONFIG_DESCRIPTIONS, MESSAGES } from '../constants/index.js';
3
+ import Enquirer from 'enquirer';
4
+ import { DEFAULT_CONFIG, CONFIG_DESCRIPTIONS, MESSAGES } from '../constants/index.js';
4
5
  import { logger } from '../logger/index.js';
5
- import { loadConfig, writeDefaultConfig, printInfo, printSuccess, printSeparator, confirmDestructiveAction } from '../utils/index.js';
6
+ import {
7
+ loadConfig,
8
+ saveConfig,
9
+ writeDefaultConfig,
10
+ printInfo,
11
+ printSuccess,
12
+ printError,
13
+ confirmDestructiveAction,
14
+ isValidConfigKey,
15
+ getValidConfigKeys,
16
+ parseConfigValue,
17
+ promptConfigValue,
18
+ formatConfigValue,
19
+ } from '../utils/index.js';
6
20
  import type { ClawtConfig } from '../types/index.js';
7
21
 
8
22
  /**
@@ -12,9 +26,9 @@ import type { ClawtConfig } from '../types/index.js';
12
26
  export function registerConfigCommand(program: Command): void {
13
27
  const configCmd = program
14
28
  .command('config')
15
- .description('查看和管理全局配置')
16
- .action(() => {
17
- handleConfig();
29
+ .description('交互式查看和修改全局配置')
30
+ .action(async () => {
31
+ await handleConfigSet();
18
32
  });
19
33
 
20
34
  configCmd
@@ -23,36 +37,20 @@ export function registerConfigCommand(program: Command): void {
23
37
  .action(async () => {
24
38
  await handleConfigReset();
25
39
  });
26
- }
27
-
28
- /**
29
- * 执行 config 命令的核心逻辑,读取并展示配置列表
30
- */
31
- function handleConfig(): void {
32
- const config = loadConfig();
33
40
 
34
- logger.info('config 命令执行,展示全局配置');
35
-
36
- printInfo(`\n${chalk.dim('配置文件路径:')} ${CONFIG_PATH}\n`);
37
- printSeparator();
38
-
39
- const keys = Object.keys(DEFAULT_CONFIG) as Array<keyof ClawtConfig>;
40
-
41
- for (let i = 0; i < keys.length; i++) {
42
- const key = keys[i];
43
- const value = config[key];
44
- const description = CONFIG_DESCRIPTIONS[key];
45
- const formattedValue = formatConfigValue(value);
46
-
47
- // 第一个配置项前增加空行,与下横线前的空行对称
48
- if (i === 0) printInfo('');
49
- printInfo(` ${chalk.bold(key)}: ${formattedValue}`);
50
- printInfo(` ${chalk.dim(description)}`);
51
- // 配置项之间及最后一项与下横线之间保持统一空行间距
52
- printInfo('');
53
- }
41
+ configCmd
42
+ .command('set [key] [value]')
43
+ .description('修改配置项(无参数进入交互式配置)')
44
+ .action(async (key?: string, value?: string) => {
45
+ await handleConfigSet(key, value);
46
+ });
54
47
 
55
- printSeparator();
48
+ configCmd
49
+ .command('get <key>')
50
+ .description('获取单个配置项的值')
51
+ .action((key: string) => {
52
+ handleConfigGet(key);
53
+ });
56
54
  }
57
55
 
58
56
  /**
@@ -76,22 +74,91 @@ async function handleConfigReset(): Promise<void> {
76
74
  }
77
75
 
78
76
  /**
79
- * 格式化配置值的显示样式
80
- * @param {ClawtConfig[keyof ClawtConfig]} value - 配置值
81
- * @returns {string} 格式化后的字符串
77
+ * 执行 config set 子命令:直接设置或交互式配置
78
+ * @param {string} [key] - 配置项名称(可选,无参数进入交互式)
79
+ * @param {string} [value] - 配置值(可选)
82
80
  */
83
- function formatConfigValue(value: ClawtConfig[keyof ClawtConfig]): string {
84
- if (typeof value === 'boolean') {
85
- return value ? chalk.green('true') : chalk.yellow('false');
81
+ async function handleConfigSet(key?: string, value?: string): Promise<void> {
82
+ // 无参数进入交互式配置
83
+ if (!key) {
84
+ await handleInteractiveConfigSet();
85
+ return;
86
+ }
87
+
88
+ // 校验 key 有效性
89
+ if (!isValidConfigKey(key)) {
90
+ printError(MESSAGES.CONFIG_INVALID_KEY(key, getValidConfigKeys()));
91
+ return;
92
+ }
93
+
94
+ // 有 key 无 value 时提示缺少参数
95
+ if (value === undefined) {
96
+ printError(MESSAGES.CONFIG_MISSING_VALUE(key));
97
+ return;
98
+ }
99
+
100
+ // 解析并校验值
101
+ const result = parseConfigValue(key, value);
102
+ if (!result.success) {
103
+ printError(result.error);
104
+ return;
86
105
  }
87
- // 对象类型(如 aliases)按键值对逐行展示
88
- if (typeof value === 'object' && value !== null) {
89
- const entries = Object.entries(value);
90
- if (entries.length === 0) {
91
- return chalk.dim('(无)');
92
- }
93
- const lines = entries.map(([k, v]) => ` ${chalk.cyan(k)} ${chalk.cyan(String(v))}`);
94
- return '\n' + lines.join('\n');
106
+
107
+ // 加载、修改、持久化
108
+ const config = loadConfig();
109
+ (config as unknown as Record<string, unknown>)[key] = result.value;
110
+ saveConfig(config);
111
+
112
+ logger.info(`config set 命令执行,设置 ${key} = ${String(result.value)}`);
113
+ printSuccess(MESSAGES.CONFIG_SET_SUCCESS(key, String(result.value)));
114
+ }
115
+
116
+ /**
117
+ * 交互式配置修改:列出所有配置项供用户选择并修改
118
+ */
119
+ async function handleInteractiveConfigSet(): Promise<void> {
120
+ const config = loadConfig();
121
+ const keys = Object.keys(DEFAULT_CONFIG) as Array<keyof ClawtConfig>;
122
+
123
+ logger.info('config set 命令执行,进入交互式配置');
124
+
125
+ // 构建选择列表,显示配置项名称、当前值和描述
126
+ const choices = keys.map((k) => ({
127
+ name: k,
128
+ message: `${k}: ${formatConfigValue(config[k])} ${chalk.dim(`— ${CONFIG_DESCRIPTIONS[k]}`)}`,
129
+ }));
130
+
131
+ // @ts-expect-error enquirer 类型声明未导出 Select 类,但运行时存在
132
+ const selectedKey: keyof ClawtConfig = await new Enquirer.Select({
133
+ message: MESSAGES.CONFIG_SELECT_PROMPT,
134
+ choices,
135
+ }).run();
136
+
137
+ // 根据类型和 allowedValues 自动选择提示策略
138
+ const currentValue = config[selectedKey];
139
+ const newValue = await promptConfigValue(selectedKey, currentValue);
140
+
141
+ // 持久化并提示成功
142
+ (config as unknown as Record<string, unknown>)[selectedKey] = newValue;
143
+ saveConfig(config);
144
+
145
+ printSuccess(MESSAGES.CONFIG_SET_SUCCESS(selectedKey, String(newValue)));
146
+ }
147
+
148
+ /**
149
+ * 执行 config get 子命令:获取单个配置项的值
150
+ * @param {string} key - 配置项名称
151
+ */
152
+ function handleConfigGet(key: string): void {
153
+ // 校验 key 有效性
154
+ if (!isValidConfigKey(key)) {
155
+ printError(MESSAGES.CONFIG_INVALID_KEY(key, getValidConfigKeys()));
156
+ return;
95
157
  }
96
- return chalk.cyan(String(value));
158
+
159
+ const config = loadConfig();
160
+ const value = config[key];
161
+
162
+ logger.info(`config get 命令执行,获取 ${key} = ${String(value)}`);
163
+ printInfo(MESSAGES.CONFIG_GET_VALUE(key, String(value)));
97
164
  }
@@ -1,4 +1,5 @@
1
1
  import type { ClawtConfig, ConfigDefinitions } from '../types/index.js';
2
+ import { VALID_TERMINAL_APPS } from './terminal.js';
2
3
 
3
4
  /** Claude Code 系统约束提示,禁止代码执行完成后构建项目验证 */
4
5
  export const APPEND_SYSTEM_PROMPT =
@@ -32,6 +33,7 @@ export const CONFIG_DEFINITIONS: ConfigDefinitions = {
32
33
  terminalApp: {
33
34
  defaultValue: 'auto',
34
35
  description: '批量 resume 使用的终端应用:auto(自动检测)、iterm2、terminal(macOS)',
36
+ allowedValues: VALID_TERMINAL_APPS,
35
37
  },
36
38
  aliases: {
37
39
  defaultValue: {} as Record<string, string>,