claude-coder 1.5.2 → 1.5.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-coder",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
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"
package/src/indicator.js CHANGED
@@ -16,6 +16,7 @@ class Indicator {
16
16
  this.lastToolTime = Date.now();
17
17
  this.sessionNum = 0;
18
18
  this.startTime = Date.now();
19
+ this._lastContentKey = '';
19
20
  }
20
21
 
21
22
  start(sessionNum) {
@@ -87,7 +88,6 @@ class Indicator {
87
88
  _render() {
88
89
  this.spinnerIndex++;
89
90
  const line = this.getStatusLine();
90
-
91
91
  const maxWidth = process.stderr.columns || 80;
92
92
  const truncated = line.length > maxWidth + 20 ? line.slice(0, maxWidth + 20) : line;
93
93
 
@@ -95,41 +95,60 @@ class Indicator {
95
95
  }
96
96
  }
97
97
 
98
- // Phase-signal logic: infer phase/step from tool calls
98
+ function extractFileTarget(toolInput) {
99
+ const raw = typeof toolInput === 'object'
100
+ ? (toolInput.file_path || toolInput.path || '')
101
+ : '';
102
+ if (!raw) return '';
103
+ return raw.split('/').slice(-2).join('/').slice(0, 40);
104
+ }
105
+
106
+ function extractBashLabel(cmd) {
107
+ if (cmd.includes('git ')) return 'Git 操作';
108
+ if (cmd.includes('npm ') || cmd.includes('pip ') || cmd.includes('pnpm ') || cmd.includes('yarn ')) return '安装依赖';
109
+ if (cmd.includes('curl') || cmd.includes('pytest') || cmd.includes('jest') || /\btest\b/.test(cmd)) return '测试验证';
110
+ if (cmd.includes('python ') || cmd.includes('node ')) return '执行脚本';
111
+ return '执行命令';
112
+ }
113
+
114
+ function extractBashTarget(cmd) {
115
+ let clean = cmd.replace(/^(?:cd\s+\S+\s*&&\s*)+/g, '').trim();
116
+ clean = clean.split(/\s*(?:\|{1,2}|;|&&|2>&1|>\s*\/dev\/null)\s*/)[0].trim();
117
+ return clean.slice(0, 40);
118
+ }
119
+
99
120
  function inferPhaseStep(indicator, toolName, toolInput) {
100
121
  const name = (toolName || '').toLowerCase();
101
122
 
102
123
  indicator.lastToolTime = Date.now();
103
124
 
104
- const rawTarget = typeof toolInput === 'object'
105
- ? (toolInput.file_path || toolInput.path || toolInput.command || toolInput.pattern || '')
106
- : String(toolInput || '');
107
- const shortTarget = rawTarget.split('/').slice(-2).join('/').slice(0, 40);
108
- indicator.toolTarget = shortTarget;
109
-
110
125
  if (name === 'write' || name === 'edit' || name === 'multiedit' || name === 'str_replace_editor' || name === 'strreplace') {
111
126
  indicator.updatePhase('coding');
127
+ indicator.updateStep('编辑文件');
128
+ indicator.toolTarget = extractFileTarget(toolInput);
112
129
  } else if (name === 'bash' || name === 'shell') {
113
130
  const cmd = typeof toolInput === 'object' ? (toolInput.command || '') : String(toolInput || '');
114
- if (cmd.includes('git ')) {
115
- indicator.updateStep('Git 操作');
116
- } else if (cmd.includes('npm ') || cmd.includes('pip ') || cmd.includes('pnpm ')) {
117
- indicator.updateStep('安装依赖');
118
- } else if (cmd.includes('test') || cmd.includes('curl') || cmd.includes('pytest')) {
119
- indicator.updateStep('测试验证');
120
- indicator.updatePhase('coding');
121
- } else {
131
+ const label = extractBashLabel(cmd);
132
+ indicator.updateStep(label);
133
+ indicator.toolTarget = extractBashTarget(cmd);
134
+ if (label === '测试验证' || label === '执行脚本' || label === '执行命令') {
122
135
  indicator.updatePhase('coding');
123
136
  }
124
137
  } else if (name === 'read' || name === 'glob' || name === 'grep' || name === 'ls') {
125
138
  indicator.updatePhase('thinking');
126
139
  indicator.updateStep('读取文件');
140
+ indicator.toolTarget = extractFileTarget(toolInput);
127
141
  } else if (name === 'task') {
128
142
  indicator.updatePhase('thinking');
129
143
  indicator.updateStep('子 Agent 搜索');
144
+ indicator.toolTarget = '';
130
145
  } else if (name === 'websearch' || name === 'webfetch') {
131
146
  indicator.updatePhase('thinking');
132
147
  indicator.updateStep('查阅文档');
148
+ indicator.toolTarget = '';
149
+ } else {
150
+ indicator.updateStep('工具调用');
151
+ indicator.toolTarget = '';
133
152
  }
134
153
 
135
154
  let summary;
@@ -137,15 +156,7 @@ function inferPhaseStep(indicator, toolName, toolInput) {
137
156
  const target = toolInput.file_path || toolInput.path || '';
138
157
  const cmd = toolInput.command || '';
139
158
  const pattern = toolInput.pattern || '';
140
- if (target) {
141
- summary = target;
142
- } else if (cmd) {
143
- summary = cmd.slice(0, 200);
144
- } else if (pattern) {
145
- summary = `pattern: ${pattern}`;
146
- } else {
147
- summary = JSON.stringify(toolInput).slice(0, 200);
148
- }
159
+ summary = target || (cmd ? cmd.slice(0, 200) : '') || (pattern ? `pattern: ${pattern}` : JSON.stringify(toolInput).slice(0, 200));
149
160
  } else {
150
161
  summary = String(toolInput || '').slice(0, 200);
151
162
  }
package/src/session.js CHANGED
@@ -73,14 +73,20 @@ function writeSessionSeparator(logStream, sessionNum, label) {
73
73
  logStream.write(`\n${sep}\n[Session ${sessionNum}] ${label} ${new Date().toISOString()}\n${sep}\n`);
74
74
  }
75
75
 
76
+ let _lastPrintedStatusKey = '';
77
+
76
78
  function logMessage(message, logStream, indicator) {
77
79
  if (message.type === 'assistant' && message.message?.content) {
78
80
  for (const block of message.message.content) {
79
81
  if (block.type === 'text' && block.text) {
80
82
  if (indicator) {
81
- const statusLine = indicator.getStatusLine();
82
83
  process.stderr.write('\r\x1b[K');
83
- if (statusLine) process.stderr.write(statusLine + '\n');
84
+ const contentKey = `${indicator.phase}|${indicator.step}|${indicator.toolTarget}`;
85
+ if (contentKey !== _lastPrintedStatusKey) {
86
+ _lastPrintedStatusKey = contentKey;
87
+ const statusLine = indicator.getStatusLine();
88
+ if (statusLine) process.stderr.write(statusLine + '\n');
89
+ }
84
90
  }
85
91
  process.stdout.write(block.text);
86
92
  if (logStream) logStream.write(block.text);