claude-coder 1.0.0 → 1.0.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/package.json +2 -2
- package/src/runner.js +25 -11
- package/src/session.js +114 -100
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-coder",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Claude Coder — Autonomous coding agent harness powered by Claude Code SDK. Scan, plan, code, validate, git-commit in a loop.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claude-coder": "bin/cli.js"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"node": ">=18.0.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@anthropic-ai/claude-agent-sdk": ">=0.
|
|
39
|
+
"@anthropic-ai/claude-agent-sdk": ">=0.1.0"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {}
|
|
42
42
|
}
|
package/src/runner.js
CHANGED
|
@@ -12,14 +12,28 @@ const { runCodingSession, runViewSession, runAddSession } = require('./session')
|
|
|
12
12
|
|
|
13
13
|
const MAX_RETRY = 3;
|
|
14
14
|
|
|
15
|
-
function requireSdk() {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
async function requireSdk() {
|
|
16
|
+
const pkgName = '@anthropic-ai/claude-agent-sdk';
|
|
17
|
+
const attempts = [
|
|
18
|
+
() => { require.resolve(pkgName); return true; },
|
|
19
|
+
() => {
|
|
20
|
+
const { createRequire } = require('module');
|
|
21
|
+
createRequire(__filename).resolve(pkgName);
|
|
22
|
+
return true;
|
|
23
|
+
},
|
|
24
|
+
() => {
|
|
25
|
+
const prefix = execSync('npm prefix -g', { encoding: 'utf8' }).trim();
|
|
26
|
+
const sdkPath = path.join(prefix, 'lib', 'node_modules', pkgName);
|
|
27
|
+
if (fs.existsSync(sdkPath)) return true;
|
|
28
|
+
throw new Error('not found');
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
for (const attempt of attempts) {
|
|
32
|
+
try { if (attempt()) return; } catch { /* try next */ }
|
|
22
33
|
}
|
|
34
|
+
console.error(`错误:未找到 ${pkgName}`);
|
|
35
|
+
console.error(`请先安装:npm install -g ${pkgName}`);
|
|
36
|
+
process.exit(1);
|
|
23
37
|
}
|
|
24
38
|
|
|
25
39
|
function getHead() {
|
|
@@ -199,7 +213,7 @@ async function run(requirement, opts = {}) {
|
|
|
199
213
|
return;
|
|
200
214
|
}
|
|
201
215
|
|
|
202
|
-
requireSdk();
|
|
216
|
+
await requireSdk();
|
|
203
217
|
const scanResult = await scan(requirement, { projectRoot });
|
|
204
218
|
if (!scanResult.success) {
|
|
205
219
|
console.log('');
|
|
@@ -215,7 +229,7 @@ async function run(requirement, opts = {}) {
|
|
|
215
229
|
}
|
|
216
230
|
|
|
217
231
|
// Coding loop
|
|
218
|
-
if (!dryRun) requireSdk();
|
|
232
|
+
if (!dryRun) await requireSdk();
|
|
219
233
|
log('info', `开始编码循环 (最多 ${maxSessions} 个会话) ...`);
|
|
220
234
|
console.log('');
|
|
221
235
|
|
|
@@ -319,7 +333,7 @@ async function run(requirement, opts = {}) {
|
|
|
319
333
|
}
|
|
320
334
|
|
|
321
335
|
async function view(requirement, opts = {}) {
|
|
322
|
-
requireSdk();
|
|
336
|
+
await requireSdk();
|
|
323
337
|
const projectRoot = getProjectRoot();
|
|
324
338
|
ensureLoopDir();
|
|
325
339
|
|
|
@@ -331,7 +345,7 @@ async function view(requirement, opts = {}) {
|
|
|
331
345
|
}
|
|
332
346
|
|
|
333
347
|
async function add(instruction, opts = {}) {
|
|
334
|
-
requireSdk();
|
|
348
|
+
await requireSdk();
|
|
335
349
|
const p = paths();
|
|
336
350
|
const projectRoot = getProjectRoot();
|
|
337
351
|
ensureLoopDir();
|
package/src/session.js
CHANGED
|
@@ -6,12 +6,75 @@ const { paths, loadConfig, buildEnvVars, getAllowedTools, log } = require('./con
|
|
|
6
6
|
const { Indicator, inferPhaseStep } = require('./indicator');
|
|
7
7
|
const { buildSystemPrompt, buildCodingPrompt, buildScanPrompt, buildViewPrompt, buildAddPrompt } = require('./prompts');
|
|
8
8
|
|
|
9
|
+
let _sdkModule = null;
|
|
10
|
+
async function loadSDK() {
|
|
11
|
+
if (_sdkModule) return _sdkModule;
|
|
12
|
+
|
|
13
|
+
const pkgName = '@anthropic-ai/claude-agent-sdk';
|
|
14
|
+
const attempts = [
|
|
15
|
+
() => import(pkgName),
|
|
16
|
+
() => {
|
|
17
|
+
const { createRequire } = require('module');
|
|
18
|
+
const resolved = createRequire(__filename).resolve(pkgName);
|
|
19
|
+
return import(resolved);
|
|
20
|
+
},
|
|
21
|
+
() => {
|
|
22
|
+
const { execSync } = require('child_process');
|
|
23
|
+
const prefix = execSync('npm prefix -g', { encoding: 'utf8' }).trim();
|
|
24
|
+
const sdkPath = path.join(prefix, 'lib', 'node_modules', pkgName, 'sdk.mjs');
|
|
25
|
+
return import(sdkPath);
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
for (const attempt of attempts) {
|
|
30
|
+
try {
|
|
31
|
+
_sdkModule = await attempt();
|
|
32
|
+
return _sdkModule;
|
|
33
|
+
} catch { /* try next */ }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
log('error', `未找到 ${pkgName}`);
|
|
37
|
+
log('error', `请先安装:npm install -g ${pkgName}`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
9
41
|
function applyEnvConfig(config) {
|
|
10
42
|
Object.assign(process.env, buildEnvVars(config));
|
|
11
43
|
}
|
|
12
44
|
|
|
45
|
+
function buildQueryOptions(config, opts = {}) {
|
|
46
|
+
const base = {
|
|
47
|
+
allowedTools: getAllowedTools(config),
|
|
48
|
+
permissionMode: 'bypassPermissions',
|
|
49
|
+
allowDangerouslySkipPermissions: true,
|
|
50
|
+
cwd: opts.projectRoot || process.cwd(),
|
|
51
|
+
env: buildEnvVars(config),
|
|
52
|
+
settingSources: ['project'],
|
|
53
|
+
};
|
|
54
|
+
if (config.model) base.model = config.model;
|
|
55
|
+
return base;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function extractResult(messages) {
|
|
59
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
60
|
+
if (messages[i].type === 'result') return messages[i];
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function logMessage(message, logStream) {
|
|
66
|
+
if (message.type === 'assistant' && message.message?.content) {
|
|
67
|
+
for (const block of message.message.content) {
|
|
68
|
+
if (block.type === 'text' && block.text) {
|
|
69
|
+
process.stdout.write(block.text);
|
|
70
|
+
if (logStream) logStream.write(block.text);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
13
76
|
async function runCodingSession(sessionNum, opts = {}) {
|
|
14
|
-
const
|
|
77
|
+
const sdk = await loadSDK();
|
|
15
78
|
const config = loadConfig();
|
|
16
79
|
applyEnvConfig(config);
|
|
17
80
|
const indicator = new Indicator();
|
|
@@ -26,46 +89,34 @@ async function runCodingSession(sessionNum, opts = {}) {
|
|
|
26
89
|
indicator.start(sessionNum);
|
|
27
90
|
|
|
28
91
|
try {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
inferPhaseStep(indicator, event.tool_name, event.tool_input);
|
|
43
|
-
return { decision: 'allow' };
|
|
44
|
-
}
|
|
45
|
-
}]
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
});
|
|
92
|
+
const queryOpts = buildQueryOptions(config, opts);
|
|
93
|
+
queryOpts.systemPrompt = systemPrompt;
|
|
94
|
+
queryOpts.hooks = {
|
|
95
|
+
PreToolUse: [{
|
|
96
|
+
matcher: '*',
|
|
97
|
+
hooks: [async (input) => {
|
|
98
|
+
inferPhaseStep(indicator, input.tool_name, input.tool_input);
|
|
99
|
+
return {};
|
|
100
|
+
}]
|
|
101
|
+
}]
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const session = sdk.query({ prompt, options: queryOpts });
|
|
49
105
|
|
|
50
|
-
|
|
106
|
+
const collected = [];
|
|
51
107
|
for await (const message of session) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
? message.content
|
|
55
|
-
: JSON.stringify(message.content);
|
|
56
|
-
process.stdout.write(text);
|
|
57
|
-
logStream.write(text);
|
|
58
|
-
}
|
|
59
|
-
result = message;
|
|
108
|
+
collected.push(message);
|
|
109
|
+
logMessage(message, logStream);
|
|
60
110
|
}
|
|
61
111
|
|
|
62
112
|
logStream.end();
|
|
63
113
|
indicator.stop();
|
|
64
114
|
|
|
115
|
+
const result = extractResult(collected);
|
|
65
116
|
return {
|
|
66
117
|
exitCode: 0,
|
|
67
|
-
cost: result?.total_cost_usd
|
|
68
|
-
tokenUsage: result?.
|
|
118
|
+
cost: result?.total_cost_usd ?? null,
|
|
119
|
+
tokenUsage: result?.usage ?? null,
|
|
69
120
|
logFile,
|
|
70
121
|
};
|
|
71
122
|
} catch (err) {
|
|
@@ -83,7 +134,7 @@ async function runCodingSession(sessionNum, opts = {}) {
|
|
|
83
134
|
}
|
|
84
135
|
|
|
85
136
|
async function runScanSession(requirement, opts = {}) {
|
|
86
|
-
const
|
|
137
|
+
const sdk = await loadSDK();
|
|
87
138
|
const config = loadConfig();
|
|
88
139
|
applyEnvConfig(config);
|
|
89
140
|
const indicator = new Indicator();
|
|
@@ -100,45 +151,33 @@ async function runScanSession(requirement, opts = {}) {
|
|
|
100
151
|
log('info', `正在调用 Claude Code 执行项目扫描(${projectType}项目)...`);
|
|
101
152
|
|
|
102
153
|
try {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
inferPhaseStep(indicator, event.tool_name, event.tool_input);
|
|
117
|
-
return { decision: 'allow' };
|
|
118
|
-
}
|
|
119
|
-
}]
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
});
|
|
154
|
+
const queryOpts = buildQueryOptions(config, opts);
|
|
155
|
+
queryOpts.systemPrompt = systemPrompt;
|
|
156
|
+
queryOpts.hooks = {
|
|
157
|
+
PreToolUse: [{
|
|
158
|
+
matcher: '*',
|
|
159
|
+
hooks: [async (input) => {
|
|
160
|
+
inferPhaseStep(indicator, input.tool_name, input.tool_input);
|
|
161
|
+
return {};
|
|
162
|
+
}]
|
|
163
|
+
}]
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const session = sdk.query({ prompt, options: queryOpts });
|
|
123
167
|
|
|
124
|
-
|
|
168
|
+
const collected = [];
|
|
125
169
|
for await (const message of session) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
? message.content
|
|
129
|
-
: JSON.stringify(message.content);
|
|
130
|
-
process.stdout.write(text);
|
|
131
|
-
logStream.write(text);
|
|
132
|
-
}
|
|
133
|
-
result = message;
|
|
170
|
+
collected.push(message);
|
|
171
|
+
logMessage(message, logStream);
|
|
134
172
|
}
|
|
135
173
|
|
|
136
174
|
logStream.end();
|
|
137
175
|
indicator.stop();
|
|
138
176
|
|
|
177
|
+
const result = extractResult(collected);
|
|
139
178
|
return {
|
|
140
179
|
exitCode: 0,
|
|
141
|
-
cost: result?.total_cost_usd
|
|
180
|
+
cost: result?.total_cost_usd ?? null,
|
|
142
181
|
logFile,
|
|
143
182
|
};
|
|
144
183
|
} catch (err) {
|
|
@@ -150,7 +189,7 @@ async function runScanSession(requirement, opts = {}) {
|
|
|
150
189
|
}
|
|
151
190
|
|
|
152
191
|
async function runViewSession(requirement, opts = {}) {
|
|
153
|
-
const
|
|
192
|
+
const sdk = await loadSDK();
|
|
154
193
|
const p = paths();
|
|
155
194
|
const config = loadConfig();
|
|
156
195
|
applyEnvConfig(config);
|
|
@@ -172,25 +211,13 @@ async function runViewSession(requirement, opts = {}) {
|
|
|
172
211
|
}
|
|
173
212
|
|
|
174
213
|
try {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
allowedTools: getAllowedTools(config),
|
|
180
|
-
permissionMode: 'bypassPermissions',
|
|
181
|
-
verbose: true,
|
|
182
|
-
cwd: opts.projectRoot || process.cwd(),
|
|
183
|
-
timeout_ms: config.timeoutMs,
|
|
184
|
-
}
|
|
185
|
-
});
|
|
214
|
+
const queryOpts = buildQueryOptions(config, opts);
|
|
215
|
+
queryOpts.systemPrompt = systemPrompt;
|
|
216
|
+
|
|
217
|
+
const session = sdk.query({ prompt, options: queryOpts });
|
|
186
218
|
|
|
187
219
|
for await (const message of session) {
|
|
188
|
-
|
|
189
|
-
const text = typeof message.content === 'string'
|
|
190
|
-
? message.content
|
|
191
|
-
: JSON.stringify(message.content);
|
|
192
|
-
process.stdout.write(text);
|
|
193
|
-
}
|
|
220
|
+
logMessage(message, null);
|
|
194
221
|
}
|
|
195
222
|
} catch (err) {
|
|
196
223
|
log('error', `观测模式错误: ${err.message}`);
|
|
@@ -198,7 +225,7 @@ async function runViewSession(requirement, opts = {}) {
|
|
|
198
225
|
}
|
|
199
226
|
|
|
200
227
|
async function runAddSession(instruction, opts = {}) {
|
|
201
|
-
const
|
|
228
|
+
const sdk = await loadSDK();
|
|
202
229
|
const config = loadConfig();
|
|
203
230
|
applyEnvConfig(config);
|
|
204
231
|
|
|
@@ -210,26 +237,13 @@ async function runAddSession(instruction, opts = {}) {
|
|
|
210
237
|
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
211
238
|
|
|
212
239
|
try {
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
allowedTools: getAllowedTools(config),
|
|
218
|
-
permissionMode: 'bypassPermissions',
|
|
219
|
-
verbose: true,
|
|
220
|
-
cwd: opts.projectRoot || process.cwd(),
|
|
221
|
-
timeout_ms: config.timeoutMs,
|
|
222
|
-
}
|
|
223
|
-
});
|
|
240
|
+
const queryOpts = buildQueryOptions(config, opts);
|
|
241
|
+
queryOpts.systemPrompt = systemPrompt;
|
|
242
|
+
|
|
243
|
+
const session = sdk.query({ prompt, options: queryOpts });
|
|
224
244
|
|
|
225
245
|
for await (const message of session) {
|
|
226
|
-
|
|
227
|
-
const text = typeof message.content === 'string'
|
|
228
|
-
? message.content
|
|
229
|
-
: JSON.stringify(message.content);
|
|
230
|
-
process.stdout.write(text);
|
|
231
|
-
logStream.write(text);
|
|
232
|
-
}
|
|
246
|
+
logMessage(message, logStream);
|
|
233
247
|
}
|
|
234
248
|
|
|
235
249
|
logStream.end();
|