clawt 2.17.1 → 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;
@@ -394,6 +395,19 @@ var COMPLETION_MESSAGES = {
394
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`
395
396
  };
396
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
+
397
411
  // src/constants/messages/index.ts
398
412
  var MESSAGES = {
399
413
  ...COMMON_MESSAGES,
@@ -456,6 +470,10 @@ var CONFIG_DEFINITIONS = {
456
470
  aliases: {
457
471
  defaultValue: {},
458
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"
459
477
  }
460
478
  };
461
479
  function deriveDefaultConfig(definitions) {
@@ -479,6 +497,12 @@ var AUTO_SAVE_COMMIT_MESSAGE = "chore: auto-save before sync";
479
497
  // src/constants/logger.ts
480
498
  var DEBUG_TIMESTAMP_FORMAT = "HH:mm:ss.SSS";
481
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
+
482
506
  // src/constants/progress.ts
483
507
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
484
508
  var SPINNER_INTERVAL_MS = 100;
@@ -2299,8 +2323,139 @@ async function promptStringValue(key, currentValue) {
2299
2323
  }).run();
2300
2324
  }
2301
2325
 
2302
- // 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";
2303
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";
2304
2459
  function registerListCommand(program2) {
2305
2460
  program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
2306
2461
  handleList(options);
@@ -2337,12 +2492,12 @@ function printListAsText(projectName, worktrees) {
2337
2492
  for (const wt of worktrees) {
2338
2493
  const status = getWorktreeStatus(wt);
2339
2494
  const isIdle = status ? isWorktreeIdle(status) : false;
2340
- const pathDisplay = isIdle ? chalk6.hex("#FF8C00")(wt.path) : wt.path;
2495
+ const pathDisplay = isIdle ? chalk7.hex("#FF8C00")(wt.path) : wt.path;
2341
2496
  printInfo(` ${pathDisplay} [${wt.branch}]`);
2342
2497
  if (status) {
2343
2498
  printInfo(` ${formatWorktreeStatus(status)}`);
2344
2499
  } else {
2345
- printInfo(` ${chalk6.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
2500
+ printInfo(` ${chalk7.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
2346
2501
  }
2347
2502
  printInfo("");
2348
2503
  }
@@ -3025,7 +3180,7 @@ async function handleMerge(options) {
3025
3180
  }
3026
3181
 
3027
3182
  // src/commands/config.ts
3028
- import chalk7 from "chalk";
3183
+ import chalk8 from "chalk";
3029
3184
  import Enquirer5 from "enquirer";
3030
3185
  function registerConfigCommand(program2) {
3031
3186
  const configCmd = program2.command("config").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u5168\u5C40\u914D\u7F6E").action(async () => {
@@ -3086,7 +3241,7 @@ async function handleInteractiveConfigSet() {
3086
3241
  const isObject = typeof DEFAULT_CONFIG[k] === "object";
3087
3242
  return {
3088
3243
  name: k,
3089
- 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]}`)}`,
3090
3245
  ...isObject && { disabled: CONFIG_ALIAS_DISABLED_HINT }
3091
3246
  };
3092
3247
  });
@@ -3141,7 +3296,7 @@ async function handleReset() {
3141
3296
  }
3142
3297
 
3143
3298
  // src/commands/status.ts
3144
- import chalk8 from "chalk";
3299
+ import chalk9 from "chalk";
3145
3300
  function registerStatusCommand(program2) {
3146
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) => {
3147
3302
  handleStatus(options);
@@ -3161,7 +3316,7 @@ function collectStatus() {
3161
3316
  const projectName = getProjectName();
3162
3317
  const currentBranch = getCurrentBranch();
3163
3318
  const isClean = isWorkingDirClean();
3164
- const main = {
3319
+ const main2 = {
3165
3320
  branch: currentBranch,
3166
3321
  isClean,
3167
3322
  projectName
@@ -3170,7 +3325,7 @@ function collectStatus() {
3170
3325
  const worktreeStatuses = worktrees.map((wt) => collectWorktreeDetailedStatus(wt, projectName));
3171
3326
  const snapshots = collectSnapshots(projectName, worktrees);
3172
3327
  return {
3173
- main,
3328
+ main: main2,
3174
3329
  worktrees: worktreeStatuses,
3175
3330
  snapshots,
3176
3331
  totalWorktrees: worktrees.length
@@ -3254,7 +3409,7 @@ function printStatusAsJson(result) {
3254
3409
  }
3255
3410
  function printStatusAsText(result) {
3256
3411
  printDoubleSeparator();
3257
- printInfo(` ${chalk8.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
3412
+ printInfo(` ${chalk9.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
3258
3413
  printDoubleSeparator();
3259
3414
  printInfo("");
3260
3415
  printMainSection(result.main);
@@ -3266,18 +3421,18 @@ function printStatusAsText(result) {
3266
3421
  printSnapshotsSection(result.snapshots);
3267
3422
  printDoubleSeparator();
3268
3423
  }
3269
- function printMainSection(main) {
3270
- printInfo(` ${chalk8.bold("\u25C6")} ${chalk8.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
3271
- printInfo(` \u5206\u652F: ${chalk8.bold(main.branch)}`);
3272
- if (main.isClean) {
3273
- 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")}`);
3274
3429
  } else {
3275
- 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")}`);
3276
3431
  }
3277
3432
  printInfo("");
3278
3433
  }
3279
3434
  function printWorktreesSection(worktrees, total) {
3280
- 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)`);
3281
3436
  printInfo("");
3282
3437
  if (worktrees.length === 0) {
3283
3438
  printInfo(` ${MESSAGES.STATUS_NO_WORKTREES}`);
@@ -3289,56 +3444,56 @@ function printWorktreesSection(worktrees, total) {
3289
3444
  }
3290
3445
  function printWorktreeItem(wt) {
3291
3446
  const statusLabel = formatChangeStatusLabel(wt.changeStatus);
3292
- printInfo(` ${chalk8.bold("\u25CF")} ${chalk8.bold(wt.branch)} [${statusLabel}]`);
3447
+ printInfo(` ${chalk9.bold("\u25CF")} ${chalk9.bold(wt.branch)} [${statusLabel}]`);
3293
3448
  if (wt.insertions > 0 || wt.deletions > 0) {
3294
- printInfo(` ${chalk8.green(`+${wt.insertions}`)} ${chalk8.red(`-${wt.deletions}`)}`);
3449
+ printInfo(` ${chalk9.green(`+${wt.insertions}`)} ${chalk9.red(`-${wt.deletions}`)}`);
3295
3450
  }
3296
3451
  if (wt.commitsAhead > 0) {
3297
- printInfo(` ${chalk8.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`)}`);
3452
+ printInfo(` ${chalk9.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`)}`);
3298
3453
  }
3299
3454
  if (wt.commitsBehind > 0) {
3300
- 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`)}`);
3301
3456
  } else {
3302
- printInfo(` ${chalk8.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65")}`);
3457
+ printInfo(` ${chalk9.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65")}`);
3303
3458
  }
