@jjlabsio/claude-crew 0.1.13 → 0.1.15
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 +6 -2
- package/hud/index.mjs +90 -12
- package/package.json +1 -1
- package/scripts/setup-hud.mjs +42 -35
|
@@ -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.15",
|
|
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.15"
|
|
32
32
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-crew",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "1인 SaaS 개발자를 위한 멀티 에이전트 오케스트레이션 — 개발, 마케팅, 일정을 한 대화에서 통합 관리",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Jaejin Song",
|
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
"./agents/planner.md",
|
|
22
22
|
"./agents/dev.md",
|
|
23
23
|
"./agents/qa.md",
|
|
24
|
-
"./agents/
|
|
24
|
+
"./agents/code-reviewer.md",
|
|
25
|
+
"./agents/techlead.md",
|
|
26
|
+
"./agents/researcher.md",
|
|
27
|
+
"./agents/explorer.md",
|
|
28
|
+
"./agents/plan-evaluator.md"
|
|
25
29
|
],
|
|
26
30
|
"skills": [
|
|
27
31
|
"./skills/"
|
package/hud/index.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import { execSync } from 'node:child_process';
|
|
|
13
13
|
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
14
14
|
import { join, dirname, basename } from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { homedir } from 'node:os';
|
|
16
17
|
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
18
19
|
// ANSI helpers
|
|
@@ -43,10 +44,37 @@ async function readStdin(timeoutMs = 1000) {
|
|
|
43
44
|
});
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Project installation info from installed_plugins.json
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
function getProjectInstallInfo(projectRoot) {
|
|
51
|
+
try {
|
|
52
|
+
const pluginsJsonPath = join(homedir(), '.claude', 'plugins', 'installed_plugins.json');
|
|
53
|
+
if (!existsSync(pluginsJsonPath)) return null;
|
|
54
|
+
const data = JSON.parse(readFileSync(pluginsJsonPath, 'utf-8'));
|
|
55
|
+
const crewEntries = data.plugins?.['claude-crew@claude-crew'] || [];
|
|
56
|
+
return crewEntries.find(e => e.projectPath === projectRoot) || null;
|
|
57
|
+
} catch { return null; }
|
|
58
|
+
}
|
|
59
|
+
|
|
46
60
|
// ---------------------------------------------------------------------------
|
|
47
61
|
// Version
|
|
48
62
|
// ---------------------------------------------------------------------------
|
|
49
|
-
function getVersion() {
|
|
63
|
+
function getVersion(installInfo) {
|
|
64
|
+
// Read from the project-specific install path
|
|
65
|
+
if (installInfo?.installPath) {
|
|
66
|
+
try {
|
|
67
|
+
const pkgPath = join(installInfo.installPath, 'package.json');
|
|
68
|
+
if (existsSync(pkgPath)) {
|
|
69
|
+
return JSON.parse(readFileSync(pkgPath, 'utf-8')).version || '0.0.0';
|
|
70
|
+
}
|
|
71
|
+
} catch { /* ignore */ }
|
|
72
|
+
}
|
|
73
|
+
// Fallback to version field from install record
|
|
74
|
+
if (installInfo?.version && installInfo.version !== 'unknown') {
|
|
75
|
+
return installInfo.version;
|
|
76
|
+
}
|
|
77
|
+
// Final fallback: own package.json (dev/local run)
|
|
50
78
|
try {
|
|
51
79
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
52
80
|
const pkgPath = join(__dirname, '..', 'package.json');
|
|
@@ -159,6 +187,35 @@ function colorizeContext(pct) {
|
|
|
159
187
|
return `ctx:${color(`${pct}%`)}`;
|
|
160
188
|
}
|
|
161
189
|
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// Rate limits (5h / weekly)
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
function getRateLimits(stdin) {
|
|
194
|
+
const rl = stdin?.rate_limits;
|
|
195
|
+
if (!rl) return null;
|
|
196
|
+
const parse = (v) => {
|
|
197
|
+
if (v == null) return null;
|
|
198
|
+
const n = typeof v === 'number' ? v : parseFloat(v);
|
|
199
|
+
return isNaN(n) ? null : Math.round(Math.min(Math.max(n, 0), 100));
|
|
200
|
+
};
|
|
201
|
+
const fiveHour = parse(rl.five_hour?.used_percentage);
|
|
202
|
+
const sevenDay = parse(rl.seven_day?.used_percentage);
|
|
203
|
+
if (fiveHour == null && sevenDay == null) return null;
|
|
204
|
+
return { fiveHour, sevenDay };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function colorizeRateLimits(limits) {
|
|
208
|
+
if (!limits) return null;
|
|
209
|
+
const colorize = (pct) => {
|
|
210
|
+
const color = pct >= 85 ? red : pct >= 70 ? yellow : green;
|
|
211
|
+
return color(`${pct}%`);
|
|
212
|
+
};
|
|
213
|
+
const parts = [];
|
|
214
|
+
if (limits.fiveHour != null) parts.push(`5h:${colorize(limits.fiveHour)}`);
|
|
215
|
+
if (limits.sevenDay != null) parts.push(`weekly:${colorize(limits.sevenDay)}`);
|
|
216
|
+
return parts.join(' ');
|
|
217
|
+
}
|
|
218
|
+
|
|
162
219
|
// ---------------------------------------------------------------------------
|
|
163
220
|
// Transcript parsing (agents + skills)
|
|
164
221
|
// ---------------------------------------------------------------------------
|
|
@@ -233,7 +290,10 @@ function parseTranscript(transcriptPath) {
|
|
|
233
290
|
if (entry.type === 'tool_result') {
|
|
234
291
|
const toolUseId = entry.tool_use_id;
|
|
235
292
|
if (toolUseId && agentMap.has(toolUseId)) {
|
|
236
|
-
agentMap.get(toolUseId)
|
|
293
|
+
const agent = agentMap.get(toolUseId);
|
|
294
|
+
agent.status = 'completed';
|
|
295
|
+
const ts = entry.timestamp || lastTimestamp;
|
|
296
|
+
if (ts) agent.endTime = new Date(ts);
|
|
237
297
|
}
|
|
238
298
|
}
|
|
239
299
|
if (entry.type === 'user') {
|
|
@@ -243,8 +303,10 @@ function parseTranscript(transcriptPath) {
|
|
|
243
303
|
if (block.type === 'tool_result') {
|
|
244
304
|
const toolUseId = block.tool_use_id;
|
|
245
305
|
if (toolUseId && agentMap.has(toolUseId)) {
|
|
246
|
-
agentMap.get(toolUseId)
|
|
247
|
-
|
|
306
|
+
const agent = agentMap.get(toolUseId);
|
|
307
|
+
agent.status = 'completed';
|
|
308
|
+
const ts = entry.timestamp || lastTimestamp;
|
|
309
|
+
if (ts) agent.endTime = new Date(ts);
|
|
248
310
|
}
|
|
249
311
|
}
|
|
250
312
|
}
|
|
@@ -279,14 +341,18 @@ function shortModelName(model) {
|
|
|
279
341
|
// ---------------------------------------------------------------------------
|
|
280
342
|
// Agent duration formatting
|
|
281
343
|
// ---------------------------------------------------------------------------
|
|
282
|
-
function formatAgentDuration(startTime) {
|
|
283
|
-
if (!startTime) return '
|
|
284
|
-
const ms = Date.
|
|
344
|
+
function formatAgentDuration(startTime, endTime) {
|
|
345
|
+
if (!startTime) return '';
|
|
346
|
+
const ms = (endTime ?? new Date()).getTime() - startTime.getTime();
|
|
347
|
+
if (ms < 1000) return '<1s';
|
|
285
348
|
const seconds = Math.floor(ms / 1000);
|
|
286
|
-
const minutes = Math.floor(seconds / 60);
|
|
287
|
-
if (seconds < 10) return '';
|
|
288
349
|
if (seconds < 60) return `${seconds}s`;
|
|
289
|
-
|
|
350
|
+
const minutes = Math.floor(seconds / 60);
|
|
351
|
+
const secs = seconds % 60;
|
|
352
|
+
if (minutes < 60) return `${minutes}m${secs}s`;
|
|
353
|
+
const hours = Math.floor(minutes / 60);
|
|
354
|
+
const mins = minutes % 60;
|
|
355
|
+
return `${hours}h${mins}m`;
|
|
290
356
|
}
|
|
291
357
|
|
|
292
358
|
// ---------------------------------------------------------------------------
|
|
@@ -309,7 +375,7 @@ function renderAgentsMultiLine(agents, maxLines = 5) {
|
|
|
309
375
|
const rawType = a.type.includes(':') ? a.type.split(':').pop() : a.type;
|
|
310
376
|
const name = rawType.padEnd(7);
|
|
311
377
|
const model = shortModelName(a.model).padEnd(8);
|
|
312
|
-
const duration = formatAgentDuration(a.startTime).padStart(
|
|
378
|
+
const duration = formatAgentDuration(a.startTime, a.endTime).padStart(6);
|
|
313
379
|
const desc = a.description.length > 40 ? a.description.slice(0, 37) + '...' : a.description;
|
|
314
380
|
|
|
315
381
|
detailLines.push(
|
|
@@ -364,7 +430,15 @@ async function main() {
|
|
|
364
430
|
}
|
|
365
431
|
|
|
366
432
|
const cwd = stdin.cwd || process.cwd();
|
|
367
|
-
|
|
433
|
+
|
|
434
|
+
// Find git project root for reliable matching against installed_plugins.json
|
|
435
|
+
const projectRoot = gitExec('git rev-parse --show-toplevel', cwd) || cwd;
|
|
436
|
+
|
|
437
|
+
// Only show HUD if claude-crew is installed in this project
|
|
438
|
+
const installInfo = getProjectInstallInfo(projectRoot);
|
|
439
|
+
if (!installInfo) return;
|
|
440
|
+
|
|
441
|
+
const version = getVersion(installInfo);
|
|
368
442
|
|
|
369
443
|
// --- Top line ---
|
|
370
444
|
const topElements = [];
|
|
@@ -405,6 +479,10 @@ async function main() {
|
|
|
405
479
|
|
|
406
480
|
midElements.push(colorizeSession(transcript.sessionStart));
|
|
407
481
|
|
|
482
|
+
const rateLimits = getRateLimits(stdin);
|
|
483
|
+
const rateLimitsStr = colorizeRateLimits(rateLimits);
|
|
484
|
+
if (rateLimitsStr) midElements.push(rateLimitsStr);
|
|
485
|
+
|
|
408
486
|
// --- Output ---
|
|
409
487
|
const outputLines = [];
|
|
410
488
|
outputLines.push(topElements.join(SEPARATOR));
|
package/package.json
CHANGED
package/scripts/setup-hud.mjs
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* CREW Session Start Hook
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Writes statusLine to the project's .claude/settings.local.json so the HUD
|
|
6
|
+
* only appears in projects where claude-crew is installed.
|
|
7
|
+
* Also removes the legacy global statusLine from ~/.claude/settings.json.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { execSync } from 'node:child_process';
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
11
12
|
import { join } from 'node:path';
|
|
12
13
|
import { homedir } from 'node:os';
|
|
13
14
|
|
|
@@ -26,61 +27,67 @@ async function readStdin(timeoutMs = 3000) {
|
|
|
26
27
|
});
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
function gitExec(cmd, cwd) {
|
|
31
|
+
try {
|
|
32
|
+
return execSync(cmd, { cwd, encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
33
|
+
} catch { return null; }
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
// ---------------------------------------------------------------------------
|
|
30
37
|
// Main
|
|
31
38
|
// ---------------------------------------------------------------------------
|
|
32
39
|
async function main() {
|
|
33
|
-
|
|
34
|
-
await readStdin();
|
|
40
|
+
const raw = await readStdin();
|
|
35
41
|
|
|
36
|
-
const configDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
|
|
37
|
-
const settingsPath = join(configDir, 'settings.json');
|
|
38
42
|
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
|
|
39
|
-
|
|
40
43
|
if (!pluginRoot) {
|
|
41
|
-
// Not running as a plugin — skip
|
|
42
44
|
console.log(JSON.stringify({ continue: true }));
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
let cwd = process.cwd();
|
|
49
|
+
if (raw) {
|
|
50
|
+
try { cwd = JSON.parse(raw).cwd || cwd; } catch { /* ignore */ }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Use git toplevel as the reliable project root
|
|
54
|
+
const projectRoot = gitExec('git rev-parse --show-toplevel', cwd) || cwd;
|
|
55
|
+
|
|
46
56
|
const hudCommand = `node "${pluginRoot}/hud/index.mjs"`;
|
|
57
|
+
const localSettingsPath = join(projectRoot, '.claude', 'settings.local.json');
|
|
47
58
|
|
|
48
59
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
// --- Write statusLine to project-level settings.local.json ---
|
|
61
|
+
let localSettings = {};
|
|
62
|
+
if (existsSync(localSettingsPath)) {
|
|
63
|
+
try { localSettings = JSON.parse(readFileSync(localSettingsPath, 'utf-8')); } catch { /* ignore */ }
|
|
52
64
|
}
|
|
53
65
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
console.log(JSON.stringify({ continue: true }));
|
|
59
|
-
return;
|
|
66
|
+
if (localSettings.statusLine?.command !== hudCommand) {
|
|
67
|
+
localSettings.statusLine = { type: 'command', command: hudCommand };
|
|
68
|
+
mkdirSync(join(projectRoot, '.claude'), { recursive: true });
|
|
69
|
+
writeFileSync(localSettingsPath, JSON.stringify(localSettings, null, 2));
|
|
60
70
|
}
|
|
61
71
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
+
}
|
|
69
83
|
|
|
70
|
-
console.log(JSON.stringify({
|
|
71
|
-
continue: true,
|
|
72
|
-
hookSpecificOutput: {
|
|
73
|
-
hookEventName: 'SessionStart',
|
|
74
|
-
additionalContext: 'CREW HUD가 자동 설정되었습니다. 다음 세션부터 statusline에 표시됩니다.',
|
|
75
|
-
},
|
|
76
|
-
}));
|
|
84
|
+
console.log(JSON.stringify({ continue: true }));
|
|
77
85
|
} catch (e) {
|
|
78
|
-
// Non-fatal — don't block session start
|
|
79
86
|
console.log(JSON.stringify({
|
|
80
87
|
continue: true,
|
|
81
88
|
hookSpecificOutput: {
|
|
82
89
|
hookEventName: 'SessionStart',
|
|
83
|
-
additionalContext: `CREW HUD 자동 설정 실패: ${e.message}
|
|
90
|
+
additionalContext: `CREW HUD 자동 설정 실패: ${e.message}`,
|
|
84
91
|
},
|
|
85
92
|
}));
|
|
86
93
|
}
|