clawt 2.7.3 → 2.8.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.
@@ -16,8 +16,10 @@
16
16
 
17
17
  ### README.md
18
18
  - 面向用户的使用文档
19
+ - 全局选项在 `## 全局选项` 章节(位于 `## 使用前提` 和 `## 命令` 之间)
19
20
  - 每个命令一个 `###` 小节,含命令格式、参数表格、简要说明、示例
20
21
  - 配置文件说明在 `## 配置文件` 章节
22
+ - 日志说明在 `## 日志` 章节(文档末尾)
21
23
  - 更新模式:更新命令说明段落,配置项表格
22
24
 
23
25
  ## 关键约定
@@ -90,3 +92,12 @@ Notes:
90
92
  - `removeSnapshot()` 同时清理 `.tree` 和 `.head` 文件
91
93
  - merge 成功后自动清理对应快照;merge 时主 worktree 脏 + 存在快照会输出警告提示
92
94
  - docs/spec.md 中 validate 章节(5.4)按 `--clean 模式`、`首次 validate`、`增量 validate` 三段描述
95
+
96
+ ## 全局选项
97
+
98
+ - `--debug` 全局选项在 `src/index.ts` 通过 Commander.js `.option()` + `preAction` 钩子实现
99
+ - `enableConsoleTransport()` 在 `src/logger/index.ts`,幂等地向 winston 添加 Console transport
100
+ - 调试相关常量在 `src/constants/logger.ts`:`DEBUG_LOG_PREFIX`、`DEBUG_TIMESTAMP_FORMAT`
101
+ - docs/spec.md 中 `--debug` 说明位于 `5.9 日志系统` 章节下的 `#### --debug 控制台调试输出` 子章节
102
+ - docs/spec.md 中 `4. 命令总览` 的命令表格后有 `**全局选项:**` 表格
103
+ - README.md 中 `## 全局选项` 章节(在 `## 使用前提` 和 `## 命令` 之间)
package/README.md CHANGED
@@ -18,6 +18,19 @@ npm i -g clawt
18
18
 
19
19
  所有命令**必须在主 worktree 的仓库根目录**下执行(即包含 `.git` 目录的原始仓库)。在子 worktree 或子目录中执行会被拒绝。
20
20
 
21
+ ## 全局选项
22
+
23
+ | 选项 | 说明 |
24
+ | ---- | ---- |
25
+ | `--debug` | 输出详细调试信息到终端,实时显示带颜色和时间戳的日志 |
26
+
27
+ `--debug` 可与任意子命令组合使用:
28
+
29
+ ```bash
30
+ clawt run -b feature-login --debug
31
+ clawt validate -b scheme --debug
32
+ ```
33
+
21
34
  ## 命令
22
35
 
23
36
  ### `clawt create` — 批量创建 worktree
@@ -313,4 +326,4 @@ feature/a.b → feature-a-b
313
326
 
314
327
  ## 日志
315
328
 
