@jjlabsio/claude-crew 0.1.15 → 0.1.16

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.
@@ -11,7 +11,7 @@
11
11
  "name": "claude-crew",
12
12
  "source": "./",
13
13
  "description": "오케스트레이터 + PM, 플래너, 개발, QA, 마케팅 에이전트 팀으로 단일 제품의 개발과 마케팅을 통합 관리",
14
- "version": "0.1.15",
14
+ "version": "0.1.16",
15
15
  "author": {
16
16
  "name": "Jaejin Song",
17
17
  "email": "wowlxx28@gmail.com"
@@ -28,5 +28,5 @@
28
28
  "category": "workflow"
29
29
  }
30
30
  ],
31
- "version": "0.1.15"
31
+ "version": "0.1.16"
32
32
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-crew",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "1인 SaaS 개발자를 위한 멀티 에이전트 오케스트레이션 — 개발, 마케팅, 일정을 한 대화에서 통합 관리",
5
5
  "author": {
6
6
  "name": "Jaejin Song",
package/hud/index.mjs CHANGED
@@ -220,7 +220,7 @@ function colorizeRateLimits(limits) {
220
220
  // Transcript parsing (agents + skills)
221
221
  // ---------------------------------------------------------------------------
222
222
  function parseTranscript(transcriptPath) {
223
- const result = { agents: [], lastSkill: null, sessionStart: null };
223
+ const result = { agents: [], todos: [], lastSkill: null, sessionStart: null };
224
224
  if (!transcriptPath || !existsSync(transcriptPath)) return result;
225
225
 
226
226
  const agentModels = loadAgentModels();
@@ -229,55 +229,120 @@ function parseTranscript(transcriptPath) {
229
229
  const content = readFileSync(transcriptPath, 'utf-8');
230
230
  const lines = content.split('\n').filter(Boolean);
231
231
 
232
- // Map of tool_use_id -> agent info
233
232
  const agentMap = new Map();
233
+ const latestTodos = [];
234
+ const taskIdToIndex = new Map();
234
235
  let lastTimestamp = null;
235
236
 
236
237
  for (const line of lines) {
237
238
  let entry;
238
239
  try { entry = JSON.parse(line); } catch { continue; }
239
240
 
240
- // Track last known timestamp
241
241
  if (entry.timestamp) {
242
242
  lastTimestamp = entry.timestamp;
243
243
  }
244
244
 
245
- // Session start time
246
245
  if (!result.sessionStart && entry.timestamp) {
247
246
  result.sessionStart = new Date(entry.timestamp);
248
247
  }
249
248
 
250
- // Track agents
249
+ // Process tool_use blocks from assistant messages
251
250
  if (entry.type === 'tool_use' || entry.type === 'assistant') {
252
- const content = entry.message?.content;
253
- if (Array.isArray(content)) {
254
- for (const block of content) {
255
- if (block.type === 'tool_use') {
256
- // Agent start
257
- if (block.name === 'Agent' || block.name === 'proxy_Agent') {
258
- const id = block.id;
259
- if (id) {
260
- const input = block.input || {};
261
- const agentType = input.subagent_type || input.type || 'general';
262
- const rawType = agentType.replace(/^claude-crew:/, '');
263
- const model = input.model || agentModels[rawType] || null;
264
- const description = input.description || input.prompt?.slice(0, 50) || '';
265
- const ts = entry.timestamp || lastTimestamp;
266
- agentMap.set(id, {
267
- id,
268
- type: agentType,
269
- model,
270
- description,
271
- startTime: ts ? new Date(ts) : null,
272
- status: 'running',
273
- });
251
+ const blocks = entry.message?.content;
252
+ if (Array.isArray(blocks)) {
253
+ for (const block of blocks) {
254
+ if (block.type !== 'tool_use') continue;
255
+
256
+ // Agent start
257
+ if (block.name === 'Agent' || block.name === 'proxy_Agent') {
258
+ const id = block.id;
259
+ if (id) {
260
+ const input = block.input || {};
261
+ const agentType = input.subagent_type || input.type || 'general';
262
+ const rawType = agentType.replace(/^claude-crew:/, '');
263
+ const model = input.model || agentModels[rawType] || null;
264
+ const description = input.description || input.prompt?.slice(0, 50) || '';
265
+ const ts = entry.timestamp || lastTimestamp;
266
+ agentMap.set(id, {
267
+ id,
268
+ type: agentType,
269
+ model,
270
+ description,
271
+ startTime: ts ? new Date(ts) : null,
272
+ status: 'running',
273
+ });
274
+ }
275
+ }
276
+
277
+ // Skill invocation
278
+ if (block.name === 'Skill' || block.name === 'proxy_Skill') {
279
+ const skillName = block.input?.skill || block.input?.name;
280
+ if (skillName) {
281
+ result.lastSkill = skillName;
282
+ }
283
+ }
284
+
285
+ // TodoWrite — full replacement of todo list
286
+ if (block.name === 'TodoWrite') {
287
+ const input = block.input || {};
288
+ if (input.todos && Array.isArray(input.todos)) {
289
+ const contentToTaskIds = new Map();
290
+ for (const [taskId, idx] of taskIdToIndex) {
291
+ if (idx < latestTodos.length) {
292
+ const c = latestTodos[idx].content;
293
+ const ids = contentToTaskIds.get(c) ?? [];
294
+ ids.push(taskId);
295
+ contentToTaskIds.set(c, ids);
296
+ }
297
+ }
298
+
299
+ latestTodos.length = 0;
300
+ taskIdToIndex.clear();
301
+ latestTodos.push(...input.todos);
302
+
303
+ for (let i = 0; i < latestTodos.length; i++) {
304
+ const ids = contentToTaskIds.get(latestTodos[i].content);
305
+ if (ids) {
306
+ for (const taskId of ids) {
307
+ taskIdToIndex.set(taskId, i);
308
+ }
309
+ contentToTaskIds.delete(latestTodos[i].content);
310
+ }
274
311
  }
275
312
  }
276
- // Skill invocation
277
- if (block.name === 'Skill' || block.name === 'proxy_Skill') {
278
- const skillName = block.input?.skill || block.input?.name;
279
- if (skillName) {
280
- result.lastSkill = skillName;
313
+ }
314
+
315
+ // TaskCreate append a single task
316
+ if (block.name === 'TaskCreate') {
317
+ const input = block.input || {};
318
+ const subject = typeof input.subject === 'string' ? input.subject : '';
319
+ const description = typeof input.description === 'string' ? input.description : '';
320
+ const todoContent = subject || description || 'Untitled task';
321
+ const status = normalizeTaskStatus(input.status) ?? 'pending';
322
+ latestTodos.push({ content: todoContent, status });
323
+
324
+ const taskId = typeof input.taskId === 'string' || typeof input.taskId === 'number'
325
+ ? String(input.taskId)
326
+ : block.id;
327
+ if (taskId) {
328
+ taskIdToIndex.set(taskId, latestTodos.length - 1);
329
+ }
330
+ }
331
+
332
+ // TaskUpdate — update status/content of existing task
333
+ if (block.name === 'TaskUpdate') {
334
+ const input = block.input || {};
335
+ const index = resolveTaskIndex(input.taskId, taskIdToIndex, latestTodos);
336
+ if (index !== null) {
337
+ const status = normalizeTaskStatus(input.status);
338
+ if (status) {
339
+ latestTodos[index] = { ...latestTodos[index], status };
340
+ }
341
+ const subject = typeof input.subject === 'string' ? input.subject : '';
342
+ const description = typeof input.description === 'string' ? input.description : '';
343
+ const newContent = subject || description;
344
+ if (newContent) {
345
+ latestTodos[index] = { ...latestTodos[index], content: newContent };
281
346
  }
282
347
  }
283
348
  }
@@ -297,9 +362,9 @@ function parseTranscript(transcriptPath) {
297
362
  }
298
363
  }
299
364
  if (entry.type === 'user') {
300
- const content = entry.message?.content;
301
- if (Array.isArray(content)) {
302
- for (const block of content) {
365
+ const blocks = entry.message?.content;
366
+ if (Array.isArray(blocks)) {
367
+ for (const block of blocks) {
303
368
  if (block.type === 'tool_result') {
304
369
  const toolUseId = block.tool_use_id;
305
370
  if (toolUseId && agentMap.has(toolUseId)) {
@@ -307,6 +372,7 @@ function parseTranscript(transcriptPath) {
307
372
  agent.status = 'completed';
308
373
  const ts = entry.timestamp || lastTimestamp;
309
374
  if (ts) agent.endTime = new Date(ts);
375
+ }
310
376
  }
311
377
  }
312
378
  }
@@ -321,11 +387,46 @@ function parseTranscript(transcriptPath) {
321
387
  if (a.startTime && (now - a.startTime.getTime()) > STALE_THRESHOLD_MS) return false;
322
388
  return true;
323
389
  });
390
+ result.todos = [...latestTodos];
324
391
  } catch { /* ignore parse errors */ }
325
392
 
326
393
  return result;
327
394
  }
328
395
 
396
+ // ---------------------------------------------------------------------------
397
+ // Todo helpers
398
+ // ---------------------------------------------------------------------------
399
+ function normalizeTaskStatus(status) {
400
+ if (typeof status !== 'string') return null;
401
+ switch (status) {
402
+ case 'pending':
403
+ case 'not_started':
404
+ return 'pending';
405
+ case 'in_progress':
406
+ case 'running':
407
+ return 'in_progress';
408
+ case 'completed':
409
+ case 'complete':
410
+ case 'done':
411
+ return 'completed';
412
+ default:
413
+ return null;
414
+ }
415
+ }
416
+
417
+ function resolveTaskIndex(taskId, taskIdToIndex, latestTodos) {
418
+ if (typeof taskId === 'string' || typeof taskId === 'number') {
419
+ const key = String(taskId);
420
+ const mapped = taskIdToIndex.get(key);
421
+ if (typeof mapped === 'number') return mapped;
422
+ if (/^\d+$/.test(key)) {
423
+ const numericIndex = parseInt(key, 10) - 1;
424
+ if (numericIndex >= 0 && numericIndex < latestTodos.length) return numericIndex;
425
+ }
426
+ }
427
+ return null;
428
+ }
429
+
329
430
  // ---------------------------------------------------------------------------
330
431
  // Agent model name (short)
331
432
  // ---------------------------------------------------------------------------
@@ -391,6 +492,29 @@ function renderAgentsMultiLine(agents, maxLines = 5) {
391
492
  return { headerPart, detailLines };
392
493
  }
393
494
 
495
+ // ---------------------------------------------------------------------------
496
+ // Todo progress rendering
497
+ // ---------------------------------------------------------------------------
498
+ function renderTodosLine(todos) {
499
+ if (!todos || todos.length === 0) return null;
500
+
501
+ const inProgress = todos.find(t => t.status === 'in_progress');
502
+ const completed = todos.filter(t => t.status === 'completed').length;
503
+ const total = todos.length;
504
+
505
+ if (!inProgress) {
506
+ if (completed === total && total > 0) {
507
+ return `${green('\u2713')} all done ${dim(`(${completed}/${total})`)}`;
508
+ }
509
+ return null;
510
+ }
511
+
512
+ const content = inProgress.content.length > 50
513
+ ? inProgress.content.slice(0, 47) + '...'
514
+ : inProgress.content;
515
+ return `${yellow('\u25b8')} ${content} ${dim(`(${completed}/${total})`)}`;
516
+ }
517
+
394
518
  // ---------------------------------------------------------------------------
395
519
  // Session duration
396
520
  // ---------------------------------------------------------------------------
@@ -483,11 +607,15 @@ async function main() {
483
607
  const rateLimitsStr = colorizeRateLimits(rateLimits);
484
608
  if (rateLimitsStr) midElements.push(rateLimitsStr);
485
609
 
610
+ // --- Todos line ---
611
+ const todosLine = renderTodosLine(transcript.todos);
612
+
486
613
  // --- Output ---
487
614
  const outputLines = [];
488
615
  outputLines.push(topElements.join(SEPARATOR));
489
616
  outputLines.push(midElements.join(SEPARATOR));
490
617
  outputLines.push(...detailLines);
618
+ if (todosLine) outputLines.push(todosLine);
491
619
 
492
620
  console.log(outputLines.filter(Boolean).join('\n'));
493
621
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlabsio/claude-crew",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "1인 SaaS 개발자를 위한 멀티 에이전트 오케스트레이션 — 개발, 마케팅, 일정을 한 대화에서 통합 관리",
5
5
  "author": "Jaejin Song <wowlxx28@gmail.com>",
6
6
  "license": "MIT",
@@ -69,18 +69,6 @@ async function main() {
69
69
  writeFileSync(localSettingsPath, JSON.stringify(localSettings, null, 2));
70
70
  }
71
71
 
72
- // --- Remove legacy global statusLine from ~/.claude/settings.json ---
73
- const globalSettingsPath = join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), 'settings.json');
74
- if (existsSync(globalSettingsPath)) {
75
- try {
76
- const globalSettings = JSON.parse(readFileSync(globalSettingsPath, 'utf-8'));
77
- if (globalSettings.statusLine) {
78
- delete globalSettings.statusLine;
79
- writeFileSync(globalSettingsPath, JSON.stringify(globalSettings, null, 2));
80
- }
81
- } catch { /* ignore */ }
82
- }
83
-
84
72
  console.log(JSON.stringify({ continue: true }));
85
73
  } catch (e) {
86
74
  console.log(JSON.stringify({