3304
3459
  if (wt.createdAt) {
3305
3460
  const relativeTime = formatRelativeTime(wt.createdAt);
3306
3461
  if (relativeTime) {
3307
- printInfo(` ${chalk8.gray(MESSAGES.STATUS_CREATED_AT(relativeTime))}`);
3462
+ printInfo(` ${chalk9.gray(MESSAGES.STATUS_CREATED_AT(relativeTime))}`);
3308
3463
  }
3309
3464
  }
3310
3465
  if (wt.snapshotTime) {
3311
3466
  const relativeTime = formatRelativeTime(wt.snapshotTime);
3312
3467
  if (relativeTime) {
3313
- printInfo(` ${chalk8.green(MESSAGES.STATUS_LAST_VALIDATED(relativeTime))}`);
3468
+ printInfo(` ${chalk9.green(MESSAGES.STATUS_LAST_VALIDATED(relativeTime))}`);
3314
3469
  }
3315
3470
  } else {
3316
- printInfo(` ${chalk8.red(MESSAGES.STATUS_NOT_VALIDATED)}`);
3471
+ printInfo(` ${chalk9.red(MESSAGES.STATUS_NOT_VALIDATED)}`);
3317
3472
  }
3318
3473
  printInfo("");
3319
3474
  }
3320
3475
  function formatChangeStatusLabel(status) {
3321
3476
  switch (status) {
3322
3477
  case "committed":
3323
- return chalk8.green(MESSAGES.STATUS_CHANGE_COMMITTED);
3478
+ return chalk9.green(MESSAGES.STATUS_CHANGE_COMMITTED);
3324
3479
  case "uncommitted":
3325
- return chalk8.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
3480
+ return chalk9.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
3326
3481
  case "conflict":
3327
- return chalk8.red(MESSAGES.STATUS_CHANGE_CONFLICT);
3482
+ return chalk9.red(MESSAGES.STATUS_CHANGE_CONFLICT);
3328
3483
  case "clean":
3329
- return chalk8.gray(MESSAGES.STATUS_CHANGE_CLEAN);
3484
+ return chalk9.gray(MESSAGES.STATUS_CHANGE_CLEAN);
3330
3485
  }
3331
3486
  }
3332
3487
  function printSnapshotsSection(snapshots) {
3333
- 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)`);
3334
3489
  if (snapshots.orphaned > 0) {
3335
- printInfo(` ${chalk8.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED(snapshots.orphaned))}`);
3490
+ printInfo(` ${chalk9.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED(snapshots.orphaned))}`);
3336
3491
  }
3337
3492
  printInfo("");
3338
3493
  }
3339
3494
 
3340
3495
  // src/commands/alias.ts
3341
- import chalk9 from "chalk";
3496
+ import chalk10 from "chalk";
3342
3497
  function getRegisteredCommandNames(program2) {
3343
3498
  return program2.commands.map((cmd) => cmd.name());
3344
3499
  }
@@ -3359,7 +3514,7 @@ ${MESSAGES.ALIAS_LIST_TITLE}
3359
3514
  `);
