clawt 2.17.0 → 2.18.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/README.md CHANGED
@@ -292,6 +292,7 @@ clawt alias remove l
292
292
  | `maxConcurrency` | `0` | run 命令最大并发数,`0` 为不限制 |
293
293
  | `terminalApp` | `"auto"` | 批量 resume 使用的终端:`auto` / `iterm2` / `terminal` |
294
294
  | `aliases` | `{}` | 命令别名映射(如 `{"l": "list", "r": "run"}`) |
295
+ | `autoUpdate` | `true` | 自动检查新版本(每 24 小时检查一次 npm registry) |
295
296
 
296
297
  ## 全局选项
297
298
 
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ var LOGS_DIR = join(CLAWT_HOME, "logs");
15
15
  var WORKTREES_DIR = join(CLAWT_HOME, "worktrees");
16
16
  var VALIDATE_SNAPSHOTS_DIR = join(CLAWT_HOME, "validate-snapshots");
17
17
  var CLAUDE_PROJECTS_DIR = join(homedir(), ".claude", "projects");
18
+ var UPDATE_CHECK_PATH = join(CLAWT_HOME, "update-check.json");
18
19
 
19
20
  // src/constants/branch.ts
20
21
  var INVALID_BRANCH_CHARS = /[\/\\.\s~:*?[\]^]+/g;
@@ -216,9 +217,6 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
216
217
  VALIDATE_CONFIRM_AUTO_SYNC: (branch) => `\u662F\u5426\u7ACB\u5373\u6267\u884C sync \u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch}\uFF1F`,
217
218
  /** 自动 sync 开始提示 */
218
219
  VALIDATE_AUTO_SYNC_START: (branch) => `\u6B63\u5728\u81EA\u52A8\u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch} ...`,
219
- /** 自动 sync 存在冲突,无法重试 */
220
- VALIDATE_AUTO_SYNC_CONFLICT: (worktreePath) => `\u540C\u6B65\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u8FDB\u5165\u76EE\u6807 worktree \u624B\u52A8\u89E3\u51B3\u51B2\u7A81\u540E\u91CD\u8BD5
221
- cd ${worktreePath}`,
222
220
  /** 用户拒绝自动 sync */
223
221
  VALIDATE_AUTO_SYNC_DECLINED: (branch) => `\u8BF7\u624B\u52A8\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`
224
222
  };
@@ -397,6 +395,19 @@ var COMPLETION_MESSAGES = {
397
395
  COMPLETION_INSTALL_WRITE_ERROR: (filePath) => `\u65E0\u6CD5\u5199\u5165\u6587\u4EF6 ${filePath}\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u6743\u9650\u6216\u624B\u52A8\u914D\u7F6E\u3002`
398
396
  };
399
397
 
398
+ // src/constants/messages/update.ts
399
+ var UPDATE_COMMANDS = {
400
+ npm: "npm i -g clawt",
401
+ pnpm: "pnpm add -g clawt",
402
+ yarn: "yarn global add clawt"
403
+ };
404
+ var UPDATE_MESSAGES = {
405
+ /** 版本更新提示 */
406
+ UPDATE_AVAILABLE: (currentVersion, latestVersion) => `clawt \u6709\u65B0\u7248\u672C\u53EF\u7528: ${currentVersion} \u2192 ${latestVersion}`,
407
+ /** 更新操作提示 */
408
+ UPDATE_HINT: (command) => `\u6267\u884C ${command} \u8FDB\u884C\u66F4\u65B0`
409
+ };
410
+
400
411
  // src/constants/messages/index.ts
401
412
  var MESSAGES = {
402
413
  ...COMMON_MESSAGES,
@@ -459,6 +470,10 @@ var CONFIG_DEFINITIONS = {
459
470
  aliases: {
460
471
  defaultValue: {},
461
472
  description: "\u547D\u4EE4\u522B\u540D\u6620\u5C04"
473
+ },
474
+ autoUpdate: {
475
+ defaultValue: true,
476
+ description: "\u662F\u5426\u542F\u7528\u81EA\u52A8\u66F4\u65B0\u68C0\u67E5\uFF08\u6BCF 24 \u5C0F\u65F6\u68C0\u67E5\u4E00\u6B21 npm registry\uFF09"
462
477
  }
463
478
  };
464
479
  function deriveDefaultConfig(definitions) {
@@ -482,6 +497,12 @@ var AUTO_SAVE_COMMIT_MESSAGE = "chore: auto-save before sync";
482
497
  // src/constants/logger.ts
483
498
  var DEBUG_TIMESTAMP_FORMAT = "HH:mm:ss.SSS";
484
499
 
500
+ // src/constants/update.ts
501
+ var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
502
+ var NPM_REGISTRY_URL = "https://registry.npmjs.org/clawt/latest";
503
+ var NPM_REGISTRY_TIMEOUT_MS = 5e3;
504
+ var PACKAGE_NAME = "clawt";
505
+
485
506
  // src/constants/progress.ts
486
507
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
487
508
  var SPINNER_INTERVAL_MS = 100;
@@ -2302,8 +2323,139 @@ async function promptStringValue(key, currentValue) {
2302
2323
  }).run();
2303
2324
  }
