clawt 2.18.0 → 2.20.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 +12 -2
- package/dist/index.js +500 -102
- package/dist/postinstall.js +31 -0
- package/docs/spec.md +171 -4
- package/package.json +1 -1
- package/src/commands/projects.ts +324 -0
- package/src/commands/resume.ts +8 -2
- package/src/constants/index.ts +1 -1
- package/src/constants/messages/index.ts +2 -0
- package/src/constants/messages/projects.ts +25 -0
- package/src/constants/prompt.ts +28 -0
- package/src/index.ts +2 -0
- package/src/types/command.ts +8 -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 -3
- package/src/utils/worktree-matcher.ts +268 -1
- package/tests/unit/commands/resume.test.ts +29 -8
- package/tests/unit/utils/formatter.test.ts +91 -1
- package/tests/unit/utils/fs.test.ts +125 -2
- package/tests/unit/utils/worktree-matcher.test.ts +142 -1
package/dist/index.js
CHANGED
|
@@ -373,6 +373,32 @@ var ALIAS_MESSAGES = {
|
|
|
373
373
|
ALIAS_LIST_TITLE: "\u5F53\u524D\u522B\u540D\u5217\u8868\uFF1A"
|
|
374
374
|
};
|
|
375
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
|
+
|
|
376
402
|
// src/constants/messages/completion.ts
|
|
377
403
|
var COMPLETION_MESSAGES = {
|
|
378
404
|
/** completion 命令的主描述 */
|
|
@@ -422,6 +448,7 @@ var MESSAGES = {
|
|
|
422
448
|
...CONFIG_CMD_MESSAGES,
|
|
423
449
|
...STATUS_MESSAGES,
|
|
424
450
|
...ALIAS_MESSAGES,
|
|
451
|
+
...PROJECTS_MESSAGES,
|
|
425
452
|
...COMPLETION_MESSAGES
|
|
426
453
|
};
|
|
427
454
|
|
|
@@ -540,8 +567,14 @@ var CLEAR_SCREEN = "\x1B[2J";
|
|
|
540
567
|
var CURSOR_HOME = "\x1B[H";
|
|
541
568
|
|
|
542
569
|
// src/constants/prompt.ts
|
|
570
|
+
import chalk from "chalk";
|
|
543
571
|
var SELECT_ALL_NAME = "__select_all__";
|
|
544
572
|
var SELECT_ALL_LABEL = "[select-all]";
|
|
573
|
+
var GROUP_SELECT_ALL_PREFIX = "__group_select_all_";
|
|
574
|
+
var GROUP_SELECT_ALL_LABEL = (dateLabel) => `[select-all: ${dateLabel}]`;
|
|
575
|
+
var GROUP_SEPARATOR_LABEL = (dateLabel, relativeTime) => `\u2550\u2550\u2550\u2550 ${chalk.bold.hex("#FF8C00")(dateLabel)}\uFF08${chalk.hex("#FF8C00")(relativeTime)}\uFF09 \u2550\u2550\u2550\u2550`;
|
|
576
|
+
var UNKNOWN_DATE_GROUP = "\u672A\u77E5\u65E5\u671F";
|
|
577
|
+
var UNKNOWN_DATE_SEPARATOR_LABEL = `\u2550\u2550\u2550\u2550 ${chalk.bold.hex("#FF8C00")("\u672A\u77E5\u65E5\u671F")} \u2550\u2550\u2550\u2550`;
|
|
545
578
|
|
|
546
579
|
// src/errors/index.ts
|
|
547
580
|
var ClawtError = class extends Error {
|
|
@@ -561,7 +594,7 @@ var ClawtError = class extends Error {
|
|
|
561
594
|
// src/logger/index.ts
|
|
562
595
|
import winston from "winston";
|
|
563
596
|
import DailyRotateFile from "winston-daily-rotate-file";
|
|
564
|
-
import
|
|
597
|
+
import chalk2 from "chalk";
|
|
565
598
|
import { existsSync, mkdirSync } from "fs";
|
|
566
599
|
if (!existsSync(LOGS_DIR)) {
|
|
567
600
|
mkdirSync(LOGS_DIR, { recursive: true });
|
|
@@ -586,13 +619,13 @@ var logger = winston.createLogger({
|
|
|
586
619
|
transports: [dailyRotateTransport]
|
|
587
620
|
});
|
|
588
621
|
var LEVEL_COLORS = {
|
|
589
|
-
error:
|
|
590
|
-
warn:
|
|
591
|
-
info:
|
|
592
|
-
debug:
|
|
622
|
+
error: chalk2.red,
|
|
623
|
+
warn: chalk2.yellow,
|
|
624
|
+
info: chalk2.cyan,
|
|
625
|
+
debug: chalk2.gray
|
|
593
626
|
};
|
|
594
627
|
function colorizeLevel(level) {
|
|
595
|
-
const colorFn = LEVEL_COLORS[level] ||
|
|
628
|
+
const colorFn = LEVEL_COLORS[level] || chalk2.white;
|
|
596
629
|
return colorFn(level.toUpperCase().padEnd(5));
|
|
597
630
|
}
|
|
598
631
|
function enableConsoleTransport() {
|
|
@@ -603,7 +636,7 @@ function enableConsoleTransport() {
|
|
|
603
636
|
return;
|
|
604
637
|
}
|
|
605
638
|
const consoleFormat = winston.format.printf(({ level, message, timestamp }) => {
|
|
606
|
-
return `${
|
|
639
|
+
return `${chalk2.gray(timestamp)} ${colorizeLevel(level)} ${message}`;
|
|
607
640
|
});
|
|
608
641
|
const consoleTransport = new winston.transports.Console({
|
|
609
642
|
level: "debug",
|
|
@@ -871,22 +904,22 @@ function getBranchCreatedAt(branchName, cwd) {
|
|
|
871
904
|
}
|
|
872
905
|
|
|
873
906
|
// src/utils/formatter.ts
|
|
874
|
-
import
|
|
907
|
+
import chalk3 from "chalk";
|
|
875
908
|
import { createInterface } from "readline";
|
|
876
909
|
function printSuccess(message) {
|
|
877
|
-
console.log(
|
|
910
|
+
console.log(chalk3.green(message));
|
|
878
911
|
}
|
|
879
912
|
function printError(message) {
|
|
880
|
-
console.error(
|
|
913
|
+
console.error(chalk3.red(`\u2717 ${message}`));
|
|
881
914
|
}
|
|
882
915
|
function printWarning(message) {
|
|
883
|
-
console.log(
|
|
916
|
+
console.log(chalk3.yellow(`\u26A0 ${message}`));
|
|
884
917
|
}
|
|
885
918
|
function printInfo(message) {
|
|
886
919
|
console.log(message);
|
|
887
920
|
}
|
|
888
921
|
function printHint(message) {
|
|
889
|
-
console.log(
|
|
922
|
+
console.log(chalk3.hex("#FF8C00")(message));
|
|
890
923
|
}
|
|
891
924
|
function printSeparator() {
|
|
892
925
|
console.log(MESSAGES.SEPARATOR);
|
|
@@ -907,7 +940,7 @@ function confirmAction(question) {
|
|
|
907
940
|
});
|
|
908
941
|
}
|
|
909
942
|
function confirmDestructiveAction(dangerousCommand, description) {
|
|
910
|
-
printWarning(`\u5373\u5C06\u6267\u884C ${
|
|
943
|
+
printWarning(`\u5373\u5C06\u6267\u884C ${chalk3.red.bold(dangerousCommand)}\uFF0C${description}`);
|
|
911
944
|
return confirmAction("\u662F\u5426\u7EE7\u7EED\uFF1F");
|
|
912
945
|
}
|
|
913
946
|
function isWorktreeIdle(status) {
|
|
@@ -915,17 +948,17 @@ function isWorktreeIdle(status) {
|
|
|
915
948
|
}
|
|
916
949
|
function formatWorktreeStatus(status) {
|
|
917
950
|
const parts = [];
|
|
918
|
-
parts.push(
|
|
951
|
+
parts.push(chalk3.yellow(`${status.commitCount} \u4E2A\u63D0\u4EA4`));
|
|
919
952
|
if (status.insertions === 0 && status.deletions === 0) {
|
|
920
953
|
parts.push("\u65E0\u53D8\u66F4");
|
|
921
954
|
} else {
|
|
922
955
|
const diffParts = [];
|
|
923
|
-
diffParts.push(
|
|
924
|
-
diffParts.push(
|
|
956
|
+
diffParts.push(chalk3.green(`+${status.insertions}`));
|
|
957
|
+
diffParts.push(chalk3.red(`-${status.deletions}`));
|
|
925
958
|
parts.push(diffParts.join(" "));
|
|
926
959
|
}
|
|
927
960
|
if (status.hasDirtyFiles) {
|
|
928
|
-
parts.push(
|
|
961
|
+
parts.push(chalk3.gray("(\u672A\u63D0\u4EA4\u4FEE\u6539)"));
|
|
929
962
|
}
|
|
930
963
|
return parts.join(" ");
|
|
931
964
|
}
|
|
@@ -967,6 +1000,32 @@ function formatRelativeTime(isoDateString) {
|
|
|
967
1000
|
const years = Math.floor(diffDays / 365);
|
|
968
1001
|
return `${years} \u5E74\u524D`;
|
|
969
1002
|
}
|
|
1003
|
+
function formatDiskSize(bytes) {
|
|
1004
|
+
const KB = 1024;
|
|
1005
|
+
const MB = KB * 1024;
|
|
1006
|
+
const GB = MB * 1024;
|
|
1007
|
+
if (bytes >= GB) {
|
|
1008
|
+
return `${(bytes / GB).toFixed(1)} GB`;
|
|
1009
|
+
}
|
|
1010
|
+
if (bytes >= MB) {
|
|
1011
|
+
return `${(bytes / MB).toFixed(1)} MB`;
|
|
1012
|
+
}
|
|
1013
|
+
if (bytes >= KB) {
|
|
1014
|
+
return `${(bytes / KB).toFixed(1)} KB`;
|
|
1015
|
+
}
|
|
1016
|
+
return `${bytes} B`;
|
|
1017
|
+
}
|
|
1018
|
+
function formatLocalISOString(date) {
|
|
1019
|
+
const tzOffsetMs = date.getTimezoneOffset() * 60 * 1e3;
|
|
1020
|
+
const localDate = new Date(date.getTime() - tzOffsetMs);
|
|
1021
|
+
const iso = localDate.toISOString().slice(0, -1);
|
|
1022
|
+
const totalMinutes = -date.getTimezoneOffset();
|
|
1023
|
+
const sign = totalMinutes >= 0 ? "+" : "-";
|
|
1024
|
+
const absMinutes = Math.abs(totalMinutes);
|
|
1025
|
+
const hours = String(Math.floor(absMinutes / 60)).padStart(2, "0");
|
|
1026
|
+
const minutes = String(absMinutes % 60).padStart(2, "0");
|
|
1027
|
+
return `${iso}${sign}${hours}:${minutes}`;
|
|
1028
|
+
}
|
|
970
1029
|
|
|
971
1030
|
// src/utils/branch.ts
|
|
972
1031
|
function sanitizeBranchName(branchName) {
|
|
@@ -1017,11 +1076,12 @@ function validateClaudeCodeInstalled() {
|
|
|
1017
1076
|
}
|
|
1018
1077
|
|
|
1019
1078
|
// src/utils/worktree.ts
|
|
1020
|
-
import { join as
|
|
1079
|
+
import { join as join3 } from "path";
|
|
1021
1080
|
import { existsSync as existsSync3, readdirSync as readdirSync2 } from "fs";
|
|
1022
1081
|
|
|
1023
1082
|
// src/utils/fs.ts
|
|
1024
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmdirSync } from "fs";
|
|
1083
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmdirSync, statSync } from "fs";
|
|
1084
|
+
import { join as join2 } from "path";
|
|
1025
1085
|
function ensureDir(dirPath) {
|
|
1026
1086
|
if (!existsSync2(dirPath)) {
|
|
1027
1087
|
mkdirSync2(dirPath, { recursive: true });
|
|
@@ -1038,11 +1098,30 @@ function removeEmptyDir(dirPath) {
|
|
|
1038
1098
|
}
|
|
1039
1099
|
return false;
|
|
1040
1100
|
}
|
|
1101
|
+
function calculateDirSize(dirPath) {
|
|
1102
|
+
let totalSize = 0;
|
|
1103
|
+
try {
|
|
1104
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
1105
|
+
for (const entry of entries) {
|
|
1106
|
+
const fullPath = join2(dirPath, entry.name);
|
|
1107
|
+
try {
|
|
1108
|
+
if (entry.isDirectory()) {
|
|
1109
|
+
totalSize += calculateDirSize(fullPath);
|
|
1110
|
+
} else if (entry.isFile()) {
|
|
1111
|
+
totalSize += statSync(fullPath).size;
|
|
1112
|
+
}
|
|
1113
|
+
} catch {
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
} catch {
|
|
1117
|
+
}
|
|
1118
|
+
return totalSize;
|
|
1119
|
+
}
|
|
1041
1120
|
|
|
1042
1121
|
// src/utils/worktree.ts
|
|
1043
1122
|
function getProjectWorktreeDir() {
|
|
1044
1123
|
const projectName = getProjectName();
|
|
1045
|
-
return
|
|
1124
|
+
return join3(WORKTREES_DIR, projectName);
|
|
1046
1125
|
}
|
|
1047
1126
|
function createWorktrees(branchName, count) {
|
|
1048
1127
|
const sanitized = sanitizeBranchName(branchName);
|
|
@@ -1052,7 +1131,7 @@ function createWorktrees(branchName, count) {
|
|
|
1052
1131
|
ensureDir(projectDir);
|
|
1053
1132
|
const results = [];
|
|
1054
1133
|
for (const name of branchNames) {
|
|
1055
|
-
const worktreePath =
|
|
1134
|
+
const worktreePath = join3(projectDir, name);
|
|
1056
1135
|
createWorktree(name, worktreePath);
|
|
1057
1136
|
results.push({ path: worktreePath, branch: name });
|
|
1058
1137
|
logger.info(`worktree \u521B\u5EFA\u5B8C\u6210: ${worktreePath} (\u5206\u652F: ${name})`);
|
|
@@ -1065,7 +1144,7 @@ function createWorktreesByBranches(branchNames) {
|
|
|
1065
1144
|
ensureDir(projectDir);
|
|
1066
1145
|
const results = [];
|
|
1067
1146
|
for (const name of branchNames) {
|
|
1068
|
-
const worktreePath =
|
|
1147
|
+
const worktreePath = join3(projectDir, name);
|
|
1069
1148
|
createWorktree(name, worktreePath);
|
|
1070
1149
|
results.push({ path: worktreePath, branch: name });
|
|
1071
1150
|
logger.info(`worktree \u521B\u5EFA\u5B8C\u6210: ${worktreePath} (\u5206\u652F: ${name})`);
|
|
@@ -1087,7 +1166,7 @@ function getProjectWorktrees() {
|
|
|
1087
1166
|
if (!entry.isDirectory()) {
|
|
1088
1167
|
continue;
|
|
1089
1168
|
}
|
|
1090
|
-
const fullPath =
|
|
1169
|
+
const fullPath = join3(projectDir, entry.name);
|
|
1091
1170
|
if (registeredPaths.has(fullPath)) {
|
|
1092
1171
|
worktrees.push({
|
|
1093
1172
|
path: fullPath,
|
|
@@ -1173,7 +1252,7 @@ import Enquirer from "enquirer";
|
|
|
1173
1252
|
// src/utils/claude.ts
|
|
1174
1253
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1175
1254
|
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
1176
|
-
import { join as
|
|
1255
|
+
import { join as join4 } from "path";
|
|
1177
1256
|
|
|
1178
1257
|
// src/utils/terminal.ts
|
|
1179
1258
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -1252,7 +1331,7 @@ function encodeClaudeProjectPath(absolutePath) {
|
|
|
1252
1331
|
}
|
|
1253
1332
|
function hasClaudeSessionHistory(worktreePath) {
|
|
1254
1333
|
const encodedName = encodeClaudeProjectPath(worktreePath);
|
|
1255
|
-
const projectDir =
|
|
1334
|
+
const projectDir = join4(CLAUDE_PROJECTS_DIR, encodedName);
|
|
1256
1335
|
if (!existsSync6(projectDir)) {
|
|
1257
1336
|
return false;
|
|
1258
1337
|
}
|
|
@@ -1309,13 +1388,13 @@ function launchInteractiveClaudeInNewTerminal(worktree, hasPreviousSession) {
|
|
|
1309
1388
|
}
|
|
1310
1389
|
|
|
1311
1390
|
// src/utils/validate-snapshot.ts
|
|
1312
|
-
import { join as
|
|
1313
|
-
import { existsSync as existsSync7, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, readdirSync as readdirSync4, rmdirSync as rmdirSync2, statSync } from "fs";
|
|
1391
|
+
import { join as join5 } from "path";
|
|
1392
|
+
import { existsSync as existsSync7, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, readdirSync as readdirSync4, rmdirSync as rmdirSync2, statSync as statSync2 } from "fs";
|
|
1314
1393
|
function getSnapshotPath(projectName, branchName) {
|
|
1315
|
-
return
|
|
1394
|
+
return join5(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.tree`);
|
|
1316
1395
|
}
|
|
1317
1396
|
function getSnapshotHeadPath(projectName, branchName) {
|
|
1318
|
-
return
|
|
1397
|
+
return join5(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.head`);
|
|
1319
1398
|
}
|
|
1320
1399
|
function hasSnapshot(projectName, branchName) {
|
|
1321
1400
|
return existsSync7(getSnapshotPath(projectName, branchName));
|
|
@@ -1323,7 +1402,7 @@ function hasSnapshot(projectName, branchName) {
|
|
|
1323
1402
|
function getSnapshotModifiedTime(projectName, branchName) {
|
|
1324
1403
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1325
1404
|
if (!existsSync7(snapshotPath)) return null;
|
|
1326
|
-
const stat =
|
|
1405
|
+
const stat = statSync2(snapshotPath);
|
|
1327
1406
|
return stat.mtime.toISOString();
|
|
1328
1407
|
}
|
|
1329
1408
|
function readSnapshot(projectName, branchName) {
|
|
@@ -1337,7 +1416,7 @@ function readSnapshot(projectName, branchName) {
|
|
|
1337
1416
|
function writeSnapshot(projectName, branchName, treeHash, headCommitHash) {
|
|
1338
1417
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1339
1418
|
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
1340
|
-
const snapshotDir =
|
|
1419
|
+
const snapshotDir = join5(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1341
1420
|
ensureDir(snapshotDir);
|
|
1342
1421
|
writeFileSync2(snapshotPath, treeHash, "utf-8");
|
|
1343
1422
|
writeFileSync2(headPath, headCommitHash, "utf-8");
|
|
@@ -1356,7 +1435,7 @@ function removeSnapshot(projectName, branchName) {
|
|
|
1356
1435
|
}
|
|
1357
1436
|
}
|
|
1358
1437
|
function getProjectSnapshotBranches(projectName) {
|
|
1359
|
-
const projectDir =
|
|
1438
|
+
const projectDir = join5(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1360
1439
|
if (!existsSync7(projectDir)) {
|
|
1361
1440
|
return [];
|
|
1362
1441
|
}
|
|
@@ -1364,13 +1443,13 @@ function getProjectSnapshotBranches(projectName) {
|
|
|
1364
1443
|
return files.filter((f) => f.endsWith(".tree")).map((f) => f.replace(/\.tree$/, ""));
|
|
1365
1444
|
}
|
|
1366
1445
|
function removeProjectSnapshots(projectName) {
|
|
1367
|
-
const projectDir =
|
|
1446
|
+
const projectDir = join5(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1368
1447
|
if (!existsSync7(projectDir)) {
|
|
1369
1448
|
return;
|
|
1370
1449
|
}
|
|
1371
1450
|
const files = readdirSync4(projectDir);
|
|
1372
1451
|
for (const file of files) {
|
|
1373
|
-
unlinkSync(
|
|
1452
|
+
unlinkSync(join5(projectDir, file));
|
|
1374
1453
|
}
|
|
1375
1454
|
try {
|
|
1376
1455
|
rmdirSync2(projectDir);
|
|
@@ -1381,6 +1460,7 @@ function removeProjectSnapshots(projectName) {
|
|
|
1381
1460
|
|
|
1382
1461
|
// src/utils/worktree-matcher.ts
|
|
1383
1462
|
import Enquirer2 from "enquirer";
|
|
1463
|
+
import { statSync as statSync3 } from "fs";
|
|
1384
1464
|
function findExactMatch(worktrees, branchName) {
|
|
1385
1465
|
return worktrees.find((wt) => wt.branch === branchName);
|
|
1386
1466
|
}
|
|
@@ -1485,9 +1565,142 @@ async function resolveTargetWorktree(worktrees, messages, branchName) {
|
|
|
1485
1565
|
const allBranches = worktrees.map((wt) => wt.branch);
|
|
1486
1566
|
throw new ClawtError(messages.noMatch(branchName, allBranches));
|
|
1487
1567
|
}
|
|
1568
|
+
function formatLocalDate(date) {
|
|
1569
|
+
const year = date.getFullYear();
|
|
1570
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
1571
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
1572
|
+
return `${year}-${month}-${day}`;
|
|
1573
|
+
}
|
|
1574
|
+
function getWorktreeCreatedDate(dirPath) {
|
|
1575
|
+
try {
|
|
1576
|
+
const stat = statSync3(dirPath);
|
|
1577
|
+
return formatLocalDate(stat.birthtime);
|
|
1578
|
+
} catch {
|
|
1579
|
+
return null;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
function formatRelativeDate(dateStr) {
|
|
1583
|
+
const today = formatLocalDate(/* @__PURE__ */ new Date());
|
|
1584
|
+
const todayMs = new Date(today).getTime();
|
|
1585
|
+
const targetMs = new Date(dateStr).getTime();
|
|
1586
|
+
const diffDays = Math.round((todayMs - targetMs) / (1e3 * 60 * 60 * 24));
|
|
1587
|
+
if (diffDays === 0) return "\u4ECA\u5929";
|
|
1588
|
+
if (diffDays === 1) return "\u6628\u5929";
|
|
1589
|
+
if (diffDays < 30) return `${diffDays} \u5929\u524D`;
|
|
1590
|
+
if (diffDays < 365) {
|
|
1591
|
+
const months = Math.floor(diffDays / 30);
|
|
1592
|
+
return `${months} \u4E2A\u6708\u524D`;
|
|
1593
|
+
}
|
|
1594
|
+
const years = Math.floor(diffDays / 365);
|
|
1595
|
+
return `${years} \u5E74\u524D`;
|
|
1596
|
+
}
|
|
1597
|
+
function groupWorktreesByDate(worktrees) {
|
|
1598
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1599
|
+
for (const wt of worktrees) {
|
|
1600
|
+
const dateKey = getWorktreeCreatedDate(wt.path) ?? UNKNOWN_DATE_GROUP;
|
|
1601
|
+
if (!groups.has(dateKey)) {
|
|
1602
|
+
groups.set(dateKey, []);
|
|
1603
|
+
}
|
|
1604
|
+
groups.get(dateKey).push(wt);
|
|
1605
|
+
}
|
|
1606
|
+
const sortedEntries = [...groups.entries()].sort((a, b) => {
|
|
1607
|
+
if (a[0] === UNKNOWN_DATE_GROUP) return 1;
|
|
1608
|
+
if (b[0] === UNKNOWN_DATE_GROUP) return -1;
|
|
1609
|
+
return b[0].localeCompare(a[0]);
|
|
1610
|
+
});
|
|
1611
|
+
return new Map(sortedEntries);
|
|
1612
|
+
}
|
|
1613
|
+
function buildGroupedChoices(groups) {
|
|
1614
|
+
const choices = [];
|
|
1615
|
+
choices.push({ name: SELECT_ALL_NAME, message: SELECT_ALL_LABEL });
|
|
1616
|
+
for (const [dateKey, worktreeList] of groups) {
|
|
1617
|
+
if (dateKey === UNKNOWN_DATE_GROUP) {
|
|
1618
|
+
choices.push({ role: "separator", message: UNKNOWN_DATE_SEPARATOR_LABEL });
|
|
1619
|
+
} else {
|
|
1620
|
+
const relativeTime = formatRelativeDate(dateKey);
|
|
1621
|
+
choices.push({ role: "separator", message: GROUP_SEPARATOR_LABEL(dateKey, relativeTime) });
|
|
1622
|
+
}
|
|
1623
|
+
const groupSelectAllName = `${GROUP_SELECT_ALL_PREFIX}${dateKey}`;
|
|
1624
|
+
choices.push({ name: groupSelectAllName, message: GROUP_SELECT_ALL_LABEL(dateKey) });
|
|
1625
|
+
for (const wt of worktreeList) {
|
|
1626
|
+
choices.push({ name: wt.branch, message: wt.branch });
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
return choices;
|
|
1630
|
+
}
|
|
1631
|
+
function buildGroupMembershipMap(groups) {
|
|
1632
|
+
const map = /* @__PURE__ */ new Map();
|
|
1633
|
+
for (const [dateKey, worktreeList] of groups) {
|
|
1634
|
+
const groupSelectAllName = `${GROUP_SELECT_ALL_PREFIX}${dateKey}`;
|
|
1635
|
+
map.set(groupSelectAllName, worktreeList.map((wt) => wt.branch));
|
|
1636
|
+
}
|
|
1637
|
+
return map;
|
|
1638
|
+
}
|
|
1639
|
+
async function promptGroupedMultiSelectBranches(worktrees, message) {
|
|
1640
|
+
const groups = groupWorktreesByDate(worktrees);
|
|
1641
|
+
const choices = buildGroupedChoices(groups);
|
|
1642
|
+
const groupMembershipMap = buildGroupMembershipMap(groups);
|
|
1643
|
+
const groupSelectAllNames = new Set(groupMembershipMap.keys());
|
|
1644
|
+
const allBranchNames = new Set(worktrees.map((wt) => wt.branch));
|
|
1645
|
+
const MultiSelect = Enquirer2.MultiSelect;
|
|
1646
|
+
class MultiSelectWithGroupSelectAll extends MultiSelect {
|
|
1647
|
+
space() {
|
|
1648
|
+
if (!this.focused) return;
|
|
1649
|
+
const focusedName = this.focused.name;
|
|
1650
|
+
if (focusedName === SELECT_ALL_NAME) {
|
|
1651
|
+
const willEnable = !this.focused.enabled;
|
|
1652
|
+
for (const ch of this.choices) {
|
|
1653
|
+
ch.enabled = willEnable;
|
|
1654
|
+
}
|
|
1655
|
+
return this.render();
|
|
1656
|
+
}
|
|
1657
|
+
if (groupSelectAllNames.has(focusedName)) {
|
|
1658
|
+
const willEnable = !this.focused.enabled;
|
|
1659
|
+
const memberNames = groupMembershipMap.get(focusedName);
|
|
1660
|
+
this.focused.enabled = willEnable;
|
|
1661
|
+
for (const ch of this.choices) {
|
|
1662
|
+
if (memberNames.includes(ch.name)) {
|
|
1663
|
+
ch.enabled = willEnable;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
syncGlobalSelectAll(this.choices);
|
|
1667
|
+
return this.render();
|
|
1668
|
+
}
|
|
1669
|
+
this.toggle(this.focused);
|
|
1670
|
+
syncGroupSelectAll(this.choices, focusedName);
|
|
1671
|
+
syncGlobalSelectAll(this.choices);
|
|
1672
|
+
return this.render();
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
function syncGlobalSelectAll(choiceList) {
|
|
1676
|
+
const selectAllChoice = choiceList.find((ch) => ch.name === SELECT_ALL_NAME);
|
|
1677
|
+
if (!selectAllChoice) return;
|
|
1678
|
+
const branchItems = choiceList.filter((ch) => allBranchNames.has(ch.name));
|
|
1679
|
+
selectAllChoice.enabled = branchItems.length > 0 && branchItems.every((ch) => ch.enabled);
|
|
1680
|
+
}
|
|
1681
|
+
function syncGroupSelectAll(choiceList, branchName) {
|
|
1682
|
+
for (const [groupName, memberNames] of groupMembershipMap) {
|
|
1683
|
+
if (!memberNames.includes(branchName)) continue;
|
|
1684
|
+
const groupChoice = choiceList.find((ch) => ch.name === groupName);
|
|
1685
|
+
if (!groupChoice) continue;
|
|
1686
|
+
const memberChoices = choiceList.filter((ch) => memberNames.includes(ch.name));
|
|
1687
|
+
groupChoice.enabled = memberChoices.length > 0 && memberChoices.every((ch) => ch.enabled);
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
const selectedBranches = await new MultiSelectWithGroupSelectAll({
|
|
1692
|
+
message,
|
|
1693
|
+
choices,
|
|
1694
|
+
// 使用空心圆/实心圆作为选中指示符
|
|
1695
|
+
symbols: {
|
|
1696
|
+
indicator: { on: "\u25CF", off: "\u25CB" }
|
|
1697
|
+
}
|
|
1698
|
+
}).run();
|
|
1699
|
+
return worktrees.filter((wt) => selectedBranches.includes(wt.branch));
|
|
1700
|
+
}
|
|
1488
1701
|
|
|
1489
1702
|
// src/utils/progress-render.ts
|
|
1490
|
-
import
|
|
1703
|
+
import chalk4 from "chalk";
|
|
1491
1704
|
import stringWidth from "string-width";
|
|
1492
1705
|
var ANSI_RESET = "\x1B[0m";
|
|
1493
1706
|
function truncateToTerminalWidth(text, maxWidth) {
|
|
@@ -1524,23 +1737,23 @@ function renderTaskLine(task, total, maxPathWidth, spinnerChar) {
|
|
|
1524
1737
|
const pathStr = task.path.padEnd(maxPathWidth);
|
|
1525
1738
|
switch (task.status) {
|
|
1526
1739
|
case "pending": {
|
|
1527
|
-
return `${indexStr} ${pathStr} ${
|
|
1740
|
+
return `${indexStr} ${pathStr} ${chalk4.gray(TASK_STATUS_ICONS.PENDING)} ${chalk4.gray(TASK_STATUS_LABELS.PENDING)}`;
|
|
1528
1741
|
}
|
|
1529
1742
|
case "running": {
|
|
1530
1743
|
const elapsed = formatDuration(Date.now() - task.startedAt);
|
|
1531
|
-
const detail = task.activity ? ` ${
|
|
1532
|
-
return `${indexStr} ${pathStr} ${
|
|
1744
|
+
const detail = task.activity ? ` ${chalk4.dim(task.activity)}` : "";
|
|
1745
|
+
return `${indexStr} ${pathStr} ${chalk4.cyan(spinnerChar)} ${chalk4.cyan(TASK_STATUS_LABELS.RUNNING)} ${chalk4.gray(elapsed)}${detail}`;
|
|
1533
1746
|
}
|
|
1534
1747
|
case "done": {
|
|
1535
1748
|
const duration = task.durationMs != null ? formatDuration(task.durationMs) : "N/A";
|
|
1536
1749
|
const cost = task.costUsd != null ? `$${task.costUsd.toFixed(2)}` : "";
|
|
1537
|
-
const preview = task.resultPreview ? ` ${
|
|
1538
|
-
return `${indexStr} ${pathStr} ${
|
|
1750
|
+
const preview = task.resultPreview ? ` ${chalk4.dim(task.resultPreview)}` : "";
|
|
1751
|
+
return `${indexStr} ${pathStr} ${chalk4.green(TASK_STATUS_ICONS.DONE)} ${chalk4.green(TASK_STATUS_LABELS.DONE)} ${chalk4.gray(duration)} ${chalk4.yellow(cost)}${preview}`;
|
|
1539
1752
|
}
|
|
1540
1753
|
case "failed": {
|
|
1541
1754
|
const duration = task.durationMs != null ? formatDuration(task.durationMs) : "N/A";
|
|
1542
|
-
const preview = task.resultPreview ? ` ${
|
|
1543
|
-
return `${indexStr} ${pathStr} ${
|
|
1755
|
+
const preview = task.resultPreview ? ` ${chalk4.dim(task.resultPreview)}` : "";
|
|
1756
|
+
return `${indexStr} ${pathStr} ${chalk4.red(TASK_STATUS_ICONS.FAILED)} ${chalk4.red(TASK_STATUS_LABELS.FAILED)} ${chalk4.gray(duration)}${preview}`;
|
|
1544
1757
|
}
|
|
1545
1758
|
}
|
|
1546
1759
|
}
|
|
@@ -1550,10 +1763,10 @@ function renderSummaryLine(tasks, total) {
|
|
|
1550
1763
|
const failed = tasks.filter((t) => t.status === "failed").length;
|
|
1551
1764
|
const pending = tasks.filter((t) => t.status === "pending").length;
|
|
1552
1765
|
const parts = [];
|
|
1553
|
-
if (running > 0) parts.push(
|
|
1554
|
-
if (done > 0) parts.push(
|
|
1555
|
-
if (failed > 0) parts.push(
|
|
1556
|
-
if (pending > 0) parts.push(
|
|
1766
|
+
if (running > 0) parts.push(chalk4.cyan(`${running}/${total} ${TASK_STATUS_LABELS.RUNNING}`));
|
|
1767
|
+
if (done > 0) parts.push(chalk4.green(`${done}/${total} ${TASK_STATUS_LABELS.DONE}`));
|
|
1768
|
+
if (failed > 0) parts.push(chalk4.red(`${failed}/${total} ${TASK_STATUS_LABELS.FAILED}`));
|
|
1769
|
+
if (pending > 0) parts.push(chalk4.gray(`${pending}/${total} ${TASK_STATUS_LABELS.PENDING}`));
|
|
1557
1770
|
return `[${parts.join(", ")}]`;
|
|
1558
1771
|
}
|
|
1559
1772
|
|
|
@@ -2168,8 +2381,8 @@ async function executeBatchTasks(worktrees, tasks, concurrency) {
|
|
|
2168
2381
|
}
|
|
2169
2382
|
|
|
2170
2383
|
// src/utils/dry-run.ts
|
|
2171
|
-
import
|
|
2172
|
-
import { join as
|
|
2384
|
+
import chalk5 from "chalk";
|
|
2385
|
+
import { join as join6 } from "path";
|
|
2173
2386
|
var DRY_RUN_TASK_DESC_MAX_LENGTH = 80;
|
|
2174
2387
|
function truncateTaskDesc(task) {
|
|
2175
2388
|
const oneLine = task.replace(/\n/g, " ").trim();
|
|
@@ -2182,7 +2395,7 @@ function printDryRunPreview(branchNames, tasks, concurrency) {
|
|
|
2182
2395
|
const projectDir = getProjectWorktreeDir();
|
|
2183
2396
|
const isInteractive = tasks.length === 0;
|
|
2184
2397
|
printDoubleSeparator();
|
|
2185
|
-
printInfo(` ${
|
|
2398
|
+
printInfo(` ${chalk5.bold(MESSAGES.DRY_RUN_TITLE)}`);
|
|
2186
2399
|
printDoubleSeparator();
|
|
2187
2400
|
const summaryParts = [
|
|
2188
2401
|
MESSAGES.DRY_RUN_TASK_COUNT(branchNames.length),
|
|
@@ -2192,31 +2405,31 @@ function printDryRunPreview(branchNames, tasks, concurrency) {
|
|
|
2192
2405
|
if (isInteractive) {
|
|
2193
2406
|
summaryParts.push(MESSAGES.DRY_RUN_INTERACTIVE_MODE);
|
|
2194
2407
|
}
|
|
2195
|
-
printInfo(summaryParts.join(
|
|
2408
|
+
printInfo(summaryParts.join(chalk5.gray(" \u2502 ")));
|
|
2196
2409
|
printSeparator();
|
|
2197
2410
|
let hasConflict = false;
|
|
2198
2411
|
for (let i = 0; i < branchNames.length; i++) {
|
|
2199
2412
|
const branch = branchNames[i];
|
|
2200
|
-
const worktreePath =
|
|
2413
|
+
const worktreePath = join6(projectDir, branch);
|
|
2201
2414
|
const exists = checkBranchExists(branch);
|
|
2202
2415
|
if (exists) hasConflict = true;
|
|
2203
2416
|
const indexLabel = `[${i + 1}/${branchNames.length}]`;
|
|
2204
2417
|
if (exists) {
|
|
2205
|
-
printInfo(`${
|
|
2418
|
+
printInfo(`${chalk5.yellow("\u26A0")} ${indexLabel} ${chalk5.yellow(branch)} ${chalk5.gray("\u2014")} ${chalk5.yellow(MESSAGES.DRY_RUN_BRANCH_EXISTS_WARNING(branch))}`);
|
|
2206
2419
|
} else {
|
|
2207
|
-
printInfo(`${
|
|
2420
|
+
printInfo(`${chalk5.green("\u2713")} ${indexLabel} ${chalk5.cyan(branch)}`);
|
|
2208
2421
|
}
|
|
2209
|
-
printInfo(` ${
|
|
2422
|
+
printInfo(` ${chalk5.gray("\u8DEF\u5F84:")} ${worktreePath}`);
|
|
2210
2423
|
if (!isInteractive) {
|
|
2211
|
-
printInfo(` ${
|
|
2424
|
+
printInfo(` ${chalk5.gray("\u4EFB\u52A1:")} ${truncateTaskDesc(tasks[i])}`);
|
|
2212
2425
|
}
|
|
2213
2426
|
printInfo("");
|
|
2214
2427
|
}
|
|
2215
2428
|
printDoubleSeparator();
|
|
2216
2429
|
if (hasConflict) {
|
|
2217
|
-
printInfo(
|
|
2430
|
+
printInfo(chalk5.yellow(`\u26A0 ${MESSAGES.DRY_RUN_HAS_CONFLICT}`));
|
|
2218
2431
|
} else {
|
|
2219
|
-
printInfo(
|
|
2432
|
+
printInfo(chalk5.green(`\u2713 ${MESSAGES.DRY_RUN_READY}`));
|
|
2220
2433
|
}
|
|
2221
2434
|
}
|
|
2222
2435
|
|
|
@@ -2234,7 +2447,7 @@ function applyAliases(program2, aliases) {
|
|
|
2234
2447
|
}
|
|
2235
2448
|
|
|
2236
2449
|
// src/utils/config-strategy.ts
|
|
2237
|
-
import
|
|
2450
|
+
import chalk6 from "chalk";
|
|
2238
2451
|
import Enquirer3 from "enquirer";
|
|
2239
2452
|
function isValidConfigKey(key) {
|
|
2240
2453
|
return key in DEFAULT_CONFIG;
|
|
@@ -2278,9 +2491,9 @@ async function promptConfigValue(key, currentValue) {
|
|
|
2278
2491
|
}
|
|
2279
2492
|
function formatConfigValue(value) {
|
|
2280
2493
|
if (typeof value === "boolean") {
|
|
2281
|
-
return value ?
|
|
2494
|
+
return value ? chalk6.green("true") : chalk6.yellow("false");
|
|
2282
2495
|
}
|
|
2283
|
-
return
|
|
2496
|
+
return chalk6.cyan(String(value));
|
|
2284
2497
|
}
|
|
2285
2498
|
async function promptBooleanValue(key, currentValue) {
|
|
2286
2499
|
const choices = [
|
|
@@ -2327,7 +2540,7 @@ async function promptStringValue(key, currentValue) {
|
|
|
2327
2540
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
2328
2541
|
import { execSync as execSync3 } from "child_process";
|
|
2329
2542
|
import { request } from "https";
|
|
2330
|
-
import
|
|
2543
|
+
import chalk7 from "chalk";
|
|
2331
2544
|
import stringWidth2 from "string-width";
|
|
2332
2545
|
function readUpdateCache() {
|
|
2333
2546
|
try {
|
|
@@ -2402,12 +2615,12 @@ function detectPackageManager() {
|
|
|
2402
2615
|
}
|
|
2403
2616
|
function printUpdateNotification(currentVersion, latestVersion) {
|
|
2404
2617
|
const updateText = UPDATE_MESSAGES.UPDATE_AVAILABLE(
|
|
2405
|
-
|
|
2406
|
-
|
|
2618
|
+
chalk7.red(currentVersion),
|
|
2619
|
+
chalk7.green(latestVersion)
|
|
2407
2620
|
);
|
|
2408
2621
|
const pm = detectPackageManager();
|
|
2409
2622
|
const updateCommand = UPDATE_COMMANDS[pm] || UPDATE_COMMANDS.npm;
|
|
2410
|
-
const commandText = UPDATE_MESSAGES.UPDATE_HINT(
|
|
2623
|
+
const commandText = UPDATE_MESSAGES.UPDATE_HINT(chalk7.cyan(updateCommand));
|
|
2411
2624
|
const updateTextWidth = stringWidth2(updateText);
|
|
2412
2625
|
const commandTextWidth = stringWidth2(commandText);
|
|
2413
2626
|
const innerWidth = Math.max(updateTextWidth, commandTextWidth) + 4;
|
|
@@ -2455,7 +2668,7 @@ async function checkForUpdates(currentVersion) {
|
|
|
2455
2668
|
}
|
|
2456
2669
|
|
|
2457
2670
|
// src/commands/list.ts
|
|
2458
|
-
import
|
|
2671
|
+
import chalk8 from "chalk";
|
|
2459
2672
|
function registerListCommand(program2) {
|
|
2460
2673
|
program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
2461
2674
|
handleList(options);
|
|
@@ -2492,12 +2705,12 @@ function printListAsText(projectName, worktrees) {
|
|
|
2492
2705
|
for (const wt of worktrees) {
|
|
2493
2706
|
const status = getWorktreeStatus(wt);
|
|
2494
2707
|
const isIdle = status ? isWorktreeIdle(status) : false;
|
|
2495
|
-
const pathDisplay = isIdle ?
|
|
2708
|
+
const pathDisplay = isIdle ? chalk8.hex("#FF8C00")(wt.path) : wt.path;
|
|
2496
2709
|
printInfo(` ${pathDisplay} [${wt.branch}]`);
|
|
2497
2710
|
if (status) {
|
|
2498
2711
|
printInfo(` ${formatWorktreeStatus(status)}`);
|
|
2499
2712
|
} else {
|
|
2500
|
-
printInfo(` ${
|
|
2713
|
+
printInfo(` ${chalk8.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
|
|
2501
2714
|
}
|
|
2502
2715
|
printInfo("");
|
|
2503
2716
|
}
|
|
@@ -2705,7 +2918,12 @@ async function handleResume(options) {
|
|
|
2705
2918
|
validateClaudeCodeInstalled();
|
|
2706
2919
|
logger.info(`resume \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F\u8FC7\u6EE4: ${options.branch ?? "(\u65E0)"}`);
|
|
2707
2920
|
const worktrees = getProjectWorktrees();
|
|
2708
|
-
|
|
2921
|
+
let targetWorktrees;
|
|
2922
|
+
if (!options.branch && worktrees.length > 1) {
|
|
2923
|
+
targetWorktrees = await promptGroupedMultiSelectBranches(worktrees, RESUME_RESOLVE_MESSAGES.selectBranch);
|
|
2924
|
+
} else {
|
|
2925
|
+
targetWorktrees = await resolveTargetWorktrees(worktrees, RESUME_RESOLVE_MESSAGES, options.branch);
|
|
2926
|
+
}
|
|
2709
2927
|
if (targetWorktrees.length === 0) {
|
|
2710
2928
|
return;
|
|
2711
2929
|
}
|
|
@@ -3180,7 +3398,7 @@ async function handleMerge(options) {
|
|
|
3180
3398
|
}
|
|
3181
3399
|
|
|
3182
3400
|
// src/commands/config.ts
|
|
3183
|
-
import
|
|
3401
|
+
import chalk9 from "chalk";
|
|
3184
3402
|
import Enquirer5 from "enquirer";
|
|
3185
3403
|
function registerConfigCommand(program2) {
|
|
3186
3404
|
const configCmd = program2.command("config").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u5168\u5C40\u914D\u7F6E").action(async () => {
|
|
@@ -3241,7 +3459,7 @@ async function handleInteractiveConfigSet() {
|
|
|
3241
3459
|
const isObject = typeof DEFAULT_CONFIG[k] === "object";
|
|
3242
3460
|
return {
|
|
3243
3461
|
name: k,
|
|
3244
|
-
message: `${k}: ${isObject ?
|
|
3462
|
+
message: `${k}: ${isObject ? chalk9.dim(JSON.stringify(config2[k])) : formatConfigValue(config2[k])} ${chalk9.dim(`\u2014 ${CONFIG_DESCRIPTIONS[k]}`)}`,
|
|
3245
3463
|
...isObject && { disabled: CONFIG_ALIAS_DISABLED_HINT }
|
|
3246
3464
|
};
|
|
3247
3465
|
});
|
|
@@ -3296,7 +3514,7 @@ async function handleReset() {
|
|
|
3296
3514
|
}
|
|
3297
3515
|
|
|
3298
3516
|
// src/commands/status.ts
|
|
3299
|
-
import
|
|
3517
|
+
import chalk10 from "chalk";
|
|
3300
3518
|
function registerStatusCommand(program2) {
|
|
3301
3519
|
program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
3302
3520
|
handleStatus(options);
|
|
@@ -3409,7 +3627,7 @@ function printStatusAsJson(result) {
|
|
|
3409
3627
|
}
|
|
3410
3628
|
function printStatusAsText(result) {
|
|
3411
3629
|
printDoubleSeparator();
|
|
3412
|
-
printInfo(` ${
|
|
3630
|
+
printInfo(` ${chalk10.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
|
|
3413
3631
|
printDoubleSeparator();
|
|
3414
3632
|
printInfo("");
|
|
3415
3633
|
printMainSection(result.main);
|
|
@@ -3422,17 +3640,17 @@ function printStatusAsText(result) {
|
|
|
3422
3640
|
printDoubleSeparator();
|
|
3423
3641
|
}
|
|
3424
3642
|
function printMainSection(main2) {
|
|
3425
|
-
printInfo(` ${
|
|
3426
|
-
printInfo(` \u5206\u652F: ${
|
|
3643
|
+
printInfo(` ${chalk10.bold("\u25C6")} ${chalk10.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
|
|
3644
|
+
printInfo(` \u5206\u652F: ${chalk10.bold(main2.branch)}`);
|
|
3427
3645
|
if (main2.isClean) {
|
|
3428
|
-
printInfo(` \u72B6\u6001: ${
|
|
3646
|
+
printInfo(` \u72B6\u6001: ${chalk10.green("\u2713 \u5E72\u51C0")}`);
|
|
3429
3647
|
} else {
|
|
3430
|
-
printInfo(` \u72B6\u6001: ${
|
|
3648
|
+
printInfo(` \u72B6\u6001: ${chalk10.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
|
|
3431
3649
|
}
|
|
3432
3650
|
printInfo("");
|
|
3433
3651
|
}
|
|
3434
3652
|
function printWorktreesSection(worktrees, total) {
|
|
3435
|
-
printInfo(` ${
|
|
3653
|
+
printInfo(` ${chalk10.bold("\u25C6")} ${chalk10.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
|
|
3436
3654
|
printInfo("");
|
|
3437
3655
|
if (worktrees.length === 0) {
|
|
3438
3656
|
printInfo(` ${MESSAGES.STATUS_NO_WORKTREES}`);
|
|
@@ -3444,56 +3662,56 @@ function printWorktreesSection(worktrees, total) {
|
|
|
3444
3662
|
}
|
|
3445
3663
|
function printWorktreeItem(wt) {
|
|
3446
3664
|
const statusLabel = formatChangeStatusLabel(wt.changeStatus);
|
|
3447
|
-
printInfo(` ${
|
|
3665
|
+
printInfo(` ${chalk10.bold("\u25CF")} ${chalk10.bold(wt.branch)} [${statusLabel}]`);
|
|
3448
3666
|
if (wt.insertions > 0 || wt.deletions > 0) {
|
|
3449
|
-
printInfo(` ${
|
|
3667
|
+
printInfo(` ${chalk10.green(`+${wt.insertions}`)} ${chalk10.red(`-${wt.deletions}`)}`);
|
|
3450
3668
|
}
|
|
3451
3669
|
if (wt.commitsAhead > 0) {
|
|
3452
|
-
printInfo(` ${
|
|
3670
|
+
printInfo(` ${chalk10.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`)}`);
|
|
3453
3671
|
}
|
|
3454
3672
|
if (wt.commitsBehind > 0) {
|
|
3455
|
-
printInfo(` ${
|
|
3673
|
+
printInfo(` ${chalk10.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`)}`);
|
|
3456
3674
|
} else {
|
|
3457
|
-
printInfo(` ${
|
|
3675
|
+
printInfo(` ${chalk10.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65")}`);
|
|
3458
3676
|
}
|
|
3459
3677
|
if (wt.createdAt) {
|
|
3460
3678
|
const relativeTime = formatRelativeTime(wt.createdAt);
|
|
3461
3679
|
if (relativeTime) {
|
|
3462
|
-
printInfo(` ${
|
|
3680
|
+
printInfo(` ${chalk10.gray(MESSAGES.STATUS_CREATED_AT(relativeTime))}`);
|
|
3463
3681
|
}
|
|
3464
3682
|
}
|
|
3465
3683
|
if (wt.snapshotTime) {
|
|
3466
3684
|
const relativeTime = formatRelativeTime(wt.snapshotTime);
|
|
3467
3685
|
if (relativeTime) {
|
|
3468
|
-
printInfo(` ${
|
|
3686
|
+
printInfo(` ${chalk10.green(MESSAGES.STATUS_LAST_VALIDATED(relativeTime))}`);
|
|
3469
3687
|
}
|
|
3470
3688
|
} else {
|
|
3471
|
-
printInfo(` ${
|
|
3689
|
+
printInfo(` ${chalk10.red(MESSAGES.STATUS_NOT_VALIDATED)}`);
|
|
3472
3690
|
}
|
|
3473
3691
|
printInfo("");
|
|
3474
3692
|
}
|
|
3475
3693
|
function formatChangeStatusLabel(status) {
|
|
3476
3694
|
switch (status) {
|
|
3477
3695
|
case "committed":
|
|
3478
|
-
return
|
|
3696
|
+
return chalk10.green(MESSAGES.STATUS_CHANGE_COMMITTED);
|
|
3479
3697
|
case "uncommitted":
|
|
3480
|
-
return
|
|
3698
|
+
return chalk10.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
|
|
3481
3699
|
case "conflict":
|
|
3482
|
-
return
|
|
3700
|
+
return chalk10.red(MESSAGES.STATUS_CHANGE_CONFLICT);
|
|
3483
3701
|
case "clean":
|
|
3484
|
-
return
|
|
3702
|
+
return chalk10.gray(MESSAGES.STATUS_CHANGE_CLEAN);
|
|
3485
3703
|
}
|
|
3486
3704
|
}
|
|
3487
3705
|
function printSnapshotsSection(snapshots) {
|
|
3488
|
-
printInfo(` ${
|
|
3706
|
+
printInfo(` ${chalk10.bold("\u25C6")} ${chalk10.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.total} \u4E2A)`);
|
|
3489
3707
|
if (snapshots.orphaned > 0) {
|
|
3490
|
-
printInfo(` ${
|
|
3708
|
+
printInfo(` ${chalk10.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED(snapshots.orphaned))}`);
|
|
3491
3709
|
}
|
|
3492
3710
|
printInfo("");
|
|
3493
3711
|
}
|
|
3494
3712
|
|
|
3495
3713
|
// src/commands/alias.ts
|
|
3496
|
-
import
|
|
3714
|
+
import chalk11 from "chalk";
|
|
3497
3715
|
function getRegisteredCommandNames(program2) {
|
|
3498
3716
|
return program2.commands.map((cmd) => cmd.name());
|
|
3499
3717
|
}
|
|
@@ -3514,7 +3732,7 @@ ${MESSAGES.ALIAS_LIST_TITLE}
|
|
|
3514
3732
|
`);
|
|
3515
3733
|
printSeparator();
|
|
3516
3734
|
for (const [alias, command] of entries) {
|
|
3517
|
-
printInfo(` ${
|
|
3735
|
+
printInfo(` ${chalk11.bold(alias)} \u2192 ${chalk11.cyan(command)}`);
|
|
3518
3736
|
}
|
|
3519
3737
|
printInfo("");
|
|
3520
3738
|
printSeparator();
|
|
@@ -3560,8 +3778,187 @@ function registerAliasCommand(program2) {
|
|
|
3560
3778
|
});
|
|
3561
3779
|
}
|
|
3562
3780
|
|
|
3781
|
+
// src/commands/projects.ts
|
|
3782
|
+
import { existsSync as existsSync9, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
3783
|
+
import { join as join7 } from "path";
|
|
3784
|
+
import chalk12 from "chalk";
|
|
3785
|
+
function registerProjectsCommand(program2) {
|
|
3786
|
+
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) => {
|
|
3787
|
+
handleProjects({ name, json: options.json });
|
|
3788
|
+
});
|
|
3789
|
+
}
|
|
3790
|
+
function handleProjects(options) {
|
|
3791
|
+
if (options.name) {
|
|
3792
|
+
handleProjectDetail(options.name, options.json);
|
|
3793
|
+
} else {
|
|
3794
|
+
handleProjectsOverview(options.json);
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
function handleProjectsOverview(json) {
|
|
3798
|
+
const result = collectProjectsOverview();
|
|
3799
|
+
logger.info(`projects \u547D\u4EE4\u6267\u884C\uFF0C\u5171 ${result.totalProjects} \u4E2A\u9879\u76EE`);
|
|
3800
|
+
if (json) {
|
|
3801
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3802
|
+
return;
|
|
3803
|
+
}
|
|
3804
|
+
printProjectsOverviewAsText(result);
|
|
3805
|
+
}
|
|
3806
|
+
function handleProjectDetail(name, json) {
|
|
3807
|
+
const projectDir = join7(WORKTREES_DIR, name);
|
|
3808
|
+
if (!existsSync9(projectDir)) {
|
|
3809
|
+
printError(MESSAGES.PROJECTS_NOT_FOUND(name));
|
|
3810
|
+
process.exit(1);
|
|
3811
|
+
}
|
|
3812
|
+
const result = collectProjectDetail(name, projectDir);
|
|
3813
|
+
logger.info(`projects \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${name}\uFF0C\u5171 ${result.worktrees.length} \u4E2A worktree`);
|
|
3814
|
+
if (json) {
|
|
3815
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3816
|
+
return;
|
|
3817
|
+
}
|
|
3818
|
+
printProjectDetailAsText(result);
|
|
3819
|
+
}
|
|
3820
|
+
function collectProjectsOverview() {
|
|
3821
|
+
if (!existsSync9(WORKTREES_DIR)) {
|
|
3822
|
+
return { projects: [], totalProjects: 0, totalDiskUsage: 0 };
|
|
3823
|
+
}
|
|
3824
|
+
const entries = readdirSync5(WORKTREES_DIR, { withFileTypes: true });
|
|
3825
|
+
const projects = [];
|
|
3826
|
+
for (const entry of entries) {
|
|
3827
|
+
if (!entry.isDirectory()) {
|
|
3828
|
+
continue;
|
|
3829
|
+
}
|
|
3830
|
+
const projectDir = join7(WORKTREES_DIR, entry.name);
|
|
3831
|
+
const overview = collectSingleProjectOverview(entry.name, projectDir);
|
|
3832
|
+
projects.push(overview);
|
|
3833
|
+
}
|
|
3834
|
+
sortByLastActiveTimeDesc(projects);
|
|
3835
|
+
const totalDiskUsage = projects.reduce((sum, p) => sum + p.diskUsage, 0);
|
|
3836
|
+
return {
|
|
3837
|
+
projects,
|
|
3838
|
+
totalProjects: projects.length,
|
|
3839
|
+
totalDiskUsage
|
|
3840
|
+
};
|
|
3841
|
+
}
|
|
3842
|
+
function collectSingleProjectOverview(name, projectDir) {
|
|
3843
|
+
const subEntries = readdirSync5(projectDir, { withFileTypes: true });
|
|
3844
|
+
const worktreeDirs = subEntries.filter((e) => e.isDirectory());
|
|
3845
|
+
const worktreeCount = worktreeDirs.length;
|
|
3846
|
+
const diskUsage = calculateDirSize(projectDir);
|
|
3847
|
+
const lastActiveTime = resolveProjectLastActiveTime(projectDir, worktreeDirs.map((e) => join7(projectDir, e.name)));
|
|
3848
|
+
return {
|
|
3849
|
+
name,
|
|
3850
|
+
worktreeCount,
|
|
3851
|
+
lastActiveTime,
|
|
3852
|
+
diskUsage
|
|
3853
|
+
};
|
|
3854
|
+
}
|
|
3855
|
+
function collectProjectDetail(name, projectDir) {
|
|
3856
|
+
const subEntries = readdirSync5(projectDir, { withFileTypes: true });
|
|
3857
|
+
const worktrees = [];
|
|
3858
|
+
for (const entry of subEntries) {
|
|
3859
|
+
if (!entry.isDirectory()) {
|
|
3860
|
+
continue;
|
|
3861
|
+
}
|
|
3862
|
+
const wtPath = join7(projectDir, entry.name);
|
|
3863
|
+
const detail = collectSingleWorktreeDetail(entry.name, wtPath);
|
|
3864
|
+
worktrees.push(detail);
|
|
3865
|
+
}
|
|
3866
|
+
worktrees.sort((a, b) => new Date(b.lastModifiedTime).getTime() - new Date(a.lastModifiedTime).getTime());
|
|
3867
|
+
const totalDiskUsage = worktrees.reduce((sum, wt) => sum + wt.diskUsage, 0);
|
|
3868
|
+
return {
|
|
3869
|
+
name,
|
|
3870
|
+
projectDir,
|
|
3871
|
+
worktrees,
|
|
3872
|
+
totalDiskUsage
|
|
3873
|
+
};
|
|
3874
|
+
}
|
|
3875
|
+
function collectSingleWorktreeDetail(branch, wtPath) {
|
|
3876
|
+
const stat = statSync4(wtPath);
|
|
3877
|
+
const diskUsage = calculateDirSize(wtPath);
|
|
3878
|
+
return {
|
|
3879
|
+
branch,
|
|
3880
|
+
path: wtPath,
|
|
3881
|
+
lastModifiedTime: formatLocalISOString(stat.mtime),
|
|
3882
|
+
diskUsage
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3885
|
+
function resolveProjectLastActiveTime(projectDir, worktreePaths) {
|
|
3886
|
+
let latestTime = statSync4(projectDir).mtime;
|
|
3887
|
+
for (const wtPath of worktreePaths) {
|
|
3888
|
+
try {
|
|
3889
|
+
const wtStat = statSync4(wtPath);
|
|
3890
|
+
if (wtStat.mtime > latestTime) {
|
|
3891
|
+
latestTime = wtStat.mtime;
|
|
3892
|
+
}
|
|
3893
|
+
} catch {
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
return formatLocalISOString(latestTime);
|
|
3897
|
+
}
|
|
3898
|
+
function sortByLastActiveTimeDesc(projects) {
|
|
3899
|
+
projects.sort((a, b) => new Date(b.lastActiveTime).getTime() - new Date(a.lastActiveTime).getTime());
|
|
3900
|
+
}
|
|
3901
|
+
function printProjectsOverviewAsText(result) {
|
|
3902
|
+
printDoubleSeparator();
|
|
3903
|
+
printInfo(` ${chalk12.bold.cyan(MESSAGES.PROJECTS_OVERVIEW_TITLE)}`);
|
|
3904
|
+
printDoubleSeparator();
|
|
3905
|
+
printInfo("");
|
|
3906
|
+
if (result.projects.length === 0) {
|
|
3907
|
+
printInfo(` ${MESSAGES.PROJECTS_NO_PROJECTS}`);
|
|
3908
|
+
printInfo("");
|
|
3909
|
+
printDoubleSeparator();
|
|
3910
|
+
return;
|
|
3911
|
+
}
|
|
3912
|
+
for (const project of result.projects) {
|
|
3913
|
+
printProjectOverviewItem(project);
|
|
3914
|
+
}
|
|
3915
|
+
printSeparator();
|
|
3916
|
+
printInfo("");
|
|
3917
|
+
printInfo(` \u5171 ${chalk12.bold(String(result.totalProjects))} \u4E2A\u9879\u76EE ${chalk12.gray(MESSAGES.PROJECTS_TOTAL_DISK_USAGE(formatDiskSize(result.totalDiskUsage)))}`);
|
|
3918
|
+
printInfo("");
|
|
3919
|
+
printDoubleSeparator();
|
|
3920
|
+
}
|
|
3921
|
+
function printProjectOverviewItem(project) {
|
|
3922
|
+
const relativeTime = formatRelativeTime(project.lastActiveTime);
|
|
3923
|
+
const activeLabel = relativeTime ? MESSAGES.PROJECTS_LAST_ACTIVE(relativeTime) : "";
|
|
3924
|
+
const diskLabel = MESSAGES.PROJECTS_DISK_USAGE(formatDiskSize(project.diskUsage));
|
|
3925
|
+
printInfo(` ${chalk12.bold("\u25CF")} ${chalk12.bold(project.name)}`);
|
|
3926
|
+
printInfo(` ${MESSAGES.PROJECTS_WORKTREE_COUNT(project.worktreeCount)} ${chalk12.gray(activeLabel)} ${chalk12.gray(diskLabel)}`);
|
|
3927
|
+
printInfo("");
|
|
3928
|
+
}
|
|
3929
|
+
function printProjectDetailAsText(result) {
|
|
3930
|
+
printDoubleSeparator();
|
|
3931
|
+
printInfo(` ${chalk12.bold.cyan(MESSAGES.PROJECTS_DETAIL_TITLE(result.name))}`);
|
|
3932
|
+
printDoubleSeparator();
|
|
3933
|
+
printInfo("");
|
|
3934
|
+
printInfo(` ${chalk12.bold("\u25C6")} ${chalk12.bold(MESSAGES.PROJECTS_PATH(result.projectDir))}`);
|
|
3935
|
+
printInfo(` ${MESSAGES.PROJECTS_TOTAL_DISK_USAGE(formatDiskSize(result.totalDiskUsage))}`);
|
|
3936
|
+
printInfo("");
|
|
3937
|
+
printSeparator();
|
|
3938
|
+
printInfo("");
|
|
3939
|
+
if (result.worktrees.length === 0) {
|
|
3940
|
+
printInfo(` ${MESSAGES.PROJECTS_DETAIL_NO_WORKTREES}`);
|
|
3941
|
+
printInfo("");
|
|
3942
|
+
printDoubleSeparator();
|
|
3943
|
+
return;
|
|
3944
|
+
}
|
|
3945
|
+
for (const wt of result.worktrees) {
|
|
3946
|
+
printWorktreeDetailItem(wt);
|
|
3947
|
+
}
|
|
3948
|
+
printDoubleSeparator();
|
|
3949
|
+
}
|
|
3950
|
+
function printWorktreeDetailItem(wt) {
|
|
3951
|
+
const relativeTime = formatRelativeTime(wt.lastModifiedTime);
|
|
3952
|
+
const modifiedLabel = relativeTime ? MESSAGES.PROJECTS_LAST_MODIFIED(relativeTime) : "";
|
|
3953
|
+
const diskLabel = MESSAGES.PROJECTS_DISK_USAGE(formatDiskSize(wt.diskUsage));
|
|
3954
|
+
printInfo(` ${chalk12.bold("\u25CF")} ${chalk12.bold(wt.branch)}`);
|
|
3955
|
+
printInfo(` ${wt.path}`);
|
|
3956
|
+
printInfo(` ${chalk12.gray(modifiedLabel)} ${chalk12.gray(diskLabel)}`);
|
|
3957
|
+
printInfo("");
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3563
3960
|
// src/commands/completion.ts
|
|
3564
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as
|
|
3961
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync11 } from "fs";
|
|
3565
3962
|
import { resolve as resolve2 } from "path";
|
|
3566
3963
|
import { homedir as homedir2 } from "os";
|
|
3567
3964
|
|
|
@@ -3612,25 +4009,25 @@ compdef _clawt_completion clawt
|
|
|
3612
4009
|
}
|
|
3613
4010
|
|
|
3614
4011
|
// src/utils/completion-engine.ts
|
|
3615
|
-
import { existsSync as
|
|
3616
|
-
import { join as
|
|
4012
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
4013
|
+
import { join as join8, dirname, basename as basename2 } from "path";
|
|
3617
4014
|
function completeFilePath(partial) {
|
|
3618
4015
|
const cwd = process.cwd();
|
|
3619
4016
|
const hasDir = partial.includes("/");
|
|
3620
|
-
const searchDir = hasDir ?
|
|
4017
|
+
const searchDir = hasDir ? join8(cwd, dirname(partial)) : cwd;
|
|
3621
4018
|
const prefix = hasDir ? basename2(partial) : partial;
|
|
3622
|
-
if (!
|
|
4019
|
+
if (!existsSync10(searchDir)) {
|
|
3623
4020
|
return [];
|
|
3624
4021
|
}
|
|
3625
|
-
const entries =
|
|
4022
|
+
const entries = readdirSync6(searchDir);
|
|
3626
4023
|
const results = [];
|
|
3627
4024
|
const dirPrefix = hasDir ? dirname(partial) + "/" : "";
|
|
3628
4025
|
for (const entry of entries) {
|
|
3629
4026
|
if (!entry.startsWith(prefix)) continue;
|
|
3630
4027
|
if (entry.startsWith(".")) continue;
|
|
3631
|
-
const fullPath =
|
|
4028
|
+
const fullPath = join8(searchDir, entry);
|
|
3632
4029
|
try {
|
|
3633
|
-
const stat =
|
|
4030
|
+
const stat = statSync5(fullPath);
|
|
3634
4031
|
if (stat.isDirectory()) {
|
|
3635
4032
|
results.push(dirPrefix + entry + "/");
|
|
3636
4033
|
} else if (stat.isFile()) {
|
|
@@ -3717,7 +4114,7 @@ function generateCompletions(program2, args) {
|
|
|
3717
4114
|
|
|
3718
4115
|
// src/commands/completion.ts
|
|
3719
4116
|
function appendToFile(filePath, content) {
|
|
3720
|
-
if (
|
|
4117
|
+
if (existsSync11(filePath)) {
|
|
3721
4118
|
const current = readFileSync5(filePath, "utf-8");
|
|
3722
4119
|
if (current.includes("clawt completion")) {
|
|
3723
4120
|
printInfo(MESSAGES.COMPLETION_INSTALL_EXISTS + ": " + filePath);
|
|
@@ -3795,6 +4192,7 @@ registerSyncCommand(program);
|
|
|
3795
4192
|
registerResetCommand(program);
|
|
3796
4193
|
registerStatusCommand(program);
|
|
3797
4194
|
registerAliasCommand(program);
|
|
4195
|
+
registerProjectsCommand(program);
|
|
3798
4196
|
registerCompletionCommand(program);
|
|
3799
4197
|
var config = loadConfig();
|
|
3800
4198
|
applyAliases(program, config.aliases);
|