@cloudbase/cli 2.8.0-beta.0 → 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/bin/tcb.js CHANGED
@@ -53,8 +53,6 @@ async function main() {
53
53
 
54
54
  // 输出版本信息
55
55
  console.log(chalk.gray(`CloudBase CLI ${pkg.version}`))
56
- console.log(chalk.gray(`CloudBase Framework ${frameworkPkg.version}`))
57
- console.log(chalk.gray(`Weda ${wedaPkg.version}`))
58
56
 
59
57
  const yargsParsedResult = yargsParser(process.argv.slice(2))
60
58
  const config = await getCloudBaseConfig(yargsParsedResult.configFile)
@@ -25,14 +25,10 @@ const notFoundError = () => {
25
25
  });
26
26
  };
27
27
  function isValidAgent(agent) {
28
- return ['claude', 'qwen', 'claude-cloudbase'].includes(agent);
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(baseUrl, apiKey) {
143
+ updateClaudeConfig(type, config) {
144
144
  return __awaiter(this, void 0, void 0, function* () {
145
- yield this.updateConfig('ai.agents.claude.baseUrl', baseUrl, 'AI_CLAUDE_BASE_URL');
146
- yield this.updateConfig('ai.agents.claude.apiKey', apiKey, 'AI_CLAUDE_API_KEY');
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(baseUrl, apiKey, model) {
167
+ updateQwenConfig(type, config) {
150
168
  return __awaiter(this, void 0, void 0, function* () {
151
- yield this.updateConfig('ai.agents.qwen.baseUrl', baseUrl, 'AI_QWEN_BASE_URL');
152
- yield this.updateConfig('ai.agents.qwen.apiKey', apiKey, 'AI_QWEN_API_KEY');
153
- yield this.updateConfig('ai.agents.qwen.model', model, 'AI_QWEN_MODEL');
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
- updateClaudeCloudbaseConfig(provider, model, transformer) {
191
+ updateCodexConfig(type, config) {
157
192
  return __awaiter(this, void 0, void 0, function* () {
158
- yield this.updateConfig('ai.agents.claude-cloudbase.provider', provider);
159
- yield this.updateConfig('ai.agents.claude-cloudbase.model', model);
160
- yield this.updateConfig('ai.agents.claude-cloudbase.transformer', transformer);
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) {
@@ -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.QWEN = exports.CLAUDE = exports.CLAUDE_CLOUDBASE = exports.DEFAULT_CONFIG = exports.CLOUDBASE_MCP_CONFIG_PATH = exports.CLAUDE_CODE_ROUTER_CONFIG_PATH = exports.ENV_LOCAL_PATH = exports.CONFIG_PATH = void 0;
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.object({
30
- baseUrl: v3_1.default.string(),
31
- apiKey: v3_1.default.string()
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.object({
38
- baseUrl: v3_1.default.string(),
39
- apiKey: v3_1.default.string(),
40
- model: v3_1.default.string()
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.CLAUDE_CLOUDBASE, exports.CLAUDE, exports.QWEN, exports.NONE];
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) {
@@ -267,7 +267,6 @@ class AICommandRouter {
267
267
  };
268
268
  let merged = deepMerge(localJson, templateJson);
269
269
  yield fs.writeJson(destPath, merged, { spaces: 2 });
270
- log.info('已深度合并 cloudbaserc.json (所有字段本地优先)');
271
270
  }
272
271
  catch (e) {
273
272
  log.warn('cloudbaserc.json 合并失败,已跳过: ' + e.message);
@@ -296,7 +295,6 @@ class AICommandRouter {
296
295
  finally { if (e_1) throw e_1.error; }
297
296
  }
298
297
  yield fs.unlink(zipPath);
299
- yield this.validateTemplateIntegrity(templateType, extractDir, log);
300
298
  });
301
299
  }
302
300
  validateTemplateIntegrity(templateType, extractDir, log) {
@@ -449,11 +447,26 @@ class AICommandRouter {
449
447
  return __awaiter(this, void 0, void 0, function* () {
450
448
  switch (agent) {
451
449
  case const_1.CLAUDE.value:
452
- return yield this.executeClaudeAgent(agentConfig, additionalArgs, log);
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
+ }
453
456
  case const_1.QWEN.value:
454
- return yield this.executeQwenAgent(agentConfig, additionalArgs, log);
455
- case const_1.CLAUDE_CLOUDBASE.value:
456
- return yield this.executeClaudeCloudbaseAgent(agentConfig, additionalArgs, log);
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
+ }
457
470
  default:
458
471
  throw new Error(`不支持的 AI 工具: ${agent}`);
459
472
  }
@@ -471,6 +484,27 @@ class AICommandRouter {
471
484
  yield this.executeCommand('qwen', additionalArgs, Object.assign(Object.assign({}, process.env), { OPENAI_API_KEY: apiKey, OPENAI_BASE_URL: baseUrl, OPENAI_MODEL: model }), log);
472
485
  });
473
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
+ }
474
508
  executeClaudeCloudbaseAgent({ provider, model, transformer }, additionalArgs, log) {
475
509
  return __awaiter(this, void 0, void 0, function* () {
476
510
  yield this.ensureClaudeCodeRouter(log);
@@ -699,6 +733,73 @@ class AICommandRouter {
699
733
  }
700
734
  });
701
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
+ }
702
803
  }
703
804
  exports.AICommandRouter = AICommandRouter;
704
805
  function safeParseJson(json) {
@@ -45,6 +45,7 @@ const constants_1 = require("../../commands/constants");
45
45
  const const_1 = require("./const");
46
46
  const config_1 = require("./config");
47
47
  const auth_1 = require("../../auth");
48
+ const output_1 = require("../output");
48
49
  class AISetupWizard {
49
50
  constructor(envId) {
50
51
  this.aiConfigManager = new config_1.AIConfigManager();
@@ -54,7 +55,7 @@ class AISetupWizard {
54
55
  return __awaiter(this, void 0, void 0, function* () {
55
56
  log.info('🤖 欢迎使用 CloudBase AI ToolKit CLI 配置向导');
56
57
  try {
57
- const defaultAgent = yield this.selectDefaultAgent();
58
+ const defaultAgent = yield this.selectAgent('选择默认使用的 AI CLI 工具:', false);
58
59
  yield this.aiConfigManager.updateDefaultAgent(defaultAgent);
59
60
  const currentAgent = defaultAgent;
60
61
  yield this.configureAgent(currentAgent, log);
@@ -73,12 +74,12 @@ class AISetupWizard {
73
74
  return __awaiter(this, void 0, void 0, function* () {
74
75
  log.info('🤖 欢迎使用 CloudBase AI ToolKit CLI 配置向导');
75
76
  try {
76
- const defaultAgent = yield this.selectDefaultAgent();
77
- yield this.aiConfigManager.updateDefaultAgent(defaultAgent);
78
77
  const currentAgent = yield this.selectCurrentAgent();
79
78
  if (currentAgent !== const_1.NONE.value) {
80
79
  yield this.configureAgent(currentAgent, log);
81
80
  }
81
+ const defaultAgent = yield this.selectDefaultAgent(log);
82
+ yield this.aiConfigManager.updateDefaultAgent(defaultAgent);
82
83
  yield this.ensureGitignore();
83
84
  log.info('✅ AI 配置完成!配置信息已保存到 .env.local 文件');
84
85
  log.info('💡 提示:请确保 .env.local 文件已添加到 .gitignore 以保护敏感信息');
@@ -137,9 +138,36 @@ class AISetupWizard {
137
138
  return envId;
138
139
  });
139
140
  }
140
- selectDefaultAgent() {
141
+ selectDefaultAgent(log) {
141
142
  return __awaiter(this, void 0, void 0, function* () {
142
- return this.selectAgent('选择默认使用的 AI CLI 工具:');
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;
143
171
  });
144
172
  }
145
173
  selectCurrentAgent() {
@@ -154,8 +182,8 @@ class AISetupWizard {
154
182
  type: 'list',
155
183
  name: 'agent',
156
184
  message,
157
- choices: [const_1.CLAUDE_CLOUDBASE, const_1.CLAUDE, const_1.QWEN, ...(includeNone ? [const_1.NONE] : [])],
158
- default: const_1.CLAUDE_CLOUDBASE.value
185
+ choices: [const_1.CLAUDE, const_1.QWEN, const_1.CODEX, ...(includeNone ? [const_1.NONE] : [])],
186
+ default: const_1.CLAUDE.value
159
187
  }
160
188
  ]);
161
189
  return agent;
@@ -169,8 +197,8 @@ class AISetupWizard {
169
197
  return yield this.configureClaudeAgent(log);
170
198
  case const_1.QWEN.value:
171
199
  return yield this.configureQwenAgent(log);
172
- case const_1.CLAUDE_CLOUDBASE.value:
173
- return yield this.configureClaudeCloudbaseAgent(log, this.envId);
200
+ case const_1.CODEX.value:
201
+ return yield this.configureCodexAgent(log);
174
202
  default:
175
203
  throw new Error(`不支持的 AI 工具: ${agent}`);
176
204
  }
@@ -178,75 +206,186 @@ class AISetupWizard {
178
206
  }
179
207
  configureClaudeAgent(log) {
180
208
  return __awaiter(this, void 0, void 0, function* () {
181
- log.info('配置说明可参考 https://docs.cloudbase.net/cli-v1/ai/claude');
182
- const { apikey, baseUrl } = yield inquirer_1.default.prompt([
209
+ log.info(`配置说明可参考 ${(0, output_1.genClickableLink)('https://docs.cloudbase.net/cli-v1/ai/claude')}`);
210
+ const { configMethod } = yield inquirer_1.default.prompt([
183
211
  {
184
- type: 'input',
185
- name: 'baseUrl',
186
- message: 'API Base URL (留空使用默认 Kimi API):',
187
- default: 'https://api.moonshot.cn/anthropic'
188
- },
189
- {
190
- type: 'password',
191
- name: 'apikey',
192
- message: 'Claude Auth Token:',
193
- 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'
194
220
  }
195
221
  ]);
196
- yield this.aiConfigManager.updateClaudeConfig(baseUrl, apikey);
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
+ }
197
267
  });
198
268
  }
199
269
  configureQwenAgent(log) {
200
270
  return __awaiter(this, void 0, void 0, function* () {
201
- log.info('配置说明可参考 https://docs.cloudbase.net/cli-v1/ai/qwen');
202
- const { apiKey, baseUrl, model } = yield inquirer_1.default.prompt([
203
- {
204
- type: 'input',
205
- name: 'baseUrl',
206
- message: 'API Base URL (留空使用默认):',
207
- default: 'https://dashscope.aliyuncs.com/compatible-mode/v1'
208
- },
209
- {
210
- type: 'password',
211
- name: 'apiKey',
212
- message: 'Qwen API Key:',
213
- validate: (input) => input.length > 0 || '请输入有效的 API Key'
214
- },
271
+ log.info(`配置说明可参考 ${(0, output_1.genClickableLink)('https://docs.cloudbase.net/cli-v1/ai/qwen')}`);
272
+ const { configMethod } = yield inquirer_1.default.prompt([
215
273
  {
216
- type: 'input',
217
- name: 'model',
218
- message: '模型名称 (留空使用默认):',
219
- default: 'qwen-turbo'
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'
220
282
  }
221
283
  ]);
222
- yield this.aiConfigManager.updateQwenConfig(baseUrl, apiKey, model);
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
+ }
223
328
  });
224
329
  }
225
- configureClaudeCloudbaseAgent(log, _envId) {
330
+ configureCodexAgent(log) {
226
331
  return __awaiter(this, void 0, void 0, function* () {
227
- log.info('配置说明可参考 https://docs.cloudbase.net/cli-v1/ai/claudeCloudbase');
228
- yield this.configureEnvId(log, _envId);
229
- 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([
230
334
  {
231
- type: 'input',
232
- name: 'provider',
233
- message: '大模型供应商(留空使用默认):',
234
- default: 'deepseek'
235
- },
236
- {
237
- type: 'input',
238
- name: 'model',
239
- message: '模型名称(留空使用默认):',
240
- default: 'deepseek-v3'
241
- },
242
- {
243
- type: 'input',
244
- name: 'transformer',
245
- message: 'Transformer 名称(留空使用默认):',
246
- 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'
247
343
  }
248
344
  ]);
249
- yield this.aiConfigManager.updateClaudeCloudbaseConfig(provider, model, transformer);
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
+ }
250
389
  });
251
390
  }
252
391
  ensureGitignore() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/cli",
3
- "version": "2.8.0-beta.0",
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/compatible-mode/v1
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, CLAUDE_CLOUDBASE, QWEN } from './const';
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
- 'claude-cloudbase'?: z.infer<(typeof CLAUDE_CLOUDBASE)['configSchema']>;
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(baseUrl: string, apiKey: string): Promise<void>;
49
- updateQwenConfig(baseUrl: string, apiKey: string, model: string): Promise<void>;
50
- updateClaudeCloudbaseConfig(provider: string, model: string, transformer: string): Promise<void>;
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 CLAUDE_CLOUDBASE: {
7
+ export declare const CLAUDE: {
8
8
  name: string;
9
9
  value: string;
10
- configSchema: z.ZodObject<{
11
- provider: z.ZodString;
12
- model: z.ZodString;
13
- transformer: z.ZodString;
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 CLAUDE: {
47
+ export declare const QWEN: {
25
48
  name: string;
26
49
  value: string;
27
- configSchema: z.ZodObject<{
28
- baseUrl: z.ZodString;
29
- apiKey: z.ZodString;
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 QWEN: {
82
+ export declare const CODEX: {
39
83
  name: string;
40
84
  value: string;
41
- configSchema: z.ZodObject<{
42
- baseUrl: z.ZodString;
43
- apiKey: z.ZodString;
44
- model: z.ZodString;
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
- provider: z.ZodString;
64
- model: z.ZodString;
65
- transformer: z.ZodString;
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
- baseUrl: z.ZodString;
80
- apiKey: z.ZodString;
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
- baseUrl: z.ZodString;
93
- apiKey: z.ZodString;
94
- model: z.ZodString;
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 {};
@@ -16,6 +16,6 @@ export declare class AISetupWizard {
16
16
  private configureAgent;
17
17
  private configureClaudeAgent;
18
18
  private configureQwenAgent;
19
- private configureClaudeCloudbaseAgent;
19
+ private configureCodexAgent;
20
20
  private ensureGitignore;
21
21
  }
@@ -1,3 +0,0 @@
1
- {
2
- "current_conversation_id": "cdf89380-7541-4c27-9937-00e8b7bc14ea"
3
- }
@@ -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
- }
@@ -1,7 +0,0 @@
1
- // Folder-specific settings
2
- //
3
- // For a full list of overridable settings, and general information on folder-specific settings,
4
- // see the documentation: https://zed.dev/docs/configuring-zed#settings-files
5
- {
6
- "format_on_save": "off"
7
- }
package/justfile DELETED
@@ -1,3 +0,0 @@
1
- pre:
2
- yarn exec prettier ./src/utils/ai --write
3
- yarn exec prettier ./src/commands/ai/index.ts --write
@@ -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_