clawt 3.1.1 → 3.1.3

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/README.md CHANGED
@@ -43,10 +43,10 @@ clawt merge -b branch-1 -m "feat: 实现xxx功能"
43
43
  ```bash
44
44
  clawt init # 以当前分支作为主工作分支进行初始化
45
45
  clawt init -b <branch> # 指定主工作分支名
46
- clawt init show # 查看当前项目的 init 配置(JSON 格式)
46
+ clawt init show # 交互式查看和修改项目配置
47
47
  ```
48
48
 
49
- 设置项目的主工作分支。重复执行会更新主工作分支配置。
49
+ 设置项目的主工作分支。重复执行会更新主工作分支配置。`init show` 提供交互式面板,可查看和修改项目配置项(如 validate 成功后自动执行的命令)。
50
50
 
51
51
  ### `clawt run` — 创建 worktree 并执行任务
52
52
 
@@ -113,7 +113,7 @@ clawt resume -b <branch> # 指定分支
113
113
  clawt resume # 交互式多选(按创建日期分组)
114
114
  ```
115
115
 
116
- 不传 `-b` 时,分支列表按创建日期分组显示,支持全局全选和按组全选。选 1 个在当前终端恢复,选多个自动在独立终端 Tab 中批量恢复(仅 macOS)。
116
+ 不传 `-b` 时,分支列表按创建日期分组显示,支持全局全选和按组全选。选 1 个默认在新终端 Tab 中恢复(设置 `resumeInPlace: true` 可改为在当前终端就地恢复),选多个自动在独立终端 Tab 中批量恢复(仅 macOS)。
117
117
 
118
118
  如果目标 worktree 存在历史会话,会自动继续上次对话(`--continue`)。
119
119
 
@@ -140,7 +140,7 @@ clawt validate -b <branch> -r "pnpm test & pnpm build" # 并行执行多个命
140
140
 
141
141
  当 patch apply 失败(目标分支与主分支差异过大)时,会自动询问是否执行 `sync` 同步主分支到目标 worktree,无需手动操作。
142
142
 
143
- `-r, --run` 选项可在 validate 成功后自动在主 worktree 中执行指定命令(如测试、构建等),命令执行失败不影响 validate 结果。支持用 `&` 分隔多个命令并行执行:
143
+ `-r, --run` 选项可在 validate 成功后自动在主 worktree 中执行指定命令(如测试、构建等),命令执行失败不影响 validate 结果。不传 `-r` 时会自动从项目配置的 `validateRunCommand` 读取(可通过 `clawt init show` 设置)。支持用 `&` 分隔多个命令并行执行:
144
144
 
145
145
  | 用法 | 行为 |
146
146
  | ---- | ---- |
@@ -290,6 +290,7 @@ clawt alias remove l
290
290
  | `confirmDestructiveOps` | `true` | 破坏性操作前确认 |
291
291
  | `maxConcurrency` | `0` | run 命令最大并发数,`0` 为不限制 |
292
292
  | `terminalApp` | `"auto"` | 批量 resume 使用的终端:`auto` / `iterm2` / `terminal` |
293
+ | `resumeInPlace` | `false` | resume 单选时在当前终端就地恢复,`false` 则在新 Tab 中打开 |
293
294
  | `aliases` | `{}` | 命令别名映射(如 `{"l": "list", "r": "run"}`) |
294
295
  | `autoUpdate` | `true` | 自动检查新版本(每 24 小时检查一次 npm registry) |
295
296
 
package/dist/index.js CHANGED
@@ -460,7 +460,11 @@ var INIT_MESSAGES = {
460
460
  /** 项目未初始化(requireProjectConfig 使用) */
461
461
  PROJECT_NOT_INITIALIZED: "\u9879\u76EE\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F",
462
462
  /** 项目配置缺少 clawtMainWorkBranch 字段 */
463
- PROJECT_CONFIG_MISSING_BRANCH: "\u9879\u76EE\u914D\u7F6E\u7F3A\u5C11\u4E3B\u5DE5\u4F5C\u5206\u652F\u4FE1\u606F\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F"
463
+ PROJECT_CONFIG_MISSING_BRANCH: "\u9879\u76EE\u914D\u7F6E\u7F3A\u5C11\u4E3B\u5DE5\u4F5C\u5206\u652F\u4FE1\u606F\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F",
464
+ /** init show 交互式面板选择配置项提示 */
465
+ INIT_SELECT_PROMPT: "\u9009\u62E9\u8981\u4FEE\u6539\u7684\u9879\u76EE\u914D\u7F6E\u9879",
466
+ /** init show 交互式面板配置项修改成功 */
467
+ INIT_SET_SUCCESS: (key, value) => `\u2713 \u9879\u76EE\u914D\u7F6E ${key} \u5DF2\u8BBE\u7F6E\u4E3A ${value}`
464
468
  };
465
469
 
466
470
  // src/constants/messages/interactive-panel.ts
@@ -580,6 +584,10 @@ var CONFIG_DEFINITIONS = {
580
584
  description: "\u6279\u91CF resume \u4F7F\u7528\u7684\u7EC8\u7AEF\u5E94\u7528\uFF1Aauto\uFF08\u81EA\u52A8\u68C0\u6D4B\uFF09\u3001iterm2\u3001terminal\uFF08macOS\uFF09",
581
585
  allowedValues: VALID_TERMINAL_APPS
582
586
  },
587
+ resumeInPlace: {
588
+ defaultValue: false,
589
+ description: "resume \u5355\u9009\u65F6\u662F\u5426\u5728\u5F53\u524D\u7EC8\u7AEF\u5C31\u5730\u6253\u5F00\uFF0Cfalse \u5219\u901A\u8FC7 terminalApp \u5728\u65B0 Tab \u4E2D\u6253\u5F00"
590
+ },
583
591
  aliases: {
584
592
  defaultValue: {},
585
593
  description: "\u547D\u4EE4\u522B\u540D\u6620\u5C04"
@@ -604,6 +612,32 @@ function deriveConfigDescriptions(definitions) {
604
612
  var DEFAULT_CONFIG = deriveDefaultConfig(CONFIG_DEFINITIONS);
605
613
  var CONFIG_DESCRIPTIONS = deriveConfigDescriptions(CONFIG_DEFINITIONS);
606
614
 
615
+ // src/constants/project-config.ts
616
+ var PROJECT_CONFIG_DEFINITIONS = {
617
+ clawtMainWorkBranch: {
618
+ defaultValue: "",
619
+ description: "\u4E3B worktree \u7684\u5DE5\u4F5C\u5206\u652F\u540D"
620
+ },
621
+ validateRunCommand: {
622
+ defaultValue: void 0,
623
+ description: "validate \u6210\u529F\u540E\u81EA\u52A8\u6267\u884C\u7684\u547D\u4EE4\uFF08-r \u7684\u9ED8\u8BA4\u503C\uFF09"
624
+ }
625
+ };
626
+ function deriveDefaultConfig2(definitions) {
627
+ const entries = Object.entries(definitions).map(
628
+ ([key, def]) => [key, def.defaultValue]
629
+ );
630
+ return Object.fromEntries(entries);
631
+ }
632
+ function deriveConfigDescriptions2(definitions) {
633
+ const entries = Object.entries(definitions).map(
634
+ ([key, def]) => [key, def.description]
635
+ );
636
+ return Object.fromEntries(entries);
637
+ }
638
+ var PROJECT_DEFAULT_CONFIG = deriveDefaultConfig2(PROJECT_CONFIG_DEFINITIONS);
639
+ var PROJECT_CONFIG_DESCRIPTIONS = deriveConfigDescriptions2(PROJECT_CONFIG_DEFINITIONS);
640
+
607
641
  // src/constants/git.ts
608
642
  var AUTO_SAVE_COMMIT_MESSAGE = "chore: auto-save before sync";
609
643
 
@@ -1255,6 +1289,10 @@ function getMainWorkBranch() {
1255
1289
  const config2 = requireProjectConfig();
1256
1290
  return config2.clawtMainWorkBranch;
1257
1291
  }
1292
+ function getValidateRunCommand() {
1293
+ const config2 = loadProjectConfig();
1294
+ return config2?.validateRunCommand || void 0;
1295
+ }
1258
1296
 
1259
1297
  // src/utils/validate-branch.ts
1260
1298
  function getValidateBranchName(branchName) {
@@ -2713,7 +2751,7 @@ function parseConfigValue(key, rawValue) {
2713
2751
  }
2714
2752
  return { success: true, value: rawValue };
2715
2753
  }
2716
- async function promptConfigValue(key, currentValue) {
2754
+ async function promptConfigValue(key, currentValue, allowedValues) {
2717
2755
  const expectedType = typeof currentValue;
2718
2756
  if (expectedType === "boolean") {
2719
2757
  return promptBooleanValue(key, currentValue);
@@ -2721,18 +2759,43 @@ async function promptConfigValue(key, currentValue) {
2721
2759
  if (expectedType === "number") {
2722
2760
  return promptNumberValue(key, currentValue);
2723
2761
  }
2724
- const definition = CONFIG_DEFINITIONS[key];
2725
- if (definition.allowedValues) {
2726
- return promptEnumValue(key, currentValue, definition.allowedValues);
2762
+ if (allowedValues) {
2763
+ return promptEnumValue(key, currentValue, allowedValues);
2727
2764
  }
2728
2765
  return promptStringValue(key, currentValue);
2729
2766
  }
2730
2767
  function formatConfigValue(value) {
2768
+ if (value === void 0 || value === null) {
2769
+ return chalk7.dim("(\u672A\u8BBE\u7F6E)");
2770
+ }
2731
2771
  if (typeof value === "boolean") {
2732
2772
  return value ? chalk7.green("true") : chalk7.yellow("false");
2733
2773
  }
2734
2774
  return chalk7.cyan(String(value));
2735
2775
  }
2776
+ async function interactiveConfigEditor(config2, definitions, options) {
2777
+ const keys = Object.keys(definitions);
2778
+ const disabledKeys = options?.disabledKeys ?? {};
2779
+ const configRecord = config2;
2780
+ const choices = keys.map((k) => {
2781
+ const isDisabled = k in disabledKeys;
2782
+ const value = configRecord[k];
2783
+ const isObject = typeof value === "object" && value !== null;
2784
+ return {
2785
+ name: k,
2786
+ message: `${k}: ${isObject || isDisabled ? chalk7.dim(isObject ? JSON.stringify(value) : String(value ?? "")) : formatConfigValue(value)} ${chalk7.dim(`\u2014 ${definitions[k].description}`)}`,
2787
+ ...isDisabled && { disabled: disabledKeys[k] }
2788
+ };
2789
+ });
2790
+ const selectedKey = await new Enquirer4.Select({
2791
+ message: options?.selectPrompt ?? MESSAGES.CONFIG_SELECT_PROMPT,
2792
+ choices
2793
+ }).run();
2794
+ const currentValue = configRecord[selectedKey];
2795
+ const definition = definitions[selectedKey];
2796
+ const newValue = await promptConfigValue(selectedKey, currentValue, definition.allowedValues);
2797
+ return { key: selectedKey, newValue };
2798
+ }
2736
2799
  async function promptBooleanValue(key, currentValue) {
2737
2800
  const choices = [
2738
2801
  { name: "true", message: "true" },
@@ -2770,7 +2833,7 @@ async function promptEnumValue(key, currentValue, allowedValues) {
2770
2833
  async function promptStringValue(key, currentValue) {
2771
2834
  return await new Enquirer4.Input({
2772
2835
  message: MESSAGES.CONFIG_INPUT_PROMPT(key),
2773
- initial: currentValue
2836
+ initial: currentValue || ""
2774
2837
  }).run();
2775
2838
  }
2776
2839
 
@@ -2905,56 +2968,6 @@ async function checkForUpdates(currentVersion) {
2905
2968
  }
2906
2969
  }
2907
2970
 
2908
- // src/utils/json.ts
2909
- function primitiveToString(value) {
2910
- if (value === void 0) {
2911
- return "undefined";
2912
- }
2913
- if (value === null) {
2914
- return "null";
2915
- }
2916
- if (typeof value === "symbol") {
2917
- return value.toString();
2918
- }
2919
- if (typeof value === "function") {
2920
- return `[Function: ${value.name || "anonymous"}]`;
2921
- }
2922
- return String(value);
2923
- }
2924
- function safeStringify(value, indent = 2) {
2925
- if (value === null || typeof value !== "object") {
2926
- return primitiveToString(value);
2927
- }
2928
- try {
2929
- const seen = /* @__PURE__ */ new WeakSet();
2930
- return JSON.stringify(
2931
- value,
2932
- (_key, val) => {
2933
- if (typeof val === "bigint") {
2934
- return val.toString();
2935
- }
2936
- if (typeof val === "undefined" || typeof val === "function" || typeof val === "symbol") {
2937
- return primitiveToString(val);
2938
- }
2939
- if (typeof val === "object" && val !== null) {
2940
- if (seen.has(val)) {
2941
- return "[Circular]";
2942
- }
2943
- seen.add(val);
2944
- }
2945
- return val;
2946
- },
2947
- indent
2948
- );
2949
- } catch {
2950
- try {
2951
- return JSON.stringify(String(value), null, indent);
2952
- } catch {
2953
- return "[Unserializable]";
2954
- }
2955
- }
2956
- }
2957
-
2958
2971
  // src/utils/interactive-panel.ts
2959
2972
  import { createInterface as createInterface2 } from "readline";
2960
2973
 
@@ -3805,7 +3818,13 @@ async function handleResume(options) {
3805
3818
  return;
3806
3819
  }
3807
3820
  if (targetWorktrees.length === 1) {
3808
- launchInteractiveClaude(targetWorktrees[0], { autoContinue: true });
3821
+ const inPlace = getConfigValue("resumeInPlace");
3822
+ if (inPlace) {
3823
+ launchInteractiveClaude(targetWorktrees[0], { autoContinue: true });
3824
+ } else {
3825
+ const hasPreviousSession = hasClaudeSessionHistory(targetWorktrees[0].path);
3826
+ launchInteractiveClaudeInNewTerminal(targetWorktrees[0], hasPreviousSession);
3827
+ }
3809
3828
  } else {
3810
3829
  await handleBatchResume(targetWorktrees);
3811
3830
  }
@@ -4106,6 +4125,12 @@ async function executeRunCommand(command, mainWorktreePath) {
4106
4125
  await executeParallelCommands(commands, mainWorktreePath);
4107
4126
  }
4108
4127
  }
4128
+ function resolveRunCommand(optionRun) {
4129
+ if (optionRun) {
4130
+ return optionRun;
4131
+ }
4132
+ return getValidateRunCommand();
4133
+ }
4109
4134
  async function handleValidate(options) {
4110
4135
  if (options.clean) {
4111
4136
  await handleValidateClean(options);
@@ -4138,8 +4163,9 @@ async function handleValidate(options) {
4138
4163
  }
4139
4164
  await handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted);
4140
4165
  }
4141
- if (options.run) {
4142
- await executeRunCommand(options.run, mainWorktreePath);
4166
+ const runCommand = resolveRunCommand(options.run);
4167
+ if (runCommand) {
4168
+ await executeRunCommand(runCommand, mainWorktreePath);
4143
4169
  }
4144
4170
  }
4145
4171
 
@@ -4264,8 +4290,6 @@ async function handleMerge(options) {
4264
4290
  }
4265
4291
 
4266
4292
  // src/commands/config.ts
4267
- import chalk11 from "chalk";
4268
- import Enquirer5 from "enquirer";
4269
4293
  function registerConfigCommand(program2) {
4270
4294
  const configCmd = program2.command("config").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u5168\u5C40\u914D\u7F6E").action(async () => {
4271
4295
  await handleConfigSet();
@@ -4319,25 +4343,19 @@ async function handleConfigSet(key, value) {
4319
4343
  }
4320
4344
  async function handleInteractiveConfigSet() {
4321
4345
  const config2 = loadConfig();
4322
- const keys = Object.keys(DEFAULT_CONFIG);
4323
4346
  logger.info("config set \u547D\u4EE4\u6267\u884C\uFF0C\u8FDB\u5165\u4EA4\u4E92\u5F0F\u914D\u7F6E");
4324
- const choices = keys.map((k) => {
4325
- const isObject = typeof DEFAULT_CONFIG[k] === "object";
4326
- return {
4327
- name: k,
4328
- message: `${k}: ${isObject ? chalk11.dim(JSON.stringify(config2[k])) : formatConfigValue(config2[k])} ${chalk11.dim(`\u2014 ${CONFIG_DESCRIPTIONS[k]}`)}`,
4329
- ...isObject && { disabled: CONFIG_ALIAS_DISABLED_HINT }
4330
- };
4347
+ const disabledKeys = {};
4348
+ for (const k of Object.keys(DEFAULT_CONFIG)) {
4349
+ if (typeof DEFAULT_CONFIG[k] === "object") {
4350
+ disabledKeys[k] = CONFIG_ALIAS_DISABLED_HINT;
4351
+ }
4352
+ }
4353
+ const { key, newValue } = await interactiveConfigEditor(config2, CONFIG_DEFINITIONS, {
4354
+ disabledKeys
4331
4355
  });
4332
- const selectedKey = await new Enquirer5.Select({
4333
- message: MESSAGES.CONFIG_SELECT_PROMPT,
4334
- choices
4335
- }).run();
4336
- const currentValue = config2[selectedKey];
4337
- const newValue = await promptConfigValue(selectedKey, currentValue);
4338
- config2[selectedKey] = newValue;
4356
+ config2[key] = newValue;
4339
4357
  saveConfig(config2);
4340
- printSuccess(MESSAGES.CONFIG_SET_SUCCESS(selectedKey, String(newValue)));
4358
+ printSuccess(MESSAGES.CONFIG_SET_SUCCESS(key, String(newValue)));
4341
4359
  }
4342
4360
  function handleConfigGet(key) {
4343
4361
  if (!isValidConfigKey(key)) {
@@ -4381,7 +4399,7 @@ async function handleReset() {
4381
4399
  }
4382
4400
 
4383
4401
  // src/commands/status.ts
4384
- import chalk12 from "chalk";
4402
+ import chalk11 from "chalk";
4385
4403
  function registerStatusCommand(program2) {
4386
4404
  program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8\uFF08\u652F\u6301 --json \u683C\u5F0F\u8F93\u51FA\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").option("-i, --interactive", "\u4EA4\u4E92\u5F0F\u9762\u677F\u6A21\u5F0F").action(async (options) => {
4387
4405
  await handleStatus(options);
@@ -4499,7 +4517,7 @@ function printStatusAsJson(result) {
4499
4517
  }
4500
4518
  function printStatusAsText(result) {
4501
4519
  printDoubleSeparator();
4502
- printInfo(` ${chalk12.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
4520
+ printInfo(` ${chalk11.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
4503
4521
  printDoubleSeparator();
4504
4522
  printInfo("");
4505
4523
  printMainSection(result.main);
@@ -4512,17 +4530,17 @@ function printStatusAsText(result) {
4512
4530
  printDoubleSeparator();
4513
4531
  }
4514
4532
  function printMainSection(main2) {
4515
- printInfo(` ${chalk12.bold("\u25C6")} ${chalk12.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
4516
- printInfo(` \u5206\u652F: ${chalk12.bold(main2.branch)}`);
4533
+ printInfo(` ${chalk11.bold("\u25C6")} ${chalk11.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
4534
+ printInfo(` \u5206\u652F: ${chalk11.bold(main2.branch)}`);
4517
4535
  if (main2.isClean) {
4518
- printInfo(` \u72B6\u6001: ${chalk12.green("\u2713 \u5E72\u51C0")}`);
4536
+ printInfo(` \u72B6\u6001: ${chalk11.green("\u2713 \u5E72\u51C0")}`);
4519
4537
  } else {
4520
- printInfo(` \u72B6\u6001: ${chalk12.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
4538
+ printInfo(` \u72B6\u6001: ${chalk11.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
4521
4539
  }
4522
4540
  printInfo("");
4523
4541
  }
4524
4542
  function printWorktreesSection(worktrees, total) {
4525
- printInfo(` ${chalk12.bold("\u25C6")} ${chalk12.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
4543
+ printInfo(` ${chalk11.bold("\u25C6")} ${chalk11.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
4526
4544
  printInfo("");
4527
4545
  if (worktrees.length === 0) {
4528
4546
  printInfo(` ${MESSAGES.STATUS_NO_WORKTREES}`);
@@ -4534,56 +4552,56 @@ function printWorktreesSection(worktrees, total) {
4534
4552
  }
4535
4553
  function printWorktreeItem(wt) {
4536
4554
  const statusLabel = formatChangeStatusLabel2(wt.changeStatus);
4537
- printInfo(` ${chalk12.bold("\u25CF")} ${chalk12.bold(wt.branch)} [${statusLabel}]`);
4555
+ printInfo(` ${chalk11.bold("\u25CF")} ${chalk11.bold(wt.branch)} [${statusLabel}]`);
4538
4556
  if (wt.insertions > 0 || wt.deletions > 0) {
4539
- printInfo(` ${chalk12.green(`+${wt.insertions}`)} ${chalk12.red(`-${wt.deletions}`)}`);
4557
+ printInfo(` ${chalk11.green(`+${wt.insertions}`)} ${chalk11.red(`-${wt.deletions}`)}`);
4540
4558
  }
4541
4559
  if (wt.commitsAhead > 0) {
4542
- printInfo(` ${chalk12.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`)}`);
4560
+ printInfo(` ${chalk11.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`)}`);
4543
4561
  }
4544
4562
  if (wt.commitsBehind > 0) {
4545
- printInfo(` ${chalk12.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`)}`);
4563
+ printInfo(` ${chalk11.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`)}`);
4546
4564
  } else {
4547
- printInfo(` ${chalk12.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65")}`);
4565
+ printInfo(` ${chalk11.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65")}`);
4548
4566
  }
4549
4567
  if (wt.createdAt) {
4550
4568
  const relativeTime = formatRelativeTime(wt.createdAt);
4551
4569
  if (relativeTime) {
4552
- printInfo(` ${chalk12.gray(MESSAGES.STATUS_CREATED_AT(relativeTime))}`);
4570
+ printInfo(` ${chalk11.gray(MESSAGES.STATUS_CREATED_AT(relativeTime))}`);
4553
4571
  }
4554
4572
  }
4555
4573
  if (wt.snapshotTime) {
4556
4574
  const relativeTime = formatRelativeTime(wt.snapshotTime);
4557
4575
  if (relativeTime) {
4558
- printInfo(` ${chalk12.green(MESSAGES.STATUS_LAST_VALIDATED(relativeTime))}`);
4576
+ printInfo(` ${chalk11.green(MESSAGES.STATUS_LAST_VALIDATED(relativeTime))}`);
4559
4577
  }
4560
4578
  } else {
4561
- printInfo(` ${chalk12.red(MESSAGES.STATUS_NOT_VALIDATED)}`);
4579
+ printInfo(` ${chalk11.red(MESSAGES.STATUS_NOT_VALIDATED)}`);
4562
4580
  }
4563
4581
  printInfo("");
4564
4582
  }
4565
4583
  function formatChangeStatusLabel2(status) {
4566
4584
  switch (status) {
4567
4585
  case "committed":
4568
- return chalk12.green(MESSAGES.STATUS_CHANGE_COMMITTED);
4586
+ return chalk11.green(MESSAGES.STATUS_CHANGE_COMMITTED);
4569
4587
  case "uncommitted":
4570
- return chalk12.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
4588
+ return chalk11.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
4571
4589
  case "conflict":
4572
- return chalk12.red(MESSAGES.STATUS_CHANGE_CONFLICT);
4590
+ return chalk11.red(MESSAGES.STATUS_CHANGE_CONFLICT);
4573
4591
  case "clean":
4574
- return chalk12.gray(MESSAGES.STATUS_CHANGE_CLEAN);
4592
+ return chalk11.gray(MESSAGES.STATUS_CHANGE_CLEAN);
4575
4593
  }
4576
4594
  }
4577
4595
  function printSnapshotsSection(snapshots) {
4578
- printInfo(` ${chalk12.bold("\u25C6")} ${chalk12.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.total} \u4E2A)`);
4596
+ printInfo(` ${chalk11.bold("\u25C6")} ${chalk11.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.total} \u4E2A)`);
4579
4597
  if (snapshots.orphaned > 0) {
4580
- printInfo(` ${chalk12.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED(snapshots.orphaned))}`);
4598
+ printInfo(` ${chalk11.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED(snapshots.orphaned))}`);
4581
4599
  }
4582
4600
  printInfo("");
4583
4601
  }
4584
4602
 
4585
4603
  // src/commands/alias.ts
4586
- import chalk13 from "chalk";
4604
+ import chalk12 from "chalk";
4587
4605
  function getRegisteredCommandNames(program2) {
4588
4606
  return program2.commands.map((cmd) => cmd.name());
4589
4607
  }
@@ -4604,7 +4622,7 @@ ${MESSAGES.ALIAS_LIST_TITLE}
4604
4622
  `);
4605
4623
  printSeparator();
4606
4624
  for (const [alias, command] of entries) {
4607
- printInfo(` ${chalk13.bold(alias)} \u2192 ${chalk13.cyan(command)}`);
4625
+ printInfo(` ${chalk12.bold(alias)} \u2192 ${chalk12.cyan(command)}`);
4608
4626
  }
4609
4627
  printInfo("");
4610
4628
  printSeparator();
@@ -4653,7 +4671,7 @@ function registerAliasCommand(program2) {
4653
4671
  // src/commands/projects.ts
4654
4672
  import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
4655
4673
  import { join as join8 } from "path";
4656
- import chalk14 from "chalk";
4674
+ import chalk13 from "chalk";
4657
4675
  function registerProjectsCommand(program2) {
4658
4676
  program2.command("projects [name]").description("\u5C55\u793A\u6240\u6709\u9879\u76EE\u7684 worktree \u6982\u89C8\uFF0C\u6216\u67E5\u770B\u6307\u5B9A\u9879\u76EE\u7684 worktree \u8BE6\u60C5").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((name, options) => {
4659
4677
  handleProjects({ name, json: options.json });
@@ -4772,7 +4790,7 @@ function sortByLastActiveTimeDesc(projects) {
4772
4790
  }
4773
4791
  function printProjectsOverviewAsText(result) {
4774
4792
  printDoubleSeparator();
4775
- printInfo(` ${chalk14.bold.cyan(MESSAGES.PROJECTS_OVERVIEW_TITLE)}`);
4793
+ printInfo(` ${chalk13.bold.cyan(MESSAGES.PROJECTS_OVERVIEW_TITLE)}`);
4776
4794
  printDoubleSeparator();
4777
4795
  printInfo("");
4778
4796
  if (result.projects.length === 0) {
@@ -4786,7 +4804,7 @@ function printProjectsOverviewAsText(result) {
4786
4804
  }
4787
4805
  printSeparator();
4788
4806
  printInfo("");
4789
- printInfo(` \u5171 ${chalk14.bold(String(result.totalProjects))} \u4E2A\u9879\u76EE ${chalk14.gray(MESSAGES.PROJECTS_TOTAL_DISK_USAGE(formatDiskSize(result.totalDiskUsage)))}`);
4807
+ printInfo(` \u5171 ${chalk13.bold(String(result.totalProjects))} \u4E2A\u9879\u76EE ${chalk13.gray(MESSAGES.PROJECTS_TOTAL_DISK_USAGE(formatDiskSize(result.totalDiskUsage)))}`);
4790
4808
  printInfo("");
4791
4809
  printDoubleSeparator();
4792
4810
  }
@@ -4794,16 +4812,16 @@ function printProjectOverviewItem(project) {
4794
4812
  const relativeTime = formatRelativeTime(project.lastActiveTime);
4795
4813
  const activeLabel = relativeTime ? MESSAGES.PROJECTS_LAST_ACTIVE(relativeTime) : "";
4796
4814
  const diskLabel = MESSAGES.PROJECTS_DISK_USAGE(formatDiskSize(project.diskUsage));
4797
- printInfo(` ${chalk14.bold("\u25CF")} ${chalk14.bold(project.name)}`);
4798
- printInfo(` ${MESSAGES.PROJECTS_WORKTREE_COUNT(project.worktreeCount)} ${chalk14.gray(activeLabel)} ${chalk14.gray(diskLabel)}`);
4815
+ printInfo(` ${chalk13.bold("\u25CF")} ${chalk13.bold(project.name)}`);
4816
+ printInfo(` ${MESSAGES.PROJECTS_WORKTREE_COUNT(project.worktreeCount)} ${chalk13.gray(activeLabel)} ${chalk13.gray(diskLabel)}`);
4799
4817
  printInfo("");
4800
4818
  }
4801
4819
  function printProjectDetailAsText(result) {
4802
4820
  printDoubleSeparator();
4803
- printInfo(` ${chalk14.bold.cyan(MESSAGES.PROJECTS_DETAIL_TITLE(result.name))}`);
4821
+ printInfo(` ${chalk13.bold.cyan(MESSAGES.PROJECTS_DETAIL_TITLE(result.name))}`);
4804
4822
  printDoubleSeparator();
4805
4823
  printInfo("");
4806
- printInfo(` ${chalk14.bold("\u25C6")} ${chalk14.bold(MESSAGES.PROJECTS_PATH(result.projectDir))}`);
4824
+ printInfo(` ${chalk13.bold("\u25C6")} ${chalk13.bold(MESSAGES.PROJECTS_PATH(result.projectDir))}`);
4807
4825
  printInfo(` ${MESSAGES.PROJECTS_TOTAL_DISK_USAGE(formatDiskSize(result.totalDiskUsage))}`);
4808
4826
  printInfo("");
4809
4827
  printSeparator();
@@ -4823,9 +4841,9 @@ function printWorktreeDetailItem(wt) {
4823
4841
  const relativeTime = formatRelativeTime(wt.lastModifiedTime);
4824
4842
  const modifiedLabel = relativeTime ? MESSAGES.PROJECTS_LAST_MODIFIED(relativeTime) : "";
4825
4843
  const diskLabel = MESSAGES.PROJECTS_DISK_USAGE(formatDiskSize(wt.diskUsage));
4826
- printInfo(` ${chalk14.bold("\u25CF")} ${chalk14.bold(wt.branch)}`);
4844
+ printInfo(` ${chalk13.bold("\u25CF")} ${chalk13.bold(wt.branch)}`);
4827
4845
  printInfo(` ${wt.path}`);
4828
- printInfo(` ${chalk14.gray(modifiedLabel)} ${chalk14.gray(diskLabel)}`);
4846
+ printInfo(` ${chalk13.gray(modifiedLabel)} ${chalk13.gray(diskLabel)}`);
4829
4847
  printInfo("");
4830
4848
  }
4831
4849
 
@@ -5048,16 +5066,23 @@ function registerInitCommand(program2) {
5048
5066
  await handleInit(options);
5049
5067
  });
5050
5068
  initCmd.addCommand(
5051
- new Cmd("show").description("\u5C55\u793A\u5F53\u524D\u9879\u76EE\u7684 init \u914D\u7F6E").action(() => {
5052
- handleInitShow();
5069
+ new Cmd("show").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u9879\u76EE\u914D\u7F6E").action(async () => {
5070
+ await handleInitShow();
5053
5071
  })
5054
5072
  );
5055
5073
  }
5056
- function handleInitShow() {
5074
+ async function handleInitShow() {
5057
5075
  validateMainWorktree();
5058
5076
  const config2 = requireProjectConfig();
5059
- const configJson = safeStringify(config2);
5060
- printInfo(MESSAGES.INIT_SHOW(configJson));
5077
+ logger.info("init show \u547D\u4EE4\u6267\u884C\uFF0C\u8FDB\u5165\u4EA4\u4E92\u5F0F\u9879\u76EE\u914D\u7F6E");
5078
+ const { key, newValue } = await interactiveConfigEditor(
5079
+ config2,
5080
+ PROJECT_CONFIG_DEFINITIONS,
5081
+ { selectPrompt: MESSAGES.INIT_SELECT_PROMPT }
5082
+ );
5083
+ const updatedConfig = { ...config2, [key]: newValue };
5084
+ saveProjectConfig(updatedConfig);
5085
+ printSuccess(MESSAGES.INIT_SET_SUCCESS(key, String(newValue)));
5061
5086
  }
5062
5087
  async function handleInit(options) {
5063
5088
  validateMainWorktree();
@@ -437,7 +437,11 @@ var INIT_MESSAGES = {
437
437
  /** 项目未初始化(requireProjectConfig 使用) */
438
438
  PROJECT_NOT_INITIALIZED: "\u9879\u76EE\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F",
439
439
  /** 项目配置缺少 clawtMainWorkBranch 字段 */
440
- PROJECT_CONFIG_MISSING_BRANCH: "\u9879\u76EE\u914D\u7F6E\u7F3A\u5C11\u4E3B\u5DE5\u4F5C\u5206\u652F\u4FE1\u606F\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F"
440
+ PROJECT_CONFIG_MISSING_BRANCH: "\u9879\u76EE\u914D\u7F6E\u7F3A\u5C11\u4E3B\u5DE5\u4F5C\u5206\u652F\u4FE1\u606F\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F",
441
+ /** init show 交互式面板选择配置项提示 */
442
+ INIT_SELECT_PROMPT: "\u9009\u62E9\u8981\u4FEE\u6539\u7684\u9879\u76EE\u914D\u7F6E\u9879",
443
+ /** init show 交互式面板配置项修改成功 */
444
+ INIT_SET_SUCCESS: (key, value) => `\u2713 \u9879\u76EE\u914D\u7F6E ${key} \u5DF2\u8BBE\u7F6E\u4E3A ${value}`
441
445
  };
442
446
 
443
447
  // src/constants/messages/interactive-panel.ts
@@ -525,6 +529,10 @@ var CONFIG_DEFINITIONS = {
525
529
  description: "\u6279\u91CF resume \u4F7F\u7528\u7684\u7EC8\u7AEF\u5E94\u7528\uFF1Aauto\uFF08\u81EA\u52A8\u68C0\u6D4B\uFF09\u3001iterm2\u3001terminal\uFF08macOS\uFF09",
526
530
  allowedValues: VALID_TERMINAL_APPS
527
531
  },
532
+ resumeInPlace: {
533
+ defaultValue: false,
534
+ description: "resume \u5355\u9009\u65F6\u662F\u5426\u5728\u5F53\u524D\u7EC8\u7AEF\u5C31\u5730\u6253\u5F00\uFF0Cfalse \u5219\u901A\u8FC7 terminalApp \u5728\u65B0 Tab \u4E2D\u6253\u5F00"
535
+ },
528
536
  aliases: {
529
537
  defaultValue: {},
530
538
  description: "\u547D\u4EE4\u522B\u540D\u6620\u5C04"
@@ -549,6 +557,32 @@ function deriveConfigDescriptions(definitions) {
549
557
  var DEFAULT_CONFIG = deriveDefaultConfig(CONFIG_DEFINITIONS);
550
558
  var CONFIG_DESCRIPTIONS = deriveConfigDescriptions(CONFIG_DEFINITIONS);
551
559
 
560
+ // src/constants/project-config.ts
561
+ var PROJECT_CONFIG_DEFINITIONS = {
562
+ clawtMainWorkBranch: {
563
+ defaultValue: "",
564
+ description: "\u4E3B worktree \u7684\u5DE5\u4F5C\u5206\u652F\u540D"
565
+ },
566
+ validateRunCommand: {
567
+ defaultValue: void 0,
568
+ description: "validate \u6210\u529F\u540E\u81EA\u52A8\u6267\u884C\u7684\u547D\u4EE4\uFF08-r \u7684\u9ED8\u8BA4\u503C\uFF09"
569
+ }
570
+ };
571
+ function deriveDefaultConfig2(definitions) {
572
+ const entries = Object.entries(definitions).map(
573
+ ([key, def]) => [key, def.defaultValue]
574
+ );
575
+ return Object.fromEntries(entries);
576
+ }
577
+ function deriveConfigDescriptions2(definitions) {
578
+ const entries = Object.entries(definitions).map(
579
+ ([key, def]) => [key, def.description]
580
+ );
581
+ return Object.fromEntries(entries);
582
+ }
583
+ var PROJECT_DEFAULT_CONFIG = deriveDefaultConfig2(PROJECT_CONFIG_DEFINITIONS);
584
+ var PROJECT_CONFIG_DESCRIPTIONS = deriveConfigDescriptions2(PROJECT_CONFIG_DEFINITIONS);
585
+
552
586
  // src/constants/update.ts
553
587
  var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
554
588
 
@@ -22,6 +22,7 @@
22
22
  "confirmDestructiveOps": true,
23
23
  "maxConcurrency": 0,
24
24
  "terminalApp": "auto",
25
+ "resumeInPlace": false,
25
26
  "aliases": {},
26
27
  "autoUpdate": true
27
28
  }
@@ -37,6 +38,7 @@
37
38
  | `confirmDestructiveOps` | `boolean` | `true` | 执行破坏性操作(reset、validate --clean)前是否提示确认 |
38
39
  | `maxConcurrency` | `number` | `0` | run 命令默认最大并发数,`0` 表示不限制 |
39
40
  | `terminalApp` | `string` | `"auto"` | 批量 resume 使用的终端应用:`auto`(自动检测)、`iterm2`、`terminal`(macOS) |
41
+ | `resumeInPlace` | `boolean` | `false` | resume 单选时是否在当前终端就地打开,`false` 则通过 `terminalApp` 在新 Tab 中打开 |
40
42
  | `aliases` | `Record<string, string>` | `{}` | 命令别名映射,键为别名,值为目标内置命令名 |
41
43
  | `autoUpdate` | `boolean` | `true` | 是否启用自动更新检查(每 24 小时通过 npm registry 检查一次新版本) |
42
44
 
package/docs/config.md CHANGED
@@ -85,7 +85,8 @@ clawt config reset
85
85
 
86
86
  - 配置项类型定义:`ConfigItemDefinition` 新增可选字段 `allowedValues`(`readonly string[]`),仅对 string 类型有效,用于枚举值校验和交互式 Select 提示
87
87
  - 值解析与提示策略:`src/utils/config-strategy.ts` 中的 `parseConfigValue()`(CLI 字符串解析)和 `promptConfigValue()`(交互式提示),基于类型和 `allowedValues` 自动分发
88
+ - 交互式配置编辑:`handleInteractiveConfigSet` 调用通用的 `interactiveConfigEditor`(`src/utils/config-strategy.ts`),传入 `CONFIG_DEFINITIONS` 和 `disabledKeys`(对象类型配置项禁用映射),不再在 config 命令中直接构建选择列表和调用 `promptConfigValue`
88
89
  - `saveConfig(config)`:`src/utils/config.ts` 中新增的通用配置写入函数,将完整配置对象持久化到文件
89
- - `formatConfigValue(value)`:支持 boolean(绿色/黄色)和 string/number(青色)的格式化显示。对象类型配置项(如 `aliases`)在交互式列表中通过 `JSON.stringify` 以暗淡色显示
90
+ - `formatConfigValue(value)`:支持 boolean(绿色/黄色)和 string/number(青色)的格式化显示。`undefined` / `null` 值显示为暗淡色的 `(未设置)`。对象类型配置项(如 `aliases`)在交互式列表中通过 `JSON.stringify` 以暗淡色显示
90
91
 
91
92
  ---
package/docs/init.md CHANGED
@@ -18,11 +18,11 @@ clawt init show
18
18
  | 参数/子命令 | 必填 | 说明 |
19
19
  | --- | --- | --- |
20
20
  | `-b` | 否 | 指定主工作分支名。不传则使用当前分支 |
21
- | `show` | 否 | 查看当前项目的 init 配置 |
21
+ | `show` | 否 | 交互式查看和修改项目配置 |
22
22
 
23
23
  **功能说明:**
24
24
 
25
- 初始化项目级配置,将指定分支记录为该项目的主工作分支(`clawtMainWorkBranch`)。该配置用于 `create` / `run` 时检测当前分支是否为主工作分支,并在偏离时提醒用户。详见 [2.6 项目级配置](#26-项目级配置)。
25
+ 初始化项目级配置,将指定分支记录为该项目的主工作分支(`clawtMainWorkBranch`)。该配置用于 `create` / `run` 时检测当前分支是否为主工作分支,并在偏离时提醒用户。`init show` 子命令提供交互式面板,可查看和修改所有项目配置项(如 `validateRunCommand`)。项目级配置的完整说明见 [project-config.md](./project-config.md)。
26
26
 
27
27
  **运行流程(设置模式):**
28
28
 
@@ -40,7 +40,12 @@ clawt init show
40
40
  1. **主 worktree 校验** (2.1)
41
41
  2. **读取项目级配置**:读取 `~/.clawt/projects/<projectName>/config.json`
42
42
  - 配置不存在 → 抛出错误 `项目尚未初始化,请先执行 clawt init 设置主工作分支`
43
- - 配置存在 → 以 JSON 格式输出配置内容
43
+ - 配置存在 → 进入交互式面板
44
+ 3. **交互式配置编辑**:调用 `interactiveConfigEditor`(`src/utils/config-strategy.ts`),基于 `PROJECT_CONFIG_DEFINITIONS` 构建配置项列表(详见 [project-config.md](./project-config.md))
45
+ - 列出所有项目配置项,显示名称、当前值和描述
46
+ - 用户选择配置项后,根据值类型自动选择输入方式(与全局配置的交互式编辑逻辑一致)
47
+ 4. **持久化修改**:将修改后的值合并到当前配置并写入配置文件
48
+ 5. **输出成功提示**:`✓ 项目配置 <key> 已设置为 <value>`
44
49
 
45
50
  **输出格式:**
46
51
 
@@ -51,10 +56,8 @@ clawt init show
51
56
  # 更新已有配置
52
57
  ✓ 已将主工作分支从 develop 更新为 main
53
58
 
54
- # show 查看配置(JSON 格式输出)
55
- {
56
- "clawtMainWorkBranch": "main"
57
- }
59
+ # show 交互式修改成功
60
+ ✓ 项目配置 validateRunCommand 已设置为 npm test
58
61
 
59
62
  # show 未初始化(抛出错误)
60
63
  项目尚未初始化,请先执行 clawt init 设置主工作分支
@@ -62,4 +65,10 @@ clawt init show
62
65
 
63
66
  **重复执行:** 支持重复执行,后一次覆盖前一次的配置。
64
67
 
68
+ **实现要点:**
69
+
70
+ - `init show` 子命令从 JSON 展示改为交互式面板,调用 `interactiveConfigEditor`(`src/utils/config-strategy.ts`)实现通用交互式配置编辑
71
+ - 配置项定义来自 `PROJECT_CONFIG_DEFINITIONS`(`src/constants/project-config.ts`),详见 [项目级配置文档](./project-config.md)
72
+ - 消息常量:`MESSAGES.INIT_SELECT_PROMPT`(选择配置项提示语)、`MESSAGES.INIT_SET_SUCCESS`(修改成功提示),定义在 `src/constants/messages/init.ts`
73
+
65
74
  ---