claude-coder 1.6.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/config.js CHANGED
@@ -1,223 +1,201 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const os = require('os');
6
-
7
- const COLOR = {
8
- red: '\x1b[0;31m',
9
- green: '\x1b[0;32m',
10
- yellow: '\x1b[1;33m',
11
- blue: '\x1b[0;34m',
12
- reset: '\x1b[0m',
13
- };
14
-
15
- function log(level, msg) {
16
- const tags = {
17
- info: `${COLOR.blue}[INFO]${COLOR.reset} `,
18
- ok: `${COLOR.green}[OK]${COLOR.reset} `,
19
- warn: `${COLOR.yellow}[WARN]${COLOR.reset} `,
20
- error: `${COLOR.red}[ERROR]${COLOR.reset}`,
21
- };
22
- console.error(`${tags[level] || ''} ${msg}`);
23
- }
24
-
25
- function getProjectRoot() {
26
- return process.cwd();
27
- }
28
-
29
- function getLoopDir() {
30
- return path.join(getProjectRoot(), '.claude-coder');
31
- }
32
-
33
- function ensureLoopDir() {
34
- const dir = getLoopDir();
35
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
36
- const runtime = path.join(dir, '.runtime');
37
- if (!fs.existsSync(runtime)) fs.mkdirSync(runtime, { recursive: true });
38
- const logs = path.join(runtime, 'logs');
39
- if (!fs.existsSync(logs)) fs.mkdirSync(logs, { recursive: true });
40
- return dir;
41
- }
42
-
43
- function getTemplatePath(name) {
44
- return path.join(__dirname, '..', 'templates', name);
45
- }
46
-
47
- function paths() {
48
- const loopDir = getLoopDir();
49
- const runtime = path.join(loopDir, '.runtime');
50
- return {
51
- loopDir,
52
- envFile: path.join(loopDir, '.env'),
53
- tasksFile: path.join(loopDir, 'tasks.json'),
54
- progressFile: path.join(loopDir, 'progress.json'),
55
- sessionResult: path.join(loopDir, 'session_result.json'),
56
- profile: path.join(loopDir, 'project_profile.json'),
57
- testsFile: path.join(loopDir, 'tests.json'),
58
- testEnvFile: path.join(loopDir, 'test.env'),
59
- playwrightAuth: path.join(loopDir, 'playwright-auth.json'),
60
- browserProfile: path.join(runtime, 'browser-profile'),
61
- mcpConfig: path.join(getProjectRoot(), '.mcp.json'),
62
- claudeMd: getTemplatePath('CLAUDE.md'),
63
- scanProtocol: getTemplatePath('SCAN_PROTOCOL.md'),
64
- testRuleTemplate: getTemplatePath('test_rule.md'),
65
- runtime,
66
- logsDir: path.join(runtime, 'logs'),
67
- };
68
- }
69
-
70
- // --------------- .env parsing ---------------
71
-
72
- function parseEnvFile(filepath) {
73
- if (!fs.existsSync(filepath)) return {};
74
- const content = fs.readFileSync(filepath, 'utf8');
75
- const vars = {};
76
- for (const line of content.split('\n')) {
77
- const trimmed = line.trim();
78
- if (!trimmed || trimmed.startsWith('#')) continue;
79
- const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
80
- if (match) {
81
- vars[match[1]] = match[2].trim().replace(/^["']|["']$/g, '');
82
- }
83
- }
84
- return vars;
85
- }
86
-
87
- // --------------- Model mapping ---------------
88
-
89
- function loadConfig() {
90
- const p = paths();
91
- const env = parseEnvFile(p.envFile);
92
- const config = {
93
- provider: env.MODEL_PROVIDER || 'claude',
94
- baseUrl: env.ANTHROPIC_BASE_URL || '',
95
- apiKey: env.ANTHROPIC_API_KEY || '',
96
- authToken: env.ANTHROPIC_AUTH_TOKEN || '',
97
- model: env.ANTHROPIC_MODEL || '',
98
- timeoutMs: parseInt(env.API_TIMEOUT_MS, 10) || 3000000,
99
- mcpToolTimeout: parseInt(env.MCP_TOOL_TIMEOUT, 10) || 30000,
100
- mcpPlaywright: env.MCP_PLAYWRIGHT === 'true',
101
- playwrightMode: env.MCP_PLAYWRIGHT_MODE || 'persistent',
102
- disableNonessential: env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC || '',
103
- effortLevel: env.CLAUDE_CODE_EFFORT_LEVEL || '',
104
- smallFastModel: env.ANTHROPIC_SMALL_FAST_MODEL || '',
105
- defaultOpus: env.ANTHROPIC_DEFAULT_OPUS_MODEL || '',
106
- defaultSonnet: env.ANTHROPIC_DEFAULT_SONNET_MODEL || '',
107
- defaultHaiku: env.ANTHROPIC_DEFAULT_HAIKU_MODEL || '',
108
- thinkingBudget: env.ANTHROPIC_THINKING_BUDGET || '',
109
- stallTimeout: parseInt(env.SESSION_STALL_TIMEOUT, 10) || 1800,
110
- editThreshold: parseInt(env.EDIT_THRESHOLD, 10) || 15,
111
- raw: env,
112
- };
113
-
114
- // GLM: default model if not set
115
- if (config.baseUrl && (config.baseUrl.includes('bigmodel.cn') || config.baseUrl.includes('z.ai'))) {
116
- if (!config.model) config.model = 'glm-4.7';
117
- }
118
-
119
- // DeepSeek chat haiku shim (prevent reasoner billing)
120
- if (config.baseUrl.includes('deepseek') && config.model === 'deepseek-chat') {
121
- config.model = 'claude-3-haiku-20240307';
122
- config.defaultOpus = 'claude-3-haiku-20240307';
123
- config.defaultSonnet = 'claude-3-haiku-20240307';
124
- config.defaultHaiku = 'claude-3-haiku-20240307';
125
- config.smallFastModel = 'claude-3-haiku-20240307';
126
- config.thinkingBudget = '0';
127
- }
128
-
129
- return config;
130
- }
131
-
132
- function buildEnvVars(config) {
133
- const env = { ...process.env };
134
- if (config.baseUrl) env.ANTHROPIC_BASE_URL = config.baseUrl;
135
- if (config.apiKey) env.ANTHROPIC_API_KEY = config.apiKey;
136
- if (config.authToken) env.ANTHROPIC_AUTH_TOKEN = config.authToken;
137
- if (config.model) env.ANTHROPIC_MODEL = config.model;
138
- if (config.timeoutMs) env.API_TIMEOUT_MS = String(config.timeoutMs);
139
- if (config.mcpToolTimeout) env.MCP_TOOL_TIMEOUT = String(config.mcpToolTimeout);
140
- if (config.smallFastModel) env.ANTHROPIC_SMALL_FAST_MODEL = config.smallFastModel;
141
- if (config.disableNonessential) env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = config.disableNonessential;
142
- if (config.effortLevel) env.CLAUDE_CODE_EFFORT_LEVEL = config.effortLevel;
143
- if (config.defaultOpus) env.ANTHROPIC_DEFAULT_OPUS_MODEL = config.defaultOpus;
144
- if (config.defaultSonnet) env.ANTHROPIC_DEFAULT_SONNET_MODEL = config.defaultSonnet;
145
- if (config.defaultHaiku) env.ANTHROPIC_DEFAULT_HAIKU_MODEL = config.defaultHaiku;
146
- if (config.thinkingBudget) env.ANTHROPIC_THINKING_BUDGET = config.thinkingBudget;
147
- return env;
148
- }
149
-
150
- // --------------- Allowed tools ---------------
151
-
152
- function getAllowedTools(config) {
153
- const tools = [
154
- 'Read', 'Edit', 'MultiEdit', 'Write',
155
- 'Bash', 'Glob', 'Grep', 'LS',
156
- 'Task',
157
- 'WebSearch', 'WebFetch',
158
- ];
159
- if (config.mcpPlaywright) tools.push('mcp__playwright__*');
160
- return tools;
161
- }
162
-
163
- // --------------- Sync to global claude settings ---------------
164
-
165
- function syncToGlobal() {
166
- const config = loadConfig();
167
- const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
168
- const settingsDir = path.dirname(settingsPath);
169
-
170
- if (!fs.existsSync(settingsDir)) fs.mkdirSync(settingsDir, { recursive: true });
171
-
172
- let settings = {};
173
- if (fs.existsSync(settingsPath)) {
174
- try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch { /* ignore */ }
175
- }
176
-
177
- for (const key of ['apiKey', 'anthropicBaseUrl', 'defaultSonnetModel', 'defaultOpusModel', 'defaultHaikuModel', 'model']) {
178
- delete settings[key];
179
- }
180
-
181
- if (!settings.env || typeof settings.env !== 'object') settings.env = {};
182
-
183
- const envVars = buildEnvVars(config);
184
- for (const [key, value] of Object.entries(envVars)) {
185
- if (key.startsWith('ANTHROPIC_') || key.endsWith('_TIMEOUT_MS') || key === 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC') {
186
- settings.env[key] = value;
187
- }
188
- }
189
-
190
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
191
- log('ok', `已同步配置到 ${settingsPath}`);
192
- }
193
-
194
- function updateEnvVar(key, value) {
195
- const p = paths();
196
- if (!fs.existsSync(p.envFile)) return false;
197
- let content = fs.readFileSync(p.envFile, 'utf8');
198
- const regex = new RegExp(`^${key}=.*$`, 'm');
199
- if (regex.test(content)) {
200
- content = content.replace(regex, `${key}=${value}`);
201
- } else {
202
- const suffix = content.endsWith('\n') ? '' : '\n';
203
- content += `${suffix}${key}=${value}\n`;
204
- }
205
- fs.writeFileSync(p.envFile, content, 'utf8');
206
- return true;
207
- }
208
-
209
- module.exports = {
210
- COLOR,
211
- log,
212
- getProjectRoot,
213
- getLoopDir,
214
- ensureLoopDir,
215
- getTemplatePath,
216
- paths,
217
- parseEnvFile,
218
- loadConfig,
219
- buildEnvVars,
220
- getAllowedTools,
221
- syncToGlobal,
222
- updateEnvVar,
223
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const COLOR = {
7
+ red: '\x1b[0;31m',
8
+ green: '\x1b[0;32m',
9
+ yellow: '\x1b[1;33m',
10
+ blue: '\x1b[0;34m',
11
+ reset: '\x1b[0m',
12
+ };
13
+
14
+ function log(level, msg) {
15
+ const tags = {
16
+ info: `${COLOR.blue}[INFO]${COLOR.reset} `,
17
+ ok: `${COLOR.green}[OK]${COLOR.reset} `,
18
+ warn: `${COLOR.yellow}[WARN]${COLOR.reset} `,
19
+ error: `${COLOR.red}[ERROR]${COLOR.reset}`,
20
+ };
21
+ console.error(`${tags[level] || ''} ${msg}`);
22
+ }
23
+
24
+ function getProjectRoot() {
25
+ return process.cwd();
26
+ }
27
+
28
+ function getLoopDir() {
29
+ return path.join(getProjectRoot(), '.claude-coder');
30
+ }
31
+
32
+ function ensureLoopDir() {
33
+ const dir = getLoopDir();
34
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
35
+ const runtime = path.join(dir, '.runtime');
36
+ if (!fs.existsSync(runtime)) fs.mkdirSync(runtime, { recursive: true });
37
+ const logs = path.join(runtime, 'logs');
38
+ if (!fs.existsSync(logs)) fs.mkdirSync(logs, { recursive: true });
39
+ return dir;
40
+ }
41
+
42
+ function getTemplatePath(name) {
43
+ return path.join(__dirname, '..', 'templates', name);
44
+ }
45
+
46
+ function getPromptPath(name) {
47
+ return path.join(__dirname, '..', 'prompts', name);
48
+ }
49
+
50
+ function paths() {
51
+ const loopDir = getLoopDir();
52
+ const runtime = path.join(loopDir, '.runtime');
53
+ return {
54
+ loopDir,
55
+ envFile: path.join(loopDir, '.env'),
56
+ tasksFile: path.join(loopDir, 'tasks.json'),
57
+ progressFile: path.join(loopDir, 'progress.json'),
58
+ sessionResult: path.join(loopDir, 'session_result.json'),
59
+ profile: path.join(loopDir, 'project_profile.json'),
60
+ testsFile: path.join(loopDir, 'tests.json'),
61
+ testEnvFile: path.join(loopDir, 'test.env'),
62
+ playwrightAuth: path.join(loopDir, 'playwright-auth.json'),
63
+ browserProfile: path.join(runtime, 'browser-profile'),
64
+ mcpConfig: path.join(getProjectRoot(), '.mcp.json'),
65
+ claudeMd: getPromptPath('CLAUDE.md'),
66
+ scanProtocol: getPromptPath('SCAN_PROTOCOL.md'),
67
+ addGuide: getPromptPath('ADD_GUIDE.md'),
68
+ codingUser: getPromptPath('coding_user.md'),
69
+ scanUser: getPromptPath('scan_user.md'),
70
+ addUser: getPromptPath('add_user.md'),
71
+ testRuleTemplate: getTemplatePath('test_rule.md'),
72
+ runtime,
73
+ logsDir: path.join(runtime, 'logs'),
74
+ };
75
+ }
76
+
77
+ // --------------- .env parsing ---------------
78
+
79
+ function parseEnvFile(filepath) {
80
+ if (!fs.existsSync(filepath)) return {};
81
+ const content = fs.readFileSync(filepath, 'utf8');
82
+ const vars = {};
83
+ for (const line of content.split('\n')) {
84
+ const trimmed = line.trim();
85
+ if (!trimmed || trimmed.startsWith('#')) continue;
86
+ const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
87
+ if (match) {
88
+ vars[match[1]] = match[2].trim().replace(/^["']|["']$/g, '');
89
+ }
90
+ }
91
+ return vars;
92
+ }
93
+
94
+ // --------------- Model mapping ---------------
95
+
96
+ function loadConfig() {
97
+ const p = paths();
98
+ const env = parseEnvFile(p.envFile);
99
+ const config = {
100
+ provider: env.MODEL_PROVIDER || 'claude',
101
+ baseUrl: env.ANTHROPIC_BASE_URL || '',
102
+ apiKey: env.ANTHROPIC_API_KEY || '',
103
+ authToken: env.ANTHROPIC_AUTH_TOKEN || '',
104
+ model: env.ANTHROPIC_MODEL || '',
105
+ timeoutMs: parseInt(env.API_TIMEOUT_MS, 10) || 3000000,
106
+ mcpToolTimeout: parseInt(env.MCP_TOOL_TIMEOUT, 10) || 30000,
107
+ mcpPlaywright: env.MCP_PLAYWRIGHT === 'true',
108
+ playwrightMode: env.MCP_PLAYWRIGHT_MODE || 'persistent',
109
+ disableNonessential: env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC || '',
110
+ effortLevel: env.CLAUDE_CODE_EFFORT_LEVEL || '',
111
+ smallFastModel: env.ANTHROPIC_SMALL_FAST_MODEL || '',
112
+ defaultOpus: env.ANTHROPIC_DEFAULT_OPUS_MODEL || '',
113
+ defaultSonnet: env.ANTHROPIC_DEFAULT_SONNET_MODEL || '',
114
+ defaultHaiku: env.ANTHROPIC_DEFAULT_HAIKU_MODEL || '',
115
+ thinkingBudget: env.ANTHROPIC_THINKING_BUDGET || '',
116
+ stallTimeout: parseInt(env.SESSION_STALL_TIMEOUT, 10) || 1200,
117
+ completionTimeout: parseInt(env.SESSION_COMPLETION_TIMEOUT, 10) || 300,
118
+ maxTurns: parseInt(env.SESSION_MAX_TURNS, 10) || 0,
119
+ editThreshold: parseInt(env.EDIT_THRESHOLD, 10) || 15,
120
+ raw: env,
121
+ };
122
+
123
+ // GLM: default model if not set
124
+ if (config.baseUrl && (config.baseUrl.includes('bigmodel.cn') || config.baseUrl.includes('z.ai'))) {
125
+ if (!config.model) config.model = 'glm-4.7';
126
+ }
127
+
128
+ // DeepSeek chat → haiku shim (prevent reasoner billing)
129
+ if (config.baseUrl.includes('deepseek') && config.model === 'deepseek-chat') {
130
+ config.model = 'claude-3-haiku-20240307';
131
+ config.defaultOpus = 'claude-3-haiku-20240307';
132
+ config.defaultSonnet = 'claude-3-haiku-20240307';
133
+ config.defaultHaiku = 'claude-3-haiku-20240307';
134
+ config.smallFastModel = 'claude-3-haiku-20240307';
135
+ config.thinkingBudget = '0';
136
+ }
137
+
138
+ return config;
139
+ }
140
+
141
+ function buildEnvVars(config) {
142
+ const env = { ...process.env };
143
+ if (config.baseUrl) env.ANTHROPIC_BASE_URL = config.baseUrl;
144
+ if (config.apiKey) env.ANTHROPIC_API_KEY = config.apiKey;
145
+ if (config.authToken) env.ANTHROPIC_AUTH_TOKEN = config.authToken;
146
+ if (config.model) env.ANTHROPIC_MODEL = config.model;
147
+ if (config.timeoutMs) env.API_TIMEOUT_MS = String(config.timeoutMs);
148
+ if (config.mcpToolTimeout) env.MCP_TOOL_TIMEOUT = String(config.mcpToolTimeout);
149
+ if (config.smallFastModel) env.ANTHROPIC_SMALL_FAST_MODEL = config.smallFastModel;
150
+ if (config.disableNonessential) env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = config.disableNonessential;
151
+ if (config.effortLevel) env.CLAUDE_CODE_EFFORT_LEVEL = config.effortLevel;
152
+ if (config.defaultOpus) env.ANTHROPIC_DEFAULT_OPUS_MODEL = config.defaultOpus;
153
+ if (config.defaultSonnet) env.ANTHROPIC_DEFAULT_SONNET_MODEL = config.defaultSonnet;
154
+ if (config.defaultHaiku) env.ANTHROPIC_DEFAULT_HAIKU_MODEL = config.defaultHaiku;
155
+ if (config.thinkingBudget) env.ANTHROPIC_THINKING_BUDGET = config.thinkingBudget;
156
+ return env;
157
+ }
158
+
159
+ // --------------- Allowed tools ---------------
160
+
161
+ function getAllowedTools(config) {
162
+ const tools = [
163
+ 'Read', 'Edit', 'MultiEdit', 'Write',
164
+ 'Bash', 'Glob', 'Grep', 'LS',
165
+ 'Task',
166
+ 'WebSearch', 'WebFetch',
167
+ ];
168
+ if (config.mcpPlaywright) tools.push('mcp__playwright__*');
169
+ return tools;
170
+ }
171
+
172
+ function updateEnvVar(key, value) {
173
+ const p = paths();
174
+ if (!fs.existsSync(p.envFile)) return false;
175
+ let content = fs.readFileSync(p.envFile, 'utf8');
176
+ const regex = new RegExp(`^${key}=.*$`, 'm');
177
+ if (regex.test(content)) {
178
+ content = content.replace(regex, `${key}=${value}`);
179
+ } else {
180
+ const suffix = content.endsWith('\n') ? '' : '\n';
181
+ content += `${suffix}${key}=${value}\n`;
182
+ }
183
+ fs.writeFileSync(p.envFile, content, 'utf8');
184
+ return true;
185
+ }
186
+
187
+ module.exports = {
188
+ COLOR,
189
+ log,
190
+ getProjectRoot,
191
+ getLoopDir,
192
+ ensureLoopDir,
193
+ getTemplatePath,
194
+ getPromptPath,
195
+ paths,
196
+ parseEnvFile,
197
+ loadConfig,
198
+ buildEnvVars,
199
+ getAllowedTools,
200
+ updateEnvVar,
201
+ };