clawt 3.9.13 → 3.10.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 +3 -0
- package/README.zh-CN.md +3 -0
- package/dist/index.js +1935 -592
- package/dist/postinstall.js +1626 -283
- package/docs/config-file.md +2 -0
- package/docs/config.md +2 -1
- package/docs/init.md +3 -2
- package/docs/project-config.md +10 -1
- package/docs/spec.md +69 -2
- package/package.json +1 -1
- package/src/commands/alias.ts +5 -4
- package/src/commands/completion.ts +2 -1
- package/src/commands/config.ts +25 -7
- package/src/commands/cover-validate.ts +3 -2
- package/src/commands/create.ts +8 -7
- package/src/commands/home.ts +2 -1
- package/src/commands/init.ts +13 -6
- package/src/commands/list.ts +6 -4
- package/src/commands/merge.ts +8 -7
- package/src/commands/projects.ts +5 -3
- package/src/commands/remove.ts +7 -6
- package/src/commands/reset.ts +3 -2
- package/src/commands/resume.ts +10 -7
- package/src/commands/run.ts +8 -7
- package/src/commands/status.ts +16 -11
- package/src/commands/sync.ts +4 -3
- package/src/commands/tasks.ts +8 -6
- package/src/commands/validate.ts +7 -6
- package/src/constants/ai-prompts.ts +11 -11
- package/src/constants/config.ts +30 -0
- package/src/constants/index.ts +3 -2
- package/src/constants/messages/alias.ts +44 -14
- package/src/constants/messages/cli-descriptions.ts +91 -0
- package/src/constants/messages/common.ts +221 -36
- package/src/constants/messages/completion.ts +43 -14
- package/src/constants/messages/config.ts +61 -18
- package/src/constants/messages/cover-validate.ts +43 -14
- package/src/constants/messages/create.ts +16 -5
- package/src/constants/messages/home.ts +19 -6
- package/src/constants/messages/index.ts +2 -0
- package/src/constants/messages/init.ts +45 -14
- package/src/constants/messages/interactive-panel.ts +183 -29
- package/src/constants/messages/merge.ts +140 -38
- package/src/constants/messages/post-create.ts +59 -19
- package/src/constants/messages/projects.ts +51 -14
- package/src/constants/messages/remove.ts +50 -15
- package/src/constants/messages/reset.ts +14 -4
- package/src/constants/messages/resume.ts +116 -19
- package/src/constants/messages/run.ts +165 -35
- package/src/constants/messages/status.ts +84 -23
- package/src/constants/messages/sync.ts +54 -17
- package/src/constants/messages/tasks.ts +21 -7
- package/src/constants/messages/update.ts +35 -11
- package/src/constants/messages/validate.ts +218 -57
- package/src/constants/progress.ts +17 -6
- package/src/constants/project-config.ts +17 -0
- package/src/constants/prompt.ts +18 -2
- package/src/constants/tasks-template.ts +56 -2
- package/src/hooks/post-create.ts +5 -2
- package/src/index.ts +6 -5
- package/src/types/config.ts +2 -0
- package/src/utils/alias.ts +2 -1
- package/src/utils/claude.ts +10 -9
- package/src/utils/config-strategy.ts +3 -3
- package/src/utils/dry-run.ts +2 -2
- package/src/utils/formatter.ts +18 -11
- package/src/utils/i18n.ts +63 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/interactive-panel-render.ts +6 -3
- package/src/utils/progress-render.ts +11 -9
- package/src/utils/prompt.ts +2 -1
- package/src/utils/task-executor.ts +10 -7
- package/src/utils/task-file.ts +2 -1
- package/src/utils/terminal.ts +9 -9
- package/src/utils/ui-prompts.ts +4 -3
- package/src/utils/update-checker.ts +1 -1
- package/src/utils/validate-branch.ts +16 -9
- package/src/utils/validate-core.ts +2 -1
- package/src/utils/validate-runner.ts +2 -2
- package/src/utils/worktree-matcher.ts +9 -7
- package/tests/unit/commands/alias.test.ts +4 -0
- package/tests/unit/commands/completion.test.ts +14 -0
- package/tests/unit/commands/config.test.ts +61 -28
- package/tests/unit/commands/cover-validate.test.ts +13 -2
- package/tests/unit/commands/init.test.ts +6 -2
- package/tests/unit/commands/merge.test.ts +14 -0
- package/tests/unit/commands/run.test.ts +17 -0
- package/tests/unit/commands/tasks.test.ts +39 -9
- package/tests/unit/constants/config.test.ts +16 -1
- package/tests/unit/constants/messages-post-create.test.ts +93 -1
- package/tests/unit/constants/messages.test.ts +85 -1
- package/tests/unit/hooks/post-create.test.ts +32 -0
- package/tests/unit/utils/alias.test.ts +14 -0
- package/tests/unit/utils/claude.test.ts +24 -4
- package/tests/unit/utils/config-strategy.test.ts +21 -0
- package/tests/unit/utils/conflict-resolver.test.ts +24 -4
- package/tests/unit/utils/formatter.test.ts +21 -0
- package/tests/unit/utils/i18n.test.ts +91 -0
- package/tests/unit/utils/progress.test.ts +39 -18
- package/tests/unit/utils/prompt.test.ts +25 -2
- package/tests/unit/utils/task-file.test.ts +73 -10
- package/tests/unit/utils/terminal-cmux.test.ts +19 -4
- package/tests/unit/utils/update-checker.test.ts +2 -0
- package/tests/unit/utils/validate-branch.test.ts +26 -1
- package/tests/unit/utils/validation.test.ts +2 -2
- package/tests/unit/utils/worktree-matcher.test.ts +2 -0
package/src/hooks/post-create.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { MESSAGES } from '../constants/index.js';
|
|
|
6
6
|
import { loadProjectConfig } from '../utils/project-config.js';
|
|
7
7
|
import { getMainWorktreePath } from '../utils/git.js';
|
|
8
8
|
import { printInfo, printSuccess, printWarning } from '../utils/formatter.js';
|
|
9
|
+
import { getCurrentLanguage } from '../utils/i18n.js';
|
|
9
10
|
import type { WorktreeInfo, ResolvedHook, PostCreateHookResult } from '../types/index.js';
|
|
10
11
|
|
|
11
12
|
/** 项目仓库中的 postCreate 脚本相对路径 */
|
|
@@ -86,7 +87,9 @@ export function resolvePostCreateHook(): ResolvedHook | null {
|
|
|
86
87
|
* @returns {string} 来源描述文本
|
|
87
88
|
*/
|
|
88
89
|
function getSourceLabel(hook: ResolvedHook): string {
|
|
89
|
-
return hook.source === 'projectConfig'
|
|
90
|
+
return hook.source === 'projectConfig'
|
|
91
|
+
? (getCurrentLanguage() === 'en' ? 'Project config (postCreate)' : '项目配置 (postCreate)')
|
|
92
|
+
: '.clawt/postCreate.sh';
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
/**
|
|
@@ -122,7 +125,7 @@ function executeOneHook(worktree: WorktreeInfo, hook: ResolvedHook): Promise<Pos
|
|
|
122
125
|
child.on('close', (code) => {
|
|
123
126
|
if (code !== null && code !== 0) {
|
|
124
127
|
result.success = false;
|
|
125
|
-
result.error = `命令退出码: ${code}`;
|
|
128
|
+
result.error = getCurrentLanguage() === 'en' ? `Command exit code: ${code}` : `命令退出码: ${code}`;
|
|
126
129
|
logger.error(`postCreate hook 失败: ${hook.command} (退出码: ${code}) @ ${worktree.path}`);
|
|
127
130
|
} else {
|
|
128
131
|
logger.info(`postCreate hook 成功: ${hook.command} @ ${worktree.path}`);
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { ClawtError } from './errors/index.js';
|
|
|
4
4
|
import { logger, enableConsoleTransport } from './logger/index.js';
|
|
5
5
|
import { EXIT_CODES } from './constants/index.js';
|
|
6
6
|
import { printError, ensureClawtDirs, loadConfig, applyAliases, checkForUpdates, setNonInteractive } from './utils/index.js';
|
|
7
|
+
import { getCurrentLanguage } from './utils/i18n.js';
|
|
7
8
|
import { registerListCommand } from './commands/list.js';
|
|
8
9
|
import { registerCreateCommand } from './commands/create.js';
|
|
9
10
|
import { registerRemoveCommand } from './commands/remove.js';
|
|
@@ -34,10 +35,10 @@ const program = new Command();
|
|
|
34
35
|
|
|
35
36
|
program
|
|
36
37
|
.name('clawt')
|
|
37
|
-
.description('本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具')
|
|
38
|
+
.description(getCurrentLanguage() === 'en' ? 'Run multiple Claude Code Agent tasks in parallel — a CLI tool integrating Git Worktree with Claude Code CLI' : '本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具')
|
|
38
39
|
.version(version)
|
|
39
|
-
.option('--debug', '输出详细调试信息到终端')
|
|
40
|
-
.option('-y, --yes', '跳过所有交互式确认,适用于脚本/CI 环境');
|
|
40
|
+
.option('--debug', getCurrentLanguage() === 'en' ? 'Output detailed debug information to terminal' : '输出详细调试信息到终端')
|
|
41
|
+
.option('-y, --yes', getCurrentLanguage() === 'en' ? 'Skip all interactive confirmations, suitable for scripts/CI environments' : '跳过所有交互式确认,适用于脚本/CI 环境');
|
|
41
42
|
|
|
42
43
|
// 在子命令 action 执行前检查 --debug 选项,按需启用控制台日志
|
|
43
44
|
program.hook('preAction', (thisCommand) => {
|
|
@@ -81,7 +82,7 @@ process.on('uncaughtException', (error) => {
|
|
|
81
82
|
logger.error(error.message);
|
|
82
83
|
process.exit(error.exitCode);
|
|
83
84
|
}
|
|
84
|
-
printError(error.message || '未知错误');
|
|
85
|
+
printError(error.message || (getCurrentLanguage() === 'en' ? 'Unknown error' : '未知错误'));
|
|
85
86
|
logger.error(`未捕获异常: ${error.message}\n${error.stack}`);
|
|
86
87
|
process.exit(EXIT_CODES.ERROR);
|
|
87
88
|
});
|
|
@@ -93,7 +94,7 @@ process.on('unhandledRejection', (reason) => {
|
|
|
93
94
|
logger.error(error.message);
|
|
94
95
|
process.exit(error.exitCode);
|
|
95
96
|
}
|
|
96
|
-
printError(error.message || '未知错误');
|
|
97
|
+
printError(error.message || (getCurrentLanguage() === 'en' ? 'Unknown error' : '未知错误'));
|
|
97
98
|
logger.error(`未处理的 Promise 拒绝: ${error.message}`);
|
|
98
99
|
process.exit(EXIT_CODES.ERROR);
|
|
99
100
|
});
|
package/src/types/config.ts
CHANGED
package/src/utils/alias.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import type { ClawtConfig } from '../types/index.js';
|
|
3
3
|
import { logger } from '../logger/index.js';
|
|
4
|
+
import { getCurrentLanguage } from './i18n.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* 根据配置中的别名映射,为已注册的命令添加 Commander.js 别名
|
|
@@ -14,7 +15,7 @@ export function applyAliases(program: Command, aliases: ClawtConfig['aliases']):
|
|
|
14
15
|
targetCmd.alias(alias);
|
|
15
16
|
logger.debug(`已注册别名: ${alias} → ${commandName}`);
|
|
16
17
|
} else {
|
|
17
|
-
logger.warn(`别名 "${alias}" 的目标命令 "${commandName}" 不存在,已跳过`);
|
|
18
|
+
logger.warn(getCurrentLanguage() === 'en' ? `Alias "${alias}" targets non-existent command "${commandName}", skipped` : `别名 "${alias}" 的目标命令 "${commandName}" 不存在,已跳过`);
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
}
|
package/src/utils/claude.ts
CHANGED
|
@@ -2,10 +2,11 @@ import { spawnSync } from 'node:child_process';
|
|
|
2
2
|
import { existsSync, readdirSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { ClawtError } from '../errors/index.js';
|
|
5
|
-
import { CLAUDE_PROJECTS_DIR } from '../constants/index.js';
|
|
5
|
+
import { CLAUDE_PROJECTS_DIR, MESSAGES } from '../constants/index.js';
|
|
6
6
|
import { resolveClaudeCodeCommand } from './project-config.js';
|
|
7
7
|
import { printInfo, printWarning } from './formatter.js';
|
|
8
8
|
import { openCommandInNewTerminalTab } from './terminal.js';
|
|
9
|
+
import { getCurrentLanguage } from './i18n.js';
|
|
9
10
|
import type { WorktreeInfo } from '../types/index.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -64,12 +65,12 @@ export function launchInteractiveClaude(worktree: WorktreeInfo, options: LaunchC
|
|
|
64
65
|
args.push('--continue');
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
printInfo(
|
|
68
|
-
printInfo(`
|
|
69
|
-
printInfo(`
|
|
70
|
-
printInfo(`
|
|
68
|
+
printInfo(MESSAGES.STARTING_CLAUDE_INTERACTIVE);
|
|
69
|
+
printInfo(` ${MESSAGES.BRANCH_LABEL} ${worktree.branch}`);
|
|
70
|
+
printInfo(` ${MESSAGES.PATH_LABEL_RESUME} ${worktree.path}`);
|
|
71
|
+
printInfo(` ${MESSAGES.COMMAND_LABEL} ${commandStr}`);
|
|
71
72
|
if (options.autoContinue) {
|
|
72
|
-
printInfo(`
|
|
73
|
+
printInfo(` ${MESSAGES.MODE_LABEL} ${hasPreviousSession ? MESSAGES.CONTINUE_SESSION : MESSAGES.NEW_SESSION}`);
|
|
73
74
|
}
|
|
74
75
|
printInfo('');
|
|
75
76
|
|
|
@@ -79,11 +80,11 @@ export function launchInteractiveClaude(worktree: WorktreeInfo, options: LaunchC
|
|
|
79
80
|
});
|
|
80
81
|
|
|
81
82
|
if (result.error) {
|
|
82
|
-
throw new ClawtError(
|
|
83
|
+
throw new ClawtError(MESSAGES.CLAUDE_START_FAILED(result.error.message));
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
if (result.status !== null && result.status !== 0) {
|
|
86
|
-
printWarning(`Claude Code 退出码: ${result.status}`);
|
|
87
|
+
printWarning(getCurrentLanguage() === 'en' ? `Claude Code exit code: ${result.status}` : `Claude Code 退出码: ${result.status}`);
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
|
|
@@ -121,7 +122,7 @@ export function buildClaudeCommand(worktree: WorktreeInfo, hasPreviousSession: b
|
|
|
121
122
|
*/
|
|
122
123
|
export function launchInteractiveClaudeInNewTerminal(worktree: WorktreeInfo, hasPreviousSession: boolean): void {
|
|
123
124
|
const command = buildClaudeCommand(worktree, hasPreviousSession);
|
|
124
|
-
const modeLabel = hasPreviousSession ?
|
|
125
|
+
const modeLabel = hasPreviousSession ? MESSAGES.CONTINUE_SESSION : MESSAGES.NEW_SESSION;
|
|
125
126
|
const tabTitle = `clawt: ${worktree.branch}`;
|
|
126
127
|
|
|
127
128
|
openCommandInNewTerminalTab(command, tabTitle);
|
|
@@ -112,7 +112,7 @@ export async function promptConfigValue(
|
|
|
112
112
|
*/
|
|
113
113
|
export function formatConfigValue(value: unknown): string {
|
|
114
114
|
if (value === undefined || value === null) {
|
|
115
|
-
return chalk.dim(
|
|
115
|
+
return chalk.dim(MESSAGES.NOT_SET);
|
|
116
116
|
}
|
|
117
117
|
if (typeof value === 'boolean') {
|
|
118
118
|
return value ? chalk.green('true') : chalk.yellow('false');
|
|
@@ -136,7 +136,7 @@ export async function interactiveConfigEditor<T extends object>(
|
|
|
136
136
|
): Promise<{ key: keyof T; newValue: unknown }> {
|
|
137
137
|
// 非交互模式下无法进行配置编辑,引导使用 config set 命令
|
|
138
138
|
if (isNonInteractive()) {
|
|
139
|
-
throw new ClawtError(
|
|
139
|
+
throw new ClawtError(MESSAGES.NON_INTERACTIVE_CONFIG_EDITOR);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
const keys = Object.keys(definitions);
|
|
@@ -203,7 +203,7 @@ async function promptNumberValue(key: string, currentValue: number): Promise<num
|
|
|
203
203
|
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
204
204
|
initial: String(currentValue),
|
|
205
205
|
validate: (val: string) => {
|
|
206
|
-
if (Number.isNaN(Number(val))) return
|
|
206
|
+
if (Number.isNaN(Number(val))) return MESSAGES.INVALID_NUMBER_PROMPT;
|
|
207
207
|
return true;
|
|
208
208
|
},
|
|
209
209
|
}).run();
|
package/src/utils/dry-run.ts
CHANGED
|
@@ -69,11 +69,11 @@ export function printDryRunPreview(branchNames: string[], tasks: string[], concu
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
// 路径行(2空格缩进)
|
|
72
|
-
printInfo(` ${chalk.gray(
|
|
72
|
+
printInfo(` ${chalk.gray(MESSAGES.PATH_LABEL)} ${worktreePath}`);
|
|
73
73
|
|
|
74
74
|
// 任务描述行(2空格缩进,非交互式模式)
|
|
75
75
|
if (!isInteractive) {
|
|
76
|
-
printInfo(` ${chalk.gray(
|
|
76
|
+
printInfo(` ${chalk.gray(MESSAGES.TASK_LABEL)} ${truncateTaskDesc(tasks[i])}`);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
printInfo('');
|
package/src/utils/formatter.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { MESSAGES } from '../constants/index.js';
|
|
|
3
3
|
import { createInterface } from 'node:readline';
|
|
4
4
|
import type { WorktreeStatus } from '../types/index.js';
|
|
5
5
|
import { isNonInteractive } from './interactive.js';
|
|
6
|
+
import { getCurrentLanguage } from './i18n.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 输出成功信息
|
|
@@ -89,8 +90,11 @@ export function confirmAction(question: string, nonInteractiveDefault: boolean =
|
|
|
89
90
|
* @returns {Promise<boolean>} 用户是否确认
|
|
90
91
|
*/
|
|
91
92
|
export function confirmDestructiveAction(dangerousCommand: string, description: string): Promise<boolean> {
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
const lang = getCurrentLanguage();
|
|
94
|
+
const continuePrompt = lang === 'en' ? 'Continue?' : '是否继续?';
|
|
95
|
+
const warningPrefix = lang === 'en' ? `About to execute ${chalk.red.bold(dangerousCommand)}, ` : `即将执行 ${chalk.red.bold(dangerousCommand)},`;
|
|
96
|
+
printWarning(`${warningPrefix}${description}`);
|
|
97
|
+
return confirmAction(continuePrompt);
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
/**
|
|
@@ -111,14 +115,16 @@ export function isWorktreeIdle(status: WorktreeStatus): boolean {
|
|
|
111
115
|
* @returns {string} 格式化后的状态字符串
|
|
112
116
|
*/
|
|
113
117
|
export function formatWorktreeStatus(status: WorktreeStatus): string {
|
|
118
|
+
const lang = getCurrentLanguage();
|
|
114
119
|
const parts: string[] = [];
|
|
115
120
|
|
|
116
121
|
// 提交数(黄色)
|
|
117
|
-
|
|
122
|
+
const commitLabel = lang === 'en' ? `${status.commitCount} commit${status.commitCount !== 1 ? 's' : ''}` : `${status.commitCount} 个提交`;
|
|
123
|
+
parts.push(chalk.yellow(commitLabel));
|
|
118
124
|
|
|
119
125
|
// 变更统计
|
|
120
126
|
if (status.insertions === 0 && status.deletions === 0) {
|
|
121
|
-
parts.push('无变更');
|
|
127
|
+
parts.push(lang === 'en' ? 'No changes' : '无变更');
|
|
122
128
|
} else {
|
|
123
129
|
const diffParts: string[] = [];
|
|
124
130
|
diffParts.push(chalk.green(`+${status.insertions}`));
|
|
@@ -128,7 +134,7 @@ export function formatWorktreeStatus(status: WorktreeStatus): string {
|
|
|
128
134
|
|
|
129
135
|
// 未提交修改提示(灰色)
|
|
130
136
|
if (status.hasDirtyFiles) {
|
|
131
|
-
parts.push(chalk.gray('(未提交修改)'));
|
|
137
|
+
parts.push(chalk.gray(lang === 'en' ? '(uncommitted changes)' : '(未提交修改)'));
|
|
132
138
|
}
|
|
133
139
|
|
|
134
140
|
return parts.join(' ');
|
|
@@ -157,6 +163,7 @@ export function formatDuration(ms: number): string {
|
|
|
157
163
|
* @returns {string | null} 中文相对时间描述,无效日期时返回 null
|
|
158
164
|
*/
|
|
159
165
|
export function formatRelativeTime(isoDateString: string): string | null {
|
|
166
|
+
const lang = getCurrentLanguage();
|
|
160
167
|
const date = new Date(isoDateString);
|
|
161
168
|
const now = new Date();
|
|
162
169
|
const diffMs = now.getTime() - date.getTime();
|
|
@@ -168,7 +175,7 @@ export function formatRelativeTime(isoDateString: string): string | null {
|
|
|
168
175
|
|
|
169
176
|
// 未来时间或不到 1 分钟
|
|
170
177
|
if (diffMs < 0 || diffMs < 60 * 1000) {
|
|
171
|
-
return '刚刚';
|
|
178
|
+
return lang === 'en' ? 'just now' : '刚刚';
|
|
172
179
|
}
|
|
173
180
|
|
|
174
181
|
const diffMinutes = Math.floor(diffMs / (1000 * 60));
|
|
@@ -176,20 +183,20 @@ export function formatRelativeTime(isoDateString: string): string | null {
|
|
|
176
183
|
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
177
184
|
|
|
178
185
|
if (diffHours < 1) {
|
|
179
|
-
return `${diffMinutes} 分钟前`;
|
|
186
|
+
return lang === 'en' ? `${diffMinutes} min ago` : `${diffMinutes} 分钟前`;
|
|
180
187
|
}
|
|
181
188
|
if (diffDays < 1) {
|
|
182
|
-
return `${diffHours} 小时前`;
|
|
189
|
+
return lang === 'en' ? `${diffHours} hr ago` : `${diffHours} 小时前`;
|
|
183
190
|
}
|
|
184
191
|
if (diffDays < 30) {
|
|
185
|
-
return `${diffDays} 天前`;
|
|
192
|
+
return lang === 'en' ? `${diffDays} day${diffDays > 1 ? 's' : ''} ago` : `${diffDays} 天前`;
|
|
186
193
|
}
|
|
187
194
|
if (diffDays < 365) {
|
|
188
195
|
const months = Math.floor(diffDays / 30);
|
|
189
|
-
return `${months} 个月前`;
|
|
196
|
+
return lang === 'en' ? `${months} month${months > 1 ? 's' : ''} ago` : `${months} 个月前`;
|
|
190
197
|
}
|
|
191
198
|
const years = Math.floor(diffDays / 365);
|
|
192
|
-
return `${years} 年前`;
|
|
199
|
+
return lang === 'en' ? `${years} year${years > 1 ? 's' : ''} ago` : `${years} 年前`;
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
/**
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { loadConfig } from './config.js';
|
|
2
|
+
|
|
3
|
+
/** 支持的语言类型 */
|
|
4
|
+
export type Language = 'en' | 'zh-CN';
|
|
5
|
+
|
|
6
|
+
/** 当前语言缓存 */
|
|
7
|
+
let currentLanguage: Language | null = null;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 获取当前语言配置
|
|
11
|
+
* 优先使用缓存,缓存不存在时从配置文件读取
|
|
12
|
+
* @returns {Language} 当前语言
|
|
13
|
+
*/
|
|
14
|
+
export function getCurrentLanguage(): Language {
|
|
15
|
+
if (currentLanguage !== null) {
|
|
16
|
+
return currentLanguage;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const config = loadConfig();
|
|
20
|
+
currentLanguage = (config.language as Language) || 'en';
|
|
21
|
+
} catch {
|
|
22
|
+
currentLanguage = 'en';
|
|
23
|
+
}
|
|
24
|
+
return currentLanguage;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 设置当前语言(用于测试和 CLI 初始化时)
|
|
29
|
+
* @param {Language} lang - 语言代码
|
|
30
|
+
*/
|
|
31
|
+
export function setCurrentLanguage(lang: Language): void {
|
|
32
|
+
currentLanguage = lang;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 重置语言缓存(配置变更后调用,使下次读取时重新加载)
|
|
37
|
+
*/
|
|
38
|
+
export function resetLanguageCache(): void {
|
|
39
|
+
currentLanguage = null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 从 i18n 双语映射条目中提取当前语言对应的值类型
|
|
44
|
+
*/
|
|
45
|
+
type ExtractLang<T> = T extends { en: infer V; 'zh-CN': infer V } ? V : never;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 创建国际化消息对象
|
|
49
|
+
* 根据当前语言从双语映射中选择对应的文本或函数
|
|
50
|
+
* 保留精确的键名和值类型,确保合并后的 MESSAGES 对象类型正确
|
|
51
|
+
* @param {T} i18nMap - 双语消息映射,每个键包含 en 和 zh-CN 两个版本
|
|
52
|
+
* @returns {{ [K in keyof T]: ExtractLang<T[K]> }} 当前语言的消息对象,键名和值类型与原始映射一致
|
|
53
|
+
*/
|
|
54
|
+
export function createMessages<T extends Record<string, { en: any; 'zh-CN': any }>>(
|
|
55
|
+
i18nMap: T
|
|
56
|
+
): { [K in keyof T]: ExtractLang<T[K]> } {
|
|
57
|
+
const lang = getCurrentLanguage();
|
|
58
|
+
const result: any = {};
|
|
59
|
+
for (const key of Object.keys(i18nMap)) {
|
|
60
|
+
result[key] = i18nMap[key][lang];
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -98,4 +98,6 @@ export { buildPanelFrame, buildGroupedWorktreeLines, buildDisplayOrder, renderDa
|
|
|
98
98
|
export type { PanelLine } from './interactive-panel-render.js';
|
|
99
99
|
export { buildConflictResolvePrompt, invokeClaudeForConflictResolve, resolveConflictsWithAI, determineConflictResolveMode, handleMergeConflict } from './conflict-resolver.js';
|
|
100
100
|
export { resolvePostCreateHook, executePostCreateHooks, runPostCreateHooks } from '../hooks/index.js';
|
|
101
|
+
export { getCurrentLanguage, setCurrentLanguage, resetLanguageCache, createMessages } from './i18n.js';
|
|
102
|
+
export type { Language } from './i18n.js';
|
|
101
103
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
UNKNOWN_DATE_GROUP,
|
|
12
12
|
VALIDATE_BRANCH_PREFIX,
|
|
13
13
|
} from '../constants/index.js';
|
|
14
|
+
import { getCurrentLanguage } from './i18n.js';
|
|
14
15
|
import {
|
|
15
16
|
PANEL_FOOTER_SHORTCUTS,
|
|
16
17
|
PANEL_FOOTER_COUNTDOWN,
|
|
@@ -218,7 +219,8 @@ export function renderDateSeparator(dateKey: string): string {
|
|
|
218
219
|
return `${leftPad}${chalk.gray(PANEL_DATE_SEPARATOR_PREFIX)} ${chalk.bold.hex(PANEL_DATE_COLOR)(PANEL_UNKNOWN_DATE)} ${chalk.gray(PANEL_DATE_SEPARATOR_PREFIX)}`;
|
|
219
220
|
}
|
|
220
221
|
const relativeDate = formatRelativeDate(dateKey);
|
|
221
|
-
|
|
222
|
+
const relativeDateText = getCurrentLanguage() === 'en' ? `(${relativeDate})` : `(${relativeDate})`;
|
|
223
|
+
return `${leftPad}${chalk.gray(PANEL_DATE_SEPARATOR_PREFIX)} ${chalk.bold.hex(PANEL_DATE_COLOR)(dateKey)}${chalk.hex(PANEL_DATE_COLOR)(relativeDateText)} ${chalk.gray(PANEL_DATE_SEPARATOR_PREFIX)}`;
|
|
222
224
|
}
|
|
223
225
|
|
|
224
226
|
/**
|
|
@@ -324,12 +326,13 @@ function renderConfiguredBranchLine(main: MainWorktreeStatus): string {
|
|
|
324
326
|
* @returns {string} 格式化的 diff 信息
|
|
325
327
|
*/
|
|
326
328
|
function renderMainBranchDiff(main: MainWorktreeStatus): string {
|
|
329
|
+
const workingDirLabel = getCurrentLanguage() === 'en' ? 'Working dir:' : '工作区:';
|
|
327
330
|
if (main.insertions === 0 && main.deletions === 0) {
|
|
328
|
-
return
|
|
331
|
+
return `${workingDirLabel} ${chalk.green(MESSAGES.STATUS_CHANGE_CLEAN)}`;
|
|
329
332
|
}
|
|
330
333
|
const insertText = chalk.green(`+${main.insertions}`);
|
|
331
334
|
const deleteText = chalk.red(`-${main.deletions}`);
|
|
332
|
-
return
|
|
335
|
+
return `${workingDirLabel} ${insertText} ${deleteText}`;
|
|
333
336
|
}
|
|
334
337
|
|
|
335
338
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import stringWidth from 'string-width';
|
|
3
|
-
import { TASK_STATUS_ICONS,
|
|
3
|
+
import { TASK_STATUS_ICONS, getTaskStatusLabels } from '../constants/index.js';
|
|
4
4
|
import { formatDuration } from './formatter.js';
|
|
5
5
|
|
|
6
6
|
/** ANSI 颜色/样式转义序列的匹配正则 */
|
|
@@ -106,27 +106,28 @@ export function getMaxPathWidth(tasks: TaskProgress[]): number {
|
|
|
106
106
|
export function renderTaskLine(task: TaskProgress, total: number, maxPathWidth: number, spinnerChar: string): string {
|
|
107
107
|
const indexStr = `[${task.index}/${total}]`;
|
|
108
108
|
const pathStr = task.path.padEnd(maxPathWidth);
|
|
109
|
+
const labels = getTaskStatusLabels();
|
|
109
110
|
|
|
110
111
|
switch (task.status) {
|
|
111
112
|
case 'pending': {
|
|
112
|
-
return `${indexStr} ${pathStr} ${chalk.gray(TASK_STATUS_ICONS.PENDING)} ${chalk.gray(
|
|
113
|
+
return `${indexStr} ${pathStr} ${chalk.gray(TASK_STATUS_ICONS.PENDING)} ${chalk.gray(labels.PENDING)}`;
|
|
113
114
|
}
|
|
114
115
|
case 'running': {
|
|
115
116
|
const elapsed = formatDuration(Date.now() - task.startedAt);
|
|
116
117
|
// 仅显示活动信息,不显示路径(路径已在第二列显示)
|
|
117
118
|
const detail = task.activity ? ` ${chalk.dim(task.activity)}` : '';
|
|
118
|
-
return `${indexStr} ${pathStr} ${chalk.cyan(spinnerChar)} ${chalk.cyan(
|
|
119
|
+
return `${indexStr} ${pathStr} ${chalk.cyan(spinnerChar)} ${chalk.cyan(labels.RUNNING)} ${chalk.gray(elapsed)}${detail}`;
|
|
119
120
|
}
|
|
120
121
|
case 'done': {
|
|
121
122
|
const duration = task.durationMs != null ? formatDuration(task.durationMs) : 'N/A';
|
|
122
123
|
const cost = task.costUsd != null ? `$${task.costUsd.toFixed(2)}` : '';
|
|
123
124
|
const preview = task.resultPreview ? ` ${chalk.dim(task.resultPreview)}` : '';
|
|
124
|
-
return `${indexStr} ${pathStr} ${chalk.green(TASK_STATUS_ICONS.DONE)} ${chalk.green(
|
|
125
|
+
return `${indexStr} ${pathStr} ${chalk.green(TASK_STATUS_ICONS.DONE)} ${chalk.green(labels.DONE)} ${chalk.gray(duration)} ${chalk.yellow(cost)}${preview}`;
|
|
125
126
|
}
|
|
126
127
|
case 'failed': {
|
|
127
128
|
const duration = task.durationMs != null ? formatDuration(task.durationMs) : 'N/A';
|
|
128
129
|
const preview = task.resultPreview ? ` ${chalk.dim(task.resultPreview)}` : '';
|
|
129
|
-
return `${indexStr} ${pathStr} ${chalk.red(TASK_STATUS_ICONS.FAILED)} ${chalk.red(
|
|
130
|
+
return `${indexStr} ${pathStr} ${chalk.red(TASK_STATUS_ICONS.FAILED)} ${chalk.red(labels.FAILED)} ${chalk.gray(duration)}${preview}`;
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
133
|
}
|
|
@@ -143,12 +144,13 @@ export function renderSummaryLine(tasks: TaskProgress[], total: number): string
|
|
|
143
144
|
const done = tasks.filter((t) => t.status === 'done').length;
|
|
144
145
|
const failed = tasks.filter((t) => t.status === 'failed').length;
|
|
145
146
|
const pending = tasks.filter((t) => t.status === 'pending').length;
|
|
147
|
+
const labels = getTaskStatusLabels();
|
|
146
148
|
|
|
147
149
|
const parts: string[] = [];
|
|
148
|
-
if (running > 0) parts.push(chalk.cyan(`${running}/${total} ${
|
|
149
|
-
if (done > 0) parts.push(chalk.green(`${done}/${total} ${
|
|
150
|
-
if (failed > 0) parts.push(chalk.red(`${failed}/${total} ${
|
|
151
|
-
if (pending > 0) parts.push(chalk.gray(`${pending}/${total} ${
|
|
150
|
+
if (running > 0) parts.push(chalk.cyan(`${running}/${total} ${labels.RUNNING}`));
|
|
151
|
+
if (done > 0) parts.push(chalk.green(`${done}/${total} ${labels.DONE}`));
|
|
152
|
+
if (failed > 0) parts.push(chalk.red(`${failed}/${total} ${labels.FAILED}`));
|
|
153
|
+
if (pending > 0) parts.push(chalk.gray(`${pending}/${total} ${labels.PENDING}`));
|
|
152
154
|
|
|
153
155
|
return `[${parts.join(', ')}]`;
|
|
154
156
|
}
|
package/src/utils/prompt.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Enquirer from 'enquirer';
|
|
2
2
|
import { isNonInteractive } from './interactive.js';
|
|
3
3
|
import { ClawtError } from '../errors/index.js';
|
|
4
|
+
import { MESSAGES } from '../constants/index.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* 多行交互式输入框
|
|
@@ -45,7 +46,7 @@ export async function promptCommitMessage(
|
|
|
45
46
|
|
|
46
47
|
// 空输入视为无效
|
|
47
48
|
if (!result.trim()) {
|
|
48
|
-
throw new ClawtError(
|
|
49
|
+
throw new ClawtError(MESSAGES.COMMIT_MESSAGE_NOT_EMPTY);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
return result.trim();
|
|
@@ -3,6 +3,7 @@ import { logger } from '../logger/index.js';
|
|
|
3
3
|
import { MESSAGES } from '../constants/index.js';
|
|
4
4
|
import type { ClaudeCodeResult, TaskResult, TaskSummary, WorktreeInfo } from '../types/index.js';
|
|
5
5
|
import { spawnProcess, killAllChildProcesses } from './shell.js';
|
|
6
|
+
import { getCurrentLanguage } from './i18n.js';
|
|
6
7
|
import { cleanupWorktrees } from './worktree.js';
|
|
7
8
|
import { getConfigValue } from './config.js';
|
|
8
9
|
import { printSuccess, printWarning, printInfo, printDoubleSeparator, confirmAction } from './formatter.js';
|
|
@@ -107,7 +108,7 @@ function executeClaudeTask(worktree: WorktreeInfo, task: string, onActivity?: Ac
|
|
|
107
108
|
worktreePath: worktree.path,
|
|
108
109
|
success,
|
|
109
110
|
result: finalResult,
|
|
110
|
-
error: success ? undefined : stderr ||
|
|
111
|
+
error: success ? undefined : stderr || MESSAGES.TASK_FAILED,
|
|
111
112
|
});
|
|
112
113
|
});
|
|
113
114
|
|
|
@@ -132,11 +133,11 @@ function executeClaudeTask(worktree: WorktreeInfo, task: string, onActivity?: Ac
|
|
|
132
133
|
*/
|
|
133
134
|
function printTaskSummary(summary: TaskSummary): void {
|
|
134
135
|
printDoubleSeparator();
|
|
135
|
-
printInfo(
|
|
136
|
-
printInfo(`
|
|
137
|
-
printInfo(`
|
|
138
|
-
printInfo(`
|
|
139
|
-
printInfo(`
|
|
136
|
+
printInfo(MESSAGES.ALL_TASKS_COMPLETED(summary.total));
|
|
137
|
+
printInfo(` ${MESSAGES.SUCCESS_LABEL} ${summary.succeeded}`);
|
|
138
|
+
printInfo(` ${MESSAGES.FAILURE_LABEL} ${summary.failed}`);
|
|
139
|
+
printInfo(` ${MESSAGES.TOTAL_DURATION_LABEL} ${(summary.totalDurationMs / 1000).toFixed(1)}s`);
|
|
140
|
+
printInfo(` ${MESSAGES.TOTAL_COST_LABEL} $${summary.totalCostUsd.toFixed(2)}`);
|
|
140
141
|
printDoubleSeparator();
|
|
141
142
|
}
|
|
142
143
|
|
|
@@ -342,7 +343,9 @@ export async function executeBatchTasks(
|
|
|
342
343
|
// 校验 continueFlags 长度与 worktrees 一致,防止调用方传入不匹配的数组
|
|
343
344
|
if (continueFlags && continueFlags.length !== count) {
|
|
344
345
|
throw new ClawtError(
|
|
345
|
-
|
|
346
|
+
getCurrentLanguage() === 'en'
|
|
347
|
+
? `continueFlags length (${continueFlags.length}) does not match task count (${count})`
|
|
348
|
+
: `continueFlags 长度 (${continueFlags.length}) 与任务数 (${count}) 不一致`,
|
|
346
349
|
);
|
|
347
350
|
}
|
|
348
351
|
|
package/src/utils/task-file.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { resolve } from 'node:path';
|
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { ClawtError } from '../errors/index.js';
|
|
4
4
|
import { MESSAGES } from '../constants/index.js';
|
|
5
|
+
import { getCurrentLanguage } from './i18n.js';
|
|
5
6
|
import type { TaskFileEntry, ParseTaskFileOptions } from '../types/index.js';
|
|
6
7
|
|
|
7
8
|
/** 匹配任务块的正则:<!-- CLAWT-TASKS:START --> ... <!-- CLAWT-TASKS:END --> */
|
|
@@ -11,7 +12,7 @@ const TASK_BLOCK_REGEX = /<!-- CLAWT-TASKS:START -->([\s\S]*?)<!-- CLAWT-TASKS:E
|
|
|
11
12
|
const BRANCH_LINE_REGEX = /^#\s*branch:\s*(.+)$/;
|
|
12
13
|
|
|
13
14
|
/** 任务列表为空时的错误提示 */
|
|
14
|
-
const EMPTY_TASKS_MESSAGE = '任务列表不能为空';
|
|
15
|
+
const EMPTY_TASKS_MESSAGE = getCurrentLanguage() === 'en' ? 'Task list cannot be empty' : '任务列表不能为空';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* 从命令行 --tasks 选项中解析出有效的任务列表
|
package/src/utils/terminal.ts
CHANGED
|
@@ -2,8 +2,9 @@ import { execFileSync } from 'node:child_process';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { ClawtError } from '../errors/index.js';
|
|
4
4
|
import { logger } from '../logger/index.js';
|
|
5
|
-
import { VALID_TERMINAL_APPS, ITERM2_APP_PATH } from '../constants/index.js';
|
|
5
|
+
import { VALID_TERMINAL_APPS, ITERM2_APP_PATH, MESSAGES } from '../constants/index.js';
|
|
6
6
|
import { getConfigValue } from './config.js';
|
|
7
|
+
import { getCurrentLanguage } from './i18n.js';
|
|
7
8
|
|
|
8
9
|
/** 终端应用类型 */
|
|
9
10
|
type TerminalApp = 'iterm2' | 'terminal' | 'cmux';
|
|
@@ -124,8 +125,7 @@ function openCommandInCmuxSurface(command: string, title: string): void {
|
|
|
124
125
|
// 环境检查:只需要检查 WORKSPACE_ID
|
|
125
126
|
if (!isCmuxEnvironment()) {
|
|
126
127
|
throw new ClawtError(
|
|
127
|
-
|
|
128
|
-
'请确保在 cmux 终端中执行 clawt resume 命令,或修改 terminalApp 配置'
|
|
128
|
+
MESSAGES.NOT_IN_CMUX
|
|
129
129
|
);
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -152,7 +152,7 @@ function openCommandInCmuxSurface(command: string, title: string): void {
|
|
|
152
152
|
// 需要灵活匹配
|
|
153
153
|
const match = newSurfaceResult.match(/(?:OK\s+)?(surface:\d+)/i);
|
|
154
154
|
if (!match) {
|
|
155
|
-
throw new Error(`无法解析 cmux new-split 输出: ${newSurfaceResult}`);
|
|
155
|
+
throw new Error(getCurrentLanguage() === 'en' ? `Failed to parse cmux new-split output: ${newSurfaceResult}` : `无法解析 cmux new-split 输出: ${newSurfaceResult}`);
|
|
156
156
|
}
|
|
157
157
|
const surfaceRef = match[1];
|
|
158
158
|
|
|
@@ -173,7 +173,7 @@ function openCommandInCmuxSurface(command: string, title: string): void {
|
|
|
173
173
|
|
|
174
174
|
} catch (error) {
|
|
175
175
|
const message = error instanceof Error ? error.message : String(error);
|
|
176
|
-
throw new ClawtError(`在 cmux 中创建 surface 失败: ${message}`);
|
|
176
|
+
throw new ClawtError(getCurrentLanguage() === 'en' ? `Failed to create surface in cmux: ${message}` : `在 cmux 中创建 surface 失败: ${message}`);
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
|
|
@@ -195,9 +195,9 @@ function executeAppleScript(script: string, terminalApp: 'iterm2' | 'terminal'):
|
|
|
195
195
|
} catch (error) {
|
|
196
196
|
const message = error instanceof Error ? error.message : String(error);
|
|
197
197
|
const accessibilityHint = terminalApp === 'terminal'
|
|
198
|
-
?
|
|
198
|
+
? MESSAGES.TERMINAL_ACCESSIBILITY_HINT
|
|
199
199
|
: '';
|
|
200
|
-
throw new ClawtError(`打开终端 Tab 失败: ${message}${accessibilityHint}`);
|
|
200
|
+
throw new ClawtError(getCurrentLanguage() === 'en' ? `Failed to open terminal tab: ${message}${accessibilityHint}` : `打开终端 Tab 失败: ${message}${accessibilityHint}`);
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
|
|
@@ -212,7 +212,7 @@ function executeAppleScript(script: string, terminalApp: 'iterm2' | 'terminal'):
|
|
|
212
212
|
*/
|
|
213
213
|
export function openCommandInNewTerminalTab(command: string, tabTitle: string): void {
|
|
214
214
|
if (process.platform !== 'darwin') {
|
|
215
|
-
throw new ClawtError(
|
|
215
|
+
throw new ClawtError(MESSAGES.BATCH_RESUME_MACOS_ONLY);
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
const terminalApp = detectTerminalApp();
|
|
@@ -230,6 +230,6 @@ export function openCommandInNewTerminalTab(command: string, tabTitle: string):
|
|
|
230
230
|
executeAppleScript(terminalScript, 'terminal');
|
|
231
231
|
break;
|
|
232
232
|
default:
|
|
233
|
-
throw new ClawtError(`不支持的终端类型: ${terminalApp}`);
|
|
233
|
+
throw new ClawtError(getCurrentLanguage() === 'en' ? `Unsupported terminal type: ${terminalApp}` : `不支持的终端类型: ${terminalApp}`);
|
|
234
234
|
}
|
|
235
235
|
}
|
package/src/utils/ui-prompts.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from '../constants/index.js';
|
|
6
6
|
import type { WorktreeInfo } from '../types/index.js';
|
|
7
7
|
import { groupWorktreesByDate, buildGroupedChoices, buildGroupMembershipMap } from './worktree-matcher.js';
|
|
8
|
+
import { getCurrentLanguage } from './i18n.js';
|
|
8
9
|
import { isNonInteractive } from './interactive.js';
|
|
9
10
|
import { ClawtError } from '../errors/index.js';
|
|
10
11
|
|
|
@@ -45,7 +46,7 @@ export type GroupedChoice = { name: string; message: string } | MultiSelectSepar
|
|
|
45
46
|
export async function promptSelectBranch(worktrees: WorktreeInfo[], message: string): Promise<WorktreeInfo> {
|
|
46
47
|
// 非交互模式下无法进行交互选择,要求用户通过 -b 精确指定
|
|
47
48
|
if (isNonInteractive()) {
|
|
48
|
-
throw new ClawtError('非交互模式下无法进行分支选择,请通过 -b 参数精确指定分支名');
|
|
49
|
+
throw new ClawtError(getCurrentLanguage() === 'en' ? 'Non-interactive mode cannot select branch, please specify branch name via -b' : '非交互模式下无法进行分支选择,请通过 -b 参数精确指定分支名');
|
|
49
50
|
}
|
|
50
51
|
// @ts-expect-error enquirer 类型声明未导出 Select 类,但运行时存在
|
|
51
52
|
const selectedBranch: string = await new Enquirer.Select({
|
|
@@ -70,7 +71,7 @@ export async function promptSelectBranch(worktrees: WorktreeInfo[], message: str
|
|
|
70
71
|
export async function promptMultiSelectBranches(worktrees: WorktreeInfo[], message: string): Promise<WorktreeInfo[]> {
|
|
71
72
|
// 非交互模式下无法进行交互多选,要求用户通过 -b 精确指定
|
|
72
73
|
if (isNonInteractive()) {
|
|
73
|
-
throw new ClawtError('非交互模式下无法进行分支多选,请通过 -b 参数精确指定分支名');
|
|
74
|
+
throw new ClawtError(getCurrentLanguage() === 'en' ? 'Non-interactive mode cannot multi-select branches, please specify branch name via -b' : '非交互模式下无法进行分支多选,请通过 -b 参数精确指定分支名');
|
|
74
75
|
}
|
|
75
76
|
// 构建 choices 列表,顶部插入全选选项
|
|
76
77
|
const branchChoices = worktrees.map((wt) => ({
|
|
@@ -143,7 +144,7 @@ export async function promptGroupedMultiSelectBranches(
|
|
|
143
144
|
): Promise<WorktreeInfo[]> {
|
|
144
145
|
// 非交互模式下无法进行交互多选,要求用户通过 -b 精确指定
|
|
145
146
|
if (isNonInteractive()) {
|
|
146
|
-
throw new ClawtError('非交互模式下无法进行分支多选,请通过 -b 参数精确指定分支名');
|
|
147
|
+
throw new ClawtError(getCurrentLanguage() === 'en' ? 'Non-interactive mode cannot multi-select branches, please specify branch name via -b' : '非交互模式下无法进行分支多选,请通过 -b 参数精确指定分支名');
|
|
147
148
|
}
|
|
148
149
|
const groups = groupWorktreesByDate(worktrees);
|
|
149
150
|
const choices = buildGroupedChoices(groups);
|
|
@@ -144,7 +144,7 @@ function printUpdateNotification(currentVersion: string, latestVersion: string):
|
|
|
144
144
|
chalk.green(latestVersion),
|
|
145
145
|
);
|
|
146
146
|
const pm = detectPackageManager();
|
|
147
|
-
const updateCommand = UPDATE_COMMANDS[pm] || UPDATE_COMMANDS.npm;
|
|
147
|
+
const updateCommand = (UPDATE_COMMANDS as Record<string, string>)[pm] || UPDATE_COMMANDS.npm;
|
|
148
148
|
const commandText = UPDATE_MESSAGES.UPDATE_HINT(chalk.cyan(updateCommand));
|
|
149
149
|
|
|
150
150
|
const updateTextWidth = stringWidth(updateText);
|