create-claude-workspace 1.1.129 → 1.1.131
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/dist/scripts/lib/tui.mjs +75 -60
- package/package.json +1 -1
package/dist/scripts/lib/tui.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ─── Terminal UI for autonomous loop ───
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// Log lines scroll naturally. Status bar + input are the last line,
|
|
3
|
+
// erased and redrawn with \r\x1b[2K (single line, no cursor-up tricks).
|
|
4
|
+
// Agent names shown as colored prefix on every log line.
|
|
5
5
|
import { appendFileSync } from 'node:fs';
|
|
6
6
|
// ─── ANSI ───
|
|
7
7
|
const a = {
|
|
@@ -57,19 +57,18 @@ export class TUI {
|
|
|
57
57
|
tokens_ = { input: 0, output: 0 };
|
|
58
58
|
iterStart_ = 0;
|
|
59
59
|
agents = [];
|
|
60
|
-
lastModel_ = '';
|
|
61
60
|
inputBuf = '';
|
|
61
|
+
ticker = null;
|
|
62
|
+
statusVisible = false; // track if status line is currently on screen
|
|
62
63
|
constructor(logFile, interactive = false) {
|
|
63
64
|
this.logFile = logFile;
|
|
64
65
|
this.interactive = interactive && process.stdin.isTTY === true;
|
|
65
66
|
if (this.interactive) {
|
|
66
67
|
this.setupInput();
|
|
67
|
-
|
|
68
|
-
this.renderStatusBar();
|
|
69
|
-
this.printInputPrompt('');
|
|
68
|
+
this.ticker = setInterval(() => this.tickStatus(), 1000);
|
|
70
69
|
}
|
|
71
70
|
}
|
|
72
|
-
// ─── Input handling
|
|
71
|
+
// ─── Input handling ───
|
|
73
72
|
setupInput() {
|
|
74
73
|
if (!process.stdin.isTTY)
|
|
75
74
|
return;
|
|
@@ -80,57 +79,83 @@ export class TUI {
|
|
|
80
79
|
if (key === '\x03') {
|
|
81
80
|
this.onHotkey?.('quit');
|
|
82
81
|
return;
|
|
83
|
-
}
|
|
84
|
-
if (key === '\x1a') {
|
|
82
|
+
}
|
|
83
|
+
if (key === '\x1a') {
|
|
85
84
|
this.paused_ = !this.paused_;
|
|
86
85
|
this.onHotkey?.(this.paused_ ? 'pause' : 'resume');
|
|
86
|
+
this.tickStatus();
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
89
|
if (key === '\x13') {
|
|
90
90
|
this.onHotkey?.('stop');
|
|
91
91
|
return;
|
|
92
|
-
}
|
|
92
|
+
}
|
|
93
93
|
if (key === '\r' || key === '\n') {
|
|
94
|
-
if (this.inputBuf.trim())
|
|
94
|
+
if (this.inputBuf.trim())
|
|
95
95
|
this.onInput?.(this.inputBuf.trim());
|
|
96
|
-
}
|
|
97
96
|
this.inputBuf = '';
|
|
98
|
-
this.
|
|
97
|
+
this.tickStatus();
|
|
99
98
|
return;
|
|
100
99
|
}
|
|
101
100
|
if (key === '\x7f' || key === '\b') {
|
|
102
101
|
this.inputBuf = this.inputBuf.slice(0, -1);
|
|
103
|
-
this.
|
|
102
|
+
this.tickStatus();
|
|
104
103
|
return;
|
|
105
104
|
}
|
|
106
105
|
if (key.length === 1 && key >= ' ') {
|
|
107
106
|
this.inputBuf += key;
|
|
108
|
-
this.
|
|
107
|
+
this.tickStatus();
|
|
109
108
|
}
|
|
110
109
|
});
|
|
111
110
|
}
|
|
112
|
-
printInputPrompt(buf) {
|
|
113
|
-
const prompt = `${a.fg.gray} › ${a.fg.white}${buf}${a.reset}`;
|
|
114
|
-
process.stdout.write(`\r\x1b[2K${prompt}`);
|
|
115
|
-
}
|
|
116
111
|
destroy() {
|
|
112
|
+
if (this.ticker) {
|
|
113
|
+
clearInterval(this.ticker);
|
|
114
|
+
this.ticker = null;
|
|
115
|
+
}
|
|
117
116
|
if (this.interactive && process.stdin.isTTY) {
|
|
118
117
|
process.stdin.setRawMode(false);
|
|
118
|
+
this.clearStatus();
|
|
119
119
|
process.stdout.write('\n');
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
setInputHandler(handler) { this.onInput = handler; }
|
|
123
123
|
setHotkeyHandler(handler) { this.onHotkey = handler; }
|
|
124
124
|
isPaused() { return this.paused_; }
|
|
125
|
+
// ─── Status line (single line, rewritten in place) ───
|
|
126
|
+
buildStatusLine() {
|
|
127
|
+
const elapsed = fmtDur(Date.now() - this.loopStart);
|
|
128
|
+
const iterTime = this.iterStart_ ? fmtDur(Date.now() - this.iterStart_) : '—';
|
|
129
|
+
const pct = this.maxIter > 0 ? Math.round((this.iteration_ / this.maxIter) * 100) : 0;
|
|
130
|
+
const tok = fmtTok(this.tokens_.input + this.tokens_.output);
|
|
131
|
+
const agent = this.agents.length > 0 ? ` │ ${agentColor(this.agents[this.agents.length - 1])}${this.agents[this.agents.length - 1]}${a.reset}` : '';
|
|
132
|
+
const task = this.taskName_ ? ` │ ${a.fg.cyan}${trunc(this.taskName_, 25)}${a.reset}` : '';
|
|
133
|
+
const pause = this.paused_ ? ` │ ${a.fg.yellow}⏸ PAUSED${a.reset}` : '';
|
|
134
|
+
const input = this.inputBuf ? ` › ${this.inputBuf}` : '';
|
|
135
|
+
return `${a.bg.gray} ${elapsed} │ Iter ${this.iteration_}/${this.maxIter} ${bar(pct, 8)} │ ${iterTime} │ ${this.tools} tools │ ${tok} tok${agent}${task}${pause}${input} ${a.reset}`;
|
|
136
|
+
}
|
|
137
|
+
tickStatus() {
|
|
138
|
+
if (!this.interactive)
|
|
139
|
+
return;
|
|
140
|
+
// Overwrite current status line in place
|
|
141
|
+
const line = this.buildStatusLine();
|
|
142
|
+
const cols = process.stdout.columns || 120;
|
|
143
|
+
const padded = line + ' '.repeat(Math.max(0, cols - strip(line).length));
|
|
144
|
+
process.stdout.write(`\r\x1b[2K${padded}`);
|
|
145
|
+
this.statusVisible = true;
|
|
146
|
+
}
|
|
147
|
+
clearStatus() {
|
|
148
|
+
if (this.statusVisible) {
|
|
149
|
+
process.stdout.write('\r\x1b[2K');
|
|
150
|
+
this.statusVisible = false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
125
153
|
// ─── Output ───
|
|
126
154
|
out(formatted, raw) {
|
|
127
155
|
if (this.interactive) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
console.log(formatted);
|
|
132
|
-
this.renderStatusBar();
|
|
133
|
-
this.printInputPrompt(this.inputBuf);
|
|
156
|
+
this.clearStatus(); // erase status line
|
|
157
|
+
console.log(formatted); // print log line (scrolls naturally)
|
|
158
|
+
this.tickStatus(); // redraw status line after log
|
|
134
159
|
}
|
|
135
160
|
else {
|
|
136
161
|
console.log(formatted);
|
|
@@ -143,16 +168,6 @@ export class TUI {
|
|
|
143
168
|
catch { /* */ }
|
|
144
169
|
}
|
|
145
170
|
}
|
|
146
|
-
renderStatusBar() {
|
|
147
|
-
const elapsed = this.loopStart ? fmtDur(Date.now() - this.loopStart) : '—';
|
|
148
|
-
const iterElapsed = this.iterStart_ ? fmtDur(Date.now() - this.iterStart_) : '—';
|
|
149
|
-
const pct = this.maxIter > 0 ? Math.round((this.iteration_ / this.maxIter) * 100) : 0;
|
|
150
|
-
const tok = fmtTok(this.tokens_.input + this.tokens_.output);
|
|
151
|
-
const taskInfo = this.taskName_ ? ` │ ${a.fg.cyan}${trunc(this.taskName_, 30)}${a.reset}` : '';
|
|
152
|
-
const pauseLabel = this.paused_ ? ` │ ${a.fg.yellow}⏸ PAUSED${a.reset}` : '';
|
|
153
|
-
const line = ` ${a.fg.gray}${elapsed}${a.reset} │ Iter ${this.iteration_}/${this.maxIter} ${bar(pct, 10)} │ ${iterElapsed} │ ${a.fg.cyan}${this.tools}${a.reset} tools │ ${a.fg.yellow}${tok}${a.reset} tok${taskInfo}${pauseLabel}`;
|
|
154
|
-
process.stdout.write(`${a.bg.gray}${line}${' '.repeat(Math.max(0, (process.stdout.columns || 80) - strip(line).length))}${a.reset}\n`);
|
|
155
|
-
}
|
|
156
171
|
fileOnly(msg) {
|
|
157
172
|
if (this.logFile) {
|
|
158
173
|
try {
|
|
@@ -161,6 +176,22 @@ export class TUI {
|
|
|
161
176
|
catch { /* */ }
|
|
162
177
|
}
|
|
163
178
|
}
|
|
179
|
+
// ─── Agent label (shown as prefix on log lines) ───
|
|
180
|
+
agentPrefix() {
|
|
181
|
+
if (this.agents.length === 0)
|
|
182
|
+
return '';
|
|
183
|
+
const current = this.agents[this.agents.length - 1];
|
|
184
|
+
const col = agentColor(current);
|
|
185
|
+
const short = current.length > 12 ? current.slice(0, 12) : current;
|
|
186
|
+
return `${col}${short}${a.reset} `;
|
|
187
|
+
}
|
|
188
|
+
indent() {
|
|
189
|
+
const prefix = this.agentPrefix();
|
|
190
|
+
if (this.agents.length <= 1)
|
|
191
|
+
return ` ${prefix}`;
|
|
192
|
+
const pipes = this.agents.slice(0, -1).map(n => `${agentColor(n)}│${a.reset}`).join('');
|
|
193
|
+
return ` ${pipes} ${prefix}`;
|
|
194
|
+
}
|
|
164
195
|
// ─── Public methods ───
|
|
165
196
|
banner() {
|
|
166
197
|
this.out('');
|
|
@@ -168,7 +199,7 @@ export class TUI {
|
|
|
168
199
|
this.out(` ${a.fg.cyan}${a.bold}║ ${a.fg.white}Claude Starter Kit — Autonomous Loop${a.fg.cyan} ║${a.reset}`);
|
|
169
200
|
this.out(` ${a.fg.cyan}${a.bold}╚══════════════════════════════════════════════╝${a.reset}`);
|
|
170
201
|
if (this.interactive) {
|
|
171
|
-
this.out(` ${a.fg.gray} Ctrl+Z pause
|
|
202
|
+
this.out(` ${a.fg.gray} Ctrl+Z pause │ Ctrl+S stop │ Ctrl+C quit │ Type to send input${a.reset}`);
|
|
172
203
|
}
|
|
173
204
|
this.out('');
|
|
174
205
|
}
|
|
@@ -230,11 +261,6 @@ export class TUI {
|
|
|
230
261
|
this.tokens_.input += msg.message.usage.input_tokens || 0;
|
|
231
262
|
this.tokens_.output += msg.message.usage.output_tokens || 0;
|
|
232
263
|
}
|
|
233
|
-
// Show model from message if available
|
|
234
|
-
const model = msg.message?.model;
|
|
235
|
-
if (model && !this.lastModel_) {
|
|
236
|
-
this.lastModel_ = model;
|
|
237
|
-
}
|
|
238
264
|
for (const block of content) {
|
|
239
265
|
if (block.type === 'text' && block.text?.trim()) {
|
|
240
266
|
this.out(`${this.indent()}${a.fg.white}${trunc(block.text, 300)}${a.reset}`, `TEXT: ${trunc(block.text, 300)}`);
|
|
@@ -250,7 +276,7 @@ export class TUI {
|
|
|
250
276
|
const input = block.input || {};
|
|
251
277
|
const pre = this.indent();
|
|
252
278
|
const time = `${a.fg.gray}${ts()}${a.reset}`;
|
|
253
|
-
// Agent delegation
|
|
279
|
+
// Agent delegation via tool
|
|
254
280
|
if (name === 'Agent') {
|
|
255
281
|
const type = input.subagent_type || input.type || 'agent';
|
|
256
282
|
const model = input.model ? ` ${a.fg.gray}(${input.model})${a.reset}` : '';
|
|
@@ -306,32 +332,27 @@ export class TUI {
|
|
|
306
332
|
}
|
|
307
333
|
}
|
|
308
334
|
onSystem(msg) {
|
|
309
|
-
// Init message — show model, agents, version
|
|
310
335
|
if (msg.subtype === 'init') {
|
|
311
336
|
const model = msg.model || 'unknown';
|
|
312
337
|
const agents = msg.agents?.length ? msg.agents.join(', ') : 'none';
|
|
313
338
|
const ver = msg.claude_code_version || '';
|
|
314
339
|
this.out(` ${a.fg.gray}${ts()}${a.reset} ${a.fg.cyan}⚙${a.reset} Claude Code ${ver} │ Model: ${a.bold}${model}${a.reset} │ Agents: ${a.fg.cyan}${agents}${a.reset}`);
|
|
315
|
-
if (msg.tools?.length) {
|
|
316
|
-
this.out(` ${a.fg.gray} Tools: ${msg.tools.join(', ')}${a.reset}`);
|
|
317
|
-
}
|
|
318
340
|
return;
|
|
319
341
|
}
|
|
320
342
|
if (msg.subtype === 'task_started') {
|
|
321
343
|
const desc = msg.description || '';
|
|
322
|
-
const taskType = msg.task_type || '';
|
|
323
344
|
const col = agentColor(desc);
|
|
324
345
|
this.agents.push(desc);
|
|
325
|
-
this.out(` ${a.fg.gray}${ts()}${a.reset} 🤖 ${col}${a.bold}${desc}${a.reset}
|
|
346
|
+
this.out(` ${a.fg.gray}${ts()}${a.reset} 🤖 ${col}${a.bold}${desc}${a.reset}`, `AGENT_START: ${desc}`);
|
|
326
347
|
return;
|
|
327
348
|
}
|
|
328
349
|
if (msg.subtype === 'task_notification') {
|
|
329
350
|
const status = msg.status || '';
|
|
351
|
+
const name = this.agents.length > 0 ? this.agents.pop() : 'agent';
|
|
352
|
+
const col = agentColor(name);
|
|
330
353
|
const icon = status === 'completed' ? `${a.fg.green}✓` : `${a.fg.red}✗`;
|
|
331
|
-
const summary = msg.summary ? ` ${a.fg.gray}${trunc(msg.summary,
|
|
332
|
-
|
|
333
|
-
this.agents.pop();
|
|
334
|
-
this.out(` ${a.fg.gray}${ts()}${a.reset} ${icon}${a.reset} Agent ${status}${summary}`, `AGENT_END: ${status} ${msg.summary || ''}`);
|
|
354
|
+
const summary = msg.summary ? ` ${a.fg.gray}${trunc(msg.summary, 60)}${a.reset}` : '';
|
|
355
|
+
this.out(` ${a.fg.gray}${ts()}${a.reset} ${icon}${a.reset} ${col}${name}${a.reset} ${status}${summary}`, `AGENT_END: ${name} ${status}`);
|
|
335
356
|
return;
|
|
336
357
|
}
|
|
337
358
|
if (msg.subtype === 'task_progress' && msg.description) {
|
|
@@ -339,7 +360,7 @@ export class TUI {
|
|
|
339
360
|
return;
|
|
340
361
|
}
|
|
341
362
|
if (msg.subtype === 'api_retry') {
|
|
342
|
-
this.out(` ${a.fg.gray}${ts()}${a.reset} ${a.fg.yellow}↻${a.reset} ${a.fg.yellow}API retry ${msg.attempt}/${msg.max_retries} (${msg.error || '
|
|
363
|
+
this.out(` ${a.fg.gray}${ts()}${a.reset} ${a.fg.yellow}↻${a.reset} ${a.fg.yellow}API retry ${msg.attempt}/${msg.max_retries} (${msg.error || ''})${a.reset}`);
|
|
343
364
|
return;
|
|
344
365
|
}
|
|
345
366
|
}
|
|
@@ -347,12 +368,6 @@ export class TUI {
|
|
|
347
368
|
if (msg.session_id)
|
|
348
369
|
this.fileOnly(`SESSION: ${msg.session_id}`);
|
|
349
370
|
}
|
|
350
|
-
// ─── Indent ───
|
|
351
|
-
indent() {
|
|
352
|
-
if (this.agents.length === 0)
|
|
353
|
-
return ' ';
|
|
354
|
-
return ' ' + this.agents.map(n => `${agentColor(n)}│${a.reset} `).join('');
|
|
355
|
-
}
|
|
356
371
|
resetAgentStack() { this.agents = []; }
|
|
357
372
|
getSessionId(msg) { return msg.session_id ?? null; }
|
|
358
373
|
}
|