create-claude-workspace 1.1.17 → 1.1.19

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.
@@ -233,43 +233,96 @@ process.on('SIGTERM', () => {
233
233
 
234
234
  // ─── Stream event display ───
235
235
 
236
+ /** Track last displayed text to avoid re-printing full content on partial message updates */
237
+ let lastAssistantText = '';
238
+
236
239
  function formatStreamEvent(event) {
237
- // Handle stream_event wrapper (from --include-partial-messages)
238
- if (event.type === 'stream_event') {
239
- const inner = event.event ?? event;
240
- const delta = inner.delta;
241
- if (delta?.type === 'text_delta' && delta.text) {
242
- return delta.text; // raw text fragment for streaming display
243
- }
244
- return null;
240
+ const type = event.type;
241
+
242
+ // System init silent (session_id extracted elsewhere)
243
+ if (type === 'system') return null;
244
+
245
+ // Ping silent
246
+ if (type === 'ping') return null;
247
+
248
+ // Rate limit event
249
+ if (type === 'rate_limit_event') {
250
+ const info = event.rate_limit_info ?? event;
251
+ const resets = info.resets_at ?? info.retry_after ?? '';
252
+ return `${C.yellow}⏳ Rate limited${resets ? ` (resets: ${resets})` : ''}${C.reset}\n`;
253
+ }
254
+
255
+ // Error event
256
+ if (type === 'error') {
257
+ const msg = event.error?.message ?? event.message ?? JSON.stringify(event);
258
+ return `${C.red}✗ Error: ${msg}${C.reset}\n`;
245
259
  }
246
260
 
247
- // Assistant message
248
- if (event.type === 'assistant') {
261
+ // Assistant message — content is always an array of content blocks
262
+ if (type === 'assistant') {
249
263
  const msg = event.message;
250
264
  if (!msg) return null;
251
- if (msg.type === 'tool_use' || msg.tool) {
252
- const name = msg.name ?? msg.tool ?? 'tool';
253
- const input = msg.input ?? {};
254
- const detail = input.command ?? input.file_path ?? input.pattern ?? input.query ?? '';
255
- const short = typeof detail === 'string' ? detail.slice(0, 120) : '';
256
- return `${C.cyan}▶ ${name}${C.reset}${short ? ` ${C.dim}${short}${C.reset}` : ''}\n`;
265
+ const content = msg.content;
266
+
267
+ // Content blocks array (the standard format)
268
+ if (Array.isArray(content)) {
269
+ const parts = [];
270
+ for (const block of content) {
271
+ if (block.type === 'tool_use') {
272
+ const input = block.input ?? {};
273
+ const detail = input.command ?? input.file_path ?? input.pattern ?? input.query ?? '';
274
+ const short = typeof detail === 'string' ? detail.slice(0, 120) : '';
275
+ parts.push(`${C.cyan}▶ ${block.name ?? 'tool'}${C.reset}${short ? ` ${C.dim}${short}${C.reset}` : ''}`);
276
+ } else if (block.type === 'text' && block.text) {
277
+ // With --include-partial-messages, we get the full accumulated text each time.
278
+ // Only print the NEW portion to avoid re-printing everything.
279
+ let text = block.text;
280
+ if (text.startsWith(lastAssistantText) && lastAssistantText.length > 0) {
281
+ text = text.slice(lastAssistantText.length);
282
+ }
283
+ if (text) {
284
+ lastAssistantText = block.text;
285
+ parts.push(text);
286
+ }
287
+ } else if (block.type === 'thinking' && block.thinking) {
288
+ // Thinking blocks — show abbreviated
289
+ const preview = block.thinking.slice(0, 200).replace(/\n/g, ' ');
290
+ parts.push(`${C.dim}💭 ${preview}${preview.length < block.thinking.length ? '...' : ''}${C.reset}`);
291
+ }
292
+ }
293
+ if (parts.length) return parts.join('\n') + '\n';
294
+ return null;
257
295
  }
258
- if (typeof msg.content === 'string' && msg.content) {
259
- return `${C.bold}${msg.content}${C.reset}\n`;
296
+
297
+ // Fallback: string content (shouldn't happen with stream-json, but just in case)
298
+ if (typeof content === 'string' && content) {
299
+ return `${content}\n`;
260
300
  }
261
301
  return null;
262
302
  }
263
303
 
264
- // Tool result
265
- if (event.type === 'result' && event.subtype === 'tool_result') {
266
- const isErr = event.is_error;
267
- const icon = isErr ? `${C.red}✗` : `${C.green}✓`;
268
- const content = typeof event.content === 'string' ? event.content : '';
269
- const preview = content.slice(0, 150).replace(/\n/g, ' ');
270
- return `${icon} ${C.dim}${preview}${C.reset}\n`;
304
+ // User message — contains tool results
305
+ if (type === 'user') {
306
+ // Reset text tracking on new turn
307
+ lastAssistantText = '';
308
+ const content = event.message?.content;
309
+ if (!Array.isArray(content)) return null;
310
+ for (const block of content) {
311
+ if (block.type === 'tool_result') {
312
+ const isErr = block.is_error;
313
+ const icon = isErr ? `${C.red}✗` : `${C.green}✓`;
314
+ const text = typeof block.content === 'string' ? block.content : '';
315
+ const preview = text.slice(0, 150).replace(/\n/g, ' ');
316
+ return `${icon} ${C.dim}${preview}${C.reset}\n`;
317
+ }
318
+ }
319
+ return null;
271
320
  }
272
321
 
322
+ // Final result — silent (parsed separately for structured output)
323
+ if (type === 'result') return null;
324
+
325
+ // Unknown event type — show for debugging
273
326
  return null;
274
327
  }
275
328
 
@@ -286,6 +339,7 @@ function runClaude(projectDir, opts, { resumeSessionId = null, resumePrompt = nu
286
339
  baseFlags.push('-p');
287
340
  baseFlags.push('--output-format', 'stream-json');
288
341
  baseFlags.push('--verbose');
342
+ baseFlags.push('--include-partial-messages');
289
343
  baseFlags.push('--max-turns', String(opts.maxTurns));
290
344
  baseFlags.push('--json-schema', RESULT_SCHEMA);
291
345
 
@@ -351,6 +405,9 @@ function runClaude(projectDir, opts, { resumeSessionId = null, resumePrompt = nu
351
405
  }
352
406
 
353
407
  // Rate limit detection from stream events
408
+ if (event.type === 'rate_limit_event') {
409
+ isRateLimit = true;
410
+ }
354
411
  if (event.type === 'error') {
355
412
  const msg = (event.error?.message ?? event.message ?? '').toLowerCase();
356
413
  if (msg.includes('rate limit') || msg.includes('rate_limit') || msg.includes('overloaded')) {
@@ -360,7 +417,9 @@ function runClaude(projectDir, opts, { resumeSessionId = null, resumePrompt = nu
360
417
 
361
418
  // Display formatted event
362
419
  const formatted = formatStreamEvent(event);
363
- if (formatted) process.stdout.write(formatted);
420
+ if (formatted) {
421
+ process.stdout.write(formatted);
422
+ }
364
423
  } catch {
365
424
  // Non-JSON line — display as-is
366
425
  process.stdout.write(line + '\n');
@@ -66,16 +66,23 @@ function toDockerPath(p) {
66
66
  * Does NOT use shell: true — on Windows, cmd.exe splits arguments after
67
67
  * '-c' into separate words, breaking 'bash -c "node ..."' commands.
68
68
  * Without shell, Node.js passes each array element as a distinct argv entry.
69
+ *
70
+ * cwd is set to DOCKER_DIR so Docker Compose finds docker-compose.yml
71
+ * in its default location. Volume/build paths in the compose file are
72
+ * resolved relative to the compose file's directory, not cwd.
69
73
  */
70
74
  function compose(args, opts = {}) {
71
- const composeFile = join(DOCKER_DIR, 'docker-compose.yml');
72
- const files = ['-f', composeFile];
73
- if (existsSync(AUTH_COMPOSE)) files.push('-f', AUTH_COMPOSE);
74
- return spawnSync('docker', ['compose', ...files, ...args], {
75
- cwd: PROJECT_DIR,
75
+ const files = [];
76
+ if (existsSync(AUTH_COMPOSE)) files.push('-f', 'docker-compose.yml', '-f', AUTH_COMPOSE);
77
+ const result = spawnSync('docker', ['compose', ...files, ...args], {
78
+ cwd: DOCKER_DIR,
76
79
  stdio: opts.capture ? 'pipe' : 'inherit',
77
80
  env: { ...process.env },
78
81
  });
82
+ if (!opts.capture && result.status !== 0 && result.status !== null) {
83
+ error(`docker compose ${args[0]} failed (exit code ${result.status})`);
84
+ }
85
+ return result;
79
86
  }
80
87
 
81
88
  function cleanup() {
@@ -247,7 +254,11 @@ info('Docker is ready.');
247
254
 
248
255
  // 2. Build
249
256
  step('2/4 Building container image...');
250
- compose(opts.rebuild ? ['build', '--no-cache'] : ['build', '-q']);
257
+ const buildResult = compose(opts.rebuild ? ['build', '--no-cache'] : ['build', '-q']);
258
+ if (buildResult.status !== 0) {
259
+ error('Docker image build failed. Fix the errors above and re-run.');
260
+ process.exit(1);
261
+ }
251
262
  info('Image built.');
252
263
 
253
264
  // 3. Auth
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-workspace",
3
- "version": "1.1.17",
3
+ "version": "1.1.19",
4
4
  "description": "Scaffold a project with Claude Code agents for autonomous AI-driven development",
5
5
  "type": "module",
6
6
  "bin": {