@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/hud/index.mjs +163 -35
- package/package.json +1 -1
- package/scripts/setup-hud.mjs +0 -12
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"name": "claude-crew",
|
|
12
12
|
"source": "./",
|
|
13
13
|
"description": "오케스트레이터 + PM, 플래너, 개발, QA, 마케팅 에이전트 팀으로 단일 제품의 개발과 마케팅을 통합 관리",
|
|
14
|
-
"version": "0.1.
|
|
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.
|
|
31
|
+
"version": "0.1.16"
|
|
32
32
|
}
|
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
|
-
//
|
|
249
|
+
// Process tool_use blocks from assistant messages
|
|
251
250
|
if (entry.type === 'tool_use' || entry.type === 'assistant') {
|
|
252
|
-
const
|
|
253
|
-
if (Array.isArray(
|
|
254
|
-
for (const block of
|
|
255
|
-
if (block.type
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
|
301
|
-
if (Array.isArray(
|
|
302
|
-
for (const block of
|
|
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
package/scripts/setup-hud.mjs
CHANGED
|
@@ -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({
|