316
- 日志保存在 `~/.clawt/logs/` 目录,按日期滚动,保留 30 天。
329
+ 日志保存在 `~/.clawt/logs/` 目录,按日期滚动,保留 30 天。使用 `--debug` 全局选项可在终端实时查看调试日志。
package/dist/index.js CHANGED
@@ -55,7 +55,7 @@ var MESSAGES = {
55
55
  /** merge 成功(无提交信息,目标 worktree 已提交过) */
56
56
  MERGE_SUCCESS_NO_MESSAGE: (branch, pushed) => `\u2713 \u5206\u652F ${branch} \u5DF2\u6210\u529F\u5408\u5E76\u5230\u5F53\u524D\u5206\u652F${pushed ? "\n \u5DF2\u63A8\u9001\u5230\u8FDC\u7A0B\u4ED3\u5E93" : ""}`,
57
57
  /** merge 冲突 */
58
- MERGE_CONFLICT: "\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u5904\u7406",
58
+ MERGE_CONFLICT: "\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u5904\u7406\uFF1A\n \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue",
59
59
  /** merge 后清理 worktree 和分支成功 */
60
60
  WORKTREE_CLEANED: (branch) => `\u2713 \u5DF2\u6E05\u7406 worktree \u548C\u5206\u652F: ${branch}`,
61
61
  /** 请提供提交信息 */
@@ -166,7 +166,11 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
166
166
  /** sync 交互选择提示 */
167
167
  SYNC_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u540C\u6B65\u7684\u5206\u652F",
168
168
  /** sync 模糊匹配到多个结果提示 */
169
- SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`
169
+ SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
170
+ /** merge 后 pull 冲突 */
171
+ PULL_CONFLICT: "\u81EA\u52A8 pull \u65F6\u53D1\u751F\u51B2\u7A81\uFF0Cmerge \u5DF2\u5B8C\u6210\u4F46\u8FDC\u7A0B\u540C\u6B65\u5931\u8D25\n \u8BF7\u624B\u52A8\u89E3\u51B3\u51B2\u7A81\uFF1A\n \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git commit\n \u7136\u540E\u6267\u884C git push \u63A8\u9001\u5230\u8FDC\u7A0B",
172
+ /** push 失败 */
173
+ PUSH_FAILED: "\u81EA\u52A8 push \u5931\u8D25\uFF0Cmerge \u548C pull \u5DF2\u5B8C\u6210\n \u8BF7\u624B\u52A8\u6267\u884C git push"
170
174
  };
171
175
 
172
176
  // src/constants/exitCodes.ts
@@ -217,6 +221,9 @@ var CONFIG_DESCRIPTIONS = deriveConfigDescriptions(CONFIG_DEFINITIONS);
217
221
  // src/constants/git.ts
218
222
  var AUTO_SAVE_COMMIT_MESSAGE = "chore: auto-save before sync";
219
223
 
224
+ // src/constants/logger.ts
225
+ var DEBUG_TIMESTAMP_FORMAT = "HH:mm:ss.SSS";
226
+
220
227
  // src/errors/index.ts
221
228
  var ClawtError = class extends Error {
222
229
  /** 退出码 */
@@ -235,6 +242,7 @@ var ClawtError = class extends Error {
235
242
  // src/logger/index.ts
236
243
  import winston from "winston";
237
244
  import DailyRotateFile from "winston-daily-rotate-file";
245
+ import chalk from "chalk";
238
246
  import { existsSync, mkdirSync } from "fs";
239
247
  if (!existsSync(LOGS_DIR)) {
240
248
  mkdirSync(LOGS_DIR, { recursive: true });
@@ -258,6 +266,35 @@ var logger = winston.createLogger({
258
266
  ),
259
267
  transports: [dailyRotateTransport]
260
268
  });
269
+ var LEVEL_COLORS = {
270
+ error: chalk.red,
271
+ warn: chalk.yellow,
272
+ info: chalk.cyan,
273
+ debug: chalk.gray
274
+ };
275
+ function colorizeLevel(level) {
276
+ const colorFn = LEVEL_COLORS[level] || chalk.white;
277
+ return colorFn(level.toUpperCase().padEnd(5));
278
+ }
279
+ function enableConsoleTransport() {
280
+ const hasConsole = logger.transports.some(
281
+ (t) => t instanceof winston.transports.Console
282
+ );
283
+ if (hasConsole) {
284
+ return;
285
+ }
286
+ const consoleFormat = winston.format.printf(({ level, message, timestamp }) => {
287
+ return `${chalk.gray(timestamp)} ${colorizeLevel(level)} ${message}`;
288
+ });
289
+ const consoleTransport = new winston.transports.Console({
290
+ level: "debug",
291
+ format: winston.format.combine(
292
+ winston.format.timestamp({ format: DEBUG_TIMESTAMP_FORMAT }),
293
+ consoleFormat
294
+ )
295
+ });
296
+ logger.add(consoleTransport);
297
+ }
261
298
 
262
299
  // src/utils/shell.ts
263
300
  import { execSync, execFileSync, spawn } from "child_process";
@@ -463,16 +500,16 @@ function gitApplyCachedCheck(patchContent, cwd) {
463
500
  }
464
501
 
465
502
  // src/utils/formatter.ts
466
- import chalk from "chalk";
503
+ import chalk2 from "chalk";
467
504
  import { createInterface } from "readline";
468
505
  function printSuccess(message) {
469
- console.log(chalk.green(message));
506
+ console.log(chalk2.green(message));
470
507
  }
471
508
  function printError(message) {
472
- console.error(chalk.red(`\u2717 ${message}`));
509
+ console.error(chalk2.red(`\u2717 ${message}`));
473
510
  }
474
511
  function printWarning(message) {
475
- console.log(chalk.yellow(`\u26A0 ${message}`));
512
+ console.log(chalk2.yellow(`\u26A0 ${message}`));
476
513
  }
477
514
  function printInfo(message) {
478
515
  console.log(message);
@@ -496,7 +533,7 @@ function confirmAction(question) {
496
533
  });
497
534
  }
498
535
  function confirmDestructiveAction(dangerousCommand, description) {
499
- printWarning(`\u5373\u5C06\u6267\u884C ${chalk.red.bold(dangerousCommand)}\uFF0C${description}`);
536
+ printWarning(`\u5373\u5C06\u6267\u884C ${chalk2.red.bold(dangerousCommand)}\uFF0C${description}`);
500
537
  return confirmAction("\u662F\u5426\u7EE7\u7EED\uFF1F");
501
538
  }
502
539
  function isWorktreeIdle(status) {
@@ -504,17 +541,17 @@ function isWorktreeIdle(status) {
504
541
  }
505
542
  function formatWorktreeStatus(status) {
506
543
  const parts = [];
507
- parts.push(chalk.yellow(`${status.commitCount} \u4E2A\u63D0\u4EA4`));
544
+ parts.push(chalk2.yellow(`${status.commitCount} \u4E2A\u63D0\u4EA4`));
508
545
  if (status.insertions === 0 && status.deletions === 0) {
509
546
  parts.push("\u65E0\u53D8\u66F4");
510
547
  } else {
511
548
  const diffParts = [];
512
- diffParts.push(chalk.green(`+${status.insertions}`));
513
- diffParts.push(chalk.red(`-${status.deletions}`));
549
+ diffParts.push(chalk2.green(`+${status.insertions}`));
550
+ diffParts.push(chalk2.red(`-${status.deletions}`));
514
551
  parts.push(diffParts.join(" "));
515
552
  }
516
553
  if (status.hasDirtyFiles) {
517
- parts.push(chalk.gray("(\u672A\u63D0\u4EA4\u4FEE\u6539)"));
554
+ parts.push(chalk2.gray("(\u672A\u63D0\u4EA4\u4FEE\u6539)"));
518
555
  }
519
556
  return parts.join(" ");
520
557
  }
@@ -822,7 +859,7 @@ async function resolveTargetWorktree(worktrees, messages, branchName) {
822
859
  }
823
860
 
824
861
  // src/commands/list.ts
825
- import chalk2 from "chalk";
862
+ import chalk3 from "chalk";
826
863
  function registerListCommand(program2) {
827
864
  program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
828
865
  handleList(options);
@@ -859,12 +896,12 @@ function printListAsText(projectName, worktrees) {
859
896
  for (const wt of worktrees) {
860
897
  const status = getWorktreeStatus(wt);
861
898
  const isIdle = status ? isWorktreeIdle(status) : false;
862
- const pathDisplay = isIdle ? chalk2.hex("#FF8C00")(wt.path) : wt.path;
899
+ const pathDisplay = isIdle ? chalk3.hex("#FF8C00")(wt.path) : wt.path;
863
900
  printInfo(` ${pathDisplay} [${wt.branch}]`);
864
901
  if (status) {
865
902
  printInfo(` ${formatWorktreeStatus(status)}`);
866
903
  } else {
867
- printInfo(` ${chalk2.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
904
+ printInfo(` ${chalk3.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
868
905
  }
869
906
  printInfo("");
870
907
  }
@@ -1430,8 +1467,21 @@ async function handleMerge(options) {
1430
1467
  }
1431
1468
  const autoPullPush = getConfigValue("autoPullPush");
1432
1469
  if (autoPullPush) {
1433
- gitPull(mainWorktreePath);
1434
- gitPush(mainWorktreePath);
1470
+ try {
1471
+ gitPull(mainWorktreePath);
1472
+ } catch {
1473
+ if (hasMergeConflict(mainWorktreePath)) {
1474
+ printWarning(MESSAGES.PULL_CONFLICT);
1475
+ return;
1476
+ }
1477
+ throw new ClawtError(MESSAGES.PULL_CONFLICT);
1478
+ }
1479
+ try {
1480
+ gitPush(mainWorktreePath);
1481
+ } catch {
1482
+ printWarning(MESSAGES.PUSH_FAILED);
1483
+ return;
1484
+ }
1435
1485
  } else {
1436
1486
  printInfo("\u5DF2\u8DF3\u8FC7\u81EA\u52A8 pull/push\uFF0C\u8BF7\u624B\u52A8\u6267\u884C git pull && git push");
1437
1487
  }
@@ -1450,7 +1500,7 @@ async function handleMerge(options) {
1450
1500
  }
1451
1501
 
1452
1502
  // src/commands/config.ts
1453
- import chalk3 from "chalk";
1503
+ import chalk4 from "chalk";
1454
1504
  function registerConfigCommand(program2) {
1455
1505
  const configCmd = program2.command("config").description("\u67E5\u770B\u548C\u7BA1\u7406\u5168\u5C40\u914D\u7F6E").action(() => {
1456
1506
  handleConfig();
@@ -1463,7 +1513,7 @@ function handleConfig() {
1463
1513
  const config = loadConfig();
1464
1514
  logger.info("config \u547D\u4EE4\u6267\u884C\uFF0C\u5C55\u793A\u5168\u5C40\u914D\u7F6E");
1465
1515
  printInfo(`
1466
- ${chalk3.dim("\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84:")} ${CONFIG_PATH}
1516
+ ${chalk4.dim("\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84:")} ${CONFIG_PATH}
1467
1517
  `);
1468
1518
  printSeparator();
1469
1519
  const keys = Object.keys(DEFAULT_CONFIG);
@@ -1473,8 +1523,8 @@ ${chalk3.dim("\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84:")} ${CONFIG_PATH}
1473
1523
  const description = CONFIG_DESCRIPTIONS[key];
1474
1524
  const formattedValue = formatConfigValue(value);
1475
1525
  if (i === 0) printInfo("");
1476
- printInfo(` ${chalk3.bold(key)}: ${formattedValue}`);
1477
- printInfo(` ${chalk3.dim(description)}`);
1526
+ printInfo(` ${chalk4.bold(key)}: ${formattedValue}`);
1527
+ printInfo(` ${chalk4.dim(description)}`);
1478
1528
  printInfo("");
1479
1529
  }
1480
1530
  printSeparator();
@@ -1494,9 +1544,9 @@ async function handleConfigReset() {
1494
1544
  }
1495
1545
  function formatConfigValue(value) {
1496
1546
  if (typeof value === "boolean") {
1497
- return value ? chalk3.green("true") : chalk3.yellow("false");
1547
+ return value ? chalk4.green("true") : chalk4.yellow("false");
1498
1548
  }
1499
- return chalk3.cyan(String(value));
1549
+ return chalk4.cyan(String(value));
1500
1550
  }
1501
1551
 
1502
1552
  // src/commands/sync.ts
@@ -1587,7 +1637,12 @@ var require2 = createRequire(import.meta.url);
1587
1637
  var { version } = require2("../package.json");
1588
1638
  ensureClawtDirs();
1589
1639
  var program = new Command();
1590
- program.name("clawt").description("\u672C\u5730\u5E76\u884C\u6267\u884C\u591A\u4E2AClaude Code Agent\u4EFB\u52A1\uFF0C\u878D\u5408 Git Worktree \u4E0E Claude Code CLI \u7684\u547D\u4EE4\u884C\u5DE5\u5177").version(version);
1640
+ program.name("clawt").description("\u672C\u5730\u5E76\u884C\u6267\u884C\u591A\u4E2AClaude Code Agent\u4EFB\u52A1\uFF0C\u878D\u5408 Git Worktree \u4E0E Claude Code CLI \u7684\u547D\u4EE4\u884C\u5DE5\u5177").version(version).option("--debug", "\u8F93\u51FA\u8BE6\u7EC6\u8C03\u8BD5\u4FE1\u606F\u5230\u7EC8\u7AEF");
1641
+ program.hook("preAction", (thisCommand) => {
1642
+ if (thisCommand.opts().debug) {
1643
+ enableConsoleTransport();
1644
+ }
1645
+ });
1591
1646
  registerListCommand(program);
1592
1647
  registerCreateCommand(program);
1593
1648
  registerRemoveCommand(program);
package/docs/spec.md CHANGED
@@ -173,6 +173,12 @@ git show-ref --verify refs/heads/<branchName> 2>/dev/null
173
173
  | `clawt sync` | 将主分支最新代码同步到目标 worktree | 5.12 |
174
174
  | `clawt reset` | 重置主 worktree 工作区和暂存区 | 5.13 |
175
175
 
176
+ **全局选项:**
177
+
178
+ | 选项 | 说明 |
179
+ | --------- | ---------------------------------------- |
180
+ | `--debug` | 输出详细调试信息到终端(启用 Console transport) |
181
+
176
182
  所有命令执行前,都必须先执行**主 worktree 校验**(见 [2.1](#21-主-worktree-的定义与定位规则))。
177
183
 
178
184
  ---
@@ -879,6 +885,42 @@ clawt list [--json]
879
885
  - 日志文件保留 30 天
880
886
  - 单个日志文件最大 10MB
881
887
 
888
+ #### `--debug` 控制台调试输出
889
+
890
+ 通过全局选项 `--debug` 可将调试日志实时输出到终端,方便排查问题。
891
+
892
+ **实现机制:**
893
+
894
+ - 在 Commander.js 的 `preAction` 钩子中检测 `--debug` 选项,按需调用 `enableConsoleTransport()` 函数
895
+ - `enableConsoleTransport()` 动态向 winston 实例添加 `Console` transport(level 为 `debug`),该函数幂等,多次调用不会重复添加 transport
896
+ - 相关常量定义在 `src/constants/logger.ts`:
897
+ - `DEBUG_LOG_PREFIX`:控制台调试输出的日志前缀标识
898
+ - `DEBUG_TIMESTAMP_FORMAT`:时间戳格式(`HH:mm:ss.SSS`,精简,不含日期)
899
+
900
+ **控制台日志格式:**
901
+
902
+ ```
903
+ HH:mm:ss.SSS LEVEL 消息内容
904
+ ```
905
+
906
+ **日志级别颜色映射:**
907
+
908
+ | 级别 | 颜色 |
909
+ | ------- | ------ |
910
+ | `error` | 红色 |
911
+ | `warn` | 黄色 |
912
+ | `info` | 青色 |
913
+ | `debug` | 灰色 |
914
+
915
+ **使用示例:**
916
+
917
+ ```bash
918
+ clawt run -b feature-login --debug
919
+ clawt validate -b feature-scheme --debug
920
+ ```
921
+
922
+ > **注意:** `--debug` 选项不影响文件日志(file transport),文件日志始终按原有策略写入。控制台输出仅在传入 `--debug` 时启用。
923
+
882
924
  ---
883
925
 
884
926
  ### 5.10 查看和管理全局配置
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "2.7.3",
3
+ "version": "2.8.0",
4
4
  "description": "本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -196,8 +196,23 @@ async function handleMerge(options: MergeOptions): Promise<void> {
196
196
  // 步骤 7:根据配置决定是否自动 pull 和 push
197
197
  const autoPullPush = getConfigValue('autoPullPush');
198
198
  if (autoPullPush) {
199
- gitPull(mainWorktreePath);
200
- gitPush(mainWorktreePath);
199
+ // pull 阶段:检测冲突并给出针对性引导
200
+ try {
201
+ gitPull(mainWorktreePath);
202
+ } catch {
203
+ if (hasMergeConflict(mainWorktreePath)) {
204
+ printWarning(MESSAGES.PULL_CONFLICT);
205
+ return;
206
+ }
207
+ throw new ClawtError(MESSAGES.PULL_CONFLICT);
208
+ }
209
+ // push 阶段
210
+ try {
211
+ gitPush(mainWorktreePath);
212
+ } catch {
213
+ printWarning(MESSAGES.PUSH_FAILED);
214
+ return;
215
+ }
201
216
  } else {
202
217
  printInfo('已跳过自动 pull/push,请手动执行 git pull && git push');
203
218
  }
@@ -5,3 +5,4 @@ export { EXIT_CODES } from './exitCodes.js';
5
5
  export { ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, PASTE_THRESHOLD_MS } from './terminal.js';
6
6
  export { DEFAULT_CONFIG, CONFIG_DESCRIPTIONS, APPEND_SYSTEM_PROMPT } from './config.js';
7
7
  export { AUTO_SAVE_COMMIT_MESSAGE } from './git.js';
8
+ export { DEBUG_LOG_PREFIX, DEBUG_TIMESTAMP_FORMAT } from './logger.js';
@@ -0,0 +1,5 @@
1
+ /** 控制台调试输出的日志前缀标识 */
2
+ export const DEBUG_LOG_PREFIX = '[DEBUG]';
3
+
4
+ /** 控制台调试日志的时间戳格式(精简,不含日期) */
5
+ export const DEBUG_TIMESTAMP_FORMAT = 'HH:mm:ss.SSS';
@@ -39,7 +39,7 @@ export const MESSAGES = {
39
39
  MERGE_SUCCESS_NO_MESSAGE: (branch: string, pushed: boolean) =>
40
40
  `✓ 分支 ${branch} 已成功合并到当前分支${pushed ? '\n 已推送到远程仓库' : ''}`,
41
41
  /** merge 冲突 */
42
- MERGE_CONFLICT: '合并存在冲突,请手动处理',
42
+ MERGE_CONFLICT: '合并存在冲突,请手动处理:\n 解决冲突后执行 git add . && git merge --continue',
43
43
  /** merge 后清理 worktree 和分支成功 */
44
44
  WORKTREE_CLEANED: (branch: string) => `✓ 已清理 worktree 和分支: ${branch}`,
45
45
  /** 请提供提交信息 */
@@ -148,4 +148,9 @@ export const MESSAGES = {
148
148
  SYNC_SELECT_BRANCH: '请选择要同步的分支',
149
149
  /** sync 模糊匹配到多个结果提示 */
150
150
  SYNC_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
151
+ /** merge 后 pull 冲突 */
152
+ PULL_CONFLICT:
153
+ '自动 pull 时发生冲突,merge 已完成但远程同步失败\n 请手动解决冲突:\n 解决冲突后执行 git add . && git commit\n 然后执行 git push 推送到远程',
154
+ /** push 失败 */
155
+ PUSH_FAILED: '自动 push 失败,merge 和 pull 已完成\n 请手动执行 git push',
151
156
  } as const;
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { Command } from 'commander';
3
3
  import { ClawtError } from './errors/index.js';
4
- import { logger } from './logger/index.js';
4
+ import { logger, enableConsoleTransport } from './logger/index.js';
5
5
  import { EXIT_CODES } from './constants/index.js';
6
6
  import { printError, ensureClawtDirs } from './utils/index.js';
7
7
  import { registerListCommand } from './commands/list.js';
@@ -27,7 +27,15 @@ const program = new Command();
27
27
  program
28
28
  .name('clawt')
29
29
  .description('本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具')
30
- .version(version);
30
+ .version(version)
31
+ .option('--debug', '输出详细调试信息到终端');
32
+
33
+ // 在子命令 action 执行前检查 --debug 选项,按需启用控制台日志
34
+ program.hook('preAction', (thisCommand) => {
35
+ if (thisCommand.opts().debug) {
36
+ enableConsoleTransport();
37
+ }
38
+ });
31
39
 
32
40
  // 注册所有命令
33
41
  registerListCommand(program);
@@ -1,6 +1,7 @@
1
1
  import winston from 'winston';
2
2
  import DailyRotateFile from 'winston-daily-rotate-file';
3
- import { LOGS_DIR } from '../constants/index.js';
3
+ import chalk from 'chalk';
4
+ import { LOGS_DIR, DEBUG_TIMESTAMP_FORMAT } from '../constants/index.js';
4
5
  import { existsSync, mkdirSync } from 'node:fs';
5
6
 
6
7
  // 确保日志目录存在
@@ -32,3 +33,51 @@ export const logger = winston.createLogger({
32
33
  ),
33
34
  transports: [dailyRotateTransport],
34
35
  });
36
+
37
+ /** 日志级别颜色映射 */
38
+ const LEVEL_COLORS: Record<string, (text: string) => string> = {
39
+ error: chalk.red,
40
+ warn: chalk.yellow,
41
+ info: chalk.cyan,
42
+ debug: chalk.gray,
43
+ };
44
+
45
+ /**
46
+ * 为日志级别添加对应颜色
47
+ * @param {string} level - 日志级别
48
+ * @returns {string} 带颜色的日志级别字符串
49
+ */
50
+ function colorizeLevel(level: string): string {
51
+ const colorFn = LEVEL_COLORS[level] || chalk.white;
52
+ return colorFn(level.toUpperCase().padEnd(5));
53
+ }
54
+
55
+ /**
56
+ * 启用控制台日志输出(--debug 模式)
57
+ * 动态向 winston 实例添加 Console transport,输出带颜色和时间戳的调试信息
58
+ * 该函数幂等,多次调用不会重复添加 transport
59
+ */
60
+ export function enableConsoleTransport(): void {
61
+ // 幂等保护:检查是否已添加过 Console transport
62
+ const hasConsole = logger.transports.some(
63
+ (t) => t instanceof winston.transports.Console,
64
+ );
65
+ if (hasConsole) {
66
+ return;
67
+ }
68
+
69
+ /** --debug 模式的控制台日志格式:精简时间戳 + 带颜色级别 + 消息 */
70
+ const consoleFormat = winston.format.printf(({ level, message, timestamp }) => {
71
+ return `${chalk.gray(timestamp)} ${colorizeLevel(level)} ${message}`;
72
+ });
73
+
74
+ const consoleTransport = new winston.transports.Console({
75
+ level: 'debug',
76
+ format: winston.format.combine(
77
+ winston.format.timestamp({ format: DEBUG_TIMESTAMP_FORMAT }),
78
+ consoleFormat,
79
+ ),
80
+ });
81
+
82
+ logger.add(consoleTransport);
83
+ }