@cloudbase/cli 2.8.0-beta.1 → 2.8.0-beta.2
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/lib/utils/ai/config.js +68 -16
- package/lib/utils/ai/const.js +56 -18
- package/lib/utils/ai/router.js +107 -4
- package/lib/utils/ai/setup.js +198 -60
- package/package.json +1 -2
- package/specs/ai-cli-bootstrap/QWEN.md +1 -1
- package/types/utils/ai/config.d.ts +21 -5
- package/types/utils/ai/const.d.ts +149 -25
- package/types/utils/ai/router.d.ts +4 -0
- package/types/utils/ai/setup.d.ts +1 -1
- package/.moonagent/conversations/config.json +0 -3
- package/.moonagent/conversations/conversations/cdf89380-7541-4c27-9937-00e8b7bc14ea.json +0 -15
- package/.zed/settings.json +0 -7
- package/justfile +0 -3
- package/specs/tcb-ai-config-env-local/design.md +0 -125
- package/specs/tcb-ai-config-env-local/requirements.md +0 -39
- package/specs/tcb-ai-config-env-local/tasks.md +0 -48
package/lib/utils/ai/config.js
CHANGED
|
@@ -25,14 +25,10 @@ const notFoundError = () => {
|
|
|
25
25
|
});
|
|
26
26
|
};
|
|
27
27
|
function isValidAgent(agent) {
|
|
28
|
-
return ['claude', 'qwen', '
|
|
28
|
+
return ['claude', 'qwen', 'codex'].includes(agent);
|
|
29
29
|
}
|
|
30
30
|
exports.isValidAgent = isValidAgent;
|
|
31
31
|
exports.TOOLKIT_CONFIGS = {
|
|
32
|
-
[const_1.CLAUDE_CLOUDBASE.value]: {
|
|
33
|
-
mcp: '.mcp.json',
|
|
34
|
-
rules: 'CLAUDE.md'
|
|
35
|
-
},
|
|
36
32
|
[const_1.CLAUDE.value]: {
|
|
37
33
|
mcp: '.mcp.json',
|
|
38
34
|
rules: 'CLAUDE.md'
|
|
@@ -40,6 +36,10 @@ exports.TOOLKIT_CONFIGS = {
|
|
|
40
36
|
[const_1.QWEN.value]: {
|
|
41
37
|
config: '.env.local',
|
|
42
38
|
rules: 'QWEN.md'
|
|
39
|
+
},
|
|
40
|
+
[const_1.CODEX.value]: {
|
|
41
|
+
config: '.env.local',
|
|
42
|
+
rules: 'CODEX.md'
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
45
|
function createConfigParser() {
|
|
@@ -140,24 +140,76 @@ class AIConfigManager {
|
|
|
140
140
|
this.envLocalManager.updateDefaultAgent(agent);
|
|
141
141
|
});
|
|
142
142
|
}
|
|
143
|
-
updateClaudeConfig(
|
|
143
|
+
updateClaudeConfig(type, config) {
|
|
144
144
|
return __awaiter(this, void 0, void 0, function* () {
|
|
145
|
-
yield this.updateConfig('ai.agents.claude.
|
|
146
|
-
|
|
145
|
+
yield this.updateConfig('ai.agents.claude.type', type);
|
|
146
|
+
if (type === 'custom') {
|
|
147
|
+
if (config.baseUrl) {
|
|
148
|
+
yield this.updateConfig('ai.agents.claude.baseUrl', config.baseUrl, 'AI_CLAUDE_BASE_URL');
|
|
149
|
+
}
|
|
150
|
+
if (config.apiKey) {
|
|
151
|
+
yield this.updateConfig('ai.agents.claude.apiKey', config.apiKey, 'AI_CLAUDE_API_KEY');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else if (type === 'cloudbase') {
|
|
155
|
+
if (config.provider) {
|
|
156
|
+
yield this.updateConfig('ai.agents.claude.provider', config.provider);
|
|
157
|
+
}
|
|
158
|
+
if (config.model) {
|
|
159
|
+
yield this.updateConfig('ai.agents.claude.model', config.model);
|
|
160
|
+
}
|
|
161
|
+
if (config.transformer) {
|
|
162
|
+
yield this.updateConfig('ai.agents.claude.transformer', config.transformer);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
147
165
|
});
|
|
148
166
|
}
|
|
149
|
-
updateQwenConfig(
|
|
167
|
+
updateQwenConfig(type, config) {
|
|
150
168
|
return __awaiter(this, void 0, void 0, function* () {
|
|
151
|
-
yield this.updateConfig('ai.agents.qwen.
|
|
152
|
-
|
|
153
|
-
|
|
169
|
+
yield this.updateConfig('ai.agents.qwen.type', type);
|
|
170
|
+
if (type === 'custom') {
|
|
171
|
+
if (config.baseUrl) {
|
|
172
|
+
yield this.updateConfig('ai.agents.qwen.baseUrl', config.baseUrl, 'AI_QWEN_BASE_URL');
|
|
173
|
+
}
|
|
174
|
+
if (config.apiKey) {
|
|
175
|
+
yield this.updateConfig('ai.agents.qwen.apiKey', config.apiKey, 'AI_QWEN_API_KEY');
|
|
176
|
+
}
|
|
177
|
+
if (config.model) {
|
|
178
|
+
yield this.updateConfig('ai.agents.qwen.model', config.model, 'AI_QWEN_MODEL');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else if (type === 'cloudbase') {
|
|
182
|
+
if (config.provider) {
|
|
183
|
+
yield this.updateConfig('ai.agents.qwen.provider', config.provider);
|
|
184
|
+
}
|
|
185
|
+
if (config.model) {
|
|
186
|
+
yield this.updateConfig('ai.agents.qwen.model', config.model);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
154
189
|
});
|
|
155
190
|
}
|
|
156
|
-
|
|
191
|
+
updateCodexConfig(type, config) {
|
|
157
192
|
return __awaiter(this, void 0, void 0, function* () {
|
|
158
|
-
yield this.updateConfig('ai.agents.
|
|
159
|
-
|
|
160
|
-
|
|
193
|
+
yield this.updateConfig('ai.agents.codex.type', type);
|
|
194
|
+
if (type === 'custom') {
|
|
195
|
+
if (config.baseUrl) {
|
|
196
|
+
yield this.updateConfig('ai.agents.codex.baseUrl', config.baseUrl, 'AI_CODEX_BASE_URL');
|
|
197
|
+
}
|
|
198
|
+
if (config.apiKey) {
|
|
199
|
+
yield this.updateConfig('ai.agents.codex.apiKey', config.apiKey, 'AI_CODEX_API_KEY');
|
|
200
|
+
}
|
|
201
|
+
if (config.model) {
|
|
202
|
+
yield this.updateConfig('ai.agents.codex.model', config.model, 'AI_CODEX_MODEL');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else if (type === 'cloudbase') {
|
|
206
|
+
if (config.provider) {
|
|
207
|
+
yield this.updateConfig('ai.agents.codex.provider', config.provider);
|
|
208
|
+
}
|
|
209
|
+
if (config.model) {
|
|
210
|
+
yield this.updateConfig('ai.agents.codex.model', config.model);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
161
213
|
});
|
|
162
214
|
}
|
|
163
215
|
updateConfig(key, value, env) {
|
package/lib/utils/ai/const.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getAgentConfigValidator = exports.getDefaultConfig = exports.AGENTS = exports.NONE = exports.
|
|
6
|
+
exports.getAgentConfigValidator = exports.getDefaultConfig = exports.AGENTS = exports.NONE = exports.CODEX = exports.QWEN = exports.CLAUDE = exports.DEFAULT_CONFIG = exports.CLOUDBASE_MCP_CONFIG_PATH = exports.CLAUDE_CODE_ROUTER_CONFIG_PATH = exports.ENV_LOCAL_PATH = exports.CONFIG_PATH = void 0;
|
|
7
7
|
const os_1 = __importDefault(require("os"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const v3_1 = __importDefault(require("zod/v3"));
|
|
@@ -14,37 +14,75 @@ exports.CLOUDBASE_MCP_CONFIG_PATH = path_1.default.join(os_1.default.homedir(),
|
|
|
14
14
|
exports.DEFAULT_CONFIG = `{
|
|
15
15
|
"envId": "{{env.ENV_ID}}"
|
|
16
16
|
}`;
|
|
17
|
-
exports.CLAUDE_CLOUDBASE = {
|
|
18
|
-
name: 'Claude Code(with 云开发,提供 Deepseek 模型免费 token 额度)',
|
|
19
|
-
value: 'claude-cloudbase',
|
|
20
|
-
configSchema: v3_1.default.object({
|
|
21
|
-
provider: v3_1.default.string(),
|
|
22
|
-
model: v3_1.default.string(),
|
|
23
|
-
transformer: v3_1.default.string()
|
|
24
|
-
})
|
|
25
|
-
};
|
|
26
17
|
exports.CLAUDE = {
|
|
27
18
|
name: 'Claude Code',
|
|
28
19
|
value: 'claude',
|
|
29
|
-
configSchema: v3_1.default
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
configSchema: v3_1.default
|
|
21
|
+
.object({
|
|
22
|
+
type: v3_1.default.enum(['custom', 'cloudbase']).optional(),
|
|
23
|
+
baseUrl: v3_1.default.string().optional(),
|
|
24
|
+
apiKey: v3_1.default.string().optional(),
|
|
25
|
+
provider: v3_1.default.string().optional(),
|
|
26
|
+
model: v3_1.default.string().optional(),
|
|
27
|
+
transformer: v3_1.default.string().optional()
|
|
28
|
+
})
|
|
29
|
+
.refine((data) => {
|
|
30
|
+
if (data.type === 'custom' || !data.type) {
|
|
31
|
+
return data.baseUrl && data.apiKey;
|
|
32
|
+
}
|
|
33
|
+
else if (data.type === 'cloudbase') {
|
|
34
|
+
return data.provider && data.model && data.transformer;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
32
37
|
})
|
|
33
38
|
};
|
|
34
39
|
exports.QWEN = {
|
|
35
40
|
name: 'Qwen Code',
|
|
36
41
|
value: 'qwen',
|
|
37
|
-
configSchema: v3_1.default
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
configSchema: v3_1.default
|
|
43
|
+
.object({
|
|
44
|
+
type: v3_1.default.enum(['custom', 'cloudbase']).optional(),
|
|
45
|
+
baseUrl: v3_1.default.string().optional(),
|
|
46
|
+
apiKey: v3_1.default.string().optional(),
|
|
47
|
+
provider: v3_1.default.string().optional(),
|
|
48
|
+
model: v3_1.default.string().optional()
|
|
49
|
+
})
|
|
50
|
+
.refine((data) => {
|
|
51
|
+
if (data.type === 'custom' || !data.type) {
|
|
52
|
+
return data.baseUrl && data.apiKey;
|
|
53
|
+
}
|
|
54
|
+
else if (data.type === 'cloudbase') {
|
|
55
|
+
return data.provider && data.model;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
})
|
|
59
|
+
};
|
|
60
|
+
exports.CODEX = {
|
|
61
|
+
name: 'OpenAI Codex',
|
|
62
|
+
value: 'codex',
|
|
63
|
+
configSchema: v3_1.default
|
|
64
|
+
.object({
|
|
65
|
+
type: v3_1.default.enum(['custom', 'cloudbase']).optional(),
|
|
66
|
+
baseUrl: v3_1.default.string().optional(),
|
|
67
|
+
apiKey: v3_1.default.string().optional(),
|
|
68
|
+
provider: v3_1.default.string().optional(),
|
|
69
|
+
model: v3_1.default.string().optional()
|
|
70
|
+
})
|
|
71
|
+
.refine((data) => {
|
|
72
|
+
if (data.type === 'custom' || !data.type) {
|
|
73
|
+
return data.baseUrl && data.apiKey && data.model;
|
|
74
|
+
}
|
|
75
|
+
else if (data.type === 'cloudbase') {
|
|
76
|
+
return data.provider && data.model;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
41
79
|
})
|
|
42
80
|
};
|
|
43
81
|
exports.NONE = {
|
|
44
82
|
name: '暂不配置',
|
|
45
83
|
value: 'none'
|
|
46
84
|
};
|
|
47
|
-
exports.AGENTS = [exports.
|
|
85
|
+
exports.AGENTS = [exports.CLAUDE, exports.QWEN, exports.CODEX, exports.NONE];
|
|
48
86
|
function getDefaultConfig(agent) {
|
|
49
87
|
const agentConfig = exports.AGENTS.find((a) => a.value === agent);
|
|
50
88
|
if (!agentConfig) {
|
package/lib/utils/ai/router.js
CHANGED
|
@@ -447,11 +447,26 @@ class AICommandRouter {
|
|
|
447
447
|
return __awaiter(this, void 0, void 0, function* () {
|
|
448
448
|
switch (agent) {
|
|
449
449
|
case const_1.CLAUDE.value:
|
|
450
|
-
|
|
450
|
+
if (agentConfig.type === 'cloudbase') {
|
|
451
|
+
return yield this.executeClaudeCloudbaseAgent(agentConfig, additionalArgs, log);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
return yield this.executeClaudeAgent(agentConfig, additionalArgs, log);
|
|
455
|
+
}
|
|
451
456
|
case const_1.QWEN.value:
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
457
|
+
if (agentConfig.type === 'cloudbase') {
|
|
458
|
+
return yield this.executeQwenCloudbaseAgent(agentConfig, additionalArgs, log);
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
return yield this.executeQwenAgent(agentConfig, additionalArgs, log);
|
|
462
|
+
}
|
|
463
|
+
case const_1.CODEX.value:
|
|
464
|
+
if (agentConfig.type === 'cloudbase') {
|
|
465
|
+
return yield this.executeCodexCloudbaseAgent(agentConfig, additionalArgs, log);
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
return yield this.executeCodexAgent(agentConfig, additionalArgs, log);
|
|
469
|
+
}
|
|
455
470
|
default:
|
|
456
471
|
throw new Error(`不支持的 AI 工具: ${agent}`);
|
|
457
472
|
}
|
|
@@ -469,6 +484,27 @@ class AICommandRouter {
|
|
|
469
484
|
yield this.executeCommand('qwen', additionalArgs, Object.assign(Object.assign({}, process.env), { OPENAI_API_KEY: apiKey, OPENAI_BASE_URL: baseUrl, OPENAI_MODEL: model }), log);
|
|
470
485
|
});
|
|
471
486
|
}
|
|
487
|
+
executeQwenCloudbaseAgent({ provider, model }, additionalArgs, log) {
|
|
488
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
489
|
+
yield this.ensureQwenCode(log);
|
|
490
|
+
const envId = yield (0, config_1.createConfigParser)().get('envId');
|
|
491
|
+
yield (0, auth_1.checkLogin)();
|
|
492
|
+
const credential = yield (0, utils_1.getCredential)({}, {});
|
|
493
|
+
const accessToken = yield (0, utils_1.rawFetchAccessToken)({
|
|
494
|
+
envId,
|
|
495
|
+
secretId: credential.secretId,
|
|
496
|
+
secretKey: credential.secretKey,
|
|
497
|
+
token: credential.token
|
|
498
|
+
});
|
|
499
|
+
if (!accessToken.access_token) {
|
|
500
|
+
log.error(`获取云开发 Access Token 失败,请运行 tcb login 后再重试,${JSON.stringify(accessToken)}`);
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
const baseUrl = `https://${envId}.api.tcloudbasegateway.com/v1/ai/${provider}`;
|
|
504
|
+
const apiKey = accessToken.access_token;
|
|
505
|
+
yield this.executeCommand('qwen', additionalArgs, Object.assign(Object.assign({}, process.env), { OPENAI_API_KEY: apiKey, OPENAI_BASE_URL: baseUrl, OPENAI_MODEL: model }), log);
|
|
506
|
+
});
|
|
507
|
+
}
|
|
472
508
|
executeClaudeCloudbaseAgent({ provider, model, transformer }, additionalArgs, log) {
|
|
473
509
|
return __awaiter(this, void 0, void 0, function* () {
|
|
474
510
|
yield this.ensureClaudeCodeRouter(log);
|
|
@@ -697,6 +733,73 @@ class AICommandRouter {
|
|
|
697
733
|
}
|
|
698
734
|
});
|
|
699
735
|
}
|
|
736
|
+
executeCodexAgent({ apiKey, baseUrl, model }, additionalArgs, log) {
|
|
737
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
738
|
+
yield this.ensureCodexCode(log);
|
|
739
|
+
const codexArgs = [
|
|
740
|
+
...(model ? ['--config', `model=${model}`] : []),
|
|
741
|
+
'--config', 'model_providers.custom.name=Custom OpenAI',
|
|
742
|
+
...(baseUrl ? ['--config', `model_providers.custom.base_url=${baseUrl}`] : []),
|
|
743
|
+
'--config', 'model_providers.custom.env_key=OPENAI_API_KEY',
|
|
744
|
+
'--config', 'model_provider=custom',
|
|
745
|
+
...additionalArgs
|
|
746
|
+
];
|
|
747
|
+
yield this.executeCommand('codex', codexArgs, Object.assign(Object.assign({}, process.env), { OPENAI_API_KEY: apiKey }), log);
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
executeCodexCloudbaseAgent({ provider, model }, additionalArgs, log) {
|
|
751
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
752
|
+
yield this.ensureCodexCode(log);
|
|
753
|
+
const envId = yield (0, config_1.createConfigParser)().get('envId');
|
|
754
|
+
yield (0, auth_1.checkLogin)();
|
|
755
|
+
const credential = yield (0, utils_1.getCredential)({}, {});
|
|
756
|
+
const accessToken = yield (0, utils_1.rawFetchAccessToken)({
|
|
757
|
+
envId,
|
|
758
|
+
secretId: credential.secretId,
|
|
759
|
+
secretKey: credential.secretKey,
|
|
760
|
+
token: credential.token
|
|
761
|
+
});
|
|
762
|
+
if (!accessToken.access_token) {
|
|
763
|
+
log.error(`获取云开发 Access Token 失败,请运行 tcb login 后再重试,${JSON.stringify(accessToken)}`);
|
|
764
|
+
process.exit(1);
|
|
765
|
+
}
|
|
766
|
+
const baseUrl = `https://${envId}.api.tcloudbasegateway.com/v1/ai/${provider}`;
|
|
767
|
+
const apiKey = accessToken.access_token;
|
|
768
|
+
const codexArgs = [
|
|
769
|
+
'--config', `model=${model}`,
|
|
770
|
+
'--config', 'model_providers.cloudbase.name=CloudBase AI',
|
|
771
|
+
'--config', `model_providers.cloudbase.base_url=${baseUrl}`,
|
|
772
|
+
'--config', 'model_providers.cloudbase.env_key=CLOUDBASE_ACCESS_TOKEN',
|
|
773
|
+
'--config', 'model_provider=cloudbase',
|
|
774
|
+
...additionalArgs
|
|
775
|
+
];
|
|
776
|
+
yield this.executeCommand('codex', codexArgs, Object.assign(Object.assign({}, process.env), { CLOUDBASE_ACCESS_TOKEN: apiKey }), log);
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
ensureCodexCode(log) {
|
|
780
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
781
|
+
if (yield this.isToolAvailable('codex')) {
|
|
782
|
+
log.debug('codex code 已安装!');
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
const { shouldInstall } = yield inquirer_1.default.prompt([
|
|
786
|
+
{
|
|
787
|
+
type: 'confirm',
|
|
788
|
+
name: 'shouldInstall',
|
|
789
|
+
message: 'codex code 未安装,是否安装?'
|
|
790
|
+
}
|
|
791
|
+
]);
|
|
792
|
+
if (shouldInstall) {
|
|
793
|
+
yield this.executeCommand('npm', ['install', '-g', '@openai/codex'], process.env, log);
|
|
794
|
+
log.debug('✅ codex code 安装完成');
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
log.info('❌ codex code 未安装,请手动安装');
|
|
798
|
+
process.exit(1);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
}
|
|
700
803
|
}
|
|
701
804
|
exports.AICommandRouter = AICommandRouter;
|
|
702
805
|
function safeParseJson(json) {
|
package/lib/utils/ai/setup.js
CHANGED
|
@@ -55,7 +55,7 @@ class AISetupWizard {
|
|
|
55
55
|
return __awaiter(this, void 0, void 0, function* () {
|
|
56
56
|
log.info('🤖 欢迎使用 CloudBase AI ToolKit CLI 配置向导');
|
|
57
57
|
try {
|
|
58
|
-
const defaultAgent = yield this.
|
|
58
|
+
const defaultAgent = yield this.selectAgent('选择默认使用的 AI CLI 工具:', false);
|
|
59
59
|
yield this.aiConfigManager.updateDefaultAgent(defaultAgent);
|
|
60
60
|
const currentAgent = defaultAgent;
|
|
61
61
|
yield this.configureAgent(currentAgent, log);
|
|
@@ -74,12 +74,12 @@ class AISetupWizard {
|
|
|
74
74
|
return __awaiter(this, void 0, void 0, function* () {
|
|
75
75
|
log.info('🤖 欢迎使用 CloudBase AI ToolKit CLI 配置向导');
|
|
76
76
|
try {
|
|
77
|
-
const defaultAgent = yield this.selectDefaultAgent();
|
|
78
|
-
yield this.aiConfigManager.updateDefaultAgent(defaultAgent);
|
|
79
77
|
const currentAgent = yield this.selectCurrentAgent();
|
|
80
78
|
if (currentAgent !== const_1.NONE.value) {
|
|
81
79
|
yield this.configureAgent(currentAgent, log);
|
|
82
80
|
}
|
|
81
|
+
const defaultAgent = yield this.selectDefaultAgent(log);
|
|
82
|
+
yield this.aiConfigManager.updateDefaultAgent(defaultAgent);
|
|
83
83
|
yield this.ensureGitignore();
|
|
84
84
|
log.info('✅ AI 配置完成!配置信息已保存到 .env.local 文件');
|
|
85
85
|
log.info('💡 提示:请确保 .env.local 文件已添加到 .gitignore 以保护敏感信息');
|
|
@@ -138,9 +138,36 @@ class AISetupWizard {
|
|
|
138
138
|
return envId;
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
|
-
selectDefaultAgent() {
|
|
141
|
+
selectDefaultAgent(log) {
|
|
142
142
|
return __awaiter(this, void 0, void 0, function* () {
|
|
143
|
-
|
|
143
|
+
const config = yield this.aiConfigManager.loadConfig().catch(() => null);
|
|
144
|
+
const configuredAgents = (config === null || config === void 0 ? void 0 : config.agents) ? Object.keys(config.agents) : [];
|
|
145
|
+
if (configuredAgents.length === 0) {
|
|
146
|
+
const errorMsg = '没有已配置的 AI 工具,请先运行 tcb ai --setup 进行配置';
|
|
147
|
+
log.error(errorMsg);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
if (configuredAgents.length === 1) {
|
|
151
|
+
const selectedAgent = configuredAgents[0];
|
|
152
|
+
const agentInfo = [const_1.CLAUDE, const_1.QWEN, const_1.CODEX].find((a) => a.value === selectedAgent);
|
|
153
|
+
const agentName = (agentInfo === null || agentInfo === void 0 ? void 0 : agentInfo.name) || selectedAgent.toUpperCase();
|
|
154
|
+
log.info(`🔧 自动选择已配置的唯一 AI 工具: ${agentName}`);
|
|
155
|
+
return selectedAgent;
|
|
156
|
+
}
|
|
157
|
+
const availableChoices = configuredAgents.map((agent) => {
|
|
158
|
+
const agentInfo = [const_1.CLAUDE, const_1.QWEN, const_1.CODEX].find((a) => a.value === agent);
|
|
159
|
+
return agentInfo || { name: agent.toUpperCase(), value: agent };
|
|
160
|
+
});
|
|
161
|
+
const { agent } = yield inquirer_1.default.prompt([
|
|
162
|
+
{
|
|
163
|
+
type: 'list',
|
|
164
|
+
name: 'agent',
|
|
165
|
+
message: '选择默认使用的 AI CLI 工具:',
|
|
166
|
+
choices: availableChoices,
|
|
167
|
+
default: configuredAgents[0]
|
|
168
|
+
}
|
|
169
|
+
]);
|
|
170
|
+
return agent;
|
|
144
171
|
});
|
|
145
172
|
}
|
|
146
173
|
selectCurrentAgent() {
|
|
@@ -155,8 +182,8 @@ class AISetupWizard {
|
|
|
155
182
|
type: 'list',
|
|
156
183
|
name: 'agent',
|
|
157
184
|
message,
|
|
158
|
-
choices: [const_1.
|
|
159
|
-
default: const_1.
|
|
185
|
+
choices: [const_1.CLAUDE, const_1.QWEN, const_1.CODEX, ...(includeNone ? [const_1.NONE] : [])],
|
|
186
|
+
default: const_1.CLAUDE.value
|
|
160
187
|
}
|
|
161
188
|
]);
|
|
162
189
|
return agent;
|
|
@@ -170,8 +197,8 @@ class AISetupWizard {
|
|
|
170
197
|
return yield this.configureClaudeAgent(log);
|
|
171
198
|
case const_1.QWEN.value:
|
|
172
199
|
return yield this.configureQwenAgent(log);
|
|
173
|
-
case const_1.
|
|
174
|
-
return yield this.
|
|
200
|
+
case const_1.CODEX.value:
|
|
201
|
+
return yield this.configureCodexAgent(log);
|
|
175
202
|
default:
|
|
176
203
|
throw new Error(`不支持的 AI 工具: ${agent}`);
|
|
177
204
|
}
|
|
@@ -180,74 +207,185 @@ class AISetupWizard {
|
|
|
180
207
|
configureClaudeAgent(log) {
|
|
181
208
|
return __awaiter(this, void 0, void 0, function* () {
|
|
182
209
|
log.info(`配置说明可参考 ${(0, output_1.genClickableLink)('https://docs.cloudbase.net/cli-v1/ai/claude')}`);
|
|
183
|
-
const {
|
|
210
|
+
const { configMethod } = yield inquirer_1.default.prompt([
|
|
184
211
|
{
|
|
185
|
-
type: '
|
|
186
|
-
name: '
|
|
187
|
-
message: '
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
message: 'Claude Auth Token:',
|
|
194
|
-
validate: (input) => input.length > 0 || '请输入有效的 Auth Token'
|
|
212
|
+
type: 'list',
|
|
213
|
+
name: 'configMethod',
|
|
214
|
+
message: '选择配置方式:',
|
|
215
|
+
choices: [
|
|
216
|
+
{ name: '使用 CloudBase 服务,一键登录,无需配置', value: 'cloudbase' },
|
|
217
|
+
{ name: '自配置 API KEY 和 BASE_URL', value: 'custom' }
|
|
218
|
+
],
|
|
219
|
+
default: 'cloudbase'
|
|
195
220
|
}
|
|
196
221
|
]);
|
|
197
|
-
|
|
222
|
+
if (configMethod === 'cloudbase') {
|
|
223
|
+
yield this.configureEnvId(log, this.envId);
|
|
224
|
+
const { provider, model, transformer } = yield inquirer_1.default.prompt([
|
|
225
|
+
{
|
|
226
|
+
type: 'input',
|
|
227
|
+
name: 'provider',
|
|
228
|
+
message: '大模型供应商(留空使用默认):',
|
|
229
|
+
default: 'deepseek'
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
type: 'input',
|
|
233
|
+
name: 'model',
|
|
234
|
+
message: '模型名称(留空使用默认):',
|
|
235
|
+
default: 'deepseek-v3'
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
type: 'input',
|
|
239
|
+
name: 'transformer',
|
|
240
|
+
message: 'Transformer 名称(留空使用默认):',
|
|
241
|
+
default: 'deepseek'
|
|
242
|
+
}
|
|
243
|
+
]);
|
|
244
|
+
yield this.aiConfigManager.updateClaudeConfig('cloudbase', {
|
|
245
|
+
provider,
|
|
246
|
+
model,
|
|
247
|
+
transformer
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
const { apikey, baseUrl } = yield inquirer_1.default.prompt([
|
|
252
|
+
{
|
|
253
|
+
type: 'input',
|
|
254
|
+
name: 'baseUrl',
|
|
255
|
+
message: 'API Base URL (留空使用默认 Kimi API):',
|
|
256
|
+
default: 'https://api.moonshot.cn/anthropic'
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
type: 'password',
|
|
260
|
+
name: 'apikey',
|
|
261
|
+
message: 'Claude Auth Token:',
|
|
262
|
+
validate: (input) => input.length > 0 || '请输入有效的 Auth Token'
|
|
263
|
+
}
|
|
264
|
+
]);
|
|
265
|
+
yield this.aiConfigManager.updateClaudeConfig('custom', { baseUrl, apiKey: apikey });
|
|
266
|
+
}
|
|
198
267
|
});
|
|
199
268
|
}
|
|
200
269
|
configureQwenAgent(log) {
|
|
201
270
|
return __awaiter(this, void 0, void 0, function* () {
|
|
202
271
|
log.info(`配置说明可参考 ${(0, output_1.genClickableLink)('https://docs.cloudbase.net/cli-v1/ai/qwen')}`);
|
|
203
|
-
const {
|
|
204
|
-
{
|
|
205
|
-
type: 'input',
|
|
206
|
-
name: 'baseUrl',
|
|
207
|
-
message: 'API Base URL (留空使用默认):',
|
|
208
|
-
default: 'https://dashscope.aliyuncs.com/compatible-mode/v1'
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
type: 'password',
|
|
212
|
-
name: 'apiKey',
|
|
213
|
-
message: 'Qwen API Key:',
|
|
214
|
-
validate: (input) => input.length > 0 || '请输入有效的 API Key'
|
|
215
|
-
},
|
|
272
|
+
const { configMethod } = yield inquirer_1.default.prompt([
|
|
216
273
|
{
|
|
217
|
-
type: '
|
|
218
|
-
name: '
|
|
219
|
-
message: '
|
|
220
|
-
|
|
274
|
+
type: 'list',
|
|
275
|
+
name: 'configMethod',
|
|
276
|
+
message: '选择配置方式:',
|
|
277
|
+
choices: [
|
|
278
|
+
{ name: '使用 CloudBase 服务,一键登录,无需配置', value: 'cloudbase' },
|
|
279
|
+
{ name: '自配置 API KEY 和 BASE_URL', value: 'custom' }
|
|
280
|
+
],
|
|
281
|
+
default: 'cloudbase'
|
|
221
282
|
}
|
|
222
283
|
]);
|
|
223
|
-
|
|
284
|
+
if (configMethod === 'cloudbase') {
|
|
285
|
+
yield this.configureEnvId(log, this.envId);
|
|
286
|
+
const { provider, model } = yield inquirer_1.default.prompt([
|
|
287
|
+
{
|
|
288
|
+
type: 'input',
|
|
289
|
+
name: 'provider',
|
|
290
|
+
message: '大模型供应商(留空使用默认):',
|
|
291
|
+
default: 'deepseek'
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
type: 'input',
|
|
295
|
+
name: 'model',
|
|
296
|
+
message: '模型名称(留空使用默认):',
|
|
297
|
+
default: 'deepseek-v3'
|
|
298
|
+
}
|
|
299
|
+
]);
|
|
300
|
+
yield this.aiConfigManager.updateQwenConfig('cloudbase', {
|
|
301
|
+
provider,
|
|
302
|
+
model
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
const { apiKey, baseUrl, model } = yield inquirer_1.default.prompt([
|
|
307
|
+
{
|
|
308
|
+
type: 'input',
|
|
309
|
+
name: 'baseUrl',
|
|
310
|
+
message: 'API Base URL (留空使用默认):',
|
|
311
|
+
default: 'https://dashscope.aliyuncs.com/compatible-mode/v1'
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
type: 'password',
|
|
315
|
+
name: 'apiKey',
|
|
316
|
+
message: 'Qwen API Key:',
|
|
317
|
+
validate: (input) => input.length > 0 || '请输入有效的 API Key'
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
type: 'input',
|
|
321
|
+
name: 'model',
|
|
322
|
+
message: '模型名称 (留空使用默认):',
|
|
323
|
+
default: 'qwen-turbo'
|
|
324
|
+
}
|
|
325
|
+
]);
|
|
326
|
+
yield this.aiConfigManager.updateQwenConfig('custom', { baseUrl, apiKey, model });
|
|
327
|
+
}
|
|
224
328
|
});
|
|
225
329
|
}
|
|
226
|
-
|
|
330
|
+
configureCodexAgent(log) {
|
|
227
331
|
return __awaiter(this, void 0, void 0, function* () {
|
|
228
|
-
log.info(`配置说明可参考 ${(0, output_1.genClickableLink)('https://docs.cloudbase.net/cli-v1/ai/
|
|
229
|
-
yield
|
|
230
|
-
const { provider, model, transformer } = yield inquirer_1.default.prompt([
|
|
332
|
+
log.info(`配置说明可参考 ${(0, output_1.genClickableLink)('https://docs.cloudbase.net/cli-v1/ai/codex')}`);
|
|
333
|
+
const { configMethod } = yield inquirer_1.default.prompt([
|
|
231
334
|
{
|
|
232
|
-
type: '
|
|
233
|
-
name: '
|
|
234
|
-
message: '
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
message: '模型名称(留空使用默认):',
|
|
241
|
-
default: 'deepseek-v3'
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
type: 'input',
|
|
245
|
-
name: 'transformer',
|
|
246
|
-
message: 'Transformer 名称(留空使用默认):',
|
|
247
|
-
default: 'deepseek'
|
|
335
|
+
type: 'list',
|
|
336
|
+
name: 'configMethod',
|
|
337
|
+
message: '选择配置方式:',
|
|
338
|
+
choices: [
|
|
339
|
+
{ name: '使用 CloudBase 服务,一键登录,无需配置', value: 'cloudbase' },
|
|
340
|
+
{ name: '自配置 API KEY 和 BASE_URL', value: 'custom' }
|
|
341
|
+
],
|
|
342
|
+
default: 'cloudbase'
|
|
248
343
|
}
|
|
249
344
|
]);
|
|
250
|
-
|
|
345
|
+
if (configMethod === 'cloudbase') {
|
|
346
|
+
yield this.configureEnvId(log, this.envId);
|
|
347
|
+
const { provider, model } = yield inquirer_1.default.prompt([
|
|
348
|
+
{
|
|
349
|
+
type: 'input',
|
|
350
|
+
name: 'provider',
|
|
351
|
+
message: '大模型供应商(留空使用默认):',
|
|
352
|
+
default: 'deepseek'
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
type: 'input',
|
|
356
|
+
name: 'model',
|
|
357
|
+
message: '模型名称(留空使用默认):',
|
|
358
|
+
default: 'deepseek-v3'
|
|
359
|
+
}
|
|
360
|
+
]);
|
|
361
|
+
yield this.aiConfigManager.updateCodexConfig('cloudbase', {
|
|
362
|
+
provider,
|
|
363
|
+
model
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
const { apiKey, baseUrl, model } = yield inquirer_1.default.prompt([
|
|
368
|
+
{
|
|
369
|
+
type: 'input',
|
|
370
|
+
name: 'baseUrl',
|
|
371
|
+
message: 'API Base URL (留空使用默认):',
|
|
372
|
+
default: 'https://api.openai.com/v1'
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
type: 'password',
|
|
376
|
+
name: 'apiKey',
|
|
377
|
+
message: 'OpenAI API Key:',
|
|
378
|
+
validate: (input) => input.length > 0 || '请输入有效的 API Key'
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
type: 'input',
|
|
382
|
+
name: 'model',
|
|
383
|
+
message: '模型名称 (留空使用默认):',
|
|
384
|
+
default: 'gpt-4'
|
|
385
|
+
}
|
|
386
|
+
]);
|
|
387
|
+
yield this.aiConfigManager.updateCodexConfig('custom', { baseUrl, apiKey, model });
|
|
388
|
+
}
|
|
251
389
|
});
|
|
252
390
|
}
|
|
253
391
|
ensureGitignore() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudbase/cli",
|
|
3
|
-
"version": "2.8.0-beta.
|
|
3
|
+
"version": "2.8.0-beta.2",
|
|
4
4
|
"description": "cli tool for cloudbase",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -95,7 +95,6 @@
|
|
|
95
95
|
"eslint-config-alloy": "^3.8.2",
|
|
96
96
|
"husky": "^3.0.9",
|
|
97
97
|
"jest": "^27",
|
|
98
|
-
"prettier": "3.6.2",
|
|
99
98
|
"rimraf": "^3.0.2",
|
|
100
99
|
"ts-jest": "^27",
|
|
101
100
|
"typescript": "^4.7.2"
|
|
@@ -19,7 +19,7 @@ npm install -g @qwen-code/qwen-code
|
|
|
19
19
|
```bash
|
|
20
20
|
# Qwen API 配置
|
|
21
21
|
OPENAI_API_KEY=your_qwen_api_key_here
|
|
22
|
-
OPENAI_BASE_URL=https://dashscope.aliyuncs.com
|
|
22
|
+
OPENAI_BASE_URL=https://dashscope.aliyuncs.com
|
|
23
23
|
OPENAI_MODEL=qwen-turbo
|
|
24
24
|
```
|
|
25
25
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ConfigParser } from '@cloudbase/toolbox';
|
|
2
|
-
import { CLAUDE,
|
|
2
|
+
import { CLAUDE, QWEN, CODEX } from './const';
|
|
3
3
|
import z from 'zod/v3';
|
|
4
4
|
export declare const CONFIG_NOT_FOUND = "CONFIG_NOT_FOUND";
|
|
5
5
|
export declare function isValidAgent(agent: unknown): agent is keyof AIConfig['agents'];
|
|
@@ -8,7 +8,7 @@ export interface AIConfig {
|
|
|
8
8
|
agents: {
|
|
9
9
|
claude?: z.infer<(typeof CLAUDE)['configSchema']>;
|
|
10
10
|
qwen?: z.infer<(typeof QWEN)['configSchema']>;
|
|
11
|
-
|
|
11
|
+
codex?: z.infer<(typeof CODEX)['configSchema']>;
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
export interface AgentConfig {
|
|
@@ -45,8 +45,24 @@ export declare class AIConfigManager {
|
|
|
45
45
|
}>;
|
|
46
46
|
updateEnvId(envId: string): Promise<void>;
|
|
47
47
|
updateDefaultAgent(agent: string): Promise<void>;
|
|
48
|
-
updateClaudeConfig(
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
updateClaudeConfig(type: 'custom' | 'cloudbase', config: {
|
|
49
|
+
baseUrl?: string;
|
|
50
|
+
apiKey?: string;
|
|
51
|
+
provider?: string;
|
|
52
|
+
model?: string;
|
|
53
|
+
transformer?: string;
|
|
54
|
+
}): Promise<void>;
|
|
55
|
+
updateQwenConfig(type: 'custom' | 'cloudbase', config: {
|
|
56
|
+
baseUrl?: string;
|
|
57
|
+
apiKey?: string;
|
|
58
|
+
provider?: string;
|
|
59
|
+
model?: string;
|
|
60
|
+
}): Promise<void>;
|
|
61
|
+
updateCodexConfig(type: 'custom' | 'cloudbase', config: {
|
|
62
|
+
baseUrl?: string;
|
|
63
|
+
apiKey?: string;
|
|
64
|
+
provider?: string;
|
|
65
|
+
model?: string;
|
|
66
|
+
}): Promise<void>;
|
|
51
67
|
private updateConfig;
|
|
52
68
|
}
|
|
@@ -4,52 +4,114 @@ export declare const ENV_LOCAL_PATH: string;
|
|
|
4
4
|
export declare const CLAUDE_CODE_ROUTER_CONFIG_PATH: string;
|
|
5
5
|
export declare const CLOUDBASE_MCP_CONFIG_PATH: string;
|
|
6
6
|
export declare const DEFAULT_CONFIG = "{\n \"envId\": \"{{env.ENV_ID}}\"\n}";
|
|
7
|
-
export declare const
|
|
7
|
+
export declare const CLAUDE: {
|
|
8
8
|
name: string;
|
|
9
9
|
value: string;
|
|
10
|
-
configSchema: z.ZodObject<{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
configSchema: z.ZodEffects<z.ZodObject<{
|
|
11
|
+
type: z.ZodOptional<z.ZodEnum<["custom", "cloudbase"]>>;
|
|
12
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
13
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
14
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
15
|
+
model: z.ZodOptional<z.ZodString>;
|
|
16
|
+
transformer: z.ZodOptional<z.ZodString>;
|
|
14
17
|
}, "strip", z.ZodTypeAny, {
|
|
18
|
+
type?: "custom" | "cloudbase";
|
|
15
19
|
transformer?: string;
|
|
20
|
+
apiKey?: string;
|
|
16
21
|
model?: string;
|
|
22
|
+
baseUrl?: string;
|
|
17
23
|
provider?: string;
|
|
18
24
|
}, {
|
|
25
|
+
type?: "custom" | "cloudbase";
|
|
19
26
|
transformer?: string;
|
|
27
|
+
apiKey?: string;
|
|
20
28
|
model?: string;
|
|
29
|
+
baseUrl?: string;
|
|
30
|
+
provider?: string;
|
|
31
|
+
}>, {
|
|
32
|
+
type?: "custom" | "cloudbase";
|
|
33
|
+
transformer?: string;
|
|
34
|
+
apiKey?: string;
|
|
35
|
+
model?: string;
|
|
36
|
+
baseUrl?: string;
|
|
37
|
+
provider?: string;
|
|
38
|
+
}, {
|
|
39
|
+
type?: "custom" | "cloudbase";
|
|
40
|
+
transformer?: string;
|
|
41
|
+
apiKey?: string;
|
|
42
|
+
model?: string;
|
|
43
|
+
baseUrl?: string;
|
|
21
44
|
provider?: string;
|
|
22
45
|
}>;
|
|
23
46
|
};
|
|
24
|
-
export declare const
|
|
47
|
+
export declare const QWEN: {
|
|
25
48
|
name: string;
|
|
26
49
|
value: string;
|
|
27
|
-
configSchema: z.ZodObject<{
|
|
28
|
-
|
|
29
|
-
|
|
50
|
+
configSchema: z.ZodEffects<z.ZodObject<{
|
|
51
|
+
type: z.ZodOptional<z.ZodEnum<["custom", "cloudbase"]>>;
|
|
52
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
53
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
54
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
55
|
+
model: z.ZodOptional<z.ZodString>;
|
|
30
56
|
}, "strip", z.ZodTypeAny, {
|
|
57
|
+
type?: "custom" | "cloudbase";
|
|
31
58
|
apiKey?: string;
|
|
59
|
+
model?: string;
|
|
32
60
|
baseUrl?: string;
|
|
61
|
+
provider?: string;
|
|
33
62
|
}, {
|
|
63
|
+
type?: "custom" | "cloudbase";
|
|
34
64
|
apiKey?: string;
|
|
65
|
+
model?: string;
|
|
35
66
|
baseUrl?: string;
|
|
67
|
+
provider?: string;
|
|
68
|
+
}>, {
|
|
69
|
+
type?: "custom" | "cloudbase";
|
|
70
|
+
apiKey?: string;
|
|
71
|
+
model?: string;
|
|
72
|
+
baseUrl?: string;
|
|
73
|
+
provider?: string;
|
|
74
|
+
}, {
|
|
75
|
+
type?: "custom" | "cloudbase";
|
|
76
|
+
apiKey?: string;
|
|
77
|
+
model?: string;
|
|
78
|
+
baseUrl?: string;
|
|
79
|
+
provider?: string;
|
|
36
80
|
}>;
|
|
37
81
|
};
|
|
38
|
-
export declare const
|
|
82
|
+
export declare const CODEX: {
|
|
39
83
|
name: string;
|
|
40
84
|
value: string;
|
|
41
|
-
configSchema: z.ZodObject<{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
85
|
+
configSchema: z.ZodEffects<z.ZodObject<{
|
|
86
|
+
type: z.ZodOptional<z.ZodEnum<["custom", "cloudbase"]>>;
|
|
87
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
88
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
89
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
90
|
+
model: z.ZodOptional<z.ZodString>;
|
|
45
91
|
}, "strip", z.ZodTypeAny, {
|
|
92
|
+
type?: "custom" | "cloudbase";
|
|
93
|
+
apiKey?: string;
|
|
94
|
+
model?: string;
|
|
95
|
+
baseUrl?: string;
|
|
96
|
+
provider?: string;
|
|
97
|
+
}, {
|
|
98
|
+
type?: "custom" | "cloudbase";
|
|
99
|
+
apiKey?: string;
|
|
100
|
+
model?: string;
|
|
101
|
+
baseUrl?: string;
|
|
102
|
+
provider?: string;
|
|
103
|
+
}>, {
|
|
104
|
+
type?: "custom" | "cloudbase";
|
|
46
105
|
apiKey?: string;
|
|
47
106
|
model?: string;
|
|
48
107
|
baseUrl?: string;
|
|
108
|
+
provider?: string;
|
|
49
109
|
}, {
|
|
110
|
+
type?: "custom" | "cloudbase";
|
|
50
111
|
apiKey?: string;
|
|
51
112
|
model?: string;
|
|
52
113
|
baseUrl?: string;
|
|
114
|
+
provider?: string;
|
|
53
115
|
}>;
|
|
54
116
|
};
|
|
55
117
|
export declare const NONE: {
|
|
@@ -59,47 +121,109 @@ export declare const NONE: {
|
|
|
59
121
|
export declare const AGENTS: readonly [{
|
|
60
122
|
name: string;
|
|
61
123
|
value: string;
|
|
62
|
-
configSchema: z.ZodObject<{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
124
|
+
configSchema: z.ZodEffects<z.ZodObject<{
|
|
125
|
+
type: z.ZodOptional<z.ZodEnum<["custom", "cloudbase"]>>;
|
|
126
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
127
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
128
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
129
|
+
model: z.ZodOptional<z.ZodString>;
|
|
130
|
+
transformer: z.ZodOptional<z.ZodString>;
|
|
66
131
|
}, "strip", z.ZodTypeAny, {
|
|
132
|
+
type?: "custom" | "cloudbase";
|
|
67
133
|
transformer?: string;
|
|
134
|
+
apiKey?: string;
|
|
68
135
|
model?: string;
|
|
136
|
+
baseUrl?: string;
|
|
69
137
|
provider?: string;
|
|
70
138
|
}, {
|
|
139
|
+
type?: "custom" | "cloudbase";
|
|
71
140
|
transformer?: string;
|
|
141
|
+
apiKey?: string;
|
|
72
142
|
model?: string;
|
|
143
|
+
baseUrl?: string;
|
|
144
|
+
provider?: string;
|
|
145
|
+
}>, {
|
|
146
|
+
type?: "custom" | "cloudbase";
|
|
147
|
+
transformer?: string;
|
|
148
|
+
apiKey?: string;
|
|
149
|
+
model?: string;
|
|
150
|
+
baseUrl?: string;
|
|
151
|
+
provider?: string;
|
|
152
|
+
}, {
|
|
153
|
+
type?: "custom" | "cloudbase";
|
|
154
|
+
transformer?: string;
|
|
155
|
+
apiKey?: string;
|
|
156
|
+
model?: string;
|
|
157
|
+
baseUrl?: string;
|
|
73
158
|
provider?: string;
|
|
74
159
|
}>;
|
|
75
160
|
}, {
|
|
76
161
|
name: string;
|
|
77
162
|
value: string;
|
|
78
|
-
configSchema: z.ZodObject<{
|
|
79
|
-
|
|
80
|
-
|
|
163
|
+
configSchema: z.ZodEffects<z.ZodObject<{
|
|
164
|
+
type: z.ZodOptional<z.ZodEnum<["custom", "cloudbase"]>>;
|
|
165
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
166
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
167
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
168
|
+
model: z.ZodOptional<z.ZodString>;
|
|
81
169
|
}, "strip", z.ZodTypeAny, {
|
|
170
|
+
type?: "custom" | "cloudbase";
|
|
82
171
|
apiKey?: string;
|
|
172
|
+
model?: string;
|
|
83
173
|
baseUrl?: string;
|
|
174
|
+
provider?: string;
|
|
84
175
|
}, {
|
|
176
|
+
type?: "custom" | "cloudbase";
|
|
85
177
|
apiKey?: string;
|
|
178
|
+
model?: string;
|
|
86
179
|
baseUrl?: string;
|
|
180
|
+
provider?: string;
|
|
181
|
+
}>, {
|
|
182
|
+
type?: "custom" | "cloudbase";
|
|
183
|
+
apiKey?: string;
|
|
184
|
+
model?: string;
|
|
185
|
+
baseUrl?: string;
|
|
186
|
+
provider?: string;
|
|
187
|
+
}, {
|
|
188
|
+
type?: "custom" | "cloudbase";
|
|
189
|
+
apiKey?: string;
|
|
190
|
+
model?: string;
|
|
191
|
+
baseUrl?: string;
|
|
192
|
+
provider?: string;
|
|
87
193
|
}>;
|
|
88
194
|
}, {
|
|
89
195
|
name: string;
|
|
90
196
|
value: string;
|
|
91
|
-
configSchema: z.ZodObject<{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
197
|
+
configSchema: z.ZodEffects<z.ZodObject<{
|
|
198
|
+
type: z.ZodOptional<z.ZodEnum<["custom", "cloudbase"]>>;
|
|
199
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
200
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
201
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
202
|
+
model: z.ZodOptional<z.ZodString>;
|
|
95
203
|
}, "strip", z.ZodTypeAny, {
|
|
204
|
+
type?: "custom" | "cloudbase";
|
|
205
|
+
apiKey?: string;
|
|
206
|
+
model?: string;
|
|
207
|
+
baseUrl?: string;
|
|
208
|
+
provider?: string;
|
|
209
|
+
}, {
|
|
210
|
+
type?: "custom" | "cloudbase";
|
|
211
|
+
apiKey?: string;
|
|
212
|
+
model?: string;
|
|
213
|
+
baseUrl?: string;
|
|
214
|
+
provider?: string;
|
|
215
|
+
}>, {
|
|
216
|
+
type?: "custom" | "cloudbase";
|
|
96
217
|
apiKey?: string;
|
|
97
218
|
model?: string;
|
|
98
219
|
baseUrl?: string;
|
|
220
|
+
provider?: string;
|
|
99
221
|
}, {
|
|
222
|
+
type?: "custom" | "cloudbase";
|
|
100
223
|
apiKey?: string;
|
|
101
224
|
model?: string;
|
|
102
225
|
baseUrl?: string;
|
|
226
|
+
provider?: string;
|
|
103
227
|
}>;
|
|
104
228
|
}, {
|
|
105
229
|
name: string;
|
|
@@ -22,6 +22,7 @@ export declare class AICommandRouter {
|
|
|
22
22
|
private executeAgentWithConfig;
|
|
23
23
|
private executeClaudeAgent;
|
|
24
24
|
private executeQwenAgent;
|
|
25
|
+
private executeQwenCloudbaseAgent;
|
|
25
26
|
private executeClaudeCloudbaseAgent;
|
|
26
27
|
private restartClaudeCodeRouter;
|
|
27
28
|
private isClaudeCodeRouterRunning;
|
|
@@ -30,5 +31,8 @@ export declare class AICommandRouter {
|
|
|
30
31
|
private ensureClaudeCodeRouter;
|
|
31
32
|
private ensureClaudeCode;
|
|
32
33
|
private ensureQwenCode;
|
|
34
|
+
private executeCodexAgent;
|
|
35
|
+
private executeCodexCloudbaseAgent;
|
|
36
|
+
private ensureCodexCode;
|
|
33
37
|
}
|
|
34
38
|
export {};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"created_at": 1753766575447,
|
|
3
|
-
"description": "Interactive conversation",
|
|
4
|
-
"id": "cdf89380-7541-4c27-9937-00e8b7bc14ea",
|
|
5
|
-
"messages": [
|
|
6
|
-
{
|
|
7
|
-
"content": "{\"content\":\"hi\",\"role\":\"user\"}",
|
|
8
|
-
"id": "0c879152-c274-4e34-b1f7-60cad238124d",
|
|
9
|
-
"role": "user",
|
|
10
|
-
"timestamp": 1753766580187
|
|
11
|
-
}
|
|
12
|
-
],
|
|
13
|
-
"name": "Session 7/29/2025, 1:22:55 PM",
|
|
14
|
-
"updated_at": 1753766580187
|
|
15
|
-
}
|
package/.zed/settings.json
DELETED
package/justfile
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
# 技术方案设计
|
|
2
|
-
|
|
3
|
-
## 整体架构
|
|
4
|
-
|
|
5
|
-
### 核心变更
|
|
6
|
-
- **文件路径**:从 `.env` 改为 `.env.local`
|
|
7
|
-
- **更新策略**:从全覆盖改为按需更新
|
|
8
|
-
- **配置管理**:支持 AI 配置项的精确管理
|
|
9
|
-
|
|
10
|
-
### 技术栈
|
|
11
|
-
- **语言**:TypeScript
|
|
12
|
-
- **文件操作**:fs-extra
|
|
13
|
-
- **配置解析**:自定义 env 文件解析器
|
|
14
|
-
- **错误处理**:CloudBaseError 统一错误处理
|
|
15
|
-
|
|
16
|
-
## 核心组件设计
|
|
17
|
-
|
|
18
|
-
### 1. EnvLocalManager 类
|
|
19
|
-
负责 `.env.local` 文件的读取、解析、更新和写入操作。
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
export class EnvLocalManager {
|
|
23
|
-
private envLocalPath: string
|
|
24
|
-
|
|
25
|
-
// 解析 .env.local 文件内容
|
|
26
|
-
parseEnvFile(content: string): Map<string, EnvLine>
|
|
27
|
-
|
|
28
|
-
// 更新 AI 相关配置
|
|
29
|
-
updateAIConfig(aiConfig: AIEnvConfig): Promise<void>
|
|
30
|
-
|
|
31
|
-
// 移除 AI 相关配置
|
|
32
|
-
removeAIConfig(agentName?: string): Promise<void>
|
|
33
|
-
|
|
34
|
-
// 按需写入文件
|
|
35
|
-
writeEnvFile(envMap: Map<string, EnvLine>): Promise<void>
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### 2. 环境变量数据结构
|
|
40
|
-
```typescript
|
|
41
|
-
interface EnvLine {
|
|
42
|
-
key: string
|
|
43
|
-
value: string
|
|
44
|
-
comment?: string
|
|
45
|
-
isAIConfig: boolean
|
|
46
|
-
originalLine: string
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface AIEnvConfig {
|
|
50
|
-
defaultAgent: string
|
|
51
|
-
agents: {
|
|
52
|
-
[agentName: string]: {
|
|
53
|
-
apiKey?: string
|
|
54
|
-
authToken?: string
|
|
55
|
-
baseUrl?: string
|
|
56
|
-
model?: string
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## 文件处理策略
|
|
63
|
-
|
|
64
|
-
### 解析策略
|
|
65
|
-
1. **行级解析**:逐行解析,保留注释和空行
|
|
66
|
-
2. **AI 配置识别**:通过 `AI_` 前缀识别 AI 相关配置
|
|
67
|
-
3. **格式保持**:保持原有文件的格式和注释结构
|
|
68
|
-
|
|
69
|
-
### 更新策略
|
|
70
|
-
1. **精确匹配**:只更新 AI 相关的环境变量
|
|
71
|
-
2. **保留格式**:维持注释、空行和非 AI 配置的原有位置
|
|
72
|
-
3. **智能合并**:新增配置追加到 AI 配置区域
|
|
73
|
-
|
|
74
|
-
## 配置项映射
|
|
75
|
-
|
|
76
|
-
### AI 配置变量命名规范
|
|
77
|
-
```
|
|
78
|
-
AI_DEFAULT_AGENT=claude
|
|
79
|
-
AI_CLAUDE_AUTH_TOKEN=sk-ant-xxx
|
|
80
|
-
AI_CLAUDE_BASE_URL=https://api.anthropic.com
|
|
81
|
-
AI_QWEN_API_KEY=sk-xxx
|
|
82
|
-
AI_QWEN_BASE_URL=https://api.openai.com
|
|
83
|
-
AI_QWEN_MODEL=qwen-plus
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### 配置区域管理
|
|
87
|
-
- **区域标识**:使用注释标记 AI 配置区域
|
|
88
|
-
- **区域更新**:只在 AI 配置区域内进行修改
|
|
89
|
-
- **区域隔离**:确保不影响其他配置区域
|
|
90
|
-
|
|
91
|
-
## 错误处理策略
|
|
92
|
-
|
|
93
|
-
### 文件操作错误
|
|
94
|
-
- 权限不足:提供明确的权限解决方案
|
|
95
|
-
- 文件锁定:重试机制和友好提示
|
|
96
|
-
- 磁盘空间:检查可用空间并提示
|
|
97
|
-
|
|
98
|
-
### 配置格式错误
|
|
99
|
-
- 解析失败:备份原文件并提供修复建议
|
|
100
|
-
- 无效配置:标记问题行并提供正确格式示例
|
|
101
|
-
- 冲突配置:智能合并或提示用户选择
|
|
102
|
-
|
|
103
|
-
## 测试策略
|
|
104
|
-
|
|
105
|
-
### 单元测试
|
|
106
|
-
- EnvLocalManager 各方法的功能测试
|
|
107
|
-
- 边界条件和异常情况测试
|
|
108
|
-
- 文件格式兼容性测试
|
|
109
|
-
|
|
110
|
-
### 集成测试
|
|
111
|
-
- AISetupWizard 与 EnvLocalManager 集成
|
|
112
|
-
- 多种配置场景的端到端测试
|
|
113
|
-
- 文件操作的并发安全性测试
|
|
114
|
-
|
|
115
|
-
## 安全性考虑
|
|
116
|
-
|
|
117
|
-
### 敏感信息保护
|
|
118
|
-
- API 密钥的安全存储
|
|
119
|
-
- 文件权限控制(600)
|
|
120
|
-
- 临时文件的安全清理
|
|
121
|
-
|
|
122
|
-
### 原子操作
|
|
123
|
-
- 写入操作的原子性保证
|
|
124
|
-
- 失败时的回滚机制
|
|
125
|
-
- 并发写入的锁定机制
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# 需求文档
|
|
2
|
-
|
|
3
|
-
## 介绍
|
|
4
|
-
|
|
5
|
-
当前 tcb ai 配置保存功能存在问题:配置保存到 `.env` 文件中,且采用全覆盖方式更新,会丢失用户其他环境变量。需要改进为保存到 `.env.local` 文件,并支持按需更新,只更新 AI 相关的配置项,保留其他现有配置。
|
|
6
|
-
|
|
7
|
-
## 需求
|
|
8
|
-
|
|
9
|
-
### 需求 1 - 配置文件路径变更
|
|
10
|
-
|
|
11
|
-
**用户故事:** 作为开发者,我希望 tcb ai 的配置能够保存到 `.env.local` 文件中,这样可以与项目的其他环境变量分离,避免配置冲突。
|
|
12
|
-
|
|
13
|
-
#### 验收标准
|
|
14
|
-
|
|
15
|
-
1. When 运行 `tcb ai --setup` 时,系统应当将 AI 配置保存到 `.env.local` 文件而不是 `.env` 文件。
|
|
16
|
-
2. When `.env.local` 文件不存在时,系统应当创建新的 `.env.local` 文件。
|
|
17
|
-
3. When `.env.local` 文件已存在时,系统应当保留现有内容并追加或更新 AI 配置部分。
|
|
18
|
-
|
|
19
|
-
### 需求 2 - 按需更新机制
|
|
20
|
-
|
|
21
|
-
**用户故事:** 作为开发者,我希望更新 AI 配置时不会覆盖我的其他环境变量,系统只更新 AI 相关的配置项。
|
|
22
|
-
|
|
23
|
-
#### 验收标准
|
|
24
|
-
|
|
25
|
-
1. When 更新 AI 配置时,系统应当只修改以 `AI_` 开头的环境变量。
|
|
26
|
-
2. When `.env.local` 文件中存在非 AI 相关的环境变量时,系统应当保留这些变量不变。
|
|
27
|
-
3. When AI 配置项已存在时,系统应当更新对应的值。
|
|
28
|
-
4. When AI 配置项不存在时,系统应当添加新的配置项。
|
|
29
|
-
|
|
30
|
-
### 需求 3 - 配置项管理
|
|
31
|
-
|
|
32
|
-
**用户故事:** 作为开发者,我希望系统能够智能管理 AI 配置项,包括添加、更新和删除过期的配置。
|
|
33
|
-
|
|
34
|
-
#### 验收标准
|
|
35
|
-
|
|
36
|
-
1. When 切换默认 AI 工具时,系统应当更新 `AI_DEFAULT_AGENT` 变量。
|
|
37
|
-
2. When 配置新的 AI 工具时,系统应当添加对应的 API_KEY/AUTH_TOKEN、BASE_URL、MODEL 等配置。
|
|
38
|
-
3. When 移除 AI 工具配置时,系统应当能够清理对应的环境变量。
|
|
39
|
-
4. When 配置文件格式错误时,系统应当提供友好的错误提示和修复建议。
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# 实施计划
|
|
2
|
-
|
|
3
|
-
- [x] 1. 创建 EnvLocalManager 核心类
|
|
4
|
-
- 创建 `src/utils/ai/envLocalManager.ts` 文件
|
|
5
|
-
- 实现环境变量解析和数据结构定义
|
|
6
|
-
- 实现文件读取和解析方法
|
|
7
|
-
- _需求: 需求1, 需求2_
|
|
8
|
-
|
|
9
|
-
- [x] 2. 实现 AI 配置更新逻辑
|
|
10
|
-
- 实现 `updateAIConfig` 方法,支持按需更新
|
|
11
|
-
- 实现配置项的添加、修改和删除
|
|
12
|
-
- 确保只更新 AI 相关的环境变量
|
|
13
|
-
- _需求: 需求2, 需求3_
|
|
14
|
-
|
|
15
|
-
- [x] 3. 实现文件写入和格式保持
|
|
16
|
-
- 实现 `writeEnvFile` 方法,保持原有格式
|
|
17
|
-
- 确保注释、空行和非AI配置的保留
|
|
18
|
-
- 实现原子写入操作,确保数据安全性
|
|
19
|
-
- _需求: 需求2_
|
|
20
|
-
|
|
21
|
-
- [x] 4. 修改 AISetupWizard 使用新的管理器
|
|
22
|
-
- 更新 `src/utils/ai/setup.ts` 中的文件路径为 `.env.local`
|
|
23
|
-
- 替换现有的文件更新逻辑为 EnvLocalManager
|
|
24
|
-
- 移除简单的覆盖和追加方法
|
|
25
|
-
- _需求: 需求1, 需求2_
|
|
26
|
-
|
|
27
|
-
- [x] 5. 更新 .gitignore 配置提醒
|
|
28
|
-
- 修改 gitignore 提醒逻辑,建议添加 `.env.local`
|
|
29
|
-
- 保持对 `.env` 的兼容提醒
|
|
30
|
-
- _需求: 需求1_
|
|
31
|
-
|
|
32
|
-
- [x] 6. 错误处理和用户体验优化
|
|
33
|
-
- 实现友好的错误提示和修复建议
|
|
34
|
-
- 添加文件操作的重试机制
|
|
35
|
-
- 实现配置验证和格式检查
|
|
36
|
-
- _需求: 需求3_
|
|
37
|
-
|
|
38
|
-
- [x] 7. 单元测试和集成测试
|
|
39
|
-
- 为 EnvLocalManager 编写完整的单元测试
|
|
40
|
-
- 编写 AISetupWizard 的集成测试
|
|
41
|
-
- 测试各种边界条件和异常情况
|
|
42
|
-
- _需求: 所有需求_
|
|
43
|
-
- **状态:已取消(按用户要求不需要测试)**
|
|
44
|
-
|
|
45
|
-
- [x] 8. 更新 TOOLKIT_CONFIGS 配置
|
|
46
|
-
- 检查并更新相关配置引用
|
|
47
|
-
- 确保整个系统的一致性
|
|
48
|
-
- _需求: 需求1_
|