@jjlabsio/claude-crew 0.1.12 → 0.1.14
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 +65 -4
- 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.14",
|
|
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.14"
|
|
32
32
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-crew",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
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
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { execSync } from 'node:child_process';
|
|
13
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
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');
|
|
@@ -57,6 +85,28 @@ function getVersion() {
|
|
|
57
85
|
return '0.0.0';
|
|
58
86
|
}
|
|
59
87
|
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Agent definitions (subagent_type → model)
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
function loadAgentModels() {
|
|
92
|
+
try {
|
|
93
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
94
|
+
const agentsDir = join(__dirname, '..', 'agents');
|
|
95
|
+
const models = {};
|
|
96
|
+
const files = readdirSync(agentsDir).filter(f => f.endsWith('.md'));
|
|
97
|
+
for (const file of files) {
|
|
98
|
+
const content = readFileSync(join(agentsDir, file), 'utf-8');
|
|
99
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
100
|
+
if (!fmMatch) continue;
|
|
101
|
+
const fm = fmMatch[1];
|
|
102
|
+
const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim();
|
|
103
|
+
const model = fm.match(/^model:\s*(.+)$/m)?.[1]?.trim();
|
|
104
|
+
if (name && model) models[name] = model;
|
|
105
|
+
}
|
|
106
|
+
return models;
|
|
107
|
+
} catch { return {}; }
|
|
108
|
+
}
|
|
109
|
+
|
|
60
110
|
// ---------------------------------------------------------------------------
|
|
61
111
|
// Git helpers
|
|
62
112
|
// ---------------------------------------------------------------------------
|
|
@@ -144,6 +194,8 @@ function parseTranscript(transcriptPath) {
|
|
|
144
194
|
const result = { agents: [], lastSkill: null, sessionStart: null };
|
|
145
195
|
if (!transcriptPath || !existsSync(transcriptPath)) return result;
|
|
146
196
|
|
|
197
|
+
const agentModels = loadAgentModels();
|
|
198
|
+
|
|
147
199
|
try {
|
|
148
200
|
const content = readFileSync(transcriptPath, 'utf-8');
|
|
149
201
|
const lines = content.split('\n').filter(Boolean);
|
|
@@ -178,7 +230,8 @@ function parseTranscript(transcriptPath) {
|
|
|
178
230
|
if (id) {
|
|
179
231
|
const input = block.input || {};
|
|
180
232
|
const agentType = input.subagent_type || input.type || 'general';
|
|
181
|
-
const
|
|
233
|
+
const rawType = agentType.replace(/^claude-crew:/, '');
|
|
234
|
+
const model = input.model || agentModels[rawType] || null;
|
|
182
235
|
const description = input.description || input.prompt?.slice(0, 50) || '';
|
|
183
236
|
const ts = entry.timestamp || lastTimestamp;
|
|
184
237
|
agentMap.set(id, {
|
|
@@ -339,7 +392,15 @@ async function main() {
|
|
|
339
392
|
}
|
|
340
393
|
|
|
341
394
|
const cwd = stdin.cwd || process.cwd();
|
|
342
|
-
|
|
395
|
+
|
|
396
|
+
// Find git project root for reliable matching against installed_plugins.json
|
|
397
|
+
const projectRoot = gitExec('git rev-parse --show-toplevel', cwd) || cwd;
|
|
398
|
+
|
|
399
|
+
// Only show HUD if claude-crew is installed in this project
|
|
400
|
+
const installInfo = getProjectInstallInfo(projectRoot);
|
|
401
|
+
if (!installInfo) return;
|
|
402
|
+
|
|
403
|
+
const version = getVersion(installInfo);
|
|
343
404
|
|
|
344
405
|
// --- Top line ---
|
|
345
406
|
const topElements = [];
|
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
|
}
|