makecc 0.2.16 → 0.2.18
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-j_MJWOjN.js +67 -0
- package/dist/client/index.html +2 -2
- package/dist/server/index.js +11 -6
- package/dist/server/services/claudeCliService.js +1 -1
- package/dist/server/services/claudeService.js +1 -1
- package/dist/server/services/configLoaderService.js +10 -46
- package/dist/server/services/nodeSyncService.js +18 -41
- package/dist/server/services/terminalService.js +1 -1
- package/dist/server/services/workflowAIService.js +169 -25
- package/dist/server/services/workflowExecutionService.js +2 -2
- package/package.json +1 -1
- package/server/index.ts +13 -6
- package/server/services/claudeCliService.ts +1 -1
- package/server/services/claudeService.ts +1 -1
- package/server/services/configLoaderService.ts +13 -62
- package/server/services/nodeSyncService.ts +20 -50
- package/server/services/terminalService.ts +1 -1
- package/server/services/workflowAIService.ts +213 -27
- package/server/services/workflowExecutionService.ts +2 -2
- package/server/types.ts +1 -1
- package/dist/client/assets/index-Bir6nP2a.js +0 -150
package/server/index.ts
CHANGED
|
@@ -242,8 +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.
|
|
246
|
-
`${config.commands.length} commands, ${config.hooks.length} hooks`
|
|
245
|
+
`Loaded config: ${config.skills.length} skills, ${config.agents.length} agents, ${config.hooks.length} hooks`
|
|
247
246
|
);
|
|
248
247
|
res.json(config);
|
|
249
248
|
} catch (error) {
|
|
@@ -256,7 +255,7 @@ app.get('/api/load/claude-config', async (req, res) => {
|
|
|
256
255
|
// Generate workflow using AI
|
|
257
256
|
app.post('/api/generate/workflow', async (req, res) => {
|
|
258
257
|
try {
|
|
259
|
-
const { prompt } = req.body as { prompt: string };
|
|
258
|
+
const { prompt, expand = true } = req.body as { prompt: string; expand?: boolean };
|
|
260
259
|
|
|
261
260
|
if (!prompt || typeof prompt !== 'string') {
|
|
262
261
|
return res.status(400).json({ message: 'Prompt is required' });
|
|
@@ -267,13 +266,21 @@ app.post('/api/generate/workflow', async (req, res) => {
|
|
|
267
266
|
const apiKey = req.headers['x-api-key'] as string;
|
|
268
267
|
const proxyUrl = req.headers['x-proxy-url'] as string;
|
|
269
268
|
|
|
270
|
-
console.log('Generating workflow for prompt:', prompt, 'mode:', apiMode);
|
|
269
|
+
console.log('Generating workflow for prompt:', prompt, 'mode:', apiMode, 'expand:', expand);
|
|
271
270
|
|
|
272
|
-
const
|
|
271
|
+
const settings = {
|
|
273
272
|
apiMode: apiMode as 'proxy' | 'direct',
|
|
274
273
|
apiKey,
|
|
275
274
|
proxyUrl,
|
|
276
|
-
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// expand=true (기본값)이면 재귀적으로 스킬/에이전트 상세 생성
|
|
278
|
+
const result = expand
|
|
279
|
+
? await workflowAIService.generateWithExpansion(prompt, settings, (event) => {
|
|
280
|
+
console.log(`[Workflow] ${event.step}: ${event.message}`);
|
|
281
|
+
})
|
|
282
|
+
: await workflowAIService.generate(prompt, settings);
|
|
283
|
+
|
|
277
284
|
console.log('Workflow generated:', result.workflowName);
|
|
278
285
|
|
|
279
286
|
res.json(result);
|
|
@@ -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[];
|
|
@@ -21,15 +21,6 @@ interface LoadedSubagent {
|
|
|
21
21
|
systemPrompt: string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
interface LoadedCommand {
|
|
25
|
-
id: string;
|
|
26
|
-
type: 'command';
|
|
27
|
-
label: string;
|
|
28
|
-
description: string;
|
|
29
|
-
commandName: string;
|
|
30
|
-
commandContent: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
24
|
interface LoadedHook {
|
|
34
25
|
id: string;
|
|
35
26
|
type: 'hook';
|
|
@@ -40,12 +31,11 @@ interface LoadedHook {
|
|
|
40
31
|
hookCommand: string;
|
|
41
32
|
}
|
|
42
33
|
|
|
43
|
-
export type LoadedNode = LoadedSkill | LoadedSubagent |
|
|
34
|
+
export type LoadedNode = LoadedSkill | LoadedSubagent | LoadedHook;
|
|
44
35
|
|
|
45
36
|
export interface ClaudeConfig {
|
|
46
37
|
skills: LoadedSkill[];
|
|
47
|
-
|
|
48
|
-
commands: LoadedCommand[];
|
|
38
|
+
agents: LoadedSubagent[];
|
|
49
39
|
hooks: LoadedHook[];
|
|
50
40
|
}
|
|
51
41
|
|
|
@@ -60,14 +50,13 @@ export class ConfigLoaderService {
|
|
|
60
50
|
* .claude/ 디렉토리에서 모든 설정 로드
|
|
61
51
|
*/
|
|
62
52
|
async loadAll(): Promise<ClaudeConfig> {
|
|
63
|
-
const [skills,
|
|
53
|
+
const [skills, agents, hooks] = await Promise.all([
|
|
64
54
|
this.loadSkills(),
|
|
65
|
-
this.
|
|
66
|
-
this.loadCommands(),
|
|
55
|
+
this.loadAgents(),
|
|
67
56
|
this.loadHooks(),
|
|
68
57
|
]);
|
|
69
58
|
|
|
70
|
-
return { skills,
|
|
59
|
+
return { skills, agents, hooks };
|
|
71
60
|
}
|
|
72
61
|
|
|
73
62
|
/**
|
|
@@ -108,11 +97,11 @@ export class ConfigLoaderService {
|
|
|
108
97
|
}
|
|
109
98
|
|
|
110
99
|
/**
|
|
111
|
-
* .claude/agents/ 에서
|
|
100
|
+
* .claude/agents/ 에서 에이전트 로드
|
|
112
101
|
*/
|
|
113
|
-
async
|
|
102
|
+
async loadAgents(): Promise<LoadedSubagent[]> {
|
|
114
103
|
const agentsDir = path.join(this.projectRoot, '.claude', 'agents');
|
|
115
|
-
const
|
|
104
|
+
const agents: LoadedSubagent[] = [];
|
|
116
105
|
|
|
117
106
|
try {
|
|
118
107
|
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
@@ -125,9 +114,9 @@ export class ConfigLoaderService {
|
|
|
125
114
|
const parsed = this.parseFrontmatter(content);
|
|
126
115
|
const agentName = entry.name.replace('.md', '');
|
|
127
116
|
|
|
128
|
-
|
|
129
|
-
id: `
|
|
130
|
-
type: '
|
|
117
|
+
agents.push({
|
|
118
|
+
id: `agent-${agentName}`,
|
|
119
|
+
type: 'agent',
|
|
131
120
|
label: parsed.frontmatter.name || agentName,
|
|
132
121
|
description: parsed.frontmatter.description || '',
|
|
133
122
|
tools: this.parseList(parsed.frontmatter.tools),
|
|
@@ -144,45 +133,7 @@ export class ConfigLoaderService {
|
|
|
144
133
|
// agents 디렉토리가 없으면 빈 배열 반환
|
|
145
134
|
}
|
|
146
135
|
|
|
147
|
-
return
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* .claude/commands/ 에서 커맨드 로드
|
|
152
|
-
*/
|
|
153
|
-
async loadCommands(): Promise<LoadedCommand[]> {
|
|
154
|
-
const commandsDir = path.join(this.projectRoot, '.claude', 'commands');
|
|
155
|
-
const commands: LoadedCommand[] = [];
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
const entries = await fs.readdir(commandsDir, { withFileTypes: true });
|
|
159
|
-
|
|
160
|
-
for (const entry of entries) {
|
|
161
|
-
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
162
|
-
const cmdPath = path.join(commandsDir, entry.name);
|
|
163
|
-
try {
|
|
164
|
-
const content = await fs.readFile(cmdPath, 'utf-8');
|
|
165
|
-
const parsed = this.parseFrontmatter(content);
|
|
166
|
-
const cmdName = entry.name.replace('.md', '');
|
|
167
|
-
|
|
168
|
-
commands.push({
|
|
169
|
-
id: `command-${cmdName}`,
|
|
170
|
-
type: 'command',
|
|
171
|
-
label: parsed.frontmatter.name || cmdName,
|
|
172
|
-
description: parsed.frontmatter.description || '',
|
|
173
|
-
commandName: cmdName,
|
|
174
|
-
commandContent: content,
|
|
175
|
-
});
|
|
176
|
-
} catch {
|
|
177
|
-
// 읽을 수 없으면 스킵
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
} catch {
|
|
182
|
-
// commands 디렉토리가 없으면 빈 배열 반환
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return commands;
|
|
136
|
+
return agents;
|
|
186
137
|
}
|
|
187
138
|
|
|
188
139
|
/**
|
|
@@ -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) {
|