makecc 0.2.16 → 0.2.17
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/dist/client/assets/{index-Dlx6ST3a.css → index-1V3GQ_8q.css} +1 -1
- package/dist/client/assets/index-CzDuVmB7.js +67 -0
- package/dist/client/index.html +2 -2
- package/dist/server/index.js +1 -1
- package/dist/server/services/claudeCliService.js +1 -1
- package/dist/server/services/claudeService.js +1 -1
- package/dist/server/services/configLoaderService.js +7 -7
- package/dist/server/services/nodeSyncService.js +18 -41
- package/dist/server/services/terminalService.js +1 -1
- package/dist/server/services/workflowAIService.js +5 -5
- package/dist/server/services/workflowExecutionService.js +2 -2
- package/package.json +1 -1
- package/server/index.ts +1 -1
- package/server/services/claudeCliService.ts +1 -1
- package/server/services/claudeService.ts +1 -1
- package/server/services/configLoaderService.ts +9 -9
- package/server/services/nodeSyncService.ts +20 -50
- package/server/services/terminalService.ts +1 -1
- package/server/services/workflowAIService.ts +7 -7
- package/server/services/workflowExecutionService.ts +2 -2
- package/server/types.ts +1 -1
- package/dist/client/assets/index-Bir6nP2a.js +0 -150
package/dist/client/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>makecc</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-CzDuVmB7.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-1V3GQ_8q.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/dist/server/index.js
CHANGED
|
@@ -219,7 +219,7 @@ app.post('/api/generate/skill', async (req, res) => {
|
|
|
219
219
|
app.get('/api/load/claude-config', async (req, res) => {
|
|
220
220
|
try {
|
|
221
221
|
const config = await configLoaderService.loadAll();
|
|
222
|
-
console.log(`Loaded config: ${config.skills.length} skills, ${config.
|
|
222
|
+
console.log(`Loaded config: ${config.skills.length} skills, ${config.agents.length} agents, ` +
|
|
223
223
|
`${config.commands.length} commands, ${config.hooks.length} hooks`);
|
|
224
224
|
res.json(config);
|
|
225
225
|
}
|
|
@@ -161,7 +161,7 @@ function findNewFiles(before, after) {
|
|
|
161
161
|
*/
|
|
162
162
|
export function buildNodePrompt(nodeType, nodeData, previousResults) {
|
|
163
163
|
const lines = [];
|
|
164
|
-
if (nodeType === '
|
|
164
|
+
if (nodeType === 'agent') {
|
|
165
165
|
const role = nodeData.role || 'assistant';
|
|
166
166
|
const description = nodeData.description || '';
|
|
167
167
|
const systemPrompt = nodeData.systemPrompt || '';
|
|
@@ -21,7 +21,7 @@ export class ClaudeService {
|
|
|
21
21
|
switch (node.type) {
|
|
22
22
|
case 'input':
|
|
23
23
|
return this.executeInputNode(node.id, node.data);
|
|
24
|
-
case '
|
|
24
|
+
case 'agent':
|
|
25
25
|
return this.executeSubagentNode(node.id, node.data, onProgress);
|
|
26
26
|
case 'skill':
|
|
27
27
|
return this.executeSkillNode(node.id, node.data);
|
|
@@ -9,13 +9,13 @@ export class ConfigLoaderService {
|
|
|
9
9
|
* .claude/ 디렉토리에서 모든 설정 로드
|
|
10
10
|
*/
|
|
11
11
|
async loadAll() {
|
|
12
|
-
const [skills,
|
|
12
|
+
const [skills, agents, commands, hooks] = await Promise.all([
|
|
13
13
|
this.loadSkills(),
|
|
14
14
|
this.loadSubagents(),
|
|
15
15
|
this.loadCommands(),
|
|
16
16
|
this.loadHooks(),
|
|
17
17
|
]);
|
|
18
|
-
return { skills,
|
|
18
|
+
return { skills, agents, commands, hooks };
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* .claude/skills/ 에서 스킬 로드
|
|
@@ -56,7 +56,7 @@ export class ConfigLoaderService {
|
|
|
56
56
|
*/
|
|
57
57
|
async loadSubagents() {
|
|
58
58
|
const agentsDir = path.join(this.projectRoot, '.claude', 'agents');
|
|
59
|
-
const
|
|
59
|
+
const agents = [];
|
|
60
60
|
try {
|
|
61
61
|
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
62
62
|
for (const entry of entries) {
|
|
@@ -66,9 +66,9 @@ export class ConfigLoaderService {
|
|
|
66
66
|
const content = await fs.readFile(agentPath, 'utf-8');
|
|
67
67
|
const parsed = this.parseFrontmatter(content);
|
|
68
68
|
const agentName = entry.name.replace('.md', '');
|
|
69
|
-
|
|
70
|
-
id: `
|
|
71
|
-
type: '
|
|
69
|
+
agents.push({
|
|
70
|
+
id: `agent-${agentName}`,
|
|
71
|
+
type: 'agent',
|
|
72
72
|
label: parsed.frontmatter.name || agentName,
|
|
73
73
|
description: parsed.frontmatter.description || '',
|
|
74
74
|
tools: this.parseList(parsed.frontmatter.tools),
|
|
@@ -86,7 +86,7 @@ export class ConfigLoaderService {
|
|
|
86
86
|
catch {
|
|
87
87
|
// agents 디렉토리가 없으면 빈 배열 반환
|
|
88
88
|
}
|
|
89
|
-
return
|
|
89
|
+
return agents;
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
92
92
|
* .claude/commands/ 에서 커맨드 로드
|
|
@@ -13,10 +13,8 @@ export class NodeSyncService {
|
|
|
13
13
|
switch (node.type) {
|
|
14
14
|
case 'skill':
|
|
15
15
|
return await this.syncSkillNode(node);
|
|
16
|
-
case '
|
|
17
|
-
return await this.
|
|
18
|
-
case 'command':
|
|
19
|
-
return await this.syncCommandNode(node);
|
|
16
|
+
case 'agent':
|
|
17
|
+
return await this.syncAgentNode(node);
|
|
20
18
|
case 'hook':
|
|
21
19
|
return await this.syncHookNode(node);
|
|
22
20
|
case 'input':
|
|
@@ -50,16 +48,11 @@ export class NodeSyncService {
|
|
|
50
48
|
await fs.rm(skillPath, { recursive: true, force: true });
|
|
51
49
|
}
|
|
52
50
|
break;
|
|
53
|
-
case '
|
|
51
|
+
case 'agent':
|
|
54
52
|
const agentName = this.toKebabCase(node.label);
|
|
55
53
|
const agentPath = path.join(this.projectRoot, '.claude', 'agents', `${agentName}.md`);
|
|
56
54
|
await fs.unlink(agentPath).catch(() => { });
|
|
57
55
|
break;
|
|
58
|
-
case 'command':
|
|
59
|
-
const cmdName = node.commandName || this.toKebabCase(node.label);
|
|
60
|
-
const cmdPath = path.join(this.projectRoot, '.claude', 'commands', `${cmdName}.md`);
|
|
61
|
-
await fs.unlink(cmdPath).catch(() => { });
|
|
62
|
-
break;
|
|
63
56
|
case 'hook':
|
|
64
57
|
await this.removeHookFromSettings(node);
|
|
65
58
|
break;
|
|
@@ -76,11 +69,11 @@ export class NodeSyncService {
|
|
|
76
69
|
*/
|
|
77
70
|
async removeReferencesToNode(nodeId, nodeType, allNodes) {
|
|
78
71
|
for (const relatedNode of allNodes) {
|
|
79
|
-
if (relatedNode.type === '
|
|
72
|
+
if (relatedNode.type === 'agent') {
|
|
80
73
|
// 서브에이전트의 skills 배열에서 삭제된 노드 제거
|
|
81
74
|
if (relatedNode.skills?.includes(nodeId)) {
|
|
82
75
|
relatedNode.skills = relatedNode.skills.filter(s => s !== nodeId);
|
|
83
|
-
await this.
|
|
76
|
+
await this.syncAgentNode(relatedNode);
|
|
84
77
|
}
|
|
85
78
|
}
|
|
86
79
|
else if (relatedNode.type === 'skill') {
|
|
@@ -116,13 +109,13 @@ export class NodeSyncService {
|
|
|
116
109
|
const sourceId = this.getNodeIdentifier(sourceNode);
|
|
117
110
|
const targetId = this.getNodeIdentifier(targetNode);
|
|
118
111
|
// 서브에이전트 → 스킬 연결
|
|
119
|
-
if (sourceNode.type === '
|
|
112
|
+
if (sourceNode.type === 'agent' && targetNode.type === 'skill') {
|
|
120
113
|
// 에이전트의 skills 필드 업데이트
|
|
121
114
|
const skills = sourceNode.skills || [];
|
|
122
115
|
if (!skills.includes(targetId)) {
|
|
123
116
|
skills.push(targetId);
|
|
124
117
|
sourceNode.skills = skills;
|
|
125
|
-
await this.
|
|
118
|
+
await this.syncAgentNode(sourceNode);
|
|
126
119
|
}
|
|
127
120
|
// 스킬의 upstream 필드 업데이트
|
|
128
121
|
const upstream = targetNode.upstream || [];
|
|
@@ -150,13 +143,13 @@ export class NodeSyncService {
|
|
|
150
143
|
}
|
|
151
144
|
}
|
|
152
145
|
// 스킬 → 서브에이전트 연결
|
|
153
|
-
if (sourceNode.type === 'skill' && targetNode.type === '
|
|
146
|
+
if (sourceNode.type === 'skill' && targetNode.type === 'agent') {
|
|
154
147
|
// 에이전트의 upstream skills 필드 업데이트
|
|
155
148
|
const skills = targetNode.skills || [];
|
|
156
149
|
if (!skills.includes(sourceId)) {
|
|
157
150
|
skills.push(sourceId);
|
|
158
151
|
targetNode.skills = skills;
|
|
159
|
-
await this.
|
|
152
|
+
await this.syncAgentNode(targetNode);
|
|
160
153
|
}
|
|
161
154
|
// 스킬의 downstream 필드 업데이트
|
|
162
155
|
const downstream = sourceNode.downstream || [];
|
|
@@ -167,13 +160,13 @@ export class NodeSyncService {
|
|
|
167
160
|
}
|
|
168
161
|
}
|
|
169
162
|
// 서브에이전트 → 서브에이전트 연결
|
|
170
|
-
if (sourceNode.type === '
|
|
163
|
+
if (sourceNode.type === 'agent' && targetNode.type === 'agent') {
|
|
171
164
|
// source의 downstream agents
|
|
172
165
|
const sourceDownstream = sourceNode.skills || [];
|
|
173
166
|
if (!sourceDownstream.includes(targetId)) {
|
|
174
167
|
sourceDownstream.push(targetId);
|
|
175
168
|
sourceNode.skills = sourceDownstream;
|
|
176
|
-
await this.
|
|
169
|
+
await this.syncAgentNode(sourceNode);
|
|
177
170
|
}
|
|
178
171
|
}
|
|
179
172
|
return { success: true };
|
|
@@ -205,10 +198,10 @@ export class NodeSyncService {
|
|
|
205
198
|
const sourceId = this.getNodeIdentifier(sourceNode);
|
|
206
199
|
const targetId = this.getNodeIdentifier(targetNode);
|
|
207
200
|
// 서브에이전트 → 스킬 연결 해제
|
|
208
|
-
if (sourceNode.type === '
|
|
201
|
+
if (sourceNode.type === 'agent' && targetNode.type === 'skill') {
|
|
209
202
|
// 에이전트에서 스킬 제거
|
|
210
203
|
sourceNode.skills = (sourceNode.skills || []).filter(s => s !== targetId);
|
|
211
|
-
await this.
|
|
204
|
+
await this.syncAgentNode(sourceNode);
|
|
212
205
|
// 스킬에서 upstream 제거
|
|
213
206
|
targetNode.upstream = (targetNode.upstream || []).filter(s => s !== sourceId);
|
|
214
207
|
await this.syncSkillNode(targetNode);
|
|
@@ -221,16 +214,16 @@ export class NodeSyncService {
|
|
|
221
214
|
await this.syncSkillNode(targetNode);
|
|
222
215
|
}
|
|
223
216
|
// 스킬 → 서브에이전트 연결 해제
|
|
224
|
-
if (sourceNode.type === 'skill' && targetNode.type === '
|
|
217
|
+
if (sourceNode.type === 'skill' && targetNode.type === 'agent') {
|
|
225
218
|
targetNode.skills = (targetNode.skills || []).filter(s => s !== sourceId);
|
|
226
|
-
await this.
|
|
219
|
+
await this.syncAgentNode(targetNode);
|
|
227
220
|
sourceNode.downstream = (sourceNode.downstream || []).filter(s => s !== targetId);
|
|
228
221
|
await this.syncSkillNode(sourceNode);
|
|
229
222
|
}
|
|
230
223
|
// 서브에이전트 → 서브에이전트 연결 해제
|
|
231
|
-
if (sourceNode.type === '
|
|
224
|
+
if (sourceNode.type === 'agent' && targetNode.type === 'agent') {
|
|
232
225
|
sourceNode.skills = (sourceNode.skills || []).filter(s => s !== targetId);
|
|
233
|
-
await this.
|
|
226
|
+
await this.syncAgentNode(sourceNode);
|
|
234
227
|
}
|
|
235
228
|
return { success: true };
|
|
236
229
|
}
|
|
@@ -292,7 +285,7 @@ ${frontmatterStr}
|
|
|
292
285
|
await fs.writeFile(skillMdPath, content, 'utf-8');
|
|
293
286
|
return { success: true, path: skillPath };
|
|
294
287
|
}
|
|
295
|
-
async
|
|
288
|
+
async syncAgentNode(node) {
|
|
296
289
|
const agentName = this.toKebabCase(node.label);
|
|
297
290
|
const agentsDir = path.join(this.projectRoot, '.claude', 'agents');
|
|
298
291
|
const agentPath = path.join(agentsDir, `${agentName}.md`);
|
|
@@ -368,22 +361,6 @@ ${body}
|
|
|
368
361
|
}
|
|
369
362
|
return { frontmatter, body };
|
|
370
363
|
}
|
|
371
|
-
async syncCommandNode(node) {
|
|
372
|
-
const cmdName = node.commandName || this.toKebabCase(node.label);
|
|
373
|
-
const commandsDir = path.join(this.projectRoot, '.claude', 'commands');
|
|
374
|
-
const cmdPath = path.join(commandsDir, `${cmdName}.md`);
|
|
375
|
-
await fs.mkdir(commandsDir, { recursive: true });
|
|
376
|
-
const content = node.commandContent || `---
|
|
377
|
-
description: ${node.description || node.label}
|
|
378
|
-
---
|
|
379
|
-
|
|
380
|
-
${node.description || '커맨드 내용을 여기에 작성하세요'}
|
|
381
|
-
|
|
382
|
-
$ARGUMENTS
|
|
383
|
-
`;
|
|
384
|
-
await fs.writeFile(cmdPath, content, 'utf-8');
|
|
385
|
-
return { success: true, path: cmdPath };
|
|
386
|
-
}
|
|
387
364
|
async syncHookNode(node) {
|
|
388
365
|
const settingsPath = path.join(this.projectRoot, '.claude', 'settings.json');
|
|
389
366
|
// 기존 settings 읽기
|
|
@@ -28,7 +28,7 @@ function generateClaudePrompt(options) {
|
|
|
28
28
|
executionOrder.forEach((node) => {
|
|
29
29
|
if (node.type === 'input')
|
|
30
30
|
return;
|
|
31
|
-
if (node.type === '
|
|
31
|
+
if (node.type === 'agent') {
|
|
32
32
|
const data = node.data;
|
|
33
33
|
lines.push(`${stepNum}. **${data.label}** (${data.role})`);
|
|
34
34
|
if (data.description) {
|
|
@@ -22,7 +22,7 @@ const SYSTEM_PROMPT = `당신은 Claude Code 워크플로우 설계 전문가입
|
|
|
22
22
|
## 사용 가능한 공식 스킬
|
|
23
23
|
${AVAILABLE_SKILLS.map(s => `- ${s.id}: ${s.description}`).join('\n')}
|
|
24
24
|
|
|
25
|
-
## 사용 가능한 도구 (
|
|
25
|
+
## 사용 가능한 도구 (agent의 tools에 사용)
|
|
26
26
|
${AVAILABLE_TOOLS.join(', ')}
|
|
27
27
|
|
|
28
28
|
## 응답 형식 (JSON)
|
|
@@ -42,7 +42,7 @@ ${AVAILABLE_TOOLS.join(', ')}
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
|
-
"type": "
|
|
45
|
+
"type": "agent",
|
|
46
46
|
"label": "에이전트 이름",
|
|
47
47
|
"description": "에이전트 역할 설명",
|
|
48
48
|
"config": {
|
|
@@ -90,7 +90,7 @@ ${AVAILABLE_TOOLS.join(', ')}
|
|
|
90
90
|
1. 항상 input 노드로 시작하고 output 노드로 종료
|
|
91
91
|
2. 기존 공식 스킬로 가능하면 official 스킬 사용
|
|
92
92
|
3. 새로운 기능이 필요하면 custom 스킬 생성 (skillContent에 SKILL.md 형식)
|
|
93
|
-
4.
|
|
93
|
+
4. agent는 복잡한 추론이나 다단계 작업에 사용
|
|
94
94
|
5. edges의 from/to는 nodes 배열의 인덱스 (0부터 시작)
|
|
95
95
|
6. 순차적으로 연결되지 않아도 됨 (병렬 처리, 합류 가능)
|
|
96
96
|
7. systemPrompt는 구체적이고 실행 가능한 지시사항으로 작성
|
|
@@ -115,8 +115,8 @@ ${AVAILABLE_TOOLS.join(', ')}
|
|
|
115
115
|
"description": "데이터를 분석하고 보고서를 작성하는 워크플로우",
|
|
116
116
|
"nodes": [
|
|
117
117
|
{ "type": "input", "label": "데이터 파일", "description": "분석할 데이터 파일", "config": { "inputType": "file" } },
|
|
118
|
-
{ "type": "
|
|
119
|
-
{ "type": "
|
|
118
|
+
{ "type": "agent", "label": "데이터 분석가", "description": "데이터를 분석하고 인사이트 도출", "config": { "role": "analyst", "tools": ["Read", "Grep", "Glob"], "model": "sonnet", "systemPrompt": "주어진 데이터를 분석하여 핵심 인사이트를 도출하세요. 통계적 요약, 트렌드, 이상치를 파악하세요." } },
|
|
119
|
+
{ "type": "agent", "label": "보고서 작성자", "description": "분석 결과로 보고서 작성", "config": { "role": "writer", "tools": ["Read", "Write"], "model": "opus", "systemPrompt": "분석 결과를 바탕으로 경영진을 위한 간결하고 명확한 보고서를 작성하세요." } },
|
|
120
120
|
{ "type": "output", "label": "분석 보고서", "description": "최종 분석 보고서", "config": { "outputType": "document" } }
|
|
121
121
|
],
|
|
122
122
|
"edges": [{ "from": 0, "to": 1 }, { "from": 1, "to": 2 }, { "from": 2, "to": 3 }]
|
|
@@ -57,7 +57,7 @@ export class WorkflowExecutionService {
|
|
|
57
57
|
switch (node.type) {
|
|
58
58
|
case 'input':
|
|
59
59
|
return this.executeInputNode(node, context.inputs);
|
|
60
|
-
case '
|
|
60
|
+
case 'agent':
|
|
61
61
|
return this.executeSubagentNode(node, previousResults, onProgress, onLog);
|
|
62
62
|
case 'skill':
|
|
63
63
|
return this.executeSkillNode(node, previousResults, onProgress, onLog);
|
|
@@ -89,7 +89,7 @@ export class WorkflowExecutionService {
|
|
|
89
89
|
onProgress?.({ nodeId: node.id, status: 'running', progress: 20 });
|
|
90
90
|
onLog?.('info', `claude -c 실행 중: ${data.label} (${data.role})`);
|
|
91
91
|
// 프롬프트 생성
|
|
92
|
-
const prompt = buildNodePrompt('
|
|
92
|
+
const prompt = buildNodePrompt('agent', data, previousResults);
|
|
93
93
|
try {
|
|
94
94
|
onProgress?.({ nodeId: node.id, status: 'running', progress: 40 });
|
|
95
95
|
const result = await executeClaudeCli({
|
package/package.json
CHANGED
package/server/index.ts
CHANGED
|
@@ -242,7 +242,7 @@ app.get('/api/load/claude-config', async (req, res) => {
|
|
|
242
242
|
try {
|
|
243
243
|
const config = await configLoaderService.loadAll();
|
|
244
244
|
console.log(
|
|
245
|
-
`Loaded config: ${config.skills.length} skills, ${config.
|
|
245
|
+
`Loaded config: ${config.skills.length} skills, ${config.agents.length} agents, ` +
|
|
246
246
|
`${config.commands.length} commands, ${config.hooks.length} hooks`
|
|
247
247
|
);
|
|
248
248
|
res.json(config);
|
|
@@ -208,7 +208,7 @@ export function buildNodePrompt(
|
|
|
208
208
|
): string {
|
|
209
209
|
const lines: string[] = [];
|
|
210
210
|
|
|
211
|
-
if (nodeType === '
|
|
211
|
+
if (nodeType === 'agent') {
|
|
212
212
|
const role = nodeData.role as string || 'assistant';
|
|
213
213
|
const description = nodeData.description as string || '';
|
|
214
214
|
const systemPrompt = nodeData.systemPrompt as string || '';
|
|
@@ -12,7 +12,7 @@ interface LoadedSkill {
|
|
|
12
12
|
|
|
13
13
|
interface LoadedSubagent {
|
|
14
14
|
id: string;
|
|
15
|
-
type: '
|
|
15
|
+
type: 'agent';
|
|
16
16
|
label: string;
|
|
17
17
|
description: string;
|
|
18
18
|
tools: string[];
|
|
@@ -44,7 +44,7 @@ export type LoadedNode = LoadedSkill | LoadedSubagent | LoadedCommand | LoadedHo
|
|
|
44
44
|
|
|
45
45
|
export interface ClaudeConfig {
|
|
46
46
|
skills: LoadedSkill[];
|
|
47
|
-
|
|
47
|
+
agents: LoadedSubagent[];
|
|
48
48
|
commands: LoadedCommand[];
|
|
49
49
|
hooks: LoadedHook[];
|
|
50
50
|
}
|
|
@@ -60,14 +60,14 @@ export class ConfigLoaderService {
|
|
|
60
60
|
* .claude/ 디렉토리에서 모든 설정 로드
|
|
61
61
|
*/
|
|
62
62
|
async loadAll(): Promise<ClaudeConfig> {
|
|
63
|
-
const [skills,
|
|
63
|
+
const [skills, agents, commands, hooks] = await Promise.all([
|
|
64
64
|
this.loadSkills(),
|
|
65
65
|
this.loadSubagents(),
|
|
66
66
|
this.loadCommands(),
|
|
67
67
|
this.loadHooks(),
|
|
68
68
|
]);
|
|
69
69
|
|
|
70
|
-
return { skills,
|
|
70
|
+
return { skills, agents, commands, hooks };
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
@@ -112,7 +112,7 @@ export class ConfigLoaderService {
|
|
|
112
112
|
*/
|
|
113
113
|
async loadSubagents(): Promise<LoadedSubagent[]> {
|
|
114
114
|
const agentsDir = path.join(this.projectRoot, '.claude', 'agents');
|
|
115
|
-
const
|
|
115
|
+
const agents: LoadedSubagent[] = [];
|
|
116
116
|
|
|
117
117
|
try {
|
|
118
118
|
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
@@ -125,9 +125,9 @@ export class ConfigLoaderService {
|
|
|
125
125
|
const parsed = this.parseFrontmatter(content);
|
|
126
126
|
const agentName = entry.name.replace('.md', '');
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
id: `
|
|
130
|
-
type: '
|
|
128
|
+
agents.push({
|
|
129
|
+
id: `agent-${agentName}`,
|
|
130
|
+
type: 'agent',
|
|
131
131
|
label: parsed.frontmatter.name || agentName,
|
|
132
132
|
description: parsed.frontmatter.description || '',
|
|
133
133
|
tools: this.parseList(parsed.frontmatter.tools),
|
|
@@ -144,7 +144,7 @@ export class ConfigLoaderService {
|
|
|
144
144
|
// agents 디렉토리가 없으면 빈 배열 반환
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
return
|
|
147
|
+
return agents;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/**
|
|
@@ -3,7 +3,7 @@ import * as path from 'path';
|
|
|
3
3
|
|
|
4
4
|
interface NodeData {
|
|
5
5
|
id: string;
|
|
6
|
-
type: 'skill' | '
|
|
6
|
+
type: 'skill' | 'agent' | 'hook' | 'input' | 'output';
|
|
7
7
|
label: string;
|
|
8
8
|
description?: string;
|
|
9
9
|
// Skill specific
|
|
@@ -11,14 +11,11 @@ interface NodeData {
|
|
|
11
11
|
skillPath?: string;
|
|
12
12
|
upstream?: string[]; // Connected upstream agents/skills
|
|
13
13
|
downstream?: string[]; // Connected downstream agents/skills
|
|
14
|
-
//
|
|
14
|
+
// Agent specific
|
|
15
15
|
tools?: string[];
|
|
16
16
|
model?: string;
|
|
17
17
|
skills?: string[]; // Connected downstream skills
|
|
18
18
|
systemPrompt?: string;
|
|
19
|
-
// Command specific
|
|
20
|
-
commandName?: string;
|
|
21
|
-
commandContent?: string;
|
|
22
19
|
// Hook specific
|
|
23
20
|
hookEvent?: string;
|
|
24
21
|
hookMatcher?: string;
|
|
@@ -47,10 +44,8 @@ export class NodeSyncService {
|
|
|
47
44
|
switch (node.type) {
|
|
48
45
|
case 'skill':
|
|
49
46
|
return await this.syncSkillNode(node);
|
|
50
|
-
case '
|
|
51
|
-
return await this.
|
|
52
|
-
case 'command':
|
|
53
|
-
return await this.syncCommandNode(node);
|
|
47
|
+
case 'agent':
|
|
48
|
+
return await this.syncAgentNode(node);
|
|
54
49
|
case 'hook':
|
|
55
50
|
return await this.syncHookNode(node);
|
|
56
51
|
case 'input':
|
|
@@ -86,16 +81,11 @@ export class NodeSyncService {
|
|
|
86
81
|
await fs.rm(skillPath, { recursive: true, force: true });
|
|
87
82
|
}
|
|
88
83
|
break;
|
|
89
|
-
case '
|
|
84
|
+
case 'agent':
|
|
90
85
|
const agentName = this.toKebabCase(node.label);
|
|
91
86
|
const agentPath = path.join(this.projectRoot, '.claude', 'agents', `${agentName}.md`);
|
|
92
87
|
await fs.unlink(agentPath).catch(() => {});
|
|
93
88
|
break;
|
|
94
|
-
case 'command':
|
|
95
|
-
const cmdName = node.commandName || this.toKebabCase(node.label);
|
|
96
|
-
const cmdPath = path.join(this.projectRoot, '.claude', 'commands', `${cmdName}.md`);
|
|
97
|
-
await fs.unlink(cmdPath).catch(() => {});
|
|
98
|
-
break;
|
|
99
89
|
case 'hook':
|
|
100
90
|
await this.removeHookFromSettings(node);
|
|
101
91
|
break;
|
|
@@ -112,11 +102,11 @@ export class NodeSyncService {
|
|
|
112
102
|
*/
|
|
113
103
|
private async removeReferencesToNode(nodeId: string, nodeType: string, allNodes: NodeData[]): Promise<void> {
|
|
114
104
|
for (const relatedNode of allNodes) {
|
|
115
|
-
if (relatedNode.type === '
|
|
105
|
+
if (relatedNode.type === 'agent') {
|
|
116
106
|
// 서브에이전트의 skills 배열에서 삭제된 노드 제거
|
|
117
107
|
if (relatedNode.skills?.includes(nodeId)) {
|
|
118
108
|
relatedNode.skills = relatedNode.skills.filter(s => s !== nodeId);
|
|
119
|
-
await this.
|
|
109
|
+
await this.syncAgentNode(relatedNode);
|
|
120
110
|
}
|
|
121
111
|
} else if (relatedNode.type === 'skill') {
|
|
122
112
|
let updated = false;
|
|
@@ -155,13 +145,13 @@ export class NodeSyncService {
|
|
|
155
145
|
const targetId = this.getNodeIdentifier(targetNode);
|
|
156
146
|
|
|
157
147
|
// 서브에이전트 → 스킬 연결
|
|
158
|
-
if (sourceNode.type === '
|
|
148
|
+
if (sourceNode.type === 'agent' && targetNode.type === 'skill') {
|
|
159
149
|
// 에이전트의 skills 필드 업데이트
|
|
160
150
|
const skills = sourceNode.skills || [];
|
|
161
151
|
if (!skills.includes(targetId)) {
|
|
162
152
|
skills.push(targetId);
|
|
163
153
|
sourceNode.skills = skills;
|
|
164
|
-
await this.
|
|
154
|
+
await this.syncAgentNode(sourceNode);
|
|
165
155
|
}
|
|
166
156
|
|
|
167
157
|
// 스킬의 upstream 필드 업데이트
|
|
@@ -193,13 +183,13 @@ export class NodeSyncService {
|
|
|
193
183
|
}
|
|
194
184
|
|
|
195
185
|
// 스킬 → 서브에이전트 연결
|
|
196
|
-
if (sourceNode.type === 'skill' && targetNode.type === '
|
|
186
|
+
if (sourceNode.type === 'skill' && targetNode.type === 'agent') {
|
|
197
187
|
// 에이전트의 upstream skills 필드 업데이트
|
|
198
188
|
const skills = targetNode.skills || [];
|
|
199
189
|
if (!skills.includes(sourceId)) {
|
|
200
190
|
skills.push(sourceId);
|
|
201
191
|
targetNode.skills = skills;
|
|
202
|
-
await this.
|
|
192
|
+
await this.syncAgentNode(targetNode);
|
|
203
193
|
}
|
|
204
194
|
|
|
205
195
|
// 스킬의 downstream 필드 업데이트
|
|
@@ -212,13 +202,13 @@ export class NodeSyncService {
|
|
|
212
202
|
}
|
|
213
203
|
|
|
214
204
|
// 서브에이전트 → 서브에이전트 연결
|
|
215
|
-
if (sourceNode.type === '
|
|
205
|
+
if (sourceNode.type === 'agent' && targetNode.type === 'agent') {
|
|
216
206
|
// source의 downstream agents
|
|
217
207
|
const sourceDownstream = sourceNode.skills || [];
|
|
218
208
|
if (!sourceDownstream.includes(targetId)) {
|
|
219
209
|
sourceDownstream.push(targetId);
|
|
220
210
|
sourceNode.skills = sourceDownstream;
|
|
221
|
-
await this.
|
|
211
|
+
await this.syncAgentNode(sourceNode);
|
|
222
212
|
}
|
|
223
213
|
}
|
|
224
214
|
|
|
@@ -255,10 +245,10 @@ export class NodeSyncService {
|
|
|
255
245
|
const targetId = this.getNodeIdentifier(targetNode);
|
|
256
246
|
|
|
257
247
|
// 서브에이전트 → 스킬 연결 해제
|
|
258
|
-
if (sourceNode.type === '
|
|
248
|
+
if (sourceNode.type === 'agent' && targetNode.type === 'skill') {
|
|
259
249
|
// 에이전트에서 스킬 제거
|
|
260
250
|
sourceNode.skills = (sourceNode.skills || []).filter(s => s !== targetId);
|
|
261
|
-
await this.
|
|
251
|
+
await this.syncAgentNode(sourceNode);
|
|
262
252
|
|
|
263
253
|
// 스킬에서 upstream 제거
|
|
264
254
|
targetNode.upstream = (targetNode.upstream || []).filter(s => s !== sourceId);
|
|
@@ -275,18 +265,18 @@ export class NodeSyncService {
|
|
|
275
265
|
}
|
|
276
266
|
|
|
277
267
|
// 스킬 → 서브에이전트 연결 해제
|
|
278
|
-
if (sourceNode.type === 'skill' && targetNode.type === '
|
|
268
|
+
if (sourceNode.type === 'skill' && targetNode.type === 'agent') {
|
|
279
269
|
targetNode.skills = (targetNode.skills || []).filter(s => s !== sourceId);
|
|
280
|
-
await this.
|
|
270
|
+
await this.syncAgentNode(targetNode);
|
|
281
271
|
|
|
282
272
|
sourceNode.downstream = (sourceNode.downstream || []).filter(s => s !== targetId);
|
|
283
273
|
await this.syncSkillNode(sourceNode);
|
|
284
274
|
}
|
|
285
275
|
|
|
286
276
|
// 서브에이전트 → 서브에이전트 연결 해제
|
|
287
|
-
if (sourceNode.type === '
|
|
277
|
+
if (sourceNode.type === 'agent' && targetNode.type === 'agent') {
|
|
288
278
|
sourceNode.skills = (sourceNode.skills || []).filter(s => s !== targetId);
|
|
289
|
-
await this.
|
|
279
|
+
await this.syncAgentNode(sourceNode);
|
|
290
280
|
}
|
|
291
281
|
|
|
292
282
|
return { success: true };
|
|
@@ -357,7 +347,7 @@ ${frontmatterStr}
|
|
|
357
347
|
return { success: true, path: skillPath };
|
|
358
348
|
}
|
|
359
349
|
|
|
360
|
-
private async
|
|
350
|
+
private async syncAgentNode(node: NodeData): Promise<{ success: boolean; path?: string; error?: string }> {
|
|
361
351
|
const agentName = this.toKebabCase(node.label);
|
|
362
352
|
const agentsDir = path.join(this.projectRoot, '.claude', 'agents');
|
|
363
353
|
const agentPath = path.join(agentsDir, `${agentName}.md`);
|
|
@@ -446,26 +436,6 @@ ${body}
|
|
|
446
436
|
return { frontmatter, body };
|
|
447
437
|
}
|
|
448
438
|
|
|
449
|
-
private async syncCommandNode(node: NodeData): Promise<{ success: boolean; path?: string; error?: string }> {
|
|
450
|
-
const cmdName = node.commandName || this.toKebabCase(node.label);
|
|
451
|
-
const commandsDir = path.join(this.projectRoot, '.claude', 'commands');
|
|
452
|
-
const cmdPath = path.join(commandsDir, `${cmdName}.md`);
|
|
453
|
-
|
|
454
|
-
await fs.mkdir(commandsDir, { recursive: true });
|
|
455
|
-
|
|
456
|
-
const content = node.commandContent || `---
|
|
457
|
-
description: ${node.description || node.label}
|
|
458
|
-
---
|
|
459
|
-
|
|
460
|
-
${node.description || '커맨드 내용을 여기에 작성하세요'}
|
|
461
|
-
|
|
462
|
-
$ARGUMENTS
|
|
463
|
-
`;
|
|
464
|
-
|
|
465
|
-
await fs.writeFile(cmdPath, content, 'utf-8');
|
|
466
|
-
return { success: true, path: cmdPath };
|
|
467
|
-
}
|
|
468
|
-
|
|
469
439
|
private async syncHookNode(node: NodeData): Promise<{ success: boolean; path?: string; error?: string }> {
|
|
470
440
|
const settingsPath = path.join(this.projectRoot, '.claude', 'settings.json');
|
|
471
441
|
|
|
@@ -49,7 +49,7 @@ function generateClaudePrompt(options: TerminalExecutionOptions): string {
|
|
|
49
49
|
executionOrder.forEach((node) => {
|
|
50
50
|
if (node.type === 'input') return;
|
|
51
51
|
|
|
52
|
-
if (node.type === '
|
|
52
|
+
if (node.type === 'agent') {
|
|
53
53
|
const data = node.data as SubagentNodeData;
|
|
54
54
|
lines.push(`${stepNum}. **${data.label}** (${data.role})`);
|
|
55
55
|
if (data.description) {
|