create-claude-workspace 1.1.150 → 1.1.152

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.
@@ -430,6 +430,7 @@ async function main() {
430
430
  let sessionId = '';
431
431
  if (opts.interactive) {
432
432
  tui.setInputHandler((text) => {
433
+ tui.addPendingInput(text);
433
434
  const msg = {
434
435
  type: 'user',
435
436
  message: { role: 'user', content: text },
@@ -22,6 +22,8 @@ const DIM = '\x1b[2m';
22
22
  const HIDE_CURSOR = '\x1b[?25l';
23
23
  const SHOW_CURSOR = '\x1b[?25h';
24
24
  const CLEAR_LINE = '\r\x1b[2K';
25
+ const CLEAR_TO_END = '\x1b[J'; // clear from cursor to end of screen
26
+ const CURSOR_UP = (n) => n > 0 ? `\x1b[${n}A` : '';
25
27
  // ─── Tool icons ───
26
28
  const ICONS = {
27
29
  Bash: '⚡', Read: '📖', Write: '✏️ ', Edit: '🔧', Glob: '🔍', Grep: '🔎',
@@ -39,7 +41,7 @@ export class TUI {
39
41
  onInput = null;
40
42
  onHotkey = null;
41
43
  state;
42
- statusBarVisible = false;
44
+ statusBarLines = 0;
43
45
  statusTimer = null;
44
46
  stdinHandler = null;
45
47
  topAgent = null;
@@ -50,6 +52,7 @@ export class TUI {
50
52
  iteration: 0, maxIter: 0, loopStart: Date.now(), iterStart: 0,
51
53
  tools: 0, tokensIn: 0, tokensOut: 0, agents: [], taskName: '',
52
54
  tasksDone: 0, tasksTotal: 0, paused: false, inputBuf: '',
55
+ pendingInputs: [],
53
56
  };
54
57
  if (this.interactive) {
55
58
  // Hide cursor to prevent flickering
@@ -122,10 +125,10 @@ export class TUI {
122
125
  }
123
126
  destroy() {
124
127
  if (this.interactive) {
125
- // Clear status bar
126
- if (this.statusBarVisible) {
127
- process.stdout.write(CLEAR_LINE);
128
- this.statusBarVisible = false;
128
+ // Clear status area
129
+ if (this.statusBarLines > 0) {
130
+ process.stdout.write(CURSOR_UP(this.statusBarLines - 1) + '\r' + CLEAR_TO_END);
131
+ this.statusBarLines = 0;
129
132
  }
130
133
  // Show cursor
131
134
  process.stdout.write(SHOW_CURSOR);
@@ -151,6 +154,19 @@ export class TUI {
151
154
  setInputHandler(h) { this.onInput = h; }
152
155
  setHotkeyHandler(h) { this.onHotkey = h; }
153
156
  isPaused() { return this.state.paused; }
157
+ addPendingInput(text) {
158
+ this.state.pendingInputs.push(text);
159
+ this.log(` ${ANSI_COLORS.gray}${ts()}${RESET} ${ANSI_COLORS.magenta}▲${RESET} ${ANSI_COLORS.white}Queued:${RESET} ${trunc(text, 60)}`);
160
+ this.renderStatusBar();
161
+ }
162
+ consumePendingInput() {
163
+ const item = this.state.pendingInputs.shift();
164
+ if (item) {
165
+ this.log(` ${ANSI_COLORS.gray}${ts()}${RESET} ${ANSI_COLORS.green}▼${RESET} ${ANSI_COLORS.white}Processing:${RESET} ${trunc(item, 60)}`);
166
+ this.renderStatusBar();
167
+ }
168
+ return item;
169
+ }
154
170
  /** Set the top-level agent name so all log lines show it from the start. */
155
171
  setTopAgent(name) {
156
172
  this.topAgent = name;
@@ -158,69 +174,92 @@ export class TUI {
158
174
  this.state.agents.push(name);
159
175
  }
160
176
  }
161
- // ─── Status bar (overwrites current line, no \n) ───
162
- buildStatusBar() {
177
+ // ─── Status area (multi-line: pending queue + input + stats) ───
178
+ buildStatusLines() {
163
179
  const s = this.state;
180
+ const lines = [];
181
+ // Line 1: Pending input queue (only if non-empty)
182
+ if (s.pendingInputs.length > 0) {
183
+ const nums = ['①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨'];
184
+ const items = s.pendingInputs.slice(0, 5).map((t, i) => `${nums[i] || '•'} ${trunc(t, 25)}`).join(' ');
185
+ const more = s.pendingInputs.length > 5 ? ` +${s.pendingInputs.length - 5}` : '';
186
+ lines.push(` ${ANSI_COLORS.magenta}[${s.pendingInputs.length} queued]${RESET} ${ANSI_COLORS.gray}${items}${more}${RESET}`);
187
+ }
188
+ // Line 2: Input prompt
189
+ if (s.inputBuf) {
190
+ lines.push(` ${ANSI_COLORS.white}› ${s.inputBuf}${RESET}`);
191
+ }
192
+ else {
193
+ lines.push(` ${ANSI_COLORS.gray}› type to send input${RESET}`);
194
+ }
195
+ // Line 3: Status bar (stats)
164
196
  const elapsed = fmtDur(Date.now() - s.loopStart);
165
197
  const iterTime = s.iterStart ? fmtDur(Date.now() - s.iterStart) : '—';
166
198
  const pct = s.maxIter > 0 ? Math.round((s.iteration / s.maxIter) * 100) : 0;
167
199
  const filled = Math.round((pct / 100) * 8);
168
- const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(8 - filled);
200
+ const bar = `${ANSI_COLORS.green}${'\u2588'.repeat(filled)}${ANSI_COLORS.gray}${'\u2591'.repeat(8 - filled)}${RESET}`;
169
201
  const tok = fmtTok(s.tokensIn + s.tokensOut);
170
202
  const cur = s.agents.length > 0 ? s.agents[s.agents.length - 1] : '';
171
- let line = ` ${elapsed} | Iter ${s.iteration}/${s.maxIter} ${bar} | ${iterTime} | ${s.tools} tools | ${tok} tok`;
172
- if (cur)
173
- line += ` | ${cur}`;
203
+ let stats = `\x1b[7m ${elapsed} | Iter ${s.iteration}/${s.maxIter} \x1b[27m ${bar} \x1b[7m ${iterTime} | ${ANSI_COLORS.cyan}${s.tools}${RESET}\x1b[7m tools | ${ANSI_COLORS.yellow}${tok}${RESET}\x1b[7m tok`;
204
+ if (cur) {
205
+ const col = ANSI_COLORS[agentColor(cur)] || '';
206
+ stats += ` | ${col}${BOLD}${cur}${RESET}\x1b[7m`;
207
+ }
174
208
  if (s.taskName)
175
- line += ` | ${trunc(s.taskName, 20)}`;
209
+ stats += ` | ${ANSI_COLORS.cyan}${trunc(s.taskName, 20)}${RESET}\x1b[7m`;
176
210
  if (s.paused)
177
- line += ' | PAUSED';
178
- line += s.inputBuf ? ` ${s.inputBuf}` : ' › type to send input';
179
- // Truncate to terminal width
180
- const cols = process.stdout.columns || 120;
181
- if (line.length > cols)
182
- line = line.slice(0, cols - 1) + '…';
183
- return line;
211
+ stats += ` | ${ANSI_COLORS.yellow}⏸ PAUSED${RESET}\x1b[7m`;
212
+ stats += ` ${RESET}`;
213
+ lines.push(stats);
214
+ return lines;
184
215
  }
185
- buildColoredStatusBar() {
216
+ /** Plain-text version for testing / non-ANSI contexts. */
217
+ buildPlainStatusLines() {
186
218
  const s = this.state;
219
+ const lines = [];
220
+ if (s.pendingInputs.length > 0) {
221
+ const items = s.pendingInputs.slice(0, 5).map((t, i) => `${i + 1}. ${trunc(t, 25)}`).join(' ');
222
+ const more = s.pendingInputs.length > 5 ? ` +${s.pendingInputs.length - 5}` : '';
223
+ lines.push(` [${s.pendingInputs.length} queued] ${items}${more}`);
224
+ }
225
+ lines.push(s.inputBuf ? ` › ${s.inputBuf}` : ' › type to send input');
187
226
  const elapsed = fmtDur(Date.now() - s.loopStart);
188
227
  const iterTime = s.iterStart ? fmtDur(Date.now() - s.iterStart) : '—';
189
228
  const pct = s.maxIter > 0 ? Math.round((s.iteration / s.maxIter) * 100) : 0;
190
229
  const filled = Math.round((pct / 100) * 8);
191
- const bar = `${ANSI_COLORS.green}${'\u2588'.repeat(filled)}${ANSI_COLORS.gray}${'\u2591'.repeat(8 - filled)}${RESET}`;
230
+ const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(8 - filled);
192
231
  const tok = fmtTok(s.tokensIn + s.tokensOut);
193
232
  const cur = s.agents.length > 0 ? s.agents[s.agents.length - 1] : '';
194
- let line = `\x1b[7m ${elapsed} | Iter ${s.iteration}/${s.maxIter} \x1b[27m ${bar} \x1b[7m ${iterTime} | ${ANSI_COLORS.cyan}${s.tools}${RESET}\x1b[7m tools | ${ANSI_COLORS.yellow}${tok}${RESET}\x1b[7m tok`;
195
- if (cur) {
196
- const col = ANSI_COLORS[agentColor(cur)] || '';
197
- line += ` | ${col}${BOLD}${cur}${RESET}\x1b[7m`;
198
- }
233
+ let stats = ` ${elapsed} | Iter ${s.iteration}/${s.maxIter} ${bar} | ${iterTime} | ${s.tools} tools | ${tok} tok`;
234
+ if (cur)
235
+ stats += ` | ${cur}`;
199
236
  if (s.taskName)
200
- line += ` | ${ANSI_COLORS.cyan}${trunc(s.taskName, 20)}${RESET}\x1b[7m`;
237
+ stats += ` | ${trunc(s.taskName, 20)}`;
201
238
  if (s.paused)
202
- line += ` | ${ANSI_COLORS.yellow}⏸ PAUSED${RESET}\x1b[7m`;
203
- if (s.inputBuf) {
204
- line += ` ${RESET}${ANSI_COLORS.white}› ${s.inputBuf}${RESET}`;
205
- }
206
- else {
207
- line += ` ${RESET}${ANSI_COLORS.gray}› type to send input${RESET}`;
239
+ stats += ' | PAUSED';
240
+ lines.push(stats);
241
+ return lines;
242
+ }
243
+ clearStatusArea() {
244
+ if (this.statusBarLines > 0) {
245
+ // Move cursor up to first status line, then clear everything below
246
+ process.stdout.write(CURSOR_UP(this.statusBarLines - 1) + '\r' + CLEAR_TO_END);
208
247
  }
209
- line += ` ${RESET}`;
210
- return line;
211
248
  }
212
249
  renderStatusBar() {
213
250
  if (!this.interactive)
214
251
  return;
215
- const line = this.buildColoredStatusBar();
216
- process.stdout.write(CLEAR_LINE + line);
217
- this.statusBarVisible = true;
252
+ this.clearStatusArea();
253
+ const lines = this.buildStatusLines();
254
+ process.stdout.write(lines.join('\n'));
255
+ this.statusBarLines = lines.length;
218
256
  }
219
257
  // ─── Log output (stdout — preserves scroll history) ───
220
258
  log(formatted, raw) {
221
- if (this.interactive && this.statusBarVisible) {
222
- // Clear the status bar line, write the log line, then re-render status bar
223
- process.stdout.write(CLEAR_LINE + formatted + '\n');
259
+ if (this.interactive && this.statusBarLines > 0) {
260
+ // Clear status area, write the log line, then re-render status
261
+ this.clearStatusArea();
262
+ process.stdout.write(formatted + '\n');
224
263
  this.renderStatusBar();
225
264
  }
226
265
  else {
@@ -299,6 +338,10 @@ export class TUI {
299
338
  this.onAssistant(message);
300
339
  break;
301
340
  case 'user':
341
+ // Detect echoed user input (string content) vs tool results (array content)
342
+ if (typeof message.message?.content === 'string' && this.state.pendingInputs.length > 0) {
343
+ this.consumePendingInput();
344
+ }
302
345
  this.onToolResult(message);
303
346
  break;
304
347
  case 'system':
@@ -288,3 +288,59 @@ describe('TUI — message handling', () => {
288
288
  expect(true).toBe(true);
289
289
  });
290
290
  });
291
+ describe('TUI — pending input queue', () => {
292
+ it('addPendingInput logs the queued message', () => {
293
+ const tui = new TUI(LOG_FILE, false);
294
+ tui.addPendingInput('fix login bug');
295
+ expect(readLog()).toContain('Queued:');
296
+ expect(readLog()).toContain('fix login bug');
297
+ });
298
+ it('consumePendingInput returns and logs first item', () => {
299
+ const tui = new TUI(LOG_FILE, false);
300
+ tui.addPendingInput('first');
301
+ tui.addPendingInput('second');
302
+ const consumed = tui.consumePendingInput();
303
+ expect(consumed).toBe('first');
304
+ expect(readLog()).toContain('Processing:');
305
+ expect(readLog()).toContain('first');
306
+ });
307
+ it('consumePendingInput returns undefined when empty', () => {
308
+ const tui = new TUI(LOG_FILE, false);
309
+ expect(tui.consumePendingInput()).toBeUndefined();
310
+ });
311
+ it('echoed user message consumes from pending queue', () => {
312
+ const tui = new TUI(LOG_FILE, false);
313
+ tui.addPendingInput('add tests');
314
+ // Simulate SDK echoing back the user message (string content, not tool_result array)
315
+ tui.handleMessage(msg({
316
+ type: 'user',
317
+ message: { role: 'user', content: 'add tests' },
318
+ }));
319
+ const log = readLog();
320
+ expect(log).toContain('Queued:');
321
+ expect(log).toContain('Processing:');
322
+ // Queue should be empty now
323
+ expect(tui.consumePendingInput()).toBeUndefined();
324
+ });
325
+ it('tool_result user messages do not consume from queue', () => {
326
+ const tui = new TUI(LOG_FILE, false);
327
+ tui.addPendingInput('pending task');
328
+ // Tool result has array content, should NOT consume
329
+ tui.handleMessage(msg({
330
+ type: 'user',
331
+ message: { content: [{ type: 'tool_result', content: 'ok', is_error: false }] },
332
+ }));
333
+ // Queue should still have the item
334
+ expect(tui.consumePendingInput()).toBe('pending task');
335
+ });
336
+ it('multiple pending inputs are consumed in FIFO order', () => {
337
+ const tui = new TUI(LOG_FILE, false);
338
+ tui.addPendingInput('first');
339
+ tui.addPendingInput('second');
340
+ tui.addPendingInput('third');
341
+ // Consume via echoed user messages
342
+ tui.handleMessage(msg({ type: 'user', message: { role: 'user', content: 'first' } }));
343
+ tui.handleMessage(msg({ type: 'user', message: { role: 'user', content: 'second' } }));
344
+ expect(tui.consumePendingInput()).toBe('third');
345
+ });
346
+ });
@@ -369,6 +369,19 @@ If `PLAN.md` exists in the project root, check whether it changed since the last
369
369
  - After merge (STEP 10), EXIT. Do NOT update MEMORY.md after merge — it was already committed in STEP 9 and arrived on main via merge. Do NOT ask whether to continue, do NOT wait for confirmation, do NOT start another task. Do NOT create any commits after merge.
370
370
  - Track complexity in MEMORY.md `Complexity This Session` for informational purposes only (S=1, M=2, L=4).
371
371
 
372
+ ### 14. User input during autonomous operation
373
+ The user can send messages mid-session via the interactive TUI. When you receive a user message during the workflow:
374
+
375
+ - **Read and understand the request** — determine what the user wants.
376
+ - **Task/feature request** ("add X", "build Y", "I want Z"): delegate to `technical-planner` to create properly structured tasks in TODO.md with correct phase, dependencies, complexity, and Type field. The planner decides priority placement — urgent requests go to the top of the current phase, normal requests go to the appropriate phase. Then **continue your current task** — do NOT drop what you're doing. The new task will be picked up in a future invocation.
377
+ - **Change to current task** ("use X instead", "also add Y here", "don't forget Z"): incorporate the feedback into your current implementation. If you're past STEP 3 (IMPLEMENT), adjust the code. If you're in STEP 2 (PLAN), adjust the plan.
378
+ - **Bug report / fix request** ("X is broken", "fix Y"): if it's related to the current task, fix it now. If it's a separate issue, create a hotfix task at the top of the current phase in TODO.md (mark as `Complexity: S`, `Type:` based on affected files).
379
+ - **Stop / abort** ("stop", "cancel", "skip this"): mark the current task as `[~]` SKIPPED in TODO.md with reason "user request", clean up worktree, commit tracking on main, EXIT.
380
+ - **Question / status** ("what are you doing?", "how far along?"): respond briefly with current step and task, then continue working.
381
+ - **Reprioritization** ("do X first", "skip phase 2", "focus on Y"): update TODO.md accordingly (reorder, mark `[~]` SKIPPED with reason). If the current task should be dropped, treat as stop/abort above.
382
+
383
+ **Do NOT pause or ask for confirmation** — process the input and continue. The user is monitoring asynchronously; they don't expect a back-and-forth conversation.
384
+
372
385
  ## Task Type Detection
373
386
 
374
387
  To determine if a task is frontend, backend, or fullstack, use this heuristic:
@@ -43,7 +43,7 @@ After the conversation, scan what already exists. Do NOT create or scaffold anyt
43
43
  - `react` or `next` → React
44
44
  - `vue` or `nuxt` → Vue
45
45
  - `svelte` or `@sveltejs/kit` → Svelte
46
- - None of the above → no frontend / backend-only / TS library
46
+ - None of the above → no frontend detected yet (for new projects, the framework will be chosen in Step 4b)
47
47
  - Store the detected framework for Step 4 (profile selection)
48
48
  - List `apps/` and `libs/` structure if they exist
49
49
  - Check for existing plan/design/UX files (TODO.md, PLAN.md, PRODUCT.md, UX.md)
@@ -182,9 +182,13 @@ Create a **minimal** CLAUDE.md in the project root by filling what is KNOWN from
182
182
  - Keep: CLI-First Principle, Code Quality & Linting, and general coding conventions — these apply to all project types
183
183
  - The result should be a working CLAUDE.md that will be EXPANDED by architect agents as they make technical decisions
184
184
 
185
- **4b. Copy frontend profile:**
185
+ **4b. Choose and copy frontend profile:**
186
186
 
187
- If a frontend framework was detected (or is expected for a new project):
187
+ **For new projects with a UI (no framework detected yet):** Ask the user which frontend framework they prefer: Angular, React, Vue, or Svelte. If they have no preference, default to React. **NEVER use plain HTML/CSS/JS** — all frontend development requires one of the four supported frameworks (there are no agents or profiles for vanilla web development).
188
+
189
+ **For existing projects:** Use the framework detected in Step 3.
190
+
191
+ Once the framework is determined:
188
192
  1. Check `.claude/profiles/` for the matching profile file:
189
193
  - Angular → `.claude/profiles/angular.md`
190
194
  - React → `.claude/profiles/react.md`
@@ -193,7 +197,7 @@ If a frontend framework was detected (or is expected for a new project):
193
197
  2. Copy the profile to `.claude/profiles/frontend.md` in the project
194
198
  3. If the profile file doesn't exist in the kit (not yet created), warn: "No profile found for [framework]. The ui-engineer, test-engineer, and senior-code-reviewer agents will rely on their native knowledge of [framework]. Consider creating `.claude/profiles/frontend.md` with project-specific conventions."
195
199
 
196
- If no frontend framework is detected (backend-only / TS library), skip this step.
200
+ If the project has no UI (backend-only / TS library), skip this step.
197
201
 
198
202
  ### Step 5: Product Planning — DELEGATE to `product-owner` agent
199
203
 
@@ -31,7 +31,7 @@ Read these files (all mandatory if they exist):
31
31
  ### 2. Determine Tech Stack (for new/empty projects)
32
32
 
33
33
  If the codebase has no `nx.json` or `package.json` (brand new project), YOU must decide the tech stack based on PRODUCT.md features:
34
- - **Does it need a UI?** → Choose frontend framework based on project needs. Check if `.claude/profiles/` has a matching profile. If the user mentioned a preference in the discovery conversation, use that.
34
+ - **Does it need a UI?** → MUST choose one of the supported frontend frameworks: **Angular, React, Vue, or Svelte**. NEVER use plain HTML/CSS/JS — there is no agent or profile for vanilla web development. Check `.claude/profiles/` for available profiles. If the user mentioned a preference in the discovery conversation, use that. If no preference, default to React (widest ecosystem).
35
35
  - **Does it need a backend?** → Default: Hono on Cloudflare Workers (production) + Node.js (local dev). Backend code must be platform-agnostic — no Node-specific or CF-specific APIs in business/domain layers.
36
36
  - **Does it need a database?** → Default: D1 (Cloudflare production) + SQLite/better-sqlite3 (local dev). Both behind repository interfaces.
37
37
  - **Package manager** → Default to `npm` unless PRODUCT.md or user context suggests otherwise.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-workspace",
3
- "version": "1.1.150",
3
+ "version": "1.1.152",
4
4
  "author": "",
5
5
  "repository": {
6
6
  "type": "git",