clawt 2.13.0 → 2.14.1
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/.claude/agent-memory/docs-sync-updater/MEMORY.md +8 -3
- package/README.md +20 -3
- package/dist/index.js +207 -64
- package/dist/postinstall.js +26 -3
- package/docs/spec.md +63 -23
- package/package.json +1 -1
- package/src/commands/config.ts +120 -48
- package/src/constants/config.ts +3 -1
- package/src/constants/index.ts +2 -1
- package/src/constants/messages/config.ts +25 -0
- package/src/constants/messages/index.ts +3 -1
- package/src/types/config.ts +2 -0
- package/src/utils/config-strategy.ts +196 -0
- package/src/utils/config.ts +8 -0
- package/src/utils/index.ts +2 -1
- package/tests/unit/commands/config.test.ts +355 -24
- package/tests/unit/constants/config.test.ts +24 -1
- package/tests/unit/utils/config-strategy.test.ts +217 -0
- package/tests/unit/utils/config.test.ts +13 -1
package/docs/spec.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
- [5.7 默认配置文件](#57-默认配置文件)
|
|
22
22
|
- [5.8 获取当前项目所有 Worktree](#58-获取当前项目所有-worktree)
|
|
23
23
|
- [5.9 日志系统](#59-日志系统)
|
|
24
|
-
- [5.10
|
|
24
|
+
- [5.10 交互式查看和修改全局配置](#510-交互式查看和修改全局配置)
|
|
25
25
|
- [5.11 在已有 Worktree 中恢复会话](#511-在已有-worktree-中恢复会话)
|
|
26
26
|
- [5.12 将主分支代码同步到目标 Worktree](#512-将主分支代码同步到目标-worktree)
|
|
27
27
|
- [5.13 重置主 Worktree 工作区和暂存区](#513-重置主-worktree-工作区和暂存区)
|
|
@@ -174,7 +174,9 @@ git show-ref --verify refs/heads/<branchName> 2>/dev/null
|
|
|
174
174
|
| `clawt merge` | 合并某个已验证的 worktree 分支到主 worktree | 5.6 |
|
|
175
175
|
| `clawt remove` | 移除 worktree(支持模糊匹配/多选/全部) | 5.5 |
|
|
176
176
|
| `clawt list` | 列出当前项目所有 worktree(支持 `--json` 格式输出) | 5.8 |
|
|
177
|
-
| `clawt config` |
|
|
177
|
+
| `clawt config` | 交互式查看和修改全局配置(等同于 `config set`) | 5.10 |
|
|
178
|
+
| `clawt config set` | 修改配置项(无参数进入交互式,有参数直接设置) | 5.10 |
|
|
179
|
+
| `clawt config get` | 获取单个配置项的值 | 5.10 |
|
|
178
180
|
| `clawt config reset` | 将配置恢复为默认值 | 5.10 |
|
|
179
181
|
| `clawt resume` | 在已有 worktree 中恢复 Claude Code 会话(支持多选批量恢复) | 5.11 |
|
|
180
182
|
| `clawt sync` | 将主分支最新代码同步到目标 worktree | 5.12 |
|
|
@@ -1000,51 +1002,82 @@ clawt validate -b feature-scheme --debug
|
|
|
1000
1002
|
|
|
1001
1003
|
---
|
|
1002
1004
|
|
|
1003
|
-
### 5.10
|
|
1005
|
+
### 5.10 交互式查看和修改全局配置
|
|
1004
1006
|
|
|
1005
1007
|
**命令:**
|
|
1006
1008
|
|
|
1007
1009
|
```bash
|
|
1008
|
-
#
|
|
1010
|
+
# 交互式修改配置(等同于 config set 无参数)
|
|
1009
1011
|
clawt config
|
|
1010
1012
|
|
|
1013
|
+
# 修改配置项(无参数进入交互式,有参数直接设置)
|
|
1014
|
+
clawt config set [key] [value]
|
|
1015
|
+
|
|
1016
|
+
# 获取单个配置项的值
|
|
1017
|
+
clawt config get <key>
|
|
1018
|
+
|
|
1011
1019
|
# 将配置恢复为默认值
|
|
1012
1020
|
clawt config reset
|
|
1013
1021
|
```
|
|
1014
1022
|
|
|
1015
|
-
####
|
|
1023
|
+
#### 交互式修改配置(`config` / `config set`)
|
|
1024
|
+
|
|
1025
|
+
直接执行 `clawt config` 或 `clawt config set`(不带参数)进入交互式配置修改模式。
|
|
1016
1026
|
|
|
1017
1027
|
**运行流程:**
|
|
1018
1028
|
|
|
1019
1029
|
1. 读取全局配置文件 `~/.clawt/config.json`
|
|
1020
|
-
2.
|
|
1021
|
-
-
|
|
1030
|
+
2. 列出所有配置项供用户选择(`Enquirer.Select`),每项显示:
|
|
1031
|
+
- 配置项名称
|
|
1022
1032
|
- 当前值(布尔值绿色/黄色,字符串青色)
|
|
1023
1033
|
- 配置项描述(灰色)
|
|
1024
|
-
|
|
1034
|
+
- 对象类型配置项(如 `aliases`)标灰不可选,提示用户通过专用命令管理
|
|
1035
|
+
3. 用户选择某个配置项后,根据值类型自动选择提示策略:
|
|
1036
|
+
- **boolean 类型** → `Select`(true / false)
|
|
1037
|
+
- **number 类型** → `Input`(带数字校验)
|
|
1038
|
+
- **string 类型 + 有 `allowedValues`** → `Select`(枚举列表)
|
|
1039
|
+
- **string 类型 + 无 `allowedValues`** → `Input`(自由输入)
|
|
1040
|
+
4. 将修改后的配置持久化到配置文件
|
|
1041
|
+
5. 输出成功提示:`✓ <key> 已设置为 <value>`
|
|
1025
1042
|
|
|
1026
|
-
|
|
1043
|
+
#### 直接设置配置项(`config set <key> <value>`)
|
|
1027
1044
|
|
|
1028
|
-
|
|
1029
|
-
配置文件路径: ~/.clawt/config.json
|
|
1030
|
-
────────────────────────────────────────
|
|
1031
|
-
autoDeleteBranch: false
|
|
1032
|
-
移除 worktree 时是否自动删除对应本地分支
|
|
1045
|
+
当带参数执行 `clawt config set <key> <value>` 时,直接修改指定配置项。
|
|
1033
1046
|
|
|
1034
|
-
|
|
1035
|
-
Claude Code CLI 启动指令
|
|
1047
|
+
**参数:**
|
|
1036
1048
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1049
|
+
| 参数 | 必填 | 说明 |
|
|
1050
|
+
| ---- | ---- | ---- |
|
|
1051
|
+
| `key` | 否 | 配置项名称(不传则进入交互式模式) |
|
|
1052
|
+
| `value` | 否 | 配置值(传了 `key` 时必填) |
|
|
1039
1053
|
|
|
1040
|
-
|
|
1041
|
-
执行破坏性操作(reset、validate --clean)前是否提示确认
|
|
1054
|
+
**运行流程:**
|
|
1042
1055
|
|
|
1043
|
-
|
|
1056
|
+
1. 校验 `key` 是否为有效的配置项名称(基于 `DEFAULT_CONFIG` 的键列表),无效则输出错误及可用配置项列表
|
|
1057
|
+
2. 校验 `value` 是否缺失,缺失则提示用法:`clawt config set <key> <value>`
|
|
1058
|
+
3. 根据目标配置项的类型解析并校验值:
|
|
1059
|
+
- **boolean** → 仅接受 `true` 或 `false`
|
|
1060
|
+
- **number** → `Number()` 解析,`NaN` 报错
|
|
1061
|
+
- **string + 有 `allowedValues`** → 校验值是否在枚举列表中
|
|
1062
|
+
- **string + 无 `allowedValues`** → 无额外校验
|
|
1063
|
+
4. 加载配置、修改目标项、持久化
|
|
1064
|
+
5. 输出成功提示:`✓ <key> 已设置为 <value>`
|
|
1044
1065
|
|
|
1045
|
-
|
|
1066
|
+
#### 获取单个配置项(`config get <key>`)
|
|
1046
1067
|
|
|
1047
|
-
|
|
1068
|
+
**参数:**
|
|
1069
|
+
|
|
1070
|
+
| 参数 | 必填 | 说明 |
|
|
1071
|
+
| ---- | ---- | ---- |
|
|
1072
|
+
| `key` | 是 | 配置项名称 |
|
|
1073
|
+
|
|
1074
|
+
**运行流程:**
|
|
1075
|
+
|
|
1076
|
+
1. 校验 `key` 是否为有效的配置项名称,无效则输出错误及可用配置项列表
|
|
1077
|
+
2. 读取配置文件,获取目标配置项的值
|
|
1078
|
+
3. 输出:`<key> = <value>`
|
|
1079
|
+
|
|
1080
|
+
#### 恢复默认配置(`config reset`)
|
|
1048
1081
|
|
|
1049
1082
|
**运行流程:**
|
|
1050
1083
|
|
|
@@ -1052,6 +1085,13 @@ clawt config reset
|
|
|
1052
1085
|
2. 将默认配置写入 `~/.clawt/config.json`(覆盖现有配置文件)
|
|
1053
1086
|
3. 输出成功提示:`✓ 配置已恢复为默认值`
|
|
1054
1087
|
|
|
1088
|
+
**实现要点:**
|
|
1089
|
+
|
|
1090
|
+
- 配置项类型定义:`ConfigItemDefinition` 新增可选字段 `allowedValues`(`readonly string[]`),仅对 string 类型有效,用于枚举值校验和交互式 Select 提示
|
|
1091
|
+
- 值解析与提示策略:`src/utils/config-strategy.ts` 中的 `parseConfigValue()`(CLI 字符串解析)和 `promptConfigValue()`(交互式提示),基于类型和 `allowedValues` 自动分发
|
|
1092
|
+
- `saveConfig(config)`:`src/utils/config.ts` 中新增的通用配置写入函数,将完整配置对象持久化到文件
|
|
1093
|
+
- `formatConfigValue(value)`:支持 boolean、string、number、对象类型(如 `aliases`,按键值对逐行展示)的格式化显示
|
|
1094
|
+
|
|
1055
1095
|
---
|
|
1056
1096
|
|
|
1057
1097
|
### 5.11 在已有 Worktree 中恢复会话
|
package/package.json
CHANGED
package/src/commands/config.ts
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import
|
|
3
|
+
import Enquirer from 'enquirer';
|
|
4
|
+
import { DEFAULT_CONFIG, CONFIG_DESCRIPTIONS, MESSAGES, CONFIG_ALIAS_DISABLED_HINT } from '../constants/index.js';
|
|
4
5
|
import { logger } from '../logger/index.js';
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
loadConfig,
|
|
8
|
+
saveConfig,
|
|
9
|
+
writeDefaultConfig,
|
|
10
|
+
printInfo,
|
|
11
|
+
printSuccess,
|
|
12
|
+
printError,
|
|
13
|
+
confirmDestructiveAction,
|
|
14
|
+
isValidConfigKey,
|
|
15
|
+
getValidConfigKeys,
|
|
16
|
+
parseConfigValue,
|
|
17
|
+
promptConfigValue,
|
|
18
|
+
formatConfigValue,
|
|
19
|
+
} from '../utils/index.js';
|
|
6
20
|
import type { ClawtConfig } from '../types/index.js';
|
|
7
21
|
|
|
8
22
|
/**
|
|
@@ -12,9 +26,9 @@ import type { ClawtConfig } from '../types/index.js';
|
|
|
12
26
|
export function registerConfigCommand(program: Command): void {
|
|
13
27
|
const configCmd = program
|
|
14
28
|
.command('config')
|
|
15
|
-
.description('
|
|
16
|
-
.action(() => {
|
|
17
|
-
|
|
29
|
+
.description('交互式查看和修改全局配置')
|
|
30
|
+
.action(async () => {
|
|
31
|
+
await handleConfigSet();
|
|
18
32
|
});
|
|
19
33
|
|
|
20
34
|
configCmd
|
|
@@ -23,36 +37,20 @@ export function registerConfigCommand(program: Command): void {
|
|
|
23
37
|
.action(async () => {
|
|
24
38
|
await handleConfigReset();
|
|
25
39
|
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 执行 config 命令的核心逻辑,读取并展示配置列表
|
|
30
|
-
*/
|
|
31
|
-
function handleConfig(): void {
|
|
32
|
-
const config = loadConfig();
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
for (let i = 0; i < keys.length; i++) {
|
|
42
|
-
const key = keys[i];
|
|
43
|
-
const value = config[key];
|
|
44
|
-
const description = CONFIG_DESCRIPTIONS[key];
|
|
45
|
-
const formattedValue = formatConfigValue(value);
|
|
46
|
-
|
|
47
|
-
// 第一个配置项前增加空行,与下横线前的空行对称
|
|
48
|
-
if (i === 0) printInfo('');
|
|
49
|
-
printInfo(` ${chalk.bold(key)}: ${formattedValue}`);
|
|
50
|
-
printInfo(` ${chalk.dim(description)}`);
|
|
51
|
-
// 配置项之间及最后一项与下横线之间保持统一空行间距
|
|
52
|
-
printInfo('');
|
|
53
|
-
}
|
|
41
|
+
configCmd
|
|
42
|
+
.command('set [key] [value]')
|
|
43
|
+
.description('修改配置项(无参数进入交互式配置)')
|
|
44
|
+
.action(async (key?: string, value?: string) => {
|
|
45
|
+
await handleConfigSet(key, value);
|
|
46
|
+
});
|
|
54
47
|
|
|
55
|
-
|
|
48
|
+
configCmd
|
|
49
|
+
.command('get <key>')
|
|
50
|
+
.description('获取单个配置项的值')
|
|
51
|
+
.action((key: string) => {
|
|
52
|
+
handleConfigGet(key);
|
|
53
|
+
});
|
|
56
54
|
}
|
|
57
55
|
|
|
58
56
|
/**
|
|
@@ -76,22 +74,96 @@ async function handleConfigReset(): Promise<void> {
|
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
/**
|
|
79
|
-
*
|
|
80
|
-
* @param {
|
|
81
|
-
* @
|
|
77
|
+
* 执行 config set 子命令:直接设置或交互式配置
|
|
78
|
+
* @param {string} [key] - 配置项名称(可选,无参数进入交互式)
|
|
79
|
+
* @param {string} [value] - 配置值(可选)
|
|
82
80
|
*/
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
async function handleConfigSet(key?: string, value?: string): Promise<void> {
|
|
82
|
+
// 无参数进入交互式配置
|
|
83
|
+
if (!key) {
|
|
84
|
+
await handleInteractiveConfigSet();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 校验 key 有效性
|
|
89
|
+
if (!isValidConfigKey(key)) {
|
|
90
|
+
printError(MESSAGES.CONFIG_INVALID_KEY(key, getValidConfigKeys()));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 有 key 无 value 时提示缺少参数
|
|
95
|
+
if (value === undefined) {
|
|
96
|
+
printError(MESSAGES.CONFIG_MISSING_VALUE(key));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 解析并校验值
|
|
101
|
+
const result = parseConfigValue(key, value);
|
|
102
|
+
if (!result.success) {
|
|
103
|
+
printError(result.error);
|
|
104
|
+
return;
|
|
86
105
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
106
|
+
|
|
107
|
+
// 加载、修改、持久化
|
|
108
|
+
const config = loadConfig();
|
|
109
|
+
(config as unknown as Record<string, unknown>)[key] = result.value;
|
|
110
|
+
saveConfig(config);
|
|
111
|
+
|
|
112
|
+
logger.info(`config set 命令执行,设置 ${key} = ${String(result.value)}`);
|
|
113
|
+
printSuccess(MESSAGES.CONFIG_SET_SUCCESS(key, String(result.value)));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 交互式配置修改:列出所有配置项供用户选择并修改
|
|
118
|
+
*/
|
|
119
|
+
async function handleInteractiveConfigSet(): Promise<void> {
|
|
120
|
+
const config = loadConfig();
|
|
121
|
+
const keys = Object.keys(DEFAULT_CONFIG) as Array<keyof ClawtConfig>;
|
|
122
|
+
|
|
123
|
+
logger.info('config set 命令执行,进入交互式配置');
|
|
124
|
+
|
|
125
|
+
// 构建选择列表,显示配置项名称、当前值和描述
|
|
126
|
+
// 对象类型配置项(如 aliases)标灰不可选,提示用户通过专用命令管理
|
|
127
|
+
const choices = keys.map((k) => {
|
|
128
|
+
const isObject = typeof DEFAULT_CONFIG[k] === 'object';
|
|
129
|
+
return {
|
|
130
|
+
name: k,
|
|
131
|
+
message: `${k}: ${isObject ? chalk.dim(JSON.stringify(config[k])) : formatConfigValue(config[k])} ${chalk.dim(`— ${CONFIG_DESCRIPTIONS[k]}`)}`,
|
|
132
|
+
...(isObject && { disabled: CONFIG_ALIAS_DISABLED_HINT }),
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// @ts-expect-error enquirer 类型声明未导出 Select 类,但运行时存在
|
|
137
|
+
const selectedKey: keyof ClawtConfig = await new Enquirer.Select({
|
|
138
|
+
message: MESSAGES.CONFIG_SELECT_PROMPT,
|
|
139
|
+
choices,
|
|
140
|
+
}).run();
|
|
141
|
+
|
|
142
|
+
// 根据类型和 allowedValues 自动选择提示策略
|
|
143
|
+
const currentValue = config[selectedKey];
|
|
144
|
+
const newValue = await promptConfigValue(selectedKey, currentValue);
|
|
145
|
+
|
|
146
|
+
// 持久化并提示成功
|
|
147
|
+
(config as unknown as Record<string, unknown>)[selectedKey] = newValue;
|
|
148
|
+
saveConfig(config);
|
|
149
|
+
|
|
150
|
+
printSuccess(MESSAGES.CONFIG_SET_SUCCESS(selectedKey, String(newValue)));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 执行 config get 子命令:获取单个配置项的值
|
|
155
|
+
* @param {string} key - 配置项名称
|
|
156
|
+
*/
|
|
157
|
+
function handleConfigGet(key: string): void {
|
|
158
|
+
// 校验 key 有效性
|
|
159
|
+
if (!isValidConfigKey(key)) {
|
|
160
|
+
printError(MESSAGES.CONFIG_INVALID_KEY(key, getValidConfigKeys()));
|
|
161
|
+
return;
|
|
95
162
|
}
|
|
96
|
-
|
|
163
|
+
|
|
164
|
+
const config = loadConfig();
|
|
165
|
+
const value = config[key];
|
|
166
|
+
|
|
167
|
+
logger.info(`config get 命令执行,获取 ${key} = ${String(value)}`);
|
|
168
|
+
printInfo(MESSAGES.CONFIG_GET_VALUE(key, String(value)));
|
|
97
169
|
}
|
package/src/constants/config.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ClawtConfig, ConfigDefinitions } from '../types/index.js';
|
|
2
|
+
import { VALID_TERMINAL_APPS } from './terminal.js';
|
|
2
3
|
|
|
3
4
|
/** Claude Code 系统约束提示,禁止代码执行完成后构建项目验证 */
|
|
4
5
|
export const APPEND_SYSTEM_PROMPT =
|
|
@@ -32,10 +33,11 @@ export const CONFIG_DEFINITIONS: ConfigDefinitions = {
|
|
|
32
33
|
terminalApp: {
|
|
33
34
|
defaultValue: 'auto',
|
|
34
35
|
description: '批量 resume 使用的终端应用:auto(自动检测)、iterm2、terminal(macOS)',
|
|
36
|
+
allowedValues: VALID_TERMINAL_APPS,
|
|
35
37
|
},
|
|
36
38
|
aliases: {
|
|
37
39
|
defaultValue: {} as Record<string, string>,
|
|
38
|
-
description: '
|
|
40
|
+
description: '命令别名映射',
|
|
39
41
|
},
|
|
40
42
|
};
|
|
41
43
|
|
package/src/constants/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export { CLAWT_HOME, CONFIG_PATH, LOGS_DIR, WORKTREES_DIR, VALIDATE_SNAPSHOTS_DIR, CLAUDE_PROJECTS_DIR } from './paths.js';
|
|
2
2
|
export { INVALID_BRANCH_CHARS } from './branch.js';
|
|
3
3
|
export { MESSAGES } from './messages/index.js';
|
|
4
|
+
export { CONFIG_ALIAS_DISABLED_HINT } from './messages/index.js';
|
|
4
5
|
export { EXIT_CODES } from './exitCodes.js';
|
|
5
6
|
export { ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, PASTE_THRESHOLD_MS, VALID_TERMINAL_APPS, ITERM2_APP_PATH } from './terminal.js';
|
|
6
|
-
export { DEFAULT_CONFIG, CONFIG_DESCRIPTIONS, APPEND_SYSTEM_PROMPT } from './config.js';
|
|
7
|
+
export { DEFAULT_CONFIG, CONFIG_DESCRIPTIONS, CONFIG_DEFINITIONS, APPEND_SYSTEM_PROMPT } from './config.js';
|
|
7
8
|
export { AUTO_SAVE_COMMIT_MESSAGE } from './git.js';
|
|
8
9
|
export { DEBUG_LOG_PREFIX, DEBUG_TIMESTAMP_FORMAT } from './logger.js';
|
|
9
10
|
export {
|
|
@@ -1,5 +1,30 @@
|
|
|
1
|
+
/** 对象类型配置项禁用提示(如 aliases 需通过专用命令管理) */
|
|
2
|
+
export const CONFIG_ALIAS_DISABLED_HINT = '(通过 clawt alias 命令管理)';
|
|
3
|
+
|
|
1
4
|
/** config 命令专属提示消息 */
|
|
2
5
|
export const CONFIG_CMD_MESSAGES = {
|
|
3
6
|
/** 配置已恢复为默认值 */
|
|
4
7
|
CONFIG_RESET_SUCCESS: '✓ 配置已恢复为默认值',
|
|
8
|
+
/** 配置项设置成功 */
|
|
9
|
+
CONFIG_SET_SUCCESS: (key: string, value: string) => `✓ ${key} 已设置为 ${value}`,
|
|
10
|
+
/** 获取配置值显示 */
|
|
11
|
+
CONFIG_GET_VALUE: (key: string, value: string) => `${key} = ${value}`,
|
|
12
|
+
/** 无效配置项名称 */
|
|
13
|
+
CONFIG_INVALID_KEY: (key: string, validKeys: string[]) =>
|
|
14
|
+
`无效的配置项: ${key}\n可用的配置项: ${validKeys.join(', ')}`,
|
|
15
|
+
/** 布尔类型值无效 */
|
|
16
|
+
CONFIG_INVALID_BOOLEAN: (key: string) =>
|
|
17
|
+
`配置项 ${key} 为布尔类型,仅接受 true 或 false`,
|
|
18
|
+
/** 数字类型值无效 */
|
|
19
|
+
CONFIG_INVALID_NUMBER: (key: string) =>
|
|
20
|
+
`配置项 ${key} 为数字类型,请输入有效的数字`,
|
|
21
|
+
/** 枚举类型配置项值无效(通用版) */
|
|
22
|
+
CONFIG_INVALID_ENUM: (key: string, validValues: readonly string[]) =>
|
|
23
|
+
`配置项 ${key} 仅接受以下值: ${validValues.join(', ')}`,
|
|
24
|
+
/** 交互式选择配置项提示 */
|
|
25
|
+
CONFIG_SELECT_PROMPT: '选择要修改的配置项',
|
|
26
|
+
/** 交互式输入新值提示 */
|
|
27
|
+
CONFIG_INPUT_PROMPT: (key: string) => `输入 ${key} 的新值`,
|
|
28
|
+
/** 缺少 value 参数提示 */
|
|
29
|
+
CONFIG_MISSING_VALUE: (key: string) => `缺少配置值,用法: clawt config set ${key} <value>`,
|
|
5
30
|
} as const;
|
|
@@ -7,7 +7,9 @@ import { SYNC_MESSAGES } from './sync.js';
|
|
|
7
7
|
import { RESUME_MESSAGES } from './resume.js';
|
|
8
8
|
import { REMOVE_MESSAGES } from './remove.js';
|
|
9
9
|
import { RESET_MESSAGES } from './reset.js';
|
|
10
|
-
import { CONFIG_CMD_MESSAGES } from './config.js';
|
|
10
|
+
import { CONFIG_CMD_MESSAGES, CONFIG_ALIAS_DISABLED_HINT } from './config.js';
|
|
11
|
+
|
|
12
|
+
export { CONFIG_ALIAS_DISABLED_HINT };
|
|
11
13
|
import { STATUS_MESSAGES } from './status.js';
|
|
12
14
|
import { ALIAS_MESSAGES } from './alias.js';
|
|
13
15
|
|
package/src/types/config.ts
CHANGED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Enquirer from 'enquirer';
|
|
3
|
+
import { DEFAULT_CONFIG, CONFIG_DEFINITIONS, MESSAGES } from '../constants/index.js';
|
|
4
|
+
import type { ClawtConfig } from '../types/index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 校验 key 是否为有效的配置项名称
|
|
8
|
+
* @param {string} key - 待校验的配置项名称
|
|
9
|
+
* @returns {boolean} 是否有效
|
|
10
|
+
*/
|
|
11
|
+
export function isValidConfigKey(key: string): key is keyof ClawtConfig {
|
|
12
|
+
return key in DEFAULT_CONFIG;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 获取所有有效配置项名称列表
|
|
17
|
+
* @returns {string[]} 配置项名称数组
|
|
18
|
+
*/
|
|
19
|
+
export function getValidConfigKeys(): string[] {
|
|
20
|
+
return Object.keys(DEFAULT_CONFIG);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 将字符串值解析并校验为目标配置项的正确类型
|
|
25
|
+
*
|
|
26
|
+
* 策略分发规则:
|
|
27
|
+
* - boolean → 解析 'true'/'false'
|
|
28
|
+
* - number → Number() 解析
|
|
29
|
+
* - string + 有 allowedValues → 枚举校验
|
|
30
|
+
* - string + 无 allowedValues → 无额外校验
|
|
31
|
+
*
|
|
32
|
+
* @param {keyof ClawtConfig} key - 配置项名称
|
|
33
|
+
* @param {string} rawValue - 原始字符串值
|
|
34
|
+
* @returns {{ success: true; value: ClawtConfig[keyof ClawtConfig] } | { success: false; error: string }} 解析结果
|
|
35
|
+
*/
|
|
36
|
+
export function parseConfigValue(
|
|
37
|
+
key: keyof ClawtConfig,
|
|
38
|
+
rawValue: string,
|
|
39
|
+
): { success: true; value: ClawtConfig[keyof ClawtConfig] } | { success: false; error: string } {
|
|
40
|
+
const expectedType = typeof DEFAULT_CONFIG[key];
|
|
41
|
+
|
|
42
|
+
// 布尔类型策略
|
|
43
|
+
if (expectedType === 'boolean') {
|
|
44
|
+
if (rawValue === 'true') return { success: true, value: true };
|
|
45
|
+
if (rawValue === 'false') return { success: true, value: false };
|
|
46
|
+
return { success: false, error: MESSAGES.CONFIG_INVALID_BOOLEAN(key) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 数字类型策略
|
|
50
|
+
if (expectedType === 'number') {
|
|
51
|
+
const num = Number(rawValue);
|
|
52
|
+
if (Number.isNaN(num)) {
|
|
53
|
+
return { success: false, error: MESSAGES.CONFIG_INVALID_NUMBER(key) };
|
|
54
|
+
}
|
|
55
|
+
return { success: true, value: num };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 字符串类型:根据 allowedValues 自动选择校验策略
|
|
59
|
+
const definition = CONFIG_DEFINITIONS[key];
|
|
60
|
+
if (definition.allowedValues && !definition.allowedValues.includes(rawValue)) {
|
|
61
|
+
return { success: false, error: MESSAGES.CONFIG_INVALID_ENUM(key, definition.allowedValues) };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { success: true, value: rawValue };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 交互式提示用户输入配置值
|
|
69
|
+
*
|
|
70
|
+
* 策略分发规则:
|
|
71
|
+
* - boolean → Select(true, false)
|
|
72
|
+
* - number → Input(带数字校验)
|
|
73
|
+
* - string + 有 allowedValues → Select(枚举列表)
|
|
74
|
+
* - string + 无 allowedValues → Input(自由输入)
|
|
75
|
+
*
|
|
76
|
+
* @param {keyof ClawtConfig} key - 配置项名称
|
|
77
|
+
* @param {ClawtConfig[keyof ClawtConfig]} currentValue - 当前值
|
|
78
|
+
* @returns {Promise<ClawtConfig[keyof ClawtConfig]>} 用户输入/选择的新值
|
|
79
|
+
*/
|
|
80
|
+
export async function promptConfigValue(
|
|
81
|
+
key: keyof ClawtConfig,
|
|
82
|
+
currentValue: ClawtConfig[keyof ClawtConfig],
|
|
83
|
+
): Promise<ClawtConfig[keyof ClawtConfig]> {
|
|
84
|
+
const expectedType = typeof currentValue;
|
|
85
|
+
|
|
86
|
+
// 布尔类型策略
|
|
87
|
+
if (expectedType === 'boolean') {
|
|
88
|
+
return promptBooleanValue(key, currentValue as boolean);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 数字类型策略
|
|
92
|
+
if (expectedType === 'number') {
|
|
93
|
+
return promptNumberValue(key, currentValue as number);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 字符串类型:根据 allowedValues 自动选择提示策略
|
|
97
|
+
const definition = CONFIG_DEFINITIONS[key];
|
|
98
|
+
if (definition.allowedValues) {
|
|
99
|
+
return promptEnumValue(key, currentValue as string, definition.allowedValues);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return promptStringValue(key, currentValue as string);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 格式化配置值的显示样式
|
|
107
|
+
* @param {ClawtConfig[keyof ClawtConfig]} value - 配置值
|
|
108
|
+
* @returns {string} 格式化后的字符串
|
|
109
|
+
*/
|
|
110
|
+
export function formatConfigValue(value: ClawtConfig[keyof ClawtConfig]): string {
|
|
111
|
+
if (typeof value === 'boolean') {
|
|
112
|
+
return value ? chalk.green('true') : chalk.yellow('false');
|
|
113
|
+
}
|
|
114
|
+
return chalk.cyan(String(value));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 交互式布尔值选择(内部辅助函数)
|
|
119
|
+
* @param {keyof ClawtConfig} key - 配置项名称
|
|
120
|
+
* @param {boolean} currentValue - 当前值
|
|
121
|
+
* @returns {Promise<boolean>} 用户选择的布尔值
|
|
122
|
+
*/
|
|
123
|
+
async function promptBooleanValue(key: keyof ClawtConfig, currentValue: boolean): Promise<boolean> {
|
|
124
|
+
const choices = [
|
|
125
|
+
{ name: 'true', message: 'true' },
|
|
126
|
+
{ name: 'false', message: 'false' },
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
// @ts-expect-error enquirer 类型声明未导出 Select 类,但运行时存在
|
|
130
|
+
const selected: string = await new Enquirer.Select({
|
|
131
|
+
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
132
|
+
choices,
|
|
133
|
+
initial: currentValue ? 0 : 1,
|
|
134
|
+
}).run();
|
|
135
|
+
|
|
136
|
+
return selected === 'true';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 交互式数字输入(内部辅助函数)
|
|
141
|
+
* @param {keyof ClawtConfig} key - 配置项名称
|
|
142
|
+
* @param {number} currentValue - 当前值
|
|
143
|
+
* @returns {Promise<number>} 用户输入的数字值
|
|
144
|
+
*/
|
|
145
|
+
async function promptNumberValue(key: keyof ClawtConfig, currentValue: number): Promise<number> {
|
|
146
|
+
// @ts-expect-error enquirer 类型声明未导出 Input 类,但运行时存在
|
|
147
|
+
const input: string = await new Enquirer.Input({
|
|
148
|
+
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
149
|
+
initial: String(currentValue),
|
|
150
|
+
validate: (val: string) => {
|
|
151
|
+
if (Number.isNaN(Number(val))) return '请输入有效的数字';
|
|
152
|
+
return true;
|
|
153
|
+
},
|
|
154
|
+
}).run();
|
|
155
|
+
|
|
156
|
+
return Number(input);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 交互式枚举值选择(内部辅助函数,用于有 allowedValues 的 string 配置项)
|
|
161
|
+
* @param {keyof ClawtConfig} key - 配置项名称
|
|
162
|
+
* @param {string} currentValue - 当前值
|
|
163
|
+
* @param {readonly string[]} allowedValues - 允许的枚举值列表
|
|
164
|
+
* @returns {Promise<string>} 用户选择的枚举值
|
|
165
|
+
*/
|
|
166
|
+
async function promptEnumValue(
|
|
167
|
+
key: keyof ClawtConfig,
|
|
168
|
+
currentValue: string,
|
|
169
|
+
allowedValues: readonly string[],
|
|
170
|
+
): Promise<string> {
|
|
171
|
+
const choices = allowedValues.map((v) => ({
|
|
172
|
+
name: v,
|
|
173
|
+
message: v,
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
// @ts-expect-error enquirer 类型声明未导出 Select 类,但运行时存在
|
|
177
|
+
return await new Enquirer.Select({
|
|
178
|
+
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
179
|
+
choices,
|
|
180
|
+
initial: allowedValues.indexOf(currentValue),
|
|
181
|
+
}).run();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 交互式字符串自由输入(内部辅助函数,用于无 allowedValues 的 string 配置项)
|
|
186
|
+
* @param {keyof ClawtConfig} key - 配置项名称
|
|
187
|
+
* @param {string} currentValue - 当前值
|
|
188
|
+
* @returns {Promise<string>} 用户输入的字符串值
|
|
189
|
+
*/
|
|
190
|
+
async function promptStringValue(key: keyof ClawtConfig, currentValue: string): Promise<string> {
|
|
191
|
+
// @ts-expect-error enquirer 类型声明未导出 Input 类,但运行时存在
|
|
192
|
+
return await new Enquirer.Input({
|
|
193
|
+
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
194
|
+
initial: currentValue,
|
|
195
|
+
}).run();
|
|
196
|
+
}
|
package/src/utils/config.ts
CHANGED
|
@@ -39,6 +39,14 @@ export function writeDefaultConfig(): void {
|
|
|
39
39
|
writeConfig(DEFAULT_CONFIG);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* 将配置对象完整写入配置文件
|
|
44
|
+
* @param {ClawtConfig} config - 要持久化的配置对象
|
|
45
|
+
*/
|
|
46
|
+
export function saveConfig(config: ClawtConfig): void {
|
|
47
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
|
48
|
+
}
|
|
49
|
+
|
|
42
50
|
/**
|
|
43
51
|
* 获取配置中指定字段的值
|
|
44
52
|
* @param {keyof ClawtConfig} key - 配置字段名
|
package/src/utils/index.ts
CHANGED
|
@@ -48,7 +48,7 @@ export {
|
|
|
48
48
|
export { sanitizeBranchName, generateBranchNames, validateBranchesNotExist } from './branch.js';
|
|
49
49
|
export { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled } from './validation.js';
|
|
50
50
|
export { createWorktrees, getProjectWorktrees, getProjectWorktreeDir, cleanupWorktrees, getWorktreeStatus, createWorktreesByBranches } from './worktree.js';
|
|
51
|
-
export { loadConfig, writeDefaultConfig, writeConfig, getConfigValue, ensureClawtDirs } from './config.js';
|
|
51
|
+
export { loadConfig, writeDefaultConfig, writeConfig, saveConfig, getConfigValue, ensureClawtDirs } from './config.js';
|
|
52
52
|
export { printSuccess, printError, printWarning, printInfo, printSeparator, printDoubleSeparator, confirmAction, confirmDestructiveAction, formatWorktreeStatus, isWorktreeIdle, formatDuration } from './formatter.js';
|
|
53
53
|
export { ensureDir, removeEmptyDir } from './fs.js';
|
|
54
54
|
export { multilineInput } from './prompt.js';
|
|
@@ -61,4 +61,5 @@ export { parseTaskFile, loadTaskFile } from './task-file.js';
|
|
|
61
61
|
export { executeBatchTasks } from './task-executor.js';
|
|
62
62
|
export { detectTerminalApp, openCommandInNewTerminalTab } from './terminal.js';
|
|
63
63
|
export { applyAliases } from './alias.js';
|
|
64
|
+
export { isValidConfigKey, getValidConfigKeys, parseConfigValue, promptConfigValue, formatConfigValue } from './config-strategy.js';
|
|
64
65
|
|