2304
2325
 
2305
- // src/commands/list.ts
2326
+ // src/utils/update-checker.ts
2327
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
2328
+ import { execSync as execSync3 } from "child_process";
2329
+ import { request } from "https";
2306
2330
  import chalk6 from "chalk";
2331
+ import stringWidth2 from "string-width";
2332
+ function readUpdateCache() {
2333
+ try {
2334
+ const raw = readFileSync4(UPDATE_CHECK_PATH, "utf-8");
2335
+ return JSON.parse(raw);
2336
+ } catch {
2337
+ return null;
2338
+ }
2339
+ }
2340
+ function writeUpdateCache(cache) {
2341
+ try {
2342
+ writeFileSync3(UPDATE_CHECK_PATH, JSON.stringify(cache, null, 2), "utf-8");
2343
+ } catch {
2344
+ }
2345
+ }
2346
+ function isCacheExpired(cache, currentVersion) {
2347
+ if (cache.currentVersion !== currentVersion) {
2348
+ return true;
2349
+ }
2350
+ return Date.now() - cache.lastCheck > UPDATE_CHECK_INTERVAL_MS;
2351
+ }
2352
+ function isNewerVersion(latest, current) {
2353
+ const latestParts = latest.split(".").map(Number);
2354
+ const currentParts = current.split(".").map(Number);
2355
+ for (let i = 0; i < 3; i++) {
2356
+ const l = latestParts[i] || 0;
2357
+ const c = currentParts[i] || 0;
2358
+ if (l > c) return true;
2359
+ if (l < c) return false;
2360
+ }
2361
+ return false;
2362
+ }
2363
+ function fetchLatestVersion() {
2364
+ return new Promise((resolve3) => {
2365
+ const req = request(NPM_REGISTRY_URL, { timeout: NPM_REGISTRY_TIMEOUT_MS }, (res) => {
2366
+ let data = "";
2367
+ res.on("data", (chunk) => {
2368
+ data += chunk.toString();
2369
+ });
2370
+ res.on("end", () => {
2371
+ try {
2372
+ const parsed = JSON.parse(data);
2373
+ resolve3(parsed.version ?? null);
2374
+ } catch {
2375
+ resolve3(null);
2376
+ }
2377
+ });
2378
+ });
2379
+ req.on("error", () => resolve3(null));
2380
+ req.on("timeout", () => {
2381
+ req.destroy();
2382
+ resolve3(null);
2383
+ });
2384
+ req.end();
2385
+ });
2386
+ }
2387
+ function detectPackageManager() {
2388
+ const checks = [
2389
+ { name: "pnpm", command: `pnpm list -g --depth=0 ${PACKAGE_NAME}` },
2390
+ { name: "yarn", command: `yarn global list --depth=0 2>/dev/null` }
2391
+ ];
2392
+ for (const { name, command } of checks) {
2393
+ try {
2394
+ const output = execSync3(command, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
2395
+ if (output.includes(PACKAGE_NAME)) {
2396
+ return name;
2397
+ }
2398
+ } catch {
2399
+ }
2400
+ }
2401
+ return "npm";
2402
+ }
2403
+ function printUpdateNotification(currentVersion, latestVersion) {
2404
+ const updateText = UPDATE_MESSAGES.UPDATE_AVAILABLE(
2405
+ chalk6.red(currentVersion),
2406
+ chalk6.green(latestVersion)
2407
+ );
2408
+ const pm = detectPackageManager();
2409
+ const updateCommand = UPDATE_COMMANDS[pm] || UPDATE_COMMANDS.npm;
2410
+ const commandText = UPDATE_MESSAGES.UPDATE_HINT(chalk6.cyan(updateCommand));
2411
+ const updateTextWidth = stringWidth2(updateText);
2412
+ const commandTextWidth = stringWidth2(commandText);
2413
+ const innerWidth = Math.max(updateTextWidth, commandTextWidth) + 4;
2414
+ const padLine = (text) => {
2415
+ const textWidth = stringWidth2(text);
2416
+ const leftPad = Math.floor((innerWidth - textWidth) / 2);
2417
+ const rightPad = innerWidth - textWidth - leftPad;
2418
+ return `\u2502${" ".repeat(leftPad)}${text}${" ".repeat(rightPad)}\u2502`;
2419
+ };
2420
+ const top = `\u256D${"\u2500".repeat(innerWidth)}\u256E`;
2421
+ const bottom = `\u2570${"\u2500".repeat(innerWidth)}\u256F`;
2422
+ const emptyLine = `\u2502${" ".repeat(innerWidth)}\u2502`;
2423
+ console.log();
2424
+ console.log(top);
2425
+ console.log(emptyLine);
2426
+ console.log(padLine(updateText));
2427
+ console.log(padLine(commandText));
2428
+ console.log(emptyLine);
2429
+ console.log(bottom);
2430
+ console.log();
2431
+ }
2432
+ async function checkForUpdates(currentVersion) {
2433
+ try {
2434
+ const cache = readUpdateCache();
2435
+ if (cache && !isCacheExpired(cache, currentVersion)) {
2436
+ if (isNewerVersion(cache.latestVersion, currentVersion)) {
2437
+ printUpdateNotification(currentVersion, cache.latestVersion);
2438
+ }
2439
+ return;
2440
+ }
2441
+ const latestVersion = await fetchLatestVersion();
2442
+ if (!latestVersion) {
2443
+ return;
2444
+ }
2445
+ writeUpdateCache({
2446
+ lastCheck: Date.now(),
2447
+ latestVersion,
2448
+ currentVersion
2449
+ });
2450
+ if (isNewerVersion(latestVersion, currentVersion)) {
2451
+ printUpdateNotification(currentVersion, latestVersion);
2452
+ }
2453
+ } catch {
2454
+ }
2455
+ }
2456
+
2457
+ // src/commands/list.ts
2458
+ import chalk7 from "chalk";
2307
2459
  function registerListCommand(program2) {
2308
2460
  program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
2309
2461
  handleList(options);
@@ -2340,12 +2492,12 @@ function printListAsText(projectName, worktrees) {
2340
2492
  for (const wt of worktrees) {
2341
2493
  const status = getWorktreeStatus(wt);
2342
2494
  const isIdle = status ? isWorktreeIdle(status) : false;
2343
- const pathDisplay = isIdle ? chalk6.hex("#FF8C00")(wt.path) : wt.path;
2495
+ const pathDisplay = isIdle ? chalk7.hex("#FF8C00")(wt.path) : wt.path;
2344
2496
  printInfo(` ${pathDisplay} [${wt.branch}]`);
2345
2497
  if (status) {
2346
2498
  printInfo(` ${formatWorktreeStatus(status)}`);
2347
2499
  } else {
2348
- printInfo(` ${chalk6.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
2500
+ printInfo(` ${chalk7.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
2349
2501
  }
2350
2502
  printInfo("");
2351
2503
  }
@@ -2740,9 +2892,6 @@ async function handlePatchApplyFailure(targetWorktreePath, branchName) {
2740
2892
  }
2741
2893
  printInfo(MESSAGES.VALIDATE_AUTO_SYNC_START(branchName));
2742
2894
  const syncResult = executeSyncForBranch(targetWorktreePath, branchName);
2743
- if (syncResult.hasConflict) {
2744
- printWarning(MESSAGES.VALIDATE_AUTO_SYNC_CONFLICT(targetWorktreePath));
2745
- }
2746
2895
  }
2747
2896
  function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName) {
2748
2897
  gitAddAll(mainWorktreePath);
@@ -3031,7 +3180,7 @@ async function handleMerge(options) {
3031
3180
  }
3032
3181
 
3033
3182
  // src/commands/config.ts
3034
- import chalk7 from "chalk";
3183
+ import chalk8 from "chalk";
3035
3184
  import Enquirer5 from "enquirer";
3036
3185
  function registerConfigCommand(program2) {
3037
3186
  const configCmd = program2.command("config").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u5168\u5C40\u914D\u7F6E").action(async () => {
@@ -3092,7 +3241,7 @@ async function handleInteractiveConfigSet() {
3092
3241
  const isObject = typeof DEFAULT_CONFIG[k] === "object";
3093
3242
  return {
3094
3243
  name: k,
3095
- message: `${k}: ${isObject ? chalk7.dim(JSON.stringify(config2[k])) : formatConfigValue(config2[k])} ${chalk7.dim(`\u2014 ${CONFIG_DESCRIPTIONS[k]}`)}`,
3244
+ message: `${k}: ${isObject ? chalk8.dim(JSON.stringify(config2[k])) : formatConfigValue(config2[k])} ${chalk8.dim(`\u2014 ${CONFIG_DESCRIPTIONS[k]}`)}`,
3096
3245
  ...isObject && { disabled: CONFIG_ALIAS_DISABLED_HINT }
3097
3246
  };
3098
3247
  });
@@ -3147,7 +3296,7 @@ async function handleReset() {
3147
3296
  }
3148
3297
 
3149
3298
  // src/commands/status.ts
3150
- import chalk8 from "chalk";
3299
+ import chalk9 from "chalk";
3151
3300
  function registerStatusCommand(program2) {
3152
3301
  program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
3153
3302
  handleStatus(options);
@@ -3167,7 +3316,7 @@ function collectStatus() {
3167
3316
  const projectName = getProjectName();
3168
3317
  const currentBranch = getCurrentBranch();
3169
3318
  const isClean = isWorkingDirClean();
3170
- const main = {
3319
+ const main2 = {
3171
3320
  branch: currentBranch,
3172
3321
  isClean,
3173
3322
  projectName
@@ -3176,7 +3325,7 @@ function collectStatus() {
3176
3325
  const worktreeStatuses = worktrees.map((wt) => collectWorktreeDetailedStatus(wt, projectName));
3177
3326
  const snapshots = collectSnapshots(projectName, worktrees);
3178
3327
  return {
3179
- main,
3328
+ main: main2,
3180
3329
  worktrees: worktreeStatuses,
3181
3330
  snapshots,
3182
3331
  totalWorktrees: worktrees.length
@@ -3260,7 +3409,7 @@ function printStatusAsJson(result) {
3260
3409
  }
3261
3410
  function printStatusAsText(result) {
3262
3411
  printDoubleSeparator();
3263
- printInfo(` ${chalk8.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
3412
+ printInfo(` ${chalk9.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
3264
3413
  printDoubleSeparator();
3265
3414
  printInfo("");
3266
3415
  printMainSection(result.main);
@@ -3272,18 +3421,18 @@ function printStatusAsText(result) {
3272
3421
  printSnapshotsSection(result.snapshots);
3273
3422
  printDoubleSeparator();
3274
3423
  }
3275
- function printMainSection(main) {
3276
- printInfo(` ${chalk8.bold("\u25C6")} ${chalk8.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
3277
- printInfo(` \u5206\u652F: ${chalk8.bold(main.branch)}`);
3278
- if (main.isClean) {
3279
- printInfo(` \u72B6\u6001: ${chalk8.green("\u2713 \u5E72\u51C0")}`);
3424
+ function printMainSection(main2) {
3425
+ printInfo(` ${chalk9.bold("\u25C6")} ${chalk9.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
3426
+ printInfo(` \u5206\u652F: ${chalk9.bold(main2.branch)}`);
3427
+ if (main2.isClean) {
3428
+ printInfo(` \u72B6\u6001: ${chalk9.green("\u2713 \u5E72\u51C0")}`);
3280
3429
  } else {
3281
- printInfo(` \u72B6\u6001: ${chalk8.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
3430
+ printInfo(` \u72B6\u6001: ${chalk9.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
3282
3431
  }
3283
3432
  printInfo("");
3284
3433
  }
3285
3434
  function printWorktreesSection(worktrees, total) {
3286
- printInfo(` ${chalk8.bold("\u25C6")} ${chalk8.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
3435
+ printInfo(` ${chalk9.bold("\u25C6")} ${chalk9.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
3287
3436
  printInfo("");
3288
3437
  if (worktrees.length === 0) {
3289
3438
  printInfo(` ${MESSAGES.STATUS_NO_WORKTREES}`);
@@ -3295,56 +3444,56 @@ function printWorktreesSection(worktrees, total) {
3295
3444
  }
3296
3445
  function printWorktreeItem(wt) {
3297
3446
  const statusLabel = formatChangeStatusLabel(wt.changeStatus);
3298
- printInfo(` ${chalk8.bold("\u25CF")} ${chalk8.bold(wt.branch)} [${statusLabel}]`);
3447
+ printInfo(` ${chalk9.bold("\u25CF")} ${chalk9.bold(wt.branch)} [${statusLabel}]`);
3299
3448
  if (wt.insertions > 0 || wt.deletions > 0) {
3300
- printInfo(` ${chalk8.green(`+${wt.insertions}`)} ${chalk8.red(`-${wt.deletions}`)}`);
3449
+ printInfo(` ${chalk9.green(`+${wt.insertions}`)} ${chalk9.red(`-${wt.deletions}`)}`);
3301
3450
  }
3302
3451
  if (wt.commitsAhead > 0) {
3303
- printInfo(` ${chalk8.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`)}`);
3452
+ printInfo(` ${chalk9.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`)}`);
3304
3453
  }
3305
3454
  if (wt.commitsBehind > 0) {
3306
- printInfo(` ${chalk8.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`)}`);
3455
+ printInfo(` ${chalk9.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`)}`);
3307
3456
  } else {
3308
- printInfo(` ${chalk8.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65")}`);
3457
+ printInfo(` ${chalk9.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65")}`);
3309
3458
  }
3310
3459
  if (wt.createdAt) {
3311
3460
  const relativeTime = formatRelativeTime(wt.createdAt);
3312
3461
  if (relativeTime) {
3313
- printInfo(` ${chalk8.gray(MESSAGES.STATUS_CREATED_AT(relativeTime))}`);
3462
+ printInfo(` ${chalk9.gray(MESSAGES.STATUS_CREATED_AT(relativeTime))}`);
3314
3463
  }
3315
3464
  }
3316
3465
  if (wt.snapshotTime) {
3317
3466
  const relativeTime = formatRelativeTime(wt.snapshotTime);
3318
3467
  if (relativeTime) {
3319
- printInfo(` ${chalk8.green(MESSAGES.STATUS_LAST_VALIDATED(relativeTime))}`);
3468
+ printInfo(` ${chalk9.green(MESSAGES.STATUS_LAST_VALIDATED(relativeTime))}`);
3320
3469
  }
3321
3470
  } else {
3322
- printInfo(` ${chalk8.red(MESSAGES.STATUS_NOT_VALIDATED)}`);
3471
+ printInfo(` ${chalk9.red(MESSAGES.STATUS_NOT_VALIDATED)}`);
3323
3472
  }
3324
3473
  printInfo("");
3325
3474
  }
3326
3475
  function formatChangeStatusLabel(status) {
3327
3476
  switch (status) {
3328
3477
  case "committed":
3329
- return chalk8.green(MESSAGES.STATUS_CHANGE_COMMITTED);
3478
+ return chalk9.green(MESSAGES.STATUS_CHANGE_COMMITTED);
3330
3479
  case "uncommitted":
3331
- return chalk8.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
3480
+ return chalk9.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
3332
3481
  case "conflict":
3333
- return chalk8.red(MESSAGES.STATUS_CHANGE_CONFLICT);
3482
+ return chalk9.red(MESSAGES.STATUS_CHANGE_CONFLICT);
3334
3483
  case "clean":
3335
- return chalk8.gray(MESSAGES.STATUS_CHANGE_CLEAN);
3484
+ return chalk9.gray(MESSAGES.STATUS_CHANGE_CLEAN);
3336
3485
  }
3337
3486
  }
3338
3487
  function printSnapshotsSection(snapshots) {
3339
- printInfo(` ${chalk8.bold("\u25C6")} ${chalk8.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.total} \u4E2A)`);
3488
+ printInfo(` ${chalk9.bold("\u25C6")} ${chalk9.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.total} \u4E2A)`);
3340
3489
  if (snapshots.orphaned > 0) {
3341
- printInfo(` ${chalk8.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED(snapshots.orphaned))}`);
3490
+ printInfo(` ${chalk9.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED(snapshots.orphaned))}`);
3342
3491
  }
3343
3492
  printInfo("");
3344
3493
  }
3345
3494
 
3346
3495
  // src/commands/alias.ts
3347
- import chalk9 from "chalk";
3496
+ import chalk10 from "chalk";
3348
3497
  function getRegisteredCommandNames(program2) {
3349
3498
  return program2.commands.map((cmd) => cmd.name());
3350
3499
  }
@@ -3365,7 +3514,7 @@ ${MESSAGES.ALIAS_LIST_TITLE}
3365
3514
  `);
3366
3515
  printSeparator();
3367
3516
  for (const [alias, command] of entries) {
3368
- printInfo(` ${chalk9.bold(alias)} \u2192 ${chalk9.cyan(command)}`);
3517
+ printInfo(` ${chalk10.bold(alias)} \u2192 ${chalk10.cyan(command)}`);
3369
3518
  }
3370
3519
  printInfo("");
3371
3520
  printSeparator();
@@ -3412,7 +3561,7 @@ function registerAliasCommand(program2) {
3412
3561
  }
3413
3562
 
3414
3563
  // src/commands/completion.ts
3415
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync10 } from "fs";
3564
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync10 } from "fs";
3416
3565
  import { resolve as resolve2 } from "path";
3417
3566
  import { homedir as homedir2 } from "os";
3418
3567
 
@@ -3569,14 +3718,14 @@ function generateCompletions(program2, args) {
3569
3718
  // src/commands/completion.ts
3570
3719
  function appendToFile(filePath, content) {
3571
3720
  if (existsSync10(filePath)) {
3572
- const current = readFileSync4(filePath, "utf-8");
3721
+ const current = readFileSync5(filePath, "utf-8");
3573
3722
  if (current.includes("clawt completion")) {
3574
3723
  printInfo(MESSAGES.COMPLETION_INSTALL_EXISTS + ": " + filePath);
3575
3724
  return;
3576
3725
  }
3577
3726
  content = current + content;
3578
3727
  }
3579
- writeFileSync3(filePath, content, "utf-8");
3728
+ writeFileSync4(filePath, content, "utf-8");
3580
3729
  printSuccess(MESSAGES.COMPLETION_INSTALL_SUCCESS + ": " + filePath);
3581
3730
  printInfo(MESSAGES.COMPLETION_INSTALL_RESTART(filePath));
3582
3731
  }
@@ -3671,4 +3820,10 @@ process.on("unhandledRejection", (reason) => {
3671
3820
  logger.error(`\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD: ${error.message}`);
3672
3821
  process.exit(EXIT_CODES.ERROR);
3673
3822
  });
3674
- program.parse(process.argv);
3823
+ async function main() {
3824
+ await program.parseAsync(process.argv);
3825
+ if (config.autoUpdate) {
3826
+ await checkForUpdates(version);
3827
+ }
3828
+ }
3829
+ main();
@@ -10,6 +10,7 @@ var LOGS_DIR = join(CLAWT_HOME, "logs");
10
10
  var WORKTREES_DIR = join(CLAWT_HOME, "worktrees");
11
11
  var VALIDATE_SNAPSHOTS_DIR = join(CLAWT_HOME, "validate-snapshots");
12
12
  var CLAUDE_PROJECTS_DIR = join(homedir(), ".claude", "projects");
13
+ var UPDATE_CHECK_PATH = join(CLAWT_HOME, "update-check.json");
13
14
 
14
15
  // src/constants/messages/common.ts
15
16
  var COMMON_MESSAGES = {
@@ -208,9 +209,6 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
208
209
  VALIDATE_CONFIRM_AUTO_SYNC: (branch) => `\u662F\u5426\u7ACB\u5373\u6267\u884C sync \u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch}\uFF1F`,
209
210
  /** 自动 sync 开始提示 */
210
211
  VALIDATE_AUTO_SYNC_START: (branch) => `\u6B63\u5728\u81EA\u52A8\u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch} ...`,
211
- /** 自动 sync 存在冲突,无法重试 */
212
- VALIDATE_AUTO_SYNC_CONFLICT: (worktreePath) => `\u540C\u6B65\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u8FDB\u5165\u76EE\u6807 worktree \u624B\u52A8\u89E3\u51B3\u51B2\u7A81\u540E\u91CD\u8BD5
213
- cd ${worktreePath}`,
214
212
  /** 用户拒绝自动 sync */
215
213
  VALIDATE_AUTO_SYNC_DECLINED: (branch) => `\u8BF7\u624B\u52A8\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`
216
214
  };
@@ -438,6 +436,10 @@ var CONFIG_DEFINITIONS = {
438
436
  aliases: {
439
437
  defaultValue: {},
440
438
  description: "\u547D\u4EE4\u522B\u540D\u6620\u5C04"
439
+ },
440
+ autoUpdate: {
441
+ defaultValue: true,
442
+ description: "\u662F\u5426\u542F\u7528\u81EA\u52A8\u66F4\u65B0\u68C0\u67E5\uFF08\u6BCF 24 \u5C0F\u65F6\u68C0\u67E5\u4E00\u6B21 npm registry\uFF09"
441
443
  }
442
444
  };
443
445
  function deriveDefaultConfig(definitions) {
@@ -455,6 +457,9 @@ function deriveConfigDescriptions(definitions) {
455
457
  var DEFAULT_CONFIG = deriveDefaultConfig(CONFIG_DEFINITIONS);
456
458
  var CONFIG_DESCRIPTIONS = deriveConfigDescriptions(CONFIG_DEFINITIONS);
457
459
 
460
+ // src/constants/update.ts
461
+ var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
462
+
458
463
  // scripts/postinstall.ts
459
464
  function ensureDirectory(dirPath) {
460
465
  if (!existsSync(dirPath)) {
package/docs/spec.md CHANGED
@@ -28,6 +28,7 @@
28
28
  - [5.14 项目全局状态总览](#514-项目全局状态总览)
29
29
  - [5.15 命令别名管理](#515-命令别名管理)
30
30
  - [5.16 Shell 自动补全](#516-clawt-completion-命令)
31
+ - [5.17 自动更新检查](#517-自动更新检查)
31
32
  - [6. 错误处理规范](#6-错误处理规范)
32
33
  - [7. 非功能性需求](#7-非功能性需求)
33
34
  - [7.1 性能](#71-性能)
@@ -146,6 +147,7 @@ git show-ref --verify refs/heads/<branchName> 2>/dev/null
146
147
  ```
147
148
  ~/.clawt/
148
149
  ├── config.json # 全局配置文件
150
+ ├── update-check.json # 更新检查缓存文件(自动生成)
149
151
  ├── logs/ # 日志目录
150
152
  │ ├── clawt-2025-02-06.log
151
153
  │ └── ...
@@ -1067,7 +1069,8 @@ clawt merge [-m <commitMessage>]
1067
1069
  "confirmDestructiveOps": true,
1068
1070
  "maxConcurrency": 0,
1069
1071
  "terminalApp": "auto",
1070
- "aliases": {}
1072
+ "aliases": {},
1073
+ "autoUpdate": true
1071
1074
  }
1072
1075
  ```
1073
1076
 
@@ -1082,6 +1085,7 @@ clawt merge [-m <commitMessage>]
1082
1085
  | `maxConcurrency` | `number` | `0` | run 命令默认最大并发数,`0` 表示不限制 |
1083
1086
  | `terminalApp` | `string` | `"auto"` | 批量 resume 使用的终端应用:`auto`(自动检测)、`iterm2`、`terminal`(macOS) |
1084
1087
  | `aliases` | `Record<string, string>` | `{}` | 命令别名映射,键为别名,值为目标内置命令名 |
1088
+ | `autoUpdate` | `boolean` | `true` | 是否启用自动更新检查(每 24 小时通过 npm registry 检查一次新版本) |
1085
1089
 
1086
1090
  ---
1087
1091
 
@@ -1816,6 +1820,102 @@ clawt completion install
1816
1820
 
1817
1821
  ---
1818
1822
 
1823
+ ### 5.17 自动更新检查
1824
+
1825
+ CLI 在每次命令执行完毕后,根据配置项 `autoUpdate` 决定是否检查 npm registry 上的最新版本。当发现新版本时,以带边框的提示框在终端输出版本更新信息和升级命令。
1826
+
1827
+ #### 触发条件
1828
+
1829
+ - 配置项 `autoUpdate` 为 `true`(默认启用)
1830
+ - 命令正常执行完毕后触发(在 `program.parseAsync()` 之后)
1831
+
1832
+ #### 检查流程
1833
+
1834
+ 1. 读取缓存文件 `~/.clawt/update-check.json`
1835
+ 2. 判断缓存是否有效:
1836
+ - 缓存不存在或解析失败 → 视为过期
1837
+ - 缓存中的 `currentVersion` 与本地版本不一致 → 视为过期
1838
+ - 距离上次检查超过 24 小时 → 视为过期
1839
+ 3. **缓存有效**:直接使用缓存中的 `latestVersion` 与本地版本比较,有新版本则打印提示
1840
+ 4. **缓存过期**:向 npm registry 发起 HTTPS 请求获取最新版本号(5 秒超时),更新缓存文件后判断并打印提示
1841
+
1842
+ #### 缓存文件
1843
+
1844
+ **路径:** `~/.clawt/update-check.json`
1845
+
1846
+ **结构:**
1847
+
1848
+ ```json
1849
+ {
1850
+ "lastCheck": 1709000000000,
1851
+ "latestVersion": "2.18.0",
1852
+ "currentVersion": "2.17.1"
1853
+ }
1854
+ ```
1855
+
1856
+ | 字段 | 类型 | 说明 |
1857
+ | ---- | ---- | ---- |
1858
+ | `lastCheck` | `number` | 上次检查时间戳(毫秒) |
1859
+ | `latestVersion` | `string` | 从 registry 获取的最新版本号 |
1860
+ | `currentVersion` | `string` | 检查时的本地版本号 |
1861
+
1862
+ #### 版本比较
1863
+
1864
+ 使用简易 semver 比较(不引入额外依赖),逐级比较 `major.minor.patch`:
1865
+
1866
+ - `latest > current` → 提示更新
1867
+ - `latest <= current` → 不提示
1868
+
1869
+ #### 包管理器检测
1870
+
1871
+ 更新提示中会显示与用户安装方式匹配的升级命令。检测逻辑依次尝试:
1872
+
1873
+ 1. `pnpm list -g --depth=0 clawt` → 匹配则提示 `pnpm add -g clawt`
1874
+ 2. `yarn global list --depth=0` → 输出含 `clawt` 则提示 `yarn global add clawt`
1875
+ 3. 以上均未匹配 → 默认提示 `npm i -g clawt`
1876
+
1877
+ #### 提示框格式
1878
+
1879
+ 当检测到新版本时,输出带 Unicode 圆角边框的居中提示框:
1880
+
1881
+ ```
1882
+ ╭──────────────────────────────────────────────╮
1883
+ │ │
1884
+ │ clawt 有新版本可用: 2.17.1 → 2.18.0 │
1885
+ │ 执行 npm i -g clawt 进行更新 │
1886
+ │ │
1887
+ ╰──────────────────────────────────────────────╯
1888
+ ```
1889
+
1890
+ 版本号和命令使用 chalk 着色:当前版本红色、最新版本绿色、更新命令青色。
1891
+
1892
+ #### 容错设计
1893
+
1894
+ 所有异常静默处理,不影响 CLI 正常功能:
1895
+
1896
+ - 网络请求失败或超时(5 秒) → 静默忽略
1897
+ - registry 返回无效 JSON 或缺少 `version` 字段 → 静默忽略
1898
+ - 缓存文件读写失败 → 静默忽略
1899
+ - `checkForUpdates()` 入口函数的最外层 `try/catch` 确保任何未预期异常都不会中断 CLI
1900
+
1901
+ #### 常量定义
1902
+
1903
+ | 常量 | 值 | 位置 |
1904
+ | ---- | -- | ---- |
1905
+ | `UPDATE_CHECK_INTERVAL_MS` | `86400000`(24 小时) | `src/constants/update.ts` |
1906
+ | `NPM_REGISTRY_URL` | `https://registry.npmjs.org/clawt/latest` | `src/constants/update.ts` |
1907
+ | `NPM_REGISTRY_TIMEOUT_MS` | `5000` | `src/constants/update.ts` |
1908
+ | `PACKAGE_NAME` | `clawt` | `src/constants/update.ts` |
1909
+ | `UPDATE_CHECK_PATH` | `~/.clawt/update-check.json` | `src/constants/paths.ts` |
1910
+
1911
+ #### 实现说明
1912
+
1913
+ - 入口函数:`checkForUpdates()`(在 `src/utils/update-checker.ts`)
1914
+ - 消息常量:`UPDATE_MESSAGES`、`UPDATE_COMMANDS`(在 `src/constants/messages/update.ts`)
1915
+ - 入口调用点:`src/index.ts` 的 `main()` 异步函数中,`program.parseAsync()` 之后根据 `config.autoUpdate` 条件调用
1916
+
1917
+ ---
1918
+
1819
1919
  ## 6. 错误处理规范
1820
1920
 
1821
1921
  ### 6.1 通用错误处理
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "2.17.0",
3
+ "version": "2.18.0",
4
4
  "description": "本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -188,10 +188,7 @@ async function handlePatchApplyFailure(targetWorktreePath: string, branchName: s
188
188
  printInfo(MESSAGES.VALIDATE_AUTO_SYNC_START(branchName));
189
189
  const syncResult = executeSyncForBranch(targetWorktreePath, branchName);
190
190
 
191
- if (syncResult.hasConflict) {
192
- // sync 存在冲突,提示用户手动解决
193
- printWarning(MESSAGES.VALIDATE_AUTO_SYNC_CONFLICT(targetWorktreePath));
194
- }
191
+ // sync 冲突提示已在 executeSyncForBranch 内部输出(SYNC_CONFLICT),此处无需重复提示
195
192
  }
196
193
 
197
194
  /**
@@ -39,6 +39,10 @@ export const CONFIG_DEFINITIONS: ConfigDefinitions = {
39
39
  defaultValue: {} as Record<string, string>,
40
40
  description: '命令别名映射',
41
41
  },
42
+ autoUpdate: {
43
+ defaultValue: true,
44
+ description: '是否启用自动更新检查(每 24 小时检查一次 npm registry)',
45
+ },
42
46
  };
43
47
 
44
48
  /**
@@ -1,12 +1,14 @@
1
- export { CLAWT_HOME, CONFIG_PATH, LOGS_DIR, WORKTREES_DIR, VALIDATE_SNAPSHOTS_DIR, CLAUDE_PROJECTS_DIR } from './paths.js';
1
+ export { CLAWT_HOME, CONFIG_PATH, LOGS_DIR, WORKTREES_DIR, VALIDATE_SNAPSHOTS_DIR, CLAUDE_PROJECTS_DIR, UPDATE_CHECK_PATH } from './paths.js';
2
2
  export { INVALID_BRANCH_CHARS } from './branch.js';
3
3
  export { MESSAGES } from './messages/index.js';
4
4
  export { CONFIG_ALIAS_DISABLED_HINT } from './messages/index.js';
5
+ export { UPDATE_MESSAGES, UPDATE_COMMANDS } from './messages/update.js';
5
6
  export { EXIT_CODES } from './exitCodes.js';
6
7
  export { ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, PASTE_THRESHOLD_MS, VALID_TERMINAL_APPS, ITERM2_APP_PATH } from './terminal.js';
7
8
  export { DEFAULT_CONFIG, CONFIG_DESCRIPTIONS, CONFIG_DEFINITIONS, APPEND_SYSTEM_PROMPT } from './config.js';
8
9
  export { AUTO_SAVE_COMMIT_MESSAGE } from './git.js';
9
10
  export { DEBUG_LOG_PREFIX, DEBUG_TIMESTAMP_FORMAT } from './logger.js';
11
+ export { UPDATE_CHECK_INTERVAL_MS, NPM_REGISTRY_URL, NPM_REGISTRY_TIMEOUT_MS, PACKAGE_NAME } from './update.js';
10
12
  export {
11
13
  SPINNER_FRAMES,
12
14
  SPINNER_INTERVAL_MS,
@@ -8,11 +8,13 @@ import { RESUME_MESSAGES } from './resume.js';
8
8
  import { REMOVE_MESSAGES } from './remove.js';
9
9
  import { RESET_MESSAGES } from './reset.js';
10
10
  import { CONFIG_CMD_MESSAGES, CONFIG_ALIAS_DISABLED_HINT } from './config.js';
11
-
12
- export { CONFIG_ALIAS_DISABLED_HINT };
13
11
  import { STATUS_MESSAGES } from './status.js';
14
12
  import { ALIAS_MESSAGES } from './alias.js';
15
13
  import { COMPLETION_MESSAGES } from './completion.js';
14
+ import { UPDATE_MESSAGES, UPDATE_COMMANDS } from './update.js';
15
+
16
+ export { CONFIG_ALIAS_DISABLED_HINT };
17
+ export { UPDATE_MESSAGES, UPDATE_COMMANDS };
16
18
 
17
19
  /**
18
20
  * 提示消息模板