3360
3515
  printSeparator();
3361
3516
  for (const [alias, command] of entries) {
3362
- printInfo(` ${chalk9.bold(alias)} \u2192 ${chalk9.cyan(command)}`);
3517
+ printInfo(` ${chalk10.bold(alias)} \u2192 ${chalk10.cyan(command)}`);
3363
3518
  }
3364
3519
  printInfo("");
3365
3520
  printSeparator();
@@ -3406,7 +3561,7 @@ function registerAliasCommand(program2) {
3406
3561
  }
3407
3562
 
3408
3563
  // src/commands/completion.ts
3409
- 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";
3410
3565
  import { resolve as resolve2 } from "path";
3411
3566
  import { homedir as homedir2 } from "os";
3412
3567
 
@@ -3563,14 +3718,14 @@ function generateCompletions(program2, args) {
3563
3718
  // src/commands/completion.ts
3564
3719
  function appendToFile(filePath, content) {
3565
3720
  if (existsSync10(filePath)) {
3566
- const current = readFileSync4(filePath, "utf-8");
3721
+ const current = readFileSync5(filePath, "utf-8");
3567
3722
  if (current.includes("clawt completion")) {
3568
3723
  printInfo(MESSAGES.COMPLETION_INSTALL_EXISTS + ": " + filePath);
3569
3724
  return;
3570
3725
  }
3571
3726
  content = current + content;
3572
3727
  }
3573
- writeFileSync3(filePath, content, "utf-8");
3728
+ writeFileSync4(filePath, content, "utf-8");
3574
3729
  printSuccess(MESSAGES.COMPLETION_INSTALL_SUCCESS + ": " + filePath);
3575
3730
  printInfo(MESSAGES.COMPLETION_INSTALL_RESTART(filePath));
3576
3731
  }
@@ -3665,4 +3820,10 @@ process.on("unhandledRejection", (reason) => {
3665
3820
  logger.error(`\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD: ${error.message}`);
3666
3821
  process.exit(EXIT_CODES.ERROR);
3667
3822
  });
3668
- 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 = {
@@ -435,6 +436,10 @@ var CONFIG_DEFINITIONS = {
435
436
  aliases: {
436
437
  defaultValue: {},
437
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"
438
443
  }
439
444
  };
440
445
  function deriveDefaultConfig(definitions) {
@@ -452,6 +457,9 @@ function deriveConfigDescriptions(definitions) {
452
457
  var DEFAULT_CONFIG = deriveDefaultConfig(CONFIG_DEFINITIONS);
453
458
  var CONFIG_DESCRIPTIONS = deriveConfigDescriptions(CONFIG_DEFINITIONS);
454
459
 
460
+ // src/constants/update.ts
461
+ var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
462
+
455
463
  // scripts/postinstall.ts
456
464
  function ensureDirectory(dirPath) {
457
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.1",
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",
@@ -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
  * 提示消息模板
@@ -0,0 +1,15 @@
1
+ /** 各包管理器对应的全局安装命令 */
2
+ export const UPDATE_COMMANDS: Record<string, string> = {
3
+ npm: 'npm i -g clawt',
4
+ pnpm: 'pnpm add -g clawt',
5
+ yarn: 'yarn global add clawt',
6
+ } as const;
7
+
8
+ /** 更新检查相关提示消息 */
9
+ export const UPDATE_MESSAGES = {
10
+ /** 版本更新提示 */
11
+ UPDATE_AVAILABLE: (currentVersion: string, latestVersion: string) =>
12
+ `clawt 有新版本可用: ${currentVersion} → ${latestVersion}`,
13
+ /** 更新操作提示 */
14
+ UPDATE_HINT: (command: string) => `执行 ${command} 进行更新`,
15
+ } as const;
@@ -18,3 +18,6 @@ export const VALIDATE_SNAPSHOTS_DIR = join(CLAWT_HOME, 'validate-snapshots');
18
18
 
19
19
  /** Claude Code 项目会话目录 ~/.claude/projects/ */
20
20
  export const CLAUDE_PROJECTS_DIR = join(homedir(), '.claude', 'projects');
21
+
22
+ /** 更新检查缓存文件路径 ~/.clawt/update-check.json */
23
+ export const UPDATE_CHECK_PATH = join(CLAWT_HOME, 'update-check.json');
@@ -0,0 +1,11 @@
1
+ /** 更新检查间隔,24 小时(毫秒) */
2
+ export const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
3
+
4
+ /** npm registry 查询地址 */
5
+ export const NPM_REGISTRY_URL = 'https://registry.npmjs.org/clawt/latest';
6
+
7
+ /** npm registry 请求超时时间(毫秒) */
8
+ export const NPM_REGISTRY_TIMEOUT_MS = 5000;
9
+
10
+ /** 包名 */
11
+ export const PACKAGE_NAME = 'clawt';