claude-coder 1.7.1 → 1.8.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/README.md +80 -46
- package/bin/cli.js +41 -43
- package/package.json +7 -2
- package/src/{auth.js → commands/auth.js} +41 -46
- package/src/commands/setup-modules/helpers.js +99 -0
- package/src/commands/setup-modules/index.js +26 -0
- package/src/commands/setup-modules/mcp.js +95 -0
- package/src/commands/setup-modules/provider.js +261 -0
- package/src/commands/setup-modules/safety.js +62 -0
- package/src/commands/setup-modules/simplify.js +53 -0
- package/src/commands/setup.js +172 -0
- package/src/common/assets.js +206 -0
- package/src/{config.js → common/config.js} +17 -93
- package/src/common/constants.js +56 -0
- package/src/{indicator.js → common/indicator.js} +45 -56
- package/src/common/interaction.js +170 -0
- package/src/common/logging.js +78 -0
- package/src/common/sdk.js +51 -0
- package/src/common/tasks.js +88 -0
- package/src/common/utils.js +162 -0
- package/src/core/coding.js +55 -0
- package/src/core/context.js +117 -0
- package/src/core/harness.js +484 -0
- package/src/core/hooks.js +533 -0
- package/src/{init.js → core/init.js} +31 -12
- package/src/core/plan.js +325 -0
- package/src/core/prompts.js +226 -0
- package/src/core/query.js +50 -0
- package/src/core/repair.js +46 -0
- package/src/core/runner.js +195 -0
- package/src/core/scan.js +89 -0
- package/src/core/session.js +57 -0
- package/src/core/simplify.js +52 -0
- package/templates/bash-process.md +12 -0
- package/templates/codingSystem.md +65 -0
- package/templates/codingUser.md +17 -0
- package/templates/coreProtocol.md +29 -0
- package/templates/guidance.json +35 -0
- package/templates/planSystem.md +78 -0
- package/templates/planUser.md +9 -0
- package/templates/playwright.md +17 -0
- package/templates/requirements.example.md +4 -3
- package/templates/scanSystem.md +120 -0
- package/templates/scanUser.md +10 -0
- package/prompts/ADD_GUIDE.md +0 -98
- package/prompts/CLAUDE.md +0 -199
- package/prompts/SCAN_PROTOCOL.md +0 -118
- package/prompts/add_user.md +0 -24
- package/prompts/coding_user.md +0 -31
- package/prompts/scan_user.md +0 -17
- package/src/hooks.js +0 -166
- package/src/prompts.js +0 -295
- package/src/runner.js +0 -396
- package/src/scanner.js +0 -62
- package/src/session.js +0 -354
- package/src/setup.js +0 -579
- package/src/tasks.js +0 -172
- package/src/validator.js +0 -181
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
const { log, COLOR, updateEnvVar } = require('../../common/config');
|
|
7
|
+
const { ensureGitignore: ensureGitignoreBase } = require('../../common/utils');
|
|
8
|
+
const { assets } = require('../../common/assets');
|
|
9
|
+
|
|
10
|
+
function createInterface() {
|
|
11
|
+
return readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function ask(rl, question) {
|
|
15
|
+
return new Promise(resolve => rl.question(question, resolve));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function askChoice(rl, prompt, min, max, defaultVal) {
|
|
19
|
+
return new Promise(async (resolve) => {
|
|
20
|
+
while (true) {
|
|
21
|
+
const raw = await ask(rl, prompt);
|
|
22
|
+
const val = raw.trim() || String(defaultVal ?? '');
|
|
23
|
+
const num = parseInt(val, 10);
|
|
24
|
+
if (num >= min && num <= max) return resolve(num);
|
|
25
|
+
console.log(`请输入 ${min}-${max}`);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function askApiKey(rl, platform, apiUrl, existingKey) {
|
|
31
|
+
if (existingKey) {
|
|
32
|
+
console.log('回车保留当前 API Key,输入新 Key 更新,输入 q 返回上层菜单:');
|
|
33
|
+
} else {
|
|
34
|
+
console.log(`请输入 ${platform} 的 API Key:`);
|
|
35
|
+
}
|
|
36
|
+
if (apiUrl) {
|
|
37
|
+
console.log(` ${COLOR.blue}获取入口: ${apiUrl}${COLOR.reset}`);
|
|
38
|
+
console.log('');
|
|
39
|
+
}
|
|
40
|
+
const key = await ask(rl, ' API Key: ');
|
|
41
|
+
const trimmed = key.trim();
|
|
42
|
+
if (trimmed.toLowerCase() === 'q') {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
if (!trimmed) {
|
|
46
|
+
if (existingKey) return existingKey;
|
|
47
|
+
console.error('API Key 不能为空');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
return trimmed;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function writeConfig(filePath, lines) {
|
|
54
|
+
const dir = path.dirname(filePath);
|
|
55
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
56
|
+
|
|
57
|
+
if (fs.existsSync(filePath)) {
|
|
58
|
+
const ts = new Date().toISOString().replace(/[:\-T]/g, '').slice(0, 14);
|
|
59
|
+
const backup = `${filePath}.bak.${ts}`;
|
|
60
|
+
fs.copyFileSync(filePath, backup);
|
|
61
|
+
log('info', `已备份旧配置到: ${backup}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fs.writeFileSync(filePath, lines.join('\n') + '\n', 'utf8');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function ensureGitignore() {
|
|
68
|
+
if (ensureGitignoreBase(assets.projectRoot)) {
|
|
69
|
+
log('info', '已更新 .gitignore');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function showCurrentConfig(existing) {
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log(`${COLOR.blue}当前配置:${COLOR.reset}`);
|
|
76
|
+
console.log(` 提供商: ${existing.MODEL_PROVIDER || '未配置'}`);
|
|
77
|
+
console.log(` BASE_URL: ${existing.ANTHROPIC_BASE_URL || '默认'}`);
|
|
78
|
+
console.log(` 模型: ${existing.ANTHROPIC_MODEL || '默认'}`);
|
|
79
|
+
console.log(` MCP: ${existing.MCP_PLAYWRIGHT === 'true' ? `已启用 (${existing.MCP_PLAYWRIGHT_MODE || 'persistent'})` : '未启用'}`);
|
|
80
|
+
const compTimeout = existing.SESSION_COMPLETION_TIMEOUT || '300';
|
|
81
|
+
const turns = existing.SESSION_MAX_TURNS || '0';
|
|
82
|
+
console.log(` 停顿超时: ${existing.SESSION_STALL_TIMEOUT || '600'} 秒`);
|
|
83
|
+
console.log(` 完成检测: ${compTimeout} 秒`);
|
|
84
|
+
console.log(` 工具轮次: ${turns === '0' ? '无限制' : turns}`);
|
|
85
|
+
const simplifyInterval = existing.SIMPLIFY_INTERVAL ?? '5';
|
|
86
|
+
const simplifyCommits = existing.SIMPLIFY_COMMITS ?? '5';
|
|
87
|
+
console.log(` 自动审查: ${simplifyInterval === '0' ? '禁用' : `每 ${simplifyInterval} 个 session`}${simplifyInterval !== '0' ? `,审查 ${simplifyCommits} 个 commit` : ''}`);
|
|
88
|
+
console.log('');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
createInterface,
|
|
93
|
+
ask,
|
|
94
|
+
askChoice,
|
|
95
|
+
askApiKey,
|
|
96
|
+
writeConfig,
|
|
97
|
+
ensureGitignore,
|
|
98
|
+
showCurrentConfig,
|
|
99
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ── setup 子模块统一入口 ──
|
|
4
|
+
|
|
5
|
+
const helpers = require('./helpers');
|
|
6
|
+
const provider = require('./provider');
|
|
7
|
+
const mcp = require('./mcp');
|
|
8
|
+
const safety = require('./safety');
|
|
9
|
+
const simplify = require('./simplify');
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
createInterface: helpers.createInterface,
|
|
13
|
+
ask: helpers.ask,
|
|
14
|
+
askChoice: helpers.askChoice,
|
|
15
|
+
askApiKey: helpers.askApiKey,
|
|
16
|
+
writeConfig: helpers.writeConfig,
|
|
17
|
+
ensureGitignore: helpers.ensureGitignore,
|
|
18
|
+
showCurrentConfig: helpers.showCurrentConfig,
|
|
19
|
+
selectProvider: provider.selectProvider,
|
|
20
|
+
updateApiKeyOnly: provider.updateApiKeyOnly,
|
|
21
|
+
configureMCP: mcp.configureMCP,
|
|
22
|
+
appendMcpConfig: mcp.appendMcpConfig,
|
|
23
|
+
updateMCPOnly: mcp.updateMCPOnly,
|
|
24
|
+
updateSafetyLimits: safety.updateSafetyLimits,
|
|
25
|
+
updateSimplifyConfig: simplify.updateSimplifyConfig,
|
|
26
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ask, askChoice } = require('./helpers');
|
|
4
|
+
const { log, COLOR, updateEnvVar } = require('../../common/config');
|
|
5
|
+
const { assets } = require('../../common/assets');
|
|
6
|
+
|
|
7
|
+
// ── MCP 配置 ──
|
|
8
|
+
|
|
9
|
+
async function configureMCP(rl) {
|
|
10
|
+
console.log('');
|
|
11
|
+
console.log('是否启用 Playwright MCP(浏览器自动化测试)?');
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log(' Playwright MCP 由微软官方维护 (github.com/microsoft/playwright-mcp)');
|
|
14
|
+
console.log(' 提供 browser_click、browser_snapshot 等 25+ 浏览器自动化工具');
|
|
15
|
+
console.log(' 适用于有 Web 前端的项目,Agent 可用它做端到端测试');
|
|
16
|
+
console.log('');
|
|
17
|
+
console.log(' 1) 是 - 启用 Playwright MCP(项目有 Web 前端)');
|
|
18
|
+
console.log(' 2) 否 - 跳过(纯后端 / CLI 项目)');
|
|
19
|
+
console.log('');
|
|
20
|
+
|
|
21
|
+
const mcpChoice = await askChoice(rl, '选择 [1-2]: ', 1, 2);
|
|
22
|
+
|
|
23
|
+
const mcpConfig = { enabled: false, mode: null };
|
|
24
|
+
|
|
25
|
+
if (mcpChoice === 1) {
|
|
26
|
+
mcpConfig.enabled = true;
|
|
27
|
+
console.log('');
|
|
28
|
+
console.log('请选择 Playwright MCP 浏览器模式:');
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log(' 1) persistent - 懒人模式(默认,推荐)');
|
|
31
|
+
console.log(' 登录一次永久生效,适合 Google SSO、企业内网 API 拉取等日常开发');
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log(' 2) isolated - 开发模式');
|
|
34
|
+
console.log(' 每次会话从快照加载,适合验证登录流程的自动化测试');
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log(' 3) extension - 连接真实浏览器(实验性)');
|
|
37
|
+
console.log(' 通过 Chrome 扩展复用已有登录态和插件');
|
|
38
|
+
console.log(' 需要安装 "Playwright MCP Bridge" 扩展');
|
|
39
|
+
console.log('');
|
|
40
|
+
|
|
41
|
+
const modeChoice = await askChoice(rl, '选择 [1-3,默认 1]: ', 1, 3, 1);
|
|
42
|
+
const modeMap = { 1: 'persistent', 2: 'isolated', 3: 'extension' };
|
|
43
|
+
mcpConfig.mode = modeMap[modeChoice];
|
|
44
|
+
|
|
45
|
+
console.log('');
|
|
46
|
+
if (mcpConfig.mode === 'extension') {
|
|
47
|
+
console.log(` ${COLOR.yellow}⚠ 前置条件:安装 Playwright MCP Bridge 浏览器扩展${COLOR.reset}`);
|
|
48
|
+
console.log(` ${COLOR.blue} https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm${COLOR.reset}`);
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(' 安装扩展后,运行 claude-coder auth 生成 .mcp.json 配置');
|
|
51
|
+
} else if (mcpConfig.mode === 'persistent') {
|
|
52
|
+
console.log(' 使用 claude-coder auth <URL> 打开浏览器完成首次登录');
|
|
53
|
+
console.log(' 登录状态将持久保存,后续 MCP 会话自动复用');
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(' 请确保已安装 Playwright:');
|
|
56
|
+
console.log(` ${COLOR.blue}npx playwright install chromium${COLOR.reset}`);
|
|
57
|
+
} else {
|
|
58
|
+
console.log(' 使用 claude-coder auth <URL> 录制登录状态到 playwright-auth.json');
|
|
59
|
+
console.log(' MCP 每次会话从此文件加载初始 cookies/localStorage');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return mcpConfig;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── MCP 配置追加到 lines ──
|
|
67
|
+
|
|
68
|
+
function appendMcpConfig(lines, mcpConfig) {
|
|
69
|
+
lines.push('', '# MCP 工具配置');
|
|
70
|
+
if (mcpConfig.enabled) {
|
|
71
|
+
lines.push('MCP_PLAYWRIGHT=true');
|
|
72
|
+
if (mcpConfig.mode) lines.push(`MCP_PLAYWRIGHT_MODE=${mcpConfig.mode}`);
|
|
73
|
+
} else {
|
|
74
|
+
lines.push('MCP_PLAYWRIGHT=false');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── 仅更新 MCP 配置 ──
|
|
79
|
+
|
|
80
|
+
async function updateMCPOnly(rl) {
|
|
81
|
+
const mcpConfig = await configureMCP(rl);
|
|
82
|
+
updateEnvVar('MCP_PLAYWRIGHT', mcpConfig.enabled ? 'true' : 'false');
|
|
83
|
+
if (mcpConfig.enabled && mcpConfig.mode) {
|
|
84
|
+
updateEnvVar('MCP_PLAYWRIGHT_MODE', mcpConfig.mode);
|
|
85
|
+
const { updateMcpConfig } = require('../auth');
|
|
86
|
+
updateMcpConfig(assets.path('mcpConfig'), mcpConfig.mode);
|
|
87
|
+
}
|
|
88
|
+
log('ok', 'MCP 配置已更新');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
configureMCP,
|
|
93
|
+
appendMcpConfig,
|
|
94
|
+
updateMCPOnly,
|
|
95
|
+
};
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ask, askChoice, askApiKey } = require('./helpers');
|
|
4
|
+
const { log, COLOR, updateEnvVar } = require('../../common/config');
|
|
5
|
+
|
|
6
|
+
// ── 提供商菜单 ──
|
|
7
|
+
|
|
8
|
+
const PROVIDER_MENU = `
|
|
9
|
+
请选择模型提供商:
|
|
10
|
+
|
|
11
|
+
1) 默认 Claude 官方模型,使用系统登录态
|
|
12
|
+
2) Coding Plan 自建 API,使用推荐的多模型路由配置
|
|
13
|
+
3) API DeepSeek 或其他 Anthropic 兼容 API
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
// ── 提供商配置函数 ──
|
|
17
|
+
|
|
18
|
+
async function configureDefault() {
|
|
19
|
+
return {
|
|
20
|
+
lines: [
|
|
21
|
+
'# Claude Coder 模型配置',
|
|
22
|
+
'# 提供商: Claude 官方',
|
|
23
|
+
'',
|
|
24
|
+
'MODEL_PROVIDER=claude',
|
|
25
|
+
'API_TIMEOUT_MS=3000000',
|
|
26
|
+
],
|
|
27
|
+
summary: 'Claude 官方模型',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function configureCodingPlan(rl, existing) {
|
|
32
|
+
// 1. 选择或输入 BASE_URL
|
|
33
|
+
console.log('请选择或输入 BASE_URL:');
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log(' 1) 智谱 GLM https://open.bigmodel.cn/api/anthropic');
|
|
36
|
+
console.log(' 2) Z.AI https://api.z.ai/api/anthropic');
|
|
37
|
+
console.log(' 3) 阿里云百炼 https://coding.dashscope.aliyuncs.com/apps/anthropic');
|
|
38
|
+
console.log(' 4) 其他(手动输入)');
|
|
39
|
+
console.log('');
|
|
40
|
+
|
|
41
|
+
const urlChoice = await askChoice(rl, '选择 [1-4,默认 1]: ', 1, 4, 1);
|
|
42
|
+
let finalUrl = '';
|
|
43
|
+
|
|
44
|
+
if (urlChoice === 4) {
|
|
45
|
+
const defaultUrl = existing.ANTHROPIC_BASE_URL || '';
|
|
46
|
+
console.log('');
|
|
47
|
+
let baseUrl = await ask(rl, ` BASE_URL${defaultUrl ? ` (回车保留: ${defaultUrl})` : ''}: `);
|
|
48
|
+
finalUrl = baseUrl.trim() || defaultUrl;
|
|
49
|
+
} else {
|
|
50
|
+
const urlMap = {
|
|
51
|
+
1: 'https://open.bigmodel.cn/api/anthropic',
|
|
52
|
+
2: 'https://api.z.ai/api/anthropic',
|
|
53
|
+
3: 'https://coding.dashscope.aliyuncs.com/apps/anthropic',
|
|
54
|
+
};
|
|
55
|
+
finalUrl = urlMap[urlChoice];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!finalUrl) {
|
|
59
|
+
console.error('BASE_URL 不能为空');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 2. 输入 API_KEY(提示获取地址)
|
|
64
|
+
const apiUrlMap = {
|
|
65
|
+
'open.bigmodel.cn': 'https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys',
|
|
66
|
+
'api.z.ai': 'https://z.ai/manage-apikey/apikey-list',
|
|
67
|
+
'dashscope.aliyuncs.com': 'https://bailian.console.aliyun.com/?tab=model#/api-key',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let apiUrlHint = '';
|
|
71
|
+
for (const [host, url] of Object.entries(apiUrlMap)) {
|
|
72
|
+
if (finalUrl.includes(host)) {
|
|
73
|
+
apiUrlHint = url;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log('');
|
|
79
|
+
if (apiUrlHint) {
|
|
80
|
+
console.log(` ${COLOR.blue}API Key 获取地址: ${apiUrlHint}${COLOR.reset}`);
|
|
81
|
+
}
|
|
82
|
+
const apiKey = await askApiKey(rl, 'Coding Plan', '', existing.ANTHROPIC_API_KEY);
|
|
83
|
+
|
|
84
|
+
// 3. 返回配置(使用「长时间自运行Agent」推荐配置)
|
|
85
|
+
return {
|
|
86
|
+
lines: [
|
|
87
|
+
'# Claude Coder 模型配置',
|
|
88
|
+
'# 提供商: Coding Plan',
|
|
89
|
+
'',
|
|
90
|
+
'MODEL_PROVIDER=coding-plan',
|
|
91
|
+
`ANTHROPIC_BASE_URL=${finalUrl}`,
|
|
92
|
+
`ANTHROPIC_API_KEY=${apiKey}`,
|
|
93
|
+
'',
|
|
94
|
+
'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1',
|
|
95
|
+
'',
|
|
96
|
+
'# 模型路由配置(可在 .claude-coder/.env 修改)',
|
|
97
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5',
|
|
98
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder-next',
|
|
99
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder-plus',
|
|
100
|
+
'ANTHROPIC_MODEL=kimi-k2.5',
|
|
101
|
+
'',
|
|
102
|
+
'API_TIMEOUT_MS=3000000',
|
|
103
|
+
],
|
|
104
|
+
summary: `Coding Plan (${finalUrl})`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function configureAPI(rl, existing) {
|
|
109
|
+
console.log('请选择 API 模式:');
|
|
110
|
+
console.log('');
|
|
111
|
+
console.log(' 1) DeepSeek Chat (V3) - 速度快成本低 [推荐]');
|
|
112
|
+
console.log(' 2) DeepSeek Reasoner (R1) - 全链路推理');
|
|
113
|
+
console.log(' 3) DeepSeek Hybrid (R1+V3) - 规划用R1,执行用V3');
|
|
114
|
+
console.log(' 4) 自定义 - 输入其他 API');
|
|
115
|
+
console.log('');
|
|
116
|
+
const choice = await askChoice(rl, '选择 [1-4,默认 1]: ', 1, 4, 1);
|
|
117
|
+
|
|
118
|
+
if (choice === 4) {
|
|
119
|
+
return await configureCustomAPI(rl, existing);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return await configureDeepSeekMode(rl, existing, choice);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function configureDeepSeekMode(rl, existing, choice) {
|
|
126
|
+
const existingKey = existing.MODEL_PROVIDER === 'deepseek' ? existing.ANTHROPIC_API_KEY : '';
|
|
127
|
+
const apiKey = await askApiKey(rl, 'DeepSeek', 'https://platform.deepseek.com/api_keys', existingKey);
|
|
128
|
+
|
|
129
|
+
const dsModel = ['deepseek-chat', 'deepseek-reasoner', 'deepseek-hybrid'][choice - 1];
|
|
130
|
+
|
|
131
|
+
const lines = [
|
|
132
|
+
'# Claude Coder 模型配置',
|
|
133
|
+
`# 提供商: DeepSeek`,
|
|
134
|
+
`# 模型: ${dsModel} | API_TIMEOUT_MS=600000 防止长输出超时(10分钟)`,
|
|
135
|
+
'',
|
|
136
|
+
'MODEL_PROVIDER=deepseek',
|
|
137
|
+
'ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic',
|
|
138
|
+
`ANTHROPIC_API_KEY=${apiKey}`,
|
|
139
|
+
`ANTHROPIC_AUTH_TOKEN=${apiKey}`,
|
|
140
|
+
'',
|
|
141
|
+
'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1',
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
if (dsModel === 'deepseek-chat') {
|
|
145
|
+
lines.push(
|
|
146
|
+
'# [DeepSeek Chat 降本策略]',
|
|
147
|
+
'ANTHROPIC_MODEL=deepseek-chat',
|
|
148
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-chat',
|
|
149
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-chat',
|
|
150
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-chat'
|
|
151
|
+
);
|
|
152
|
+
} else if (dsModel === 'deepseek-reasoner') {
|
|
153
|
+
lines.push(
|
|
154
|
+
'# [DeepSeek Pure Reasoner 模式]',
|
|
155
|
+
'ANTHROPIC_MODEL=deepseek-reasoner',
|
|
156
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-reasoner',
|
|
157
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-reasoner',
|
|
158
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-reasoner'
|
|
159
|
+
);
|
|
160
|
+
} else {
|
|
161
|
+
lines.push(
|
|
162
|
+
'# [DeepSeek Hybrid 混合模式]',
|
|
163
|
+
'ANTHROPIC_MODEL=deepseek-reasoner',
|
|
164
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-reasoner',
|
|
165
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-chat',
|
|
166
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-chat'
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
lines.push('API_TIMEOUT_MS=600000');
|
|
171
|
+
|
|
172
|
+
return { lines, summary: `DeepSeek (${dsModel})` };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function configureCustomAPI(rl, existing) {
|
|
176
|
+
const defaultUrl = existing.MODEL_PROVIDER === 'custom' ? existing.ANTHROPIC_BASE_URL || '' : '';
|
|
177
|
+
console.log('请输入 Anthropic 兼容的 BASE_URL:');
|
|
178
|
+
let baseUrl = await ask(rl, ` URL${defaultUrl ? ` (回车保留: ${defaultUrl})` : ''}: `);
|
|
179
|
+
baseUrl = baseUrl.trim() || defaultUrl;
|
|
180
|
+
|
|
181
|
+
if (!baseUrl) {
|
|
182
|
+
console.error('BASE_URL 不能为空');
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const existingKey = existing.MODEL_PROVIDER === 'custom' ? existing.ANTHROPIC_API_KEY : '';
|
|
187
|
+
const apiKey = await askApiKey(rl, '自定义 API', '', existingKey);
|
|
188
|
+
|
|
189
|
+
console.log('');
|
|
190
|
+
const modelInput = await ask(rl, '默认模型名称(回车跳过): ');
|
|
191
|
+
const model = modelInput.trim();
|
|
192
|
+
|
|
193
|
+
const lines = [
|
|
194
|
+
'# Claude Coder 模型配置',
|
|
195
|
+
'# 提供商: 自定义 API',
|
|
196
|
+
'',
|
|
197
|
+
'MODEL_PROVIDER=custom',
|
|
198
|
+
`ANTHROPIC_BASE_URL=${baseUrl}`,
|
|
199
|
+
`ANTHROPIC_API_KEY=${apiKey}`,
|
|
200
|
+
'',
|
|
201
|
+
'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1',
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
if (model) {
|
|
205
|
+
lines.push(`ANTHROPIC_MODEL=${model}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
lines.push('API_TIMEOUT_MS=3000000');
|
|
209
|
+
|
|
210
|
+
return { lines, summary: `自定义 API (${baseUrl})` };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── 提供商选择 ──
|
|
214
|
+
|
|
215
|
+
const PROVIDER_CONFIG = [configureDefault, configureCodingPlan, configureAPI];
|
|
216
|
+
|
|
217
|
+
async function selectProvider(rl, existing, showHeader = true) {
|
|
218
|
+
if (showHeader) console.log(PROVIDER_MENU);
|
|
219
|
+
const choice = await askChoice(rl, '选择 [1-3]: ', 1, 3);
|
|
220
|
+
console.log('');
|
|
221
|
+
return PROVIDER_CONFIG[choice - 1](rl, existing);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── 更新 API Key ──
|
|
225
|
+
|
|
226
|
+
async function updateApiKeyOnly(rl, existing) {
|
|
227
|
+
const provider = existing.MODEL_PROVIDER;
|
|
228
|
+
if (!provider || provider === 'claude') {
|
|
229
|
+
log('warn', 'Claude 官方无需配置 API Key(使用系统登录态)');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const apiUrlMap = {
|
|
234
|
+
'coding-plan': '',
|
|
235
|
+
'deepseek': 'https://platform.deepseek.com/api_keys',
|
|
236
|
+
'custom': '',
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const apiKey = await askApiKey(rl, provider, apiUrlMap[provider] || '', existing.ANTHROPIC_API_KEY);
|
|
240
|
+
if (apiKey === null) {
|
|
241
|
+
log('info', '已取消,返回菜单');
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
updateEnvVar('ANTHROPIC_API_KEY', apiKey);
|
|
245
|
+
if (provider === 'deepseek') {
|
|
246
|
+
updateEnvVar('ANTHROPIC_AUTH_TOKEN', apiKey);
|
|
247
|
+
}
|
|
248
|
+
log('ok', 'API Key 已更新');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
module.exports = {
|
|
252
|
+
PROVIDER_MENU,
|
|
253
|
+
PROVIDER_CONFIG,
|
|
254
|
+
configureDefault,
|
|
255
|
+
configureCodingPlan,
|
|
256
|
+
configureAPI,
|
|
257
|
+
configureDeepSeekMode,
|
|
258
|
+
configureCustomAPI,
|
|
259
|
+
selectProvider,
|
|
260
|
+
updateApiKeyOnly,
|
|
261
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ask } = require('./helpers');
|
|
4
|
+
const { log, COLOR, updateEnvVar } = require('../../common/config');
|
|
5
|
+
|
|
6
|
+
// ── 安全限制配置 ──
|
|
7
|
+
|
|
8
|
+
async function updateSafetyLimits(rl, existing) {
|
|
9
|
+
const currentStall = existing.SESSION_STALL_TIMEOUT || '600';
|
|
10
|
+
const currentCompletion = existing.SESSION_COMPLETION_TIMEOUT || '300';
|
|
11
|
+
const currentTurns = existing.SESSION_MAX_TURNS || '0';
|
|
12
|
+
|
|
13
|
+
console.log(`${COLOR.blue}当前安全限制:${COLOR.reset}`);
|
|
14
|
+
console.log(` 停顿超时: ${currentStall} 秒 (${Math.floor(parseInt(currentStall) / 60)} 分钟)`);
|
|
15
|
+
console.log(` 完成检测超时: ${currentCompletion} 秒 (${Math.ceil(parseInt(currentCompletion) / 60)} 分钟)`);
|
|
16
|
+
console.log(` 最大工具轮次: ${currentTurns === '0' ? '无限制' : currentTurns}`);
|
|
17
|
+
console.log('');
|
|
18
|
+
console.log(`${COLOR.yellow}说明:${COLOR.reset}`);
|
|
19
|
+
console.log(' 完成检测 — 模型写入 session_result.json 后缩短等待,解决"完成但不退出"');
|
|
20
|
+
console.log(' 停顿超时 — 长时间无工具调用时自动中断(通用兜底)');
|
|
21
|
+
console.log(' 最大轮次 — 限制总轮次,仅 CI 推荐(默认 0 = 无限制)');
|
|
22
|
+
console.log('');
|
|
23
|
+
|
|
24
|
+
const stallInput = await ask(rl, `停顿超时秒数(回车保留 ${currentStall}): `);
|
|
25
|
+
if (stallInput.trim()) {
|
|
26
|
+
const seconds = parseInt(stallInput.trim(), 10);
|
|
27
|
+
if (isNaN(seconds) || seconds < 60) {
|
|
28
|
+
log('warn', '停顿超时需 >= 60 秒,跳过');
|
|
29
|
+
} else {
|
|
30
|
+
updateEnvVar('SESSION_STALL_TIMEOUT', String(seconds));
|
|
31
|
+
log('ok', `停顿超时已设置为 ${seconds} 秒 (${Math.floor(seconds / 60)} 分钟)`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log('');
|
|
36
|
+
const compInput = await ask(rl, `完成检测超时秒数(回车保留 ${currentCompletion}): `);
|
|
37
|
+
if (compInput.trim()) {
|
|
38
|
+
const seconds = parseInt(compInput.trim(), 10);
|
|
39
|
+
if (isNaN(seconds) || seconds < 30) {
|
|
40
|
+
log('warn', '完成检测超时需 >= 30 秒,跳过');
|
|
41
|
+
} else {
|
|
42
|
+
updateEnvVar('SESSION_COMPLETION_TIMEOUT', String(seconds));
|
|
43
|
+
log('ok', `完成检测超时已设置为 ${seconds} 秒`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log('');
|
|
48
|
+
const turnsInput = await ask(rl, `最大工具轮次(回车保留 ${currentTurns === '0' ? '无限制' : currentTurns},输入 0 = 无限制): `);
|
|
49
|
+
if (turnsInput.trim()) {
|
|
50
|
+
const turns = parseInt(turnsInput.trim(), 10);
|
|
51
|
+
if (isNaN(turns) || turns < 0) {
|
|
52
|
+
log('warn', '请输入 >= 0 的整数,跳过');
|
|
53
|
+
} else {
|
|
54
|
+
updateEnvVar('SESSION_MAX_TURNS', String(turns));
|
|
55
|
+
log('ok', `最大工具轮次已设置为 ${turns === 0 ? '无限制' : turns}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
updateSafetyLimits,
|
|
62
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ask } = require('./helpers');
|
|
4
|
+
const { log, COLOR, updateEnvVar } = require('../../common/config');
|
|
5
|
+
|
|
6
|
+
// ── 自动审查配置 ──
|
|
7
|
+
|
|
8
|
+
async function updateSimplifyConfig(rl, existing) {
|
|
9
|
+
const currentInterval = existing.SIMPLIFY_INTERVAL ?? '5';
|
|
10
|
+
const currentCommits = existing.SIMPLIFY_COMMITS ?? '5';
|
|
11
|
+
|
|
12
|
+
console.log(`${COLOR.blue}自动代码审查配置:${COLOR.reset}`);
|
|
13
|
+
console.log(` 当前状态: ${currentInterval === '0' ? '禁用' : `每 ${currentInterval} 个 session 运行一次`}`);
|
|
14
|
+
console.log(` 审查范围: ${currentCommits} 个 commit`);
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log(`${COLOR.yellow}说明:${COLOR.reset}`);
|
|
17
|
+
console.log(' 自动审查 — 在 run() 循环中定期运行代码审查,检查代码复用性、质量、效率');
|
|
18
|
+
console.log(' 审查间隔 — 每 N 个成功的 session 后运行一次(0 = 禁用)');
|
|
19
|
+
console.log(' 审查范围 — 审查最近 N 个 commit 的代码变更');
|
|
20
|
+
console.log('');
|
|
21
|
+
|
|
22
|
+
const intervalInput = await ask(rl, `审查间隔(输入 0 禁用,回车保留 ${currentInterval === '0' ? '禁用' : currentInterval}): `);
|
|
23
|
+
if (intervalInput.trim()) {
|
|
24
|
+
const interval = parseInt(intervalInput.trim(), 10);
|
|
25
|
+
if (isNaN(interval) || interval < 0) {
|
|
26
|
+
log('warn', '请输入 >= 0 的整数,跳过');
|
|
27
|
+
} else {
|
|
28
|
+
updateEnvVar('SIMPLIFY_INTERVAL', String(interval));
|
|
29
|
+
log('ok', `自动审查已${interval === 0 ? '禁用' : `设置为每 ${interval} 个 session 运行一次`}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const effectiveInterval = intervalInput.trim()
|
|
34
|
+
? String(parseInt(intervalInput.trim(), 10) || 0)
|
|
35
|
+
: currentInterval;
|
|
36
|
+
if (effectiveInterval !== '0') {
|
|
37
|
+
console.log('');
|
|
38
|
+
const commitsInput = await ask(rl, `审查 commit 数量(回车保留 ${currentCommits}): `);
|
|
39
|
+
if (commitsInput.trim()) {
|
|
40
|
+
const commits = parseInt(commitsInput.trim(), 10);
|
|
41
|
+
if (isNaN(commits) || commits < 1) {
|
|
42
|
+
log('warn', '请输入 >= 1 的整数,跳过');
|
|
43
|
+
} else {
|
|
44
|
+
updateEnvVar('SIMPLIFY_COMMITS', String(commits));
|
|
45
|
+
log('ok', `审查范围已设置为 ${commits} 个 commit`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
updateSimplifyConfig,
|
|
53
|
+
};
|