clawt 2.17.1 → 2.19.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 +11 -0
- package/dist/index.js +477 -63
- package/dist/postinstall.js +35 -0
- package/docs/spec.md +242 -1
- package/package.json +1 -1
- package/src/commands/projects.ts +324 -0
- package/src/constants/config.ts +4 -0
- package/src/constants/index.ts +3 -1
- package/src/constants/messages/index.ts +6 -2
- package/src/constants/messages/projects.ts +25 -0
- 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 +16 -2
- package/src/types/command.ts +8 -0
- package/src/types/config.ts +2 -0
- package/src/types/index.ts +2 -1
- package/src/types/project.ts +45 -0
- package/src/utils/formatter.ts +46 -0
- package/src/utils/fs.ts +32 -1
- package/src/utils/index.ts +3 -2
- 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/formatter.test.ts +91 -1
- package/tests/unit/utils/fs.test.ts +125 -2
- package/tests/unit/utils/update-checker.test.ts +439 -0
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;
|
|
@@ -372,6 +373,32 @@ var ALIAS_MESSAGES = {
|
|
|
372
373
|
ALIAS_LIST_TITLE: "\u5F53\u524D\u522B\u540D\u5217\u8868\uFF1A"
|
|
373
374
|
};
|
|
374
375
|
|
|
376
|
+
// src/constants/messages/projects.ts
|
|
377
|
+
var PROJECTS_MESSAGES = {
|
|
378
|
+
/** projects 命令全局概览标题 */
|
|
379
|
+
PROJECTS_OVERVIEW_TITLE: "\u9879\u76EE\u6982\u89C8",
|
|
380
|
+
/** projects 命令指定项目详情标题 */
|
|
381
|
+
PROJECTS_DETAIL_TITLE: (projectName) => `\u9879\u76EE\u8BE6\u60C5: ${projectName}`,
|
|
382
|
+
/** 无项目提示 */
|
|
383
|
+
PROJECTS_NO_PROJECTS: "(\u6682\u65E0\u9879\u76EE\uFF0Cworktrees \u76EE\u5F55\u4E3A\u7A7A)",
|
|
384
|
+
/** 项目不存在提示 */
|
|
385
|
+
PROJECTS_NOT_FOUND: (name) => `\u9879\u76EE ${name} \u4E0D\u5B58\u5728`,
|
|
386
|
+
/** worktree 数量标签 */
|
|
387
|
+
PROJECTS_WORKTREE_COUNT: (count) => `${count} \u4E2A worktree`,
|
|
388
|
+
/** 最近活跃时间标签 */
|
|
389
|
+
PROJECTS_LAST_ACTIVE: (relativeTime) => `\u6700\u8FD1\u6D3B\u8DC3: ${relativeTime}`,
|
|
390
|
+
/** 磁盘占用标签 */
|
|
391
|
+
PROJECTS_DISK_USAGE: (size) => `\u78C1\u76D8\u5360\u7528: ${size}`,
|
|
392
|
+
/** 总磁盘占用标签 */
|
|
393
|
+
PROJECTS_TOTAL_DISK_USAGE: (size) => `\u603B\u5360\u7528: ${size}`,
|
|
394
|
+
/** projects 详情无 worktree */
|
|
395
|
+
PROJECTS_DETAIL_NO_WORKTREES: "(\u8BE5\u9879\u76EE\u4E0B\u65E0 worktree)",
|
|
396
|
+
/** 路径标签 */
|
|
397
|
+
PROJECTS_PATH: (path2) => `\u8DEF\u5F84: ${path2}`,
|
|
398
|
+
/** 最后修改时间标签 */
|
|
399
|
+
PROJECTS_LAST_MODIFIED: (relativeTime) => `\u6700\u540E\u4FEE\u6539: ${relativeTime}`
|
|
400
|
+
};
|
|
401
|
+
|
|
375
402
|
// src/constants/messages/completion.ts
|
|
376
403
|
var COMPLETION_MESSAGES = {
|
|
377
404
|
/** completion 命令的主描述 */
|
|
@@ -394,6 +421,19 @@ var COMPLETION_MESSAGES = {
|
|
|
394
421
|
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
422
|
};
|
|
396
423
|
|
|
424
|
+
// src/constants/messages/update.ts
|
|
425
|
+
var UPDATE_COMMANDS = {
|
|
426
|
+
npm: "npm i -g clawt",
|
|
427
|
+
pnpm: "pnpm add -g clawt",
|
|
428
|
+
yarn: "yarn global add clawt"
|
|
429
|
+
};
|
|
430
|
+
var UPDATE_MESSAGES = {
|
|
431
|
+
/** 版本更新提示 */
|
|
432
|
+
UPDATE_AVAILABLE: (currentVersion, latestVersion) => `clawt \u6709\u65B0\u7248\u672C\u53EF\u7528: ${currentVersion} \u2192 ${latestVersion}`,
|
|
433
|
+
/** 更新操作提示 */
|
|
434
|
+
UPDATE_HINT: (command) => `\u6267\u884C ${command} \u8FDB\u884C\u66F4\u65B0`
|
|
435
|
+
};
|
|
436
|
+
|
|
397
437
|
// src/constants/messages/index.ts
|
|
398
438
|
var MESSAGES = {
|
|
399
439
|
...COMMON_MESSAGES,
|
|
@@ -408,6 +448,7 @@ var MESSAGES = {
|
|
|
408
448
|
...CONFIG_CMD_MESSAGES,
|
|
409
449
|
...STATUS_MESSAGES,
|
|
410
450
|
...ALIAS_MESSAGES,
|
|
451
|
+
...PROJECTS_MESSAGES,
|
|
411
452
|
...COMPLETION_MESSAGES
|
|
412
453
|
};
|
|
413
454
|
|
|
@@ -456,6 +497,10 @@ var CONFIG_DEFINITIONS = {
|
|
|
456
497
|
aliases: {
|
|
457
498
|
defaultValue: {},
|
|
458
499
|
description: "\u547D\u4EE4\u522B\u540D\u6620\u5C04"
|
|
500
|
+
},
|
|
501
|
+
autoUpdate: {
|
|
502
|
+
defaultValue: true,
|
|
503
|
+
description: "\u662F\u5426\u542F\u7528\u81EA\u52A8\u66F4\u65B0\u68C0\u67E5\uFF08\u6BCF 24 \u5C0F\u65F6\u68C0\u67E5\u4E00\u6B21 npm registry\uFF09"
|
|
459
504
|
}
|
|
460
505
|
};
|
|
461
506
|
function deriveDefaultConfig(definitions) {
|
|
@@ -479,6 +524,12 @@ var AUTO_SAVE_COMMIT_MESSAGE = "chore: auto-save before sync";
|
|
|
479
524
|
// src/constants/logger.ts
|
|
480
525
|
var DEBUG_TIMESTAMP_FORMAT = "HH:mm:ss.SSS";
|
|
481
526
|
|
|
527
|
+
// src/constants/update.ts
|
|
528
|
+
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
529
|
+
var NPM_REGISTRY_URL = "https://registry.npmjs.org/clawt/latest";
|
|
530
|
+
var NPM_REGISTRY_TIMEOUT_MS = 5e3;
|
|
531
|
+
var PACKAGE_NAME = "clawt";
|
|
532
|
+
|
|
482
533
|
// src/constants/progress.ts
|
|
483
534
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
484
535
|
var SPINNER_INTERVAL_MS = 100;
|
|
@@ -943,6 +994,32 @@ function formatRelativeTime(isoDateString) {
|
|
|
943
994
|
const years = Math.floor(diffDays / 365);
|
|
944
995
|
return `${years} \u5E74\u524D`;
|
|
945
996
|
}
|
|
997
|
+
function formatDiskSize(bytes) {
|
|
998
|
+
const KB = 1024;
|
|
999
|
+
const MB = KB * 1024;
|
|
1000
|
+
const GB = MB * 1024;
|
|
1001
|
+
if (bytes >= GB) {
|
|
1002
|
+
return `${(bytes / GB).toFixed(1)} GB`;
|
|
1003
|
+
}
|
|
1004
|
+
if (bytes >= MB) {
|
|
1005
|
+
return `${(bytes / MB).toFixed(1)} MB`;
|
|
1006
|
+
}
|
|
1007
|
+
if (bytes >= KB) {
|
|
1008
|
+
return `${(bytes / KB).toFixed(1)} KB`;
|
|
1009
|
+
}
|
|
1010
|
+
return `${bytes} B`;
|
|
1011
|
+
}
|
|
1012
|
+
function formatLocalISOString(date) {
|
|
1013
|
+
const tzOffsetMs = date.getTimezoneOffset() * 60 * 1e3;
|
|
1014
|
+
const localDate = new Date(date.getTime() - tzOffsetMs);
|
|
1015
|
+
const iso = localDate.toISOString().slice(0, -1);
|
|
1016
|
+
const totalMinutes = -date.getTimezoneOffset();
|
|
1017
|
+
const sign = totalMinutes >= 0 ? "+" : "-";
|
|
1018
|
+
const absMinutes = Math.abs(totalMinutes);
|
|
1019
|
+
const hours = String(Math.floor(absMinutes / 60)).padStart(2, "0");
|
|
1020
|
+
const minutes = String(absMinutes % 60).padStart(2, "0");
|
|
1021
|
+
return `${iso}${sign}${hours}:${minutes}`;
|
|
1022
|
+
}
|
|
946
1023
|
|
|
947
1024
|
// src/utils/branch.ts
|
|
948
1025
|
function sanitizeBranchName(branchName) {
|
|
@@ -993,11 +1070,12 @@ function validateClaudeCodeInstalled() {
|
|
|
993
1070
|
}
|
|
994
1071
|
|
|
995
1072
|
// src/utils/worktree.ts
|
|
996
|
-
import { join as
|
|
1073
|
+
import { join as join3 } from "path";
|
|
997
1074
|
import { existsSync as existsSync3, readdirSync as readdirSync2 } from "fs";
|
|
998
1075
|
|
|
999
1076
|
// src/utils/fs.ts
|
|
1000
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmdirSync } from "fs";
|
|
1077
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmdirSync, statSync } from "fs";
|
|
1078
|
+
import { join as join2 } from "path";
|
|
1001
1079
|
function ensureDir(dirPath) {
|
|
1002
1080
|
if (!existsSync2(dirPath)) {
|
|
1003
1081
|
mkdirSync2(dirPath, { recursive: true });
|
|
@@ -1014,11 +1092,30 @@ function removeEmptyDir(dirPath) {
|
|
|
1014
1092
|
}
|
|
1015
1093
|
return false;
|
|
1016
1094
|
}
|
|
1095
|
+
function calculateDirSize(dirPath) {
|
|
1096
|
+
let totalSize = 0;
|
|
1097
|
+
try {
|
|
1098
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
1099
|
+
for (const entry of entries) {
|
|
1100
|
+
const fullPath = join2(dirPath, entry.name);
|
|
1101
|
+
try {
|
|
1102
|
+
if (entry.isDirectory()) {
|
|
1103
|
+
totalSize += calculateDirSize(fullPath);
|
|
1104
|
+
} else if (entry.isFile()) {
|
|
1105
|
+
totalSize += statSync(fullPath).size;
|
|
1106
|
+
}
|
|
1107
|
+
} catch {
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
} catch {
|
|
1111
|
+
}
|
|
1112
|
+
return totalSize;
|
|
1113
|
+
}
|
|
1017
1114
|
|
|
1018
1115
|
// src/utils/worktree.ts
|
|
1019
1116
|
function getProjectWorktreeDir() {
|
|
1020
1117
|
const projectName = getProjectName();
|
|
1021
|
-
return
|
|
1118
|
+
return join3(WORKTREES_DIR, projectName);
|
|
1022
1119
|
}
|
|
1023
1120
|
function createWorktrees(branchName, count) {
|
|
1024
1121
|
const sanitized = sanitizeBranchName(branchName);
|
|
@@ -1028,7 +1125,7 @@ function createWorktrees(branchName, count) {
|
|
|
1028
1125
|
ensureDir(projectDir);
|
|
1029
1126
|
const results = [];
|
|
1030
1127
|
for (const name of branchNames) {
|
|
1031
|
-
const worktreePath =
|
|
1128
|
+
const worktreePath = join3(projectDir, name);
|
|
1032
1129
|
createWorktree(name, worktreePath);
|
|
1033
1130
|
results.push({ path: worktreePath, branch: name });
|
|
1034
1131
|
logger.info(`worktree \u521B\u5EFA\u5B8C\u6210: ${worktreePath} (\u5206\u652F: ${name})`);
|
|
@@ -1041,7 +1138,7 @@ function createWorktreesByBranches(branchNames) {
|
|
|
1041
1138
|
ensureDir(projectDir);
|
|
1042
1139
|
const results = [];
|
|
1043
1140
|
for (const name of branchNames) {
|
|
1044
|
-
const worktreePath =
|
|
1141
|
+
const worktreePath = join3(projectDir, name);
|
|
1045
1142
|
createWorktree(name, worktreePath);
|
|
1046
1143
|
results.push({ path: worktreePath, branch: name });
|
|
1047
1144
|
logger.info(`worktree \u521B\u5EFA\u5B8C\u6210: ${worktreePath} (\u5206\u652F: ${name})`);
|
|
@@ -1063,7 +1160,7 @@ function getProjectWorktrees() {
|
|
|
1063
1160
|
if (!entry.isDirectory()) {
|
|
1064
1161
|
continue;
|
|
1065
1162
|
}
|
|
1066
|
-
const fullPath =
|
|
1163
|
+
const fullPath = join3(projectDir, entry.name);
|
|
1067
1164
|
if (registeredPaths.has(fullPath)) {
|
|
1068
1165
|
worktrees.push({
|
|
1069
1166
|
path: fullPath,
|
|
@@ -1149,7 +1246,7 @@ import Enquirer from "enquirer";
|
|
|
1149
1246
|
// src/utils/claude.ts
|
|
1150
1247
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1151
1248
|
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
1152
|
-
import { join as
|
|
1249
|
+
import { join as join4 } from "path";
|
|
1153
1250
|
|
|
1154
1251
|
// src/utils/terminal.ts
|
|
1155
1252
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -1228,7 +1325,7 @@ function encodeClaudeProjectPath(absolutePath) {
|
|
|
1228
1325
|
}
|
|
1229
1326
|
function hasClaudeSessionHistory(worktreePath) {
|
|
1230
1327
|
const encodedName = encodeClaudeProjectPath(worktreePath);
|
|
1231
|
-
const projectDir =
|
|
1328
|
+
const projectDir = join4(CLAUDE_PROJECTS_DIR, encodedName);
|
|
1232
1329
|
if (!existsSync6(projectDir)) {
|
|
1233
1330
|
return false;
|
|
1234
1331
|
}
|
|
@@ -1285,13 +1382,13 @@ function launchInteractiveClaudeInNewTerminal(worktree, hasPreviousSession) {
|
|
|
1285
1382
|
}
|
|
1286
1383
|
|
|
1287
1384
|
// src/utils/validate-snapshot.ts
|
|
1288
|
-
import { join as
|
|
1289
|
-
import { existsSync as existsSync7, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, readdirSync as readdirSync4, rmdirSync as rmdirSync2, statSync } from "fs";
|
|
1385
|
+
import { join as join5 } from "path";
|
|
1386
|
+
import { existsSync as existsSync7, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, readdirSync as readdirSync4, rmdirSync as rmdirSync2, statSync as statSync2 } from "fs";
|
|
1290
1387
|
function getSnapshotPath(projectName, branchName) {
|
|
1291
|
-
return
|
|
1388
|
+
return join5(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.tree`);
|
|
1292
1389
|
}
|
|
1293
1390
|
function getSnapshotHeadPath(projectName, branchName) {
|
|
1294
|
-
return
|
|
1391
|
+
return join5(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.head`);
|
|
1295
1392
|
}
|
|
1296
1393
|
function hasSnapshot(projectName, branchName) {
|
|
1297
1394
|
return existsSync7(getSnapshotPath(projectName, branchName));
|
|
@@ -1299,7 +1396,7 @@ function hasSnapshot(projectName, branchName) {
|
|
|
1299
1396
|
function getSnapshotModifiedTime(projectName, branchName) {
|
|
1300
1397
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1301
1398
|
if (!existsSync7(snapshotPath)) return null;
|
|
1302
|
-
const stat =
|
|
1399
|
+
const stat = statSync2(snapshotPath);
|
|
1303
1400
|
return stat.mtime.toISOString();
|
|
1304
1401
|
}
|
|
1305
1402
|
function readSnapshot(projectName, branchName) {
|
|
@@ -1313,7 +1410,7 @@ function readSnapshot(projectName, branchName) {
|
|
|
1313
1410
|
function writeSnapshot(projectName, branchName, treeHash, headCommitHash) {
|
|
1314
1411
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1315
1412
|
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
1316
|
-
const snapshotDir =
|
|
1413
|
+
const snapshotDir = join5(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1317
1414
|
ensureDir(snapshotDir);
|
|
1318
1415
|
writeFileSync2(snapshotPath, treeHash, "utf-8");
|
|
1319
1416
|
writeFileSync2(headPath, headCommitHash, "utf-8");
|
|
@@ -1332,7 +1429,7 @@ function removeSnapshot(projectName, branchName) {
|
|
|
1332
1429
|
}
|
|
1333
1430
|
}
|
|
1334
1431
|
function getProjectSnapshotBranches(projectName) {
|
|
1335
|
-
const projectDir =
|
|
1432
|
+
const projectDir = join5(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1336
1433
|
if (!existsSync7(projectDir)) {
|
|
1337
1434
|
return [];
|
|
1338
1435
|
}
|
|
@@ -1340,13 +1437,13 @@ function getProjectSnapshotBranches(projectName) {
|
|
|
1340
1437
|
return files.filter((f) => f.endsWith(".tree")).map((f) => f.replace(/\.tree$/, ""));
|
|
1341
1438
|
}
|
|
1342
1439
|
function removeProjectSnapshots(projectName) {
|
|
1343
|
-
const projectDir =
|
|
1440
|
+
const projectDir = join5(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1344
1441
|
if (!existsSync7(projectDir)) {
|
|
1345
1442
|
return;
|
|
1346
1443
|
}
|
|
1347
1444
|
const files = readdirSync4(projectDir);
|
|
1348
1445
|
for (const file of files) {
|
|
1349
|
-
unlinkSync(
|
|
1446
|
+
unlinkSync(join5(projectDir, file));
|
|
1350
1447
|
}
|
|
1351
1448
|
try {
|
|
1352
1449
|
rmdirSync2(projectDir);
|
|
@@ -2145,7 +2242,7 @@ async function executeBatchTasks(worktrees, tasks, concurrency) {
|
|
|
2145
2242
|
|
|
2146
2243
|
// src/utils/dry-run.ts
|
|
2147
2244
|
import chalk4 from "chalk";
|
|
2148
|
-
import { join as
|
|
2245
|
+
import { join as join6 } from "path";
|
|
2149
2246
|
var DRY_RUN_TASK_DESC_MAX_LENGTH = 80;
|
|
2150
2247
|
function truncateTaskDesc(task) {
|
|
2151
2248
|
const oneLine = task.replace(/\n/g, " ").trim();
|
|
@@ -2173,7 +2270,7 @@ function printDryRunPreview(branchNames, tasks, concurrency) {
|
|
|
2173
2270
|
let hasConflict = false;
|
|
2174
2271
|
for (let i = 0; i < branchNames.length; i++) {
|
|
2175
2272
|
const branch = branchNames[i];
|
|
2176
|
-
const worktreePath =
|
|
2273
|
+
const worktreePath = join6(projectDir, branch);
|
|
2177
2274
|
const exists = checkBranchExists(branch);
|
|
2178
2275
|
if (exists) hasConflict = true;
|
|
2179
2276
|
const indexLabel = `[${i + 1}/${branchNames.length}]`;
|
|
@@ -2299,8 +2396,139 @@ async function promptStringValue(key, currentValue) {
|
|
|
2299
2396
|
}).run();
|
|
2300
2397
|
}
|
|
2301
2398
|
|
|
2302
|
-
// src/
|
|
2399
|
+
// src/utils/update-checker.ts
|
|
2400
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
2401
|
+
import { execSync as execSync3 } from "child_process";
|
|
2402
|
+
import { request } from "https";
|
|
2303
2403
|
import chalk6 from "chalk";
|
|
2404
|
+
import stringWidth2 from "string-width";
|
|
2405
|
+
function readUpdateCache() {
|
|
2406
|
+
try {
|
|
2407
|
+
const raw = readFileSync4(UPDATE_CHECK_PATH, "utf-8");
|
|
2408
|
+
return JSON.parse(raw);
|
|
2409
|
+
} catch {
|
|
2410
|
+
return null;
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
function writeUpdateCache(cache) {
|
|
2414
|
+
try {
|
|
2415
|
+
writeFileSync3(UPDATE_CHECK_PATH, JSON.stringify(cache, null, 2), "utf-8");
|
|
2416
|
+
} catch {
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
function isCacheExpired(cache, currentVersion) {
|
|
2420
|
+
if (cache.currentVersion !== currentVersion) {
|
|
2421
|
+
return true;
|
|
2422
|
+
}
|
|
2423
|
+
return Date.now() - cache.lastCheck > UPDATE_CHECK_INTERVAL_MS;
|
|
2424
|
+
}
|
|
2425
|
+
function isNewerVersion(latest, current) {
|
|
2426
|
+
const latestParts = latest.split(".").map(Number);
|
|
2427
|
+
const currentParts = current.split(".").map(Number);
|
|
2428
|
+
for (let i = 0; i < 3; i++) {
|
|
2429
|
+
const l = latestParts[i] || 0;
|
|
2430
|
+
const c = currentParts[i] || 0;
|
|
2431
|
+
if (l > c) return true;
|
|
2432
|
+
if (l < c) return false;
|
|
2433
|
+
}
|
|
2434
|
+
return false;
|
|
2435
|
+
}
|
|
2436
|
+
function fetchLatestVersion() {
|
|
2437
|
+
return new Promise((resolve3) => {
|
|
2438
|
+
const req = request(NPM_REGISTRY_URL, { timeout: NPM_REGISTRY_TIMEOUT_MS }, (res) => {
|
|
2439
|
+
let data = "";
|
|
2440
|
+
res.on("data", (chunk) => {
|
|
2441
|
+
data += chunk.toString();
|
|
2442
|
+
});
|
|
2443
|
+
res.on("end", () => {
|
|
2444
|
+
try {
|
|
2445
|
+
const parsed = JSON.parse(data);
|
|
2446
|
+
resolve3(parsed.version ?? null);
|
|
2447
|
+
} catch {
|
|
2448
|
+
resolve3(null);
|
|
2449
|
+
}
|
|
2450
|
+
});
|
|
2451
|
+
});
|
|
2452
|
+
req.on("error", () => resolve3(null));
|
|
2453
|
+
req.on("timeout", () => {
|
|
2454
|
+
req.destroy();
|
|
2455
|
+
resolve3(null);
|
|
2456
|
+
});
|
|
2457
|
+
req.end();
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
function detectPackageManager() {
|
|
2461
|
+
const checks = [
|
|
2462
|
+
{ name: "pnpm", command: `pnpm list -g --depth=0 ${PACKAGE_NAME}` },
|
|
2463
|
+
{ name: "yarn", command: `yarn global list --depth=0 2>/dev/null` }
|
|
2464
|
+
];
|
|
2465
|
+
for (const { name, command } of checks) {
|
|
2466
|
+
try {
|
|
2467
|
+
const output = execSync3(command, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
2468
|
+
if (output.includes(PACKAGE_NAME)) {
|
|
2469
|
+
return name;
|
|
2470
|
+
}
|
|
2471
|
+
} catch {
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
return "npm";
|
|
2475
|
+
}
|
|
2476
|
+
function printUpdateNotification(currentVersion, latestVersion) {
|
|
2477
|
+
const updateText = UPDATE_MESSAGES.UPDATE_AVAILABLE(
|
|
2478
|
+
chalk6.red(currentVersion),
|
|
2479
|
+
chalk6.green(latestVersion)
|
|
2480
|
+
);
|
|
2481
|
+
const pm = detectPackageManager();
|
|
2482
|
+
const updateCommand = UPDATE_COMMANDS[pm] || UPDATE_COMMANDS.npm;
|
|
2483
|
+
const commandText = UPDATE_MESSAGES.UPDATE_HINT(chalk6.cyan(updateCommand));
|
|
2484
|
+
const updateTextWidth = stringWidth2(updateText);
|
|
2485
|
+
const commandTextWidth = stringWidth2(commandText);
|
|
2486
|
+
const innerWidth = Math.max(updateTextWidth, commandTextWidth) + 4;
|
|
2487
|
+
const padLine = (text) => {
|
|
2488
|
+
const textWidth = stringWidth2(text);
|
|
2489
|
+
const leftPad = Math.floor((innerWidth - textWidth) / 2);
|
|
2490
|
+
const rightPad = innerWidth - textWidth - leftPad;
|
|
2491
|
+
return `\u2502${" ".repeat(leftPad)}${text}${" ".repeat(rightPad)}\u2502`;
|
|
2492
|
+
};
|
|
2493
|
+
const top = `\u256D${"\u2500".repeat(innerWidth)}\u256E`;
|
|
2494
|
+
const bottom = `\u2570${"\u2500".repeat(innerWidth)}\u256F`;
|
|
2495
|
+
const emptyLine = `\u2502${" ".repeat(innerWidth)}\u2502`;
|
|
2496
|
+
console.log();
|
|
2497
|
+
console.log(top);
|
|
2498
|
+
console.log(emptyLine);
|
|
2499
|
+
console.log(padLine(updateText));
|
|
2500
|
+
console.log(padLine(commandText));
|
|
2501
|
+
console.log(emptyLine);
|
|
2502
|
+
console.log(bottom);
|
|
2503
|
+
console.log();
|
|
2504
|
+
}
|
|
2505
|
+
async function checkForUpdates(currentVersion) {
|
|
2506
|
+
try {
|
|
2507
|
+
const cache = readUpdateCache();
|
|
2508
|
+
if (cache && !isCacheExpired(cache, currentVersion)) {
|
|
2509
|
+
if (isNewerVersion(cache.latestVersion, currentVersion)) {
|
|
2510
|
+
printUpdateNotification(currentVersion, cache.latestVersion);
|
|
2511
|
+
}
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
const latestVersion = await fetchLatestVersion();
|
|
2515
|
+
if (!latestVersion) {
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
writeUpdateCache({
|
|
2519
|
+
lastCheck: Date.now(),
|
|
2520
|
+
latestVersion,
|
|
2521
|
+
currentVersion
|
|
2522
|
+
});
|
|
2523
|
+
if (isNewerVersion(latestVersion, currentVersion)) {
|
|
2524
|
+
printUpdateNotification(currentVersion, latestVersion);
|
|
2525
|
+
}
|
|
2526
|
+
} catch {
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
// src/commands/list.ts
|
|
2531
|
+
import chalk7 from "chalk";
|
|
2304
2532
|
function registerListCommand(program2) {
|
|
2305
2533
|
program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
2306
2534
|
handleList(options);
|
|
@@ -2337,12 +2565,12 @@ function printListAsText(projectName, worktrees) {
|
|
|
2337
2565
|
for (const wt of worktrees) {
|
|
2338
2566
|
const status = getWorktreeStatus(wt);
|
|
2339
2567
|
const isIdle = status ? isWorktreeIdle(status) : false;
|
|
2340
|
-
const pathDisplay = isIdle ?
|
|
2568
|
+
const pathDisplay = isIdle ? chalk7.hex("#FF8C00")(wt.path) : wt.path;
|
|
2341
2569
|
printInfo(` ${pathDisplay} [${wt.branch}]`);
|
|
2342
2570
|
if (status) {
|
|
2343
2571
|
printInfo(` ${formatWorktreeStatus(status)}`);
|
|
2344
2572
|
} else {
|
|
2345
|
-
printInfo(` ${
|
|
2573
|
+
printInfo(` ${chalk7.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
|
|
2346
2574
|
}
|
|
2347
2575
|
printInfo("");
|
|
2348
2576
|
}
|
|
@@ -3025,7 +3253,7 @@ async function handleMerge(options) {
|
|
|
3025
3253
|
}
|
|
3026
3254
|
|
|
3027
3255
|
// src/commands/config.ts
|
|
3028
|
-
import
|
|
3256
|
+
import chalk8 from "chalk";
|
|
3029
3257
|
import Enquirer5 from "enquirer";
|
|
3030
3258
|
function registerConfigCommand(program2) {
|
|
3031
3259
|
const configCmd = program2.command("config").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u5168\u5C40\u914D\u7F6E").action(async () => {
|
|
@@ -3086,7 +3314,7 @@ async function handleInteractiveConfigSet() {
|
|
|
3086
3314
|
const isObject = typeof DEFAULT_CONFIG[k] === "object";
|
|
3087
3315
|
return {
|
|
3088
3316
|
name: k,
|
|
3089
|
-
message: `${k}: ${isObject ?
|
|
3317
|
+
message: `${k}: ${isObject ? chalk8.dim(JSON.stringify(config2[k])) : formatConfigValue(config2[k])} ${chalk8.dim(`\u2014 ${CONFIG_DESCRIPTIONS[k]}`)}`,
|
|
3090
3318
|
...isObject && { disabled: CONFIG_ALIAS_DISABLED_HINT }
|
|
3091
3319
|
};
|
|
3092
3320
|
});
|
|
@@ -3141,7 +3369,7 @@ async function handleReset() {
|
|
|
3141
3369
|
}
|
|
3142
3370
|
|
|
3143
3371
|
// src/commands/status.ts
|
|
3144
|
-
import
|
|
3372
|
+
import chalk9 from "chalk";
|
|
3145
3373
|
function registerStatusCommand(program2) {
|
|
3146
3374
|
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
3375
|
handleStatus(options);
|
|
@@ -3161,7 +3389,7 @@ function collectStatus() {
|
|
|
3161
3389
|
const projectName = getProjectName();
|
|
3162
3390
|
const currentBranch = getCurrentBranch();
|
|
3163
3391
|
const isClean = isWorkingDirClean();
|
|
3164
|
-
const
|
|
3392
|
+
const main2 = {
|
|
3165
3393
|
branch: currentBranch,
|
|
3166
3394
|
isClean,
|
|
3167
3395
|
projectName
|
|
@@ -3170,7 +3398,7 @@ function collectStatus() {
|
|
|
3170
3398
|
const worktreeStatuses = worktrees.map((wt) => collectWorktreeDetailedStatus(wt, projectName));
|
|
3171
3399
|
const snapshots = collectSnapshots(projectName, worktrees);
|
|
3172
3400
|
return {
|
|
3173
|
-
main,
|
|
3401
|
+
main: main2,
|
|
3174
3402
|
worktrees: worktreeStatuses,
|
|
3175
3403
|
snapshots,
|
|
3176
3404
|
totalWorktrees: worktrees.length
|
|
@@ -3254,7 +3482,7 @@ function printStatusAsJson(result) {
|
|
|
3254
3482
|
}
|
|
3255
3483
|
function printStatusAsText(result) {
|
|
3256
3484
|
printDoubleSeparator();
|
|
3257
|
-
printInfo(` ${
|
|
3485
|
+
printInfo(` ${chalk9.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
|
|
3258
3486
|
printDoubleSeparator();
|
|
3259
3487
|
printInfo("");
|
|
3260
3488
|
printMainSection(result.main);
|
|
@@ -3266,18 +3494,18 @@ function printStatusAsText(result) {
|
|
|
3266
3494
|
printSnapshotsSection(result.snapshots);
|
|
3267
3495
|
printDoubleSeparator();
|
|
3268
3496
|
}
|
|
3269
|
-
function printMainSection(
|
|
3270
|
-
printInfo(` ${
|
|
3271
|
-
printInfo(` \u5206\u652F: ${
|
|
3272
|
-
if (
|
|
3273
|
-
printInfo(` \u72B6\u6001: ${
|
|
3497
|
+
function printMainSection(main2) {
|
|
3498
|
+
printInfo(` ${chalk9.bold("\u25C6")} ${chalk9.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
|
|
3499
|
+
printInfo(` \u5206\u652F: ${chalk9.bold(main2.branch)}`);
|
|
3500
|
+
if (main2.isClean) {
|
|
3501
|
+
printInfo(` \u72B6\u6001: ${chalk9.green("\u2713 \u5E72\u51C0")}`);
|
|
3274
3502
|
} else {
|
|
3275
|
-
printInfo(` \u72B6\u6001: ${
|
|
3503
|
+
printInfo(` \u72B6\u6001: ${chalk9.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
|
|
3276
3504
|
}
|
|
3277
3505
|
printInfo("");
|
|
3278
3506
|
}
|
|
3279
3507
|
function printWorktreesSection(worktrees, total) {
|
|
3280
|
-
printInfo(` ${
|
|
3508
|
+
printInfo(` ${chalk9.bold("\u25C6")} ${chalk9.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
|
|
3281
3509
|
printInfo("");
|
|
3282
3510
|
if (worktrees.length === 0) {
|
|
3283
3511
|
printInfo(` ${MESSAGES.STATUS_NO_WORKTREES}`);
|
|
@@ -3289,56 +3517,56 @@ function printWorktreesSection(worktrees, total) {
|
|
|
3289
3517
|
}
|
|
3290
3518
|
function printWorktreeItem(wt) {
|
|
3291
3519
|
const statusLabel = formatChangeStatusLabel(wt.changeStatus);
|
|
3292
|
-
printInfo(` ${
|
|
3520
|
+
printInfo(` ${chalk9.bold("\u25CF")} ${chalk9.bold(wt.branch)} [${statusLabel}]`);
|
|
3293
3521
|
if (wt.insertions > 0 || wt.deletions > 0) {
|
|
3294
|
-
printInfo(` ${
|
|
3522
|
+
printInfo(` ${chalk9.green(`+${wt.insertions}`)} ${chalk9.red(`-${wt.deletions}`)}`);
|
|
3295
3523
|
}
|
|
3296
3524
|
if (wt.commitsAhead > 0) {
|
|
3297
|
-
printInfo(` ${
|
|
3525
|
+
printInfo(` ${chalk9.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`)}`);
|
|
3298
3526
|
}
|
|
3299
3527
|
if (wt.commitsBehind > 0) {
|
|
3300
|
-
printInfo(` ${
|
|
3528
|
+
printInfo(` ${chalk9.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`)}`);
|
|
3301
3529
|
} else {
|
|
3302
|
-
printInfo(` ${
|
|
3530
|
+
printInfo(` ${chalk9.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65")}`);
|
|
3303
3531
|
}
|
|
3304
3532
|
if (wt.createdAt) {
|
|
3305
3533
|
const relativeTime = formatRelativeTime(wt.createdAt);
|
|
3306
3534
|
if (relativeTime) {
|
|
3307
|
-
printInfo(` ${
|
|
3535
|
+
printInfo(` ${chalk9.gray(MESSAGES.STATUS_CREATED_AT(relativeTime))}`);
|
|
3308
3536
|
}
|
|
3309
3537
|
}
|
|
3310
3538
|
if (wt.snapshotTime) {
|
|
3311
3539
|
const relativeTime = formatRelativeTime(wt.snapshotTime);
|
|
3312
3540
|
if (relativeTime) {
|
|
3313
|
-
printInfo(` ${
|
|
3541
|
+
printInfo(` ${chalk9.green(MESSAGES.STATUS_LAST_VALIDATED(relativeTime))}`);
|
|
3314
3542
|
}
|
|
3315
3543
|
} else {
|
|
3316
|
-
printInfo(` ${
|
|
3544
|
+
printInfo(` ${chalk9.red(MESSAGES.STATUS_NOT_VALIDATED)}`);
|
|
3317
3545
|
}
|
|
3318
3546
|
printInfo("");
|
|
3319
3547
|
}
|
|
3320
3548
|
function formatChangeStatusLabel(status) {
|
|
3321
3549
|
switch (status) {
|
|
3322
3550
|
case "committed":
|
|
3323
|
-
return
|
|
3551
|
+
return chalk9.green(MESSAGES.STATUS_CHANGE_COMMITTED);
|
|
3324
3552
|
case "uncommitted":
|
|
3325
|
-
return
|
|
3553
|
+
return chalk9.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
|
|
3326
3554
|
case "conflict":
|
|
3327
|
-
return
|
|
3555
|
+
return chalk9.red(MESSAGES.STATUS_CHANGE_CONFLICT);
|
|
3328
3556
|
case "clean":
|
|
3329
|
-
return
|
|
3557
|
+
return chalk9.gray(MESSAGES.STATUS_CHANGE_CLEAN);
|
|
3330
3558
|
}
|
|
3331
3559
|
}
|
|
3332
3560
|
function printSnapshotsSection(snapshots) {
|
|
3333
|
-
printInfo(` ${
|
|
3561
|
+
printInfo(` ${chalk9.bold("\u25C6")} ${chalk9.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.total} \u4E2A)`);
|
|
3334
3562
|
if (snapshots.orphaned > 0) {
|
|
3335
|
-
printInfo(` ${
|
|
3563
|
+
printInfo(` ${chalk9.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED(snapshots.orphaned))}`);
|
|
3336
3564
|
}
|
|
3337
3565
|
printInfo("");
|
|
3338
3566
|
}
|
|
3339
3567
|
|
|
3340
3568
|
// src/commands/alias.ts
|
|
3341
|
-
import
|
|
3569
|
+
import chalk10 from "chalk";
|
|
3342
3570
|
function getRegisteredCommandNames(program2) {
|
|
3343
3571
|
return program2.commands.map((cmd) => cmd.name());
|
|
3344
3572
|
}
|
|
@@ -3359,7 +3587,7 @@ ${MESSAGES.ALIAS_LIST_TITLE}
|
|
|
3359
3587
|
`);
|
|
3360
3588
|
printSeparator();
|
|
3361
3589
|
for (const [alias, command] of entries) {
|
|
3362
|
-
printInfo(` ${
|
|
3590
|
+
printInfo(` ${chalk10.bold(alias)} \u2192 ${chalk10.cyan(command)}`);
|
|
3363
3591
|
}
|
|
3364
3592
|
printInfo("");
|
|
3365
3593
|
printSeparator();
|
|
@@ -3405,8 +3633,187 @@ function registerAliasCommand(program2) {
|
|
|
3405
3633
|
});
|
|
3406
3634
|
}
|
|
3407
3635
|
|
|
3636
|
+
// src/commands/projects.ts
|
|
3637
|
+
import { existsSync as existsSync9, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
|
|
3638
|
+
import { join as join7 } from "path";
|
|
3639
|
+
import chalk11 from "chalk";
|
|
3640
|
+
function registerProjectsCommand(program2) {
|
|
3641
|
+
program2.command("projects [name]").description("\u5C55\u793A\u6240\u6709\u9879\u76EE\u7684 worktree \u6982\u89C8\uFF0C\u6216\u67E5\u770B\u6307\u5B9A\u9879\u76EE\u7684 worktree \u8BE6\u60C5").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((name, options) => {
|
|
3642
|
+
handleProjects({ name, json: options.json });
|
|
3643
|
+
});
|
|
3644
|
+
}
|
|
3645
|
+
function handleProjects(options) {
|
|
3646
|
+
if (options.name) {
|
|
3647
|
+
handleProjectDetail(options.name, options.json);
|
|
3648
|
+
} else {
|
|
3649
|
+
handleProjectsOverview(options.json);
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
function handleProjectsOverview(json) {
|
|
3653
|
+
const result = collectProjectsOverview();
|
|
3654
|
+
logger.info(`projects \u547D\u4EE4\u6267\u884C\uFF0C\u5171 ${result.totalProjects} \u4E2A\u9879\u76EE`);
|
|
3655
|
+
if (json) {
|
|
3656
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3657
|
+
return;
|
|
3658
|
+
}
|
|
3659
|
+
printProjectsOverviewAsText(result);
|
|
3660
|
+
}
|
|
3661
|
+
function handleProjectDetail(name, json) {
|
|
3662
|
+
const projectDir = join7(WORKTREES_DIR, name);
|
|
3663
|
+
if (!existsSync9(projectDir)) {
|
|
3664
|
+
printError(MESSAGES.PROJECTS_NOT_FOUND(name));
|
|
3665
|
+
process.exit(1);
|
|
3666
|
+
}
|
|
3667
|
+
const result = collectProjectDetail(name, projectDir);
|
|
3668
|
+
logger.info(`projects \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${name}\uFF0C\u5171 ${result.worktrees.length} \u4E2A worktree`);
|
|
3669
|
+
if (json) {
|
|
3670
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3671
|
+
return;
|
|
3672
|
+
}
|
|
3673
|
+
printProjectDetailAsText(result);
|
|
3674
|
+
}
|
|
3675
|
+
function collectProjectsOverview() {
|
|
3676
|
+
if (!existsSync9(WORKTREES_DIR)) {
|
|
3677
|
+
return { projects: [], totalProjects: 0, totalDiskUsage: 0 };
|
|
3678
|
+
}
|
|
3679
|
+
const entries = readdirSync5(WORKTREES_DIR, { withFileTypes: true });
|
|
3680
|
+
const projects = [];
|
|
3681
|
+
for (const entry of entries) {
|
|
3682
|
+
if (!entry.isDirectory()) {
|
|
3683
|
+
continue;
|
|
3684
|
+
}
|
|
3685
|
+
const projectDir = join7(WORKTREES_DIR, entry.name);
|
|
3686
|
+
const overview = collectSingleProjectOverview(entry.name, projectDir);
|
|
3687
|
+
projects.push(overview);
|
|
3688
|
+
}
|
|
3689
|
+
sortByLastActiveTimeDesc(projects);
|
|
3690
|
+
const totalDiskUsage = projects.reduce((sum, p) => sum + p.diskUsage, 0);
|
|
3691
|
+
return {
|
|
3692
|
+
projects,
|
|
3693
|
+
totalProjects: projects.length,
|
|
3694
|
+
totalDiskUsage
|
|
3695
|
+
};
|
|
3696
|
+
}
|
|
3697
|
+
function collectSingleProjectOverview(name, projectDir) {
|
|
3698
|
+
const subEntries = readdirSync5(projectDir, { withFileTypes: true });
|
|
3699
|
+
const worktreeDirs = subEntries.filter((e) => e.isDirectory());
|
|
3700
|
+
const worktreeCount = worktreeDirs.length;
|
|
3701
|
+
const diskUsage = calculateDirSize(projectDir);
|
|
3702
|
+
const lastActiveTime = resolveProjectLastActiveTime(projectDir, worktreeDirs.map((e) => join7(projectDir, e.name)));
|
|
3703
|
+
return {
|
|
3704
|
+
name,
|
|
3705
|
+
worktreeCount,
|
|
3706
|
+
lastActiveTime,
|
|
3707
|
+
diskUsage
|
|
3708
|
+
};
|
|
3709
|
+
}
|
|
3710
|
+
function collectProjectDetail(name, projectDir) {
|
|
3711
|
+
const subEntries = readdirSync5(projectDir, { withFileTypes: true });
|
|
3712
|
+
const worktrees = [];
|
|
3713
|
+
for (const entry of subEntries) {
|
|
3714
|
+
if (!entry.isDirectory()) {
|
|
3715
|
+
continue;
|
|
3716
|
+
}
|
|
3717
|
+
const wtPath = join7(projectDir, entry.name);
|
|
3718
|
+
const detail = collectSingleWorktreeDetail(entry.name, wtPath);
|
|
3719
|
+
worktrees.push(detail);
|
|
3720
|
+
}
|
|
3721
|
+
worktrees.sort((a, b) => new Date(b.lastModifiedTime).getTime() - new Date(a.lastModifiedTime).getTime());
|
|
3722
|
+
const totalDiskUsage = worktrees.reduce((sum, wt) => sum + wt.diskUsage, 0);
|
|
3723
|
+
return {
|
|
3724
|
+
name,
|
|
3725
|
+
projectDir,
|
|
3726
|
+
worktrees,
|
|
3727
|
+
totalDiskUsage
|
|
3728
|
+
};
|
|
3729
|
+
}
|
|
3730
|
+
function collectSingleWorktreeDetail(branch, wtPath) {
|
|
3731
|
+
const stat = statSync3(wtPath);
|
|
3732
|
+
const diskUsage = calculateDirSize(wtPath);
|
|
3733
|
+
return {
|
|
3734
|
+
branch,
|
|
3735
|
+
path: wtPath,
|
|
3736
|
+
lastModifiedTime: formatLocalISOString(stat.mtime),
|
|
3737
|
+
diskUsage
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
function resolveProjectLastActiveTime(projectDir, worktreePaths) {
|
|
3741
|
+
let latestTime = statSync3(projectDir).mtime;
|
|
3742
|
+
for (const wtPath of worktreePaths) {
|
|
3743
|
+
try {
|
|
3744
|
+
const wtStat = statSync3(wtPath);
|
|
3745
|
+
if (wtStat.mtime > latestTime) {
|
|
3746
|
+
latestTime = wtStat.mtime;
|
|
3747
|
+
}
|
|
3748
|
+
} catch {
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
return formatLocalISOString(latestTime);
|
|
3752
|
+
}
|
|
3753
|
+
function sortByLastActiveTimeDesc(projects) {
|
|
3754
|
+
projects.sort((a, b) => new Date(b.lastActiveTime).getTime() - new Date(a.lastActiveTime).getTime());
|
|
3755
|
+
}
|
|
3756
|
+
function printProjectsOverviewAsText(result) {
|
|
3757
|
+
printDoubleSeparator();
|
|
3758
|
+
printInfo(` ${chalk11.bold.cyan(MESSAGES.PROJECTS_OVERVIEW_TITLE)}`);
|
|
3759
|
+
printDoubleSeparator();
|
|
3760
|
+
printInfo("");
|
|
3761
|
+
if (result.projects.length === 0) {
|
|
3762
|
+
printInfo(` ${MESSAGES.PROJECTS_NO_PROJECTS}`);
|
|
3763
|
+
printInfo("");
|
|
3764
|
+
printDoubleSeparator();
|
|
3765
|
+
return;
|
|
3766
|
+
}
|
|
3767
|
+
for (const project of result.projects) {
|
|
3768
|
+
printProjectOverviewItem(project);
|
|
3769
|
+
}
|
|
3770
|
+
printSeparator();
|
|
3771
|
+
printInfo("");
|
|
3772
|
+
printInfo(` \u5171 ${chalk11.bold(String(result.totalProjects))} \u4E2A\u9879\u76EE ${chalk11.gray(MESSAGES.PROJECTS_TOTAL_DISK_USAGE(formatDiskSize(result.totalDiskUsage)))}`);
|
|
3773
|
+
printInfo("");
|
|
3774
|
+
printDoubleSeparator();
|
|
3775
|
+
}
|
|
3776
|
+
function printProjectOverviewItem(project) {
|
|
3777
|
+
const relativeTime = formatRelativeTime(project.lastActiveTime);
|
|
3778
|
+
const activeLabel = relativeTime ? MESSAGES.PROJECTS_LAST_ACTIVE(relativeTime) : "";
|
|
3779
|
+
const diskLabel = MESSAGES.PROJECTS_DISK_USAGE(formatDiskSize(project.diskUsage));
|
|
3780
|
+
printInfo(` ${chalk11.bold("\u25CF")} ${chalk11.bold(project.name)}`);
|
|
3781
|
+
printInfo(` ${MESSAGES.PROJECTS_WORKTREE_COUNT(project.worktreeCount)} ${chalk11.gray(activeLabel)} ${chalk11.gray(diskLabel)}`);
|
|
3782
|
+
printInfo("");
|
|
3783
|
+
}
|
|
3784
|
+
function printProjectDetailAsText(result) {
|
|
3785
|
+
printDoubleSeparator();
|
|
3786
|
+
printInfo(` ${chalk11.bold.cyan(MESSAGES.PROJECTS_DETAIL_TITLE(result.name))}`);
|
|
3787
|
+
printDoubleSeparator();
|
|
3788
|
+
printInfo("");
|
|
3789
|
+
printInfo(` ${chalk11.bold("\u25C6")} ${chalk11.bold(MESSAGES.PROJECTS_PATH(result.projectDir))}`);
|
|
3790
|
+
printInfo(` ${MESSAGES.PROJECTS_TOTAL_DISK_USAGE(formatDiskSize(result.totalDiskUsage))}`);
|
|
3791
|
+
printInfo("");
|
|
3792
|
+
printSeparator();
|
|
3793
|
+
printInfo("");
|
|
3794
|
+
if (result.worktrees.length === 0) {
|
|
3795
|
+
printInfo(` ${MESSAGES.PROJECTS_DETAIL_NO_WORKTREES}`);
|
|
3796
|
+
printInfo("");
|
|
3797
|
+
printDoubleSeparator();
|
|
3798
|
+
return;
|
|
3799
|
+
}
|
|
3800
|
+
for (const wt of result.worktrees) {
|
|
3801
|
+
printWorktreeDetailItem(wt);
|
|
3802
|
+
}
|
|
3803
|
+
printDoubleSeparator();
|
|
3804
|
+
}
|
|
3805
|
+
function printWorktreeDetailItem(wt) {
|
|
3806
|
+
const relativeTime = formatRelativeTime(wt.lastModifiedTime);
|
|
3807
|
+
const modifiedLabel = relativeTime ? MESSAGES.PROJECTS_LAST_MODIFIED(relativeTime) : "";
|
|
3808
|
+
const diskLabel = MESSAGES.PROJECTS_DISK_USAGE(formatDiskSize(wt.diskUsage));
|
|
3809
|
+
printInfo(` ${chalk11.bold("\u25CF")} ${chalk11.bold(wt.branch)}`);
|
|
3810
|
+
printInfo(` ${wt.path}`);
|
|
3811
|
+
printInfo(` ${chalk11.gray(modifiedLabel)} ${chalk11.gray(diskLabel)}`);
|
|
3812
|
+
printInfo("");
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3408
3815
|
// src/commands/completion.ts
|
|
3409
|
-
import { readFileSync as
|
|
3816
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync11 } from "fs";
|
|
3410
3817
|
import { resolve as resolve2 } from "path";
|
|
3411
3818
|
import { homedir as homedir2 } from "os";
|
|
3412
3819
|
|
|
@@ -3457,25 +3864,25 @@ compdef _clawt_completion clawt
|
|
|
3457
3864
|
}
|
|
3458
3865
|
|
|
3459
3866
|
// src/utils/completion-engine.ts
|
|
3460
|
-
import { existsSync as
|
|
3461
|
-
import { join as
|
|
3867
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
3868
|
+
import { join as join8, dirname, basename as basename2 } from "path";
|
|
3462
3869
|
function completeFilePath(partial) {
|
|
3463
3870
|
const cwd = process.cwd();
|
|
3464
3871
|
const hasDir = partial.includes("/");
|
|
3465
|
-
const searchDir = hasDir ?
|
|
3872
|
+
const searchDir = hasDir ? join8(cwd, dirname(partial)) : cwd;
|
|
3466
3873
|
const prefix = hasDir ? basename2(partial) : partial;
|
|
3467
|
-
if (!
|
|
3874
|
+
if (!existsSync10(searchDir)) {
|
|
3468
3875
|
return [];
|
|
3469
3876
|
}
|
|
3470
|
-
const entries =
|
|
3877
|
+
const entries = readdirSync6(searchDir);
|
|
3471
3878
|
const results = [];
|
|
3472
3879
|
const dirPrefix = hasDir ? dirname(partial) + "/" : "";
|
|
3473
3880
|
for (const entry of entries) {
|
|
3474
3881
|
if (!entry.startsWith(prefix)) continue;
|
|
3475
3882
|
if (entry.startsWith(".")) continue;
|
|
3476
|
-
const fullPath =
|
|
3883
|
+
const fullPath = join8(searchDir, entry);
|
|
3477
3884
|
try {
|
|
3478
|
-
const stat =
|
|
3885
|
+
const stat = statSync4(fullPath);
|
|
3479
3886
|
if (stat.isDirectory()) {
|
|
3480
3887
|
results.push(dirPrefix + entry + "/");
|
|
3481
3888
|
} else if (stat.isFile()) {
|
|
@@ -3562,15 +3969,15 @@ function generateCompletions(program2, args) {
|
|
|
3562
3969
|
|
|
3563
3970
|
// src/commands/completion.ts
|
|
3564
3971
|
function appendToFile(filePath, content) {
|
|
3565
|
-
if (
|
|
3566
|
-
const current =
|
|
3972
|
+
if (existsSync11(filePath)) {
|
|
3973
|
+
const current = readFileSync5(filePath, "utf-8");
|
|
3567
3974
|
if (current.includes("clawt completion")) {
|
|
3568
3975
|
printInfo(MESSAGES.COMPLETION_INSTALL_EXISTS + ": " + filePath);
|
|
3569
3976
|
return;
|
|
3570
3977
|
}
|
|
3571
3978
|
content = current + content;
|
|
3572
3979
|
}
|
|
3573
|
-
|
|
3980
|
+
writeFileSync4(filePath, content, "utf-8");
|
|
3574
3981
|
printSuccess(MESSAGES.COMPLETION_INSTALL_SUCCESS + ": " + filePath);
|
|
3575
3982
|
printInfo(MESSAGES.COMPLETION_INSTALL_RESTART(filePath));
|
|
3576
3983
|
}
|
|
@@ -3640,6 +4047,7 @@ registerSyncCommand(program);
|
|
|
3640
4047
|
registerResetCommand(program);
|
|
3641
4048
|
registerStatusCommand(program);
|
|
3642
4049
|
registerAliasCommand(program);
|
|
4050
|
+
registerProjectsCommand(program);
|
|
3643
4051
|
registerCompletionCommand(program);
|
|
3644
4052
|
var config = loadConfig();
|
|
3645
4053
|
applyAliases(program, config.aliases);
|
|
@@ -3665,4 +4073,10 @@ process.on("unhandledRejection", (reason) => {
|
|
|
3665
4073
|
logger.error(`\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD: ${error.message}`);
|
|
3666
4074
|
process.exit(EXIT_CODES.ERROR);
|
|
3667
4075
|
});
|
|
3668
|
-
|
|
4076
|
+
async function main() {
|
|
4077
|
+
await program.parseAsync(process.argv);
|
|
4078
|
+
if (config.autoUpdate) {
|
|
4079
|
+
await checkForUpdates(version);
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
main();
|