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 +1 -0
- package/dist/index.js +197 -36
- package/dist/postinstall.js +8 -0
- package/docs/spec.md +101 -1
- package/package.json +1 -1
- 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/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;
|
|
@@ -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/
|
|
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 ?
|
|
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(` ${
|
|
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
|
|
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 ?
|
|
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
|
|
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
|
|
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(` ${
|
|
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(
|
|
3270
|
-
printInfo(` ${
|
|
3271
|
-
printInfo(` \u5206\u652F: ${
|
|
3272
|
-
if (
|
|
3273
|
-
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")}`);
|
|
3274
3429
|
} else {
|
|
3275
|
-
printInfo(` \u72B6\u6001: ${
|
|
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(` ${
|
|
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(` ${
|
|
3447
|
+
printInfo(` ${chalk9.bold("\u25CF")} ${chalk9.bold(wt.branch)} [${statusLabel}]`);
|
|
3293
3448
|
if (wt.insertions > 0 || wt.deletions > 0) {
|
|
3294
|
-
printInfo(` ${
|
|
3449
|
+
printInfo(` ${chalk9.green(`+${wt.insertions}`)} ${chalk9.red(`-${wt.deletions}`)}`);
|
|
3295
3450
|
}
|
|
3296
3451
|
if (wt.commitsAhead > 0) {
|
|
3297
|
-
printInfo(` ${
|
|
3452
|
+
printInfo(` ${chalk9.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`)}`);
|
|
3298
3453
|
}
|
|
3299
3454
|
if (wt.commitsBehind > 0) {
|
|
3300
|
-
printInfo(` ${
|
|
3455
|
+
printInfo(` ${chalk9.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`)}`);
|
|
3301
3456
|
} else {
|
|
3302
|
-
printInfo(` ${
|
|
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(` ${
|
|
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(` ${
|
|
3468
|
+
printInfo(` ${chalk9.green(MESSAGES.STATUS_LAST_VALIDATED(relativeTime))}`);
|
|
3314
3469
|
}
|
|
3315
3470
|
} else {
|
|
3316
|
-
printInfo(` ${
|
|
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
|
|
3478
|
+
return chalk9.green(MESSAGES.STATUS_CHANGE_COMMITTED);
|
|
3324
3479
|
case "uncommitted":
|
|
3325
|
-
return
|
|
3480
|
+
return chalk9.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
|
|
3326
3481
|
case "conflict":
|
|
3327
|
-
return
|
|
3482
|
+
return chalk9.red(MESSAGES.STATUS_CHANGE_CONFLICT);
|
|
3328
3483
|
case "clean":
|
|
3329
|
-
return
|
|
3484
|
+
return chalk9.gray(MESSAGES.STATUS_CHANGE_CLEAN);
|
|
3330
3485
|
}
|
|
3331
3486
|
}
|
|
3332
3487
|
function printSnapshotsSection(snapshots) {
|
|
3333
|
-
printInfo(` ${
|
|
3488
|
+
printInfo(` ${chalk9.bold("\u25C6")} ${chalk9.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.total} \u4E2A)`);
|
|
3334
3489
|
if (snapshots.orphaned > 0) {
|
|
3335
|
-
printInfo(` ${
|
|
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
|
|
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(` ${
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 = {
|
|
@@ -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
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
|
* 提示消息模板
|
|
@@ -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;
|
package/src/constants/paths.ts
CHANGED
|
@@ -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';
|