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.
Files changed (3) hide show
  1. package/package.json +2 -2
  2. package/src/runner.js +25 -11
  3. 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.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.14.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
- try {
17
- require.resolve('@anthropic-ai/claude-agent-sdk');
18
- } catch {
19
- console.error('错误:未找到 @anthropic-ai/claude-agent-sdk');
20
- console.error('请先安装:npm install -g @anthropic-ai/claude-agent-sdk');
21
- process.exit(1);
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 { query } = require('@anthropic-ai/claude-agent-sdk');
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 session = query({
30
- prompt,
31
- options: {
32
- systemPrompt,
33
- allowedTools: getAllowedTools(config),
34
- permissionMode: 'bypassPermissions',
35
- verbose: true,
36
- cwd: opts.projectRoot || process.cwd(),
37
- timeout_ms: config.timeoutMs,
38
- hooks: {
39
- PreToolUse: [{
40
- matcher: '*',
41
- callback: (event) => {
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
- let result;
106
+ const collected = [];
51
107
  for await (const message of session) {
52
- if (message.content) {
53
- const text = typeof message.content === 'string'
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 || null,
68
- tokenUsage: result?.message?.usage || null,
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 { query } = require('@anthropic-ai/claude-agent-sdk');
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 session = query({
104
- prompt,
105
- options: {
106
- systemPrompt,
107
- allowedTools: getAllowedTools(config),
108
- permissionMode: 'bypassPermissions',
109
- verbose: true,
110
- cwd: opts.projectRoot || process.cwd(),
111
- timeout_ms: config.timeoutMs,
112
- hooks: {
113
- PreToolUse: [{
114
- matcher: '*',
115
- callback: (event) => {
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
- let result;
168
+ const collected = [];
125
169
  for await (const message of session) {
126
- if (message.content) {
127
- const text = typeof message.content === 'string'
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 || null,
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 { query } = require('@anthropic-ai/claude-agent-sdk');
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 session = query({
176
- prompt,
177
- options: {
178
- systemPrompt,
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
- if (message.content) {
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 { query } = require('@anthropic-ai/claude-agent-sdk');
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 session = query({
214
- prompt,
215
- options: {
216
- systemPrompt,
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
- if (message.content) {
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();