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.
@@ -1,7 +1,7 @@
1
1
  // ─── Terminal UI for autonomous loop ───
2
- // Two modes:
3
- // Default: beautiful colored line-by-line output (works everywhere)
4
- // Interactive (--interactive): adds input prompt + keyboard controls (needs capable terminal)
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
- // Initial status bar + input prompt
68
- this.renderStatusBar();
69
- this.printInputPrompt('');
68
+ this.ticker = setInterval(() => this.tickStatus(), 1000);
70
69
  }
71
70
  }
72
- // ─── Input handling (interactive only) ───
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
- } // Ctrl+C
84
- if (key === '\x1a') { // Ctrl+Z
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
- } // Ctrl+S
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.printInputPrompt(this.inputBuf);
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.printInputPrompt(this.inputBuf);
102
+ this.tickStatus();
104
103
  return;
105
104
  }
106
105
  if (key.length === 1 && key >= ' ') {
107
106
  this.inputBuf += key;
108
- this.printInputPrompt(this.inputBuf);
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
- // Move up 2 lines (status + input), clear them, print log, redraw
129
- process.stdout.write('\x1b[2K\r'); // clear input line
130
- process.stdout.write('\x1b[A\x1b[2K\r'); // move up, clear status line
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/resume │ Ctrl+S stop │ Ctrl+C quit${a.reset}`);
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}${taskType ? ` ${a.fg.gray}(${taskType})${a.reset}` : ''}`, `AGENT_START: ${desc} ${taskType}`);
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, 80)}${a.reset}` : '';
332
- if (this.agents.length > 0)
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 || 'unknown'})${a.reset}`);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-workspace",
3
- "version": "1.1.129",
3
+ "version": "1.1.131",
4
4
  "author": "",
5
5
  "repository": {
6
6
  "type": "git",