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 +1 -0
- package/dist/index.js +197 -42
- package/dist/postinstall.js +8 -3
- package/docs/spec.md +101 -1
- package/package.json +1 -1
- package/src/commands/validate.ts +1 -4
- package/src/constants/config.ts +4 -0
- package/src/constants/index.ts +3 -1
- package/src/constants/messages/index.ts +4 -2
- package/src/constants/messages/update.ts +15 -0
- package/src/constants/messages/validate.ts +0 -3
- package/src/constants/paths.ts +3 -0
- package/src/constants/update.ts +11 -0
- package/src/index.ts +14 -2
- package/src/types/config.ts +2 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/update-checker.ts +213 -0
- package/tests/unit/commands/alias.test.ts +1 -0
- package/tests/unit/commands/config.test.ts +4 -0
- package/tests/unit/utils/config-strategy.test.ts +4 -1
- package/tests/unit/utils/update-checker.test.ts +439 -0
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/
|
|
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 ?
|
|
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(` ${
|
|
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
|
|
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 ?
|
|
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
|
|
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
|
|
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(` ${
|
|
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(
|
|
3276
|
-
printInfo(` ${
|
|
3277
|
-
printInfo(` \u5206\u652F: ${
|
|
3278
|
-
if (
|
|
3279
|
-
printInfo(` \u72B6\u6001: ${
|
|
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: ${
|
|
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(` ${
|
|
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(` ${
|
|
3447
|
+
printInfo(` ${chalk9.bold("\u25CF")} ${chalk9.bold(wt.branch)} [${statusLabel}]`);
|
|
3299
3448
|
if (wt.insertions > 0 || wt.deletions > 0) {
|
|
3300
|
-
printInfo(` ${
|
|
3449
|
+
printInfo(` ${chalk9.green(`+${wt.insertions}`)} ${chalk9.red(`-${wt.deletions}`)}`);
|
|
3301
3450
|
}
|
|
3302
3451
|
if (wt.commitsAhead > 0) {
|
|
3303
|
-
printInfo(` ${
|
|
3452
|
+
printInfo(` ${chalk9.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`)}`);
|
|
3304
3453
|
}
|
|
3305
3454
|
if (wt.commitsBehind > 0) {
|
|
3306
|
-
printInfo(` ${
|
|
3455
|
+
printInfo(` ${chalk9.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`)}`);
|
|
3307
3456
|
} else {
|
|
3308
|
-
printInfo(` ${
|
|
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(` ${
|
|
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(` ${
|
|
3468
|
+
printInfo(` ${chalk9.green(MESSAGES.STATUS_LAST_VALIDATED(relativeTime))}`);
|
|
3320
3469
|
}
|
|
3321
3470
|
} else {
|
|
3322
|
-
printInfo(` ${
|
|
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
|
|
3478
|
+
return chalk9.green(MESSAGES.STATUS_CHANGE_COMMITTED);
|
|
3330
3479
|
case "uncommitted":
|
|
3331
|
-
return
|
|
3480
|
+
return chalk9.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
|
|
3332
3481
|
case "conflict":
|
|
3333
|
-
return
|
|
3482
|
+
return chalk9.red(MESSAGES.STATUS_CHANGE_CONFLICT);
|
|
3334
3483
|
case "clean":
|
|
3335
|
-
return
|
|
3484
|
+
return chalk9.gray(MESSAGES.STATUS_CHANGE_CLEAN);
|
|
3336
3485
|
}
|
|
3337
3486
|
}
|
|
3338
3487
|
function printSnapshotsSection(snapshots) {
|
|
3339
|
-
printInfo(` ${
|
|
3488
|
+
printInfo(` ${chalk9.bold("\u25C6")} ${chalk9.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.total} \u4E2A)`);
|
|
3340
3489
|
if (snapshots.orphaned > 0) {
|
|
3341
|
-
printInfo(` ${
|
|
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
|
|
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(` ${
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
3823
|
+
async function main() {
|
|
3824
|
+
await program.parseAsync(process.argv);
|
|
3825
|
+
if (config.autoUpdate) {
|
|
3826
|
+
await checkForUpdates(version);
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
main();
|
package/dist/postinstall.js
CHANGED
|
@@ -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
package/src/commands/validate.ts
CHANGED
|
@@ -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
|
-
|
|
192
|
-
// sync 存在冲突,提示用户手动解决
|
|
193
|
-
printWarning(MESSAGES.VALIDATE_AUTO_SYNC_CONFLICT(targetWorktreePath));
|
|
194
|
-
}
|
|
191
|
+
// sync 冲突提示已在 executeSyncForBranch 内部输出(SYNC_CONFLICT),此处无需重复提示
|
|
195
192
|
}
|
|
196
193
|
|
|
197
194
|
/**
|
package/src/constants/config.ts
CHANGED
package/src/constants/index.ts
CHANGED
|
@@ -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
|
* 提示消息模板
|