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/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.subagents.length} agents, ` +
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 result = await workflowAIService.generate(prompt, {
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 === 'subagent') {
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 || '';
@@ -40,7 +40,7 @@ export class ClaudeService {
40
40
  case 'input':
41
41
  return this.executeInputNode(node.id, node.data as InputNodeData);
42
42
 
43
- case 'subagent':
43
+ case 'agent':
44
44
  return this.executeSubagentNode(
45
45
  node.id,
46
46
  node.data as SubagentNodeData,
@@ -12,7 +12,7 @@ interface LoadedSkill {
12
12
 
13
13
  interface LoadedSubagent {
14
14
  id: string;
15
- type: 'subagent';
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 | LoadedCommand | LoadedHook;
34
+ export type LoadedNode = LoadedSkill | LoadedSubagent | LoadedHook;
44
35
 
45
36
  export interface ClaudeConfig {
46
37
  skills: LoadedSkill[];
47
- subagents: LoadedSubagent[];
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, subagents, commands, hooks] = await Promise.all([
53
+ const [skills, agents, hooks] = await Promise.all([
64
54
  this.loadSkills(),
65
- this.loadSubagents(),
66
- this.loadCommands(),
55
+ this.loadAgents(),
67
56
  this.loadHooks(),
68
57
  ]);
69
58
 
70
- return { skills, subagents, commands, hooks };
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 loadSubagents(): Promise<LoadedSubagent[]> {
102
+ async loadAgents(): Promise<LoadedSubagent[]> {
114
103
  const agentsDir = path.join(this.projectRoot, '.claude', 'agents');
115
- const subagents: LoadedSubagent[] = [];
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
- subagents.push({
129
- id: `subagent-${agentName}`,
130
- type: 'subagent',
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 subagents;
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' | 'subagent' | 'command' | 'hook' | 'input' | 'output';
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
- // Subagent specific
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 'subagent':
51
- return await this.syncSubagentNode(node);
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 'subagent':
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 === 'subagent') {
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.syncSubagentNode(relatedNode);
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 === 'subagent' && targetNode.type === 'skill') {
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.syncSubagentNode(sourceNode);
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 === 'subagent') {
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.syncSubagentNode(targetNode);
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 === 'subagent' && targetNode.type === 'subagent') {
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.syncSubagentNode(sourceNode);
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 === 'subagent' && targetNode.type === 'skill') {
248
+ if (sourceNode.type === 'agent' && targetNode.type === 'skill') {
259
249
  // 에이전트에서 스킬 제거
260
250
  sourceNode.skills = (sourceNode.skills || []).filter(s => s !== targetId);
261
- await this.syncSubagentNode(sourceNode);
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 === 'subagent') {
268
+ if (sourceNode.type === 'skill' && targetNode.type === 'agent') {
279
269
  targetNode.skills = (targetNode.skills || []).filter(s => s !== sourceId);
280
- await this.syncSubagentNode(targetNode);
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 === 'subagent' && targetNode.type === 'subagent') {
277
+ if (sourceNode.type === 'agent' && targetNode.type === 'agent') {
288
278
  sourceNode.skills = (sourceNode.skills || []).filter(s => s !== targetId);
289
- await this.syncSubagentNode(sourceNode);
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 syncSubagentNode(node: NodeData): Promise<{ success: boolean; path?: string; error?: string }> {
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 === 'subagent') {
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) {