makecc 0.2.17 → 0.2.19
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-NJhP4DZc.js +69 -0
- package/dist/client/assets/index-yCwPL34L.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/index.js +82 -6
- package/dist/server/services/configLoaderService.js +5 -41
- package/dist/server/services/projectService.js +332 -0
- package/dist/server/services/skillGeneratorService.js +147 -26
- package/dist/server/services/workflowAIService.js +169 -23
- package/package.json +2 -1
- package/server/index.ts +88 -6
- package/server/services/configLoaderService.ts +6 -55
- package/server/services/projectService.ts +424 -0
- package/server/services/skillGeneratorService.ts +147 -26
- package/server/services/workflowAIService.ts +212 -23
- package/dist/client/assets/index-1V3GQ_8q.css +0 -1
- package/dist/client/assets/index-CzDuVmB7.js +0 -67
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import { skillGeneratorService } from './skillGeneratorService';
|
|
2
3
|
// 사용 가능한 공식 스킬 목록
|
|
3
4
|
const AVAILABLE_SKILLS = [
|
|
4
5
|
{ id: 'image-gen-nanobanana', name: 'Image Generator', description: 'Google Gemini 기반 AI 이미지 생성' },
|
|
@@ -29,22 +30,23 @@ ${AVAILABLE_TOOLS.join(', ')}
|
|
|
29
30
|
반드시 아래 형식의 유효한 JSON으로 응답하세요. 다른 텍스트 없이 JSON만 반환하세요.
|
|
30
31
|
|
|
31
32
|
{
|
|
32
|
-
"workflowName": "
|
|
33
|
-
"description": "워크플로우 설명",
|
|
33
|
+
"workflowName": "Workflow Name in English",
|
|
34
|
+
"description": "워크플로우 설명 (한글 가능)",
|
|
34
35
|
"nodes": [
|
|
35
36
|
{
|
|
36
37
|
"type": "input",
|
|
37
|
-
"label": "
|
|
38
|
-
"description": "입력 설명",
|
|
38
|
+
"label": "input-name-in-english",
|
|
39
|
+
"description": "입력 설명 (한글 가능)",
|
|
39
40
|
"config": {
|
|
40
41
|
"inputType": "text",
|
|
41
|
-
"placeholder": "입력 안내"
|
|
42
|
+
"placeholder": "입력 안내",
|
|
43
|
+
"defaultValue": "워크플로우에 적합한 예시 입력값"
|
|
42
44
|
}
|
|
43
45
|
},
|
|
44
46
|
{
|
|
45
47
|
"type": "agent",
|
|
46
|
-
"label": "
|
|
47
|
-
"description": "에이전트 역할 설명",
|
|
48
|
+
"label": "agent-name-in-english",
|
|
49
|
+
"description": "에이전트 역할 설명 (한글 가능)",
|
|
48
50
|
"config": {
|
|
49
51
|
"role": "researcher|writer|analyst|coder|custom",
|
|
50
52
|
"tools": ["Read", "Write"],
|
|
@@ -54,8 +56,8 @@ ${AVAILABLE_TOOLS.join(', ')}
|
|
|
54
56
|
},
|
|
55
57
|
{
|
|
56
58
|
"type": "skill",
|
|
57
|
-
"label": "
|
|
58
|
-
"description": "스킬 설명",
|
|
59
|
+
"label": "skill-name-in-english",
|
|
60
|
+
"description": "스킬 설명 (한글 가능)",
|
|
59
61
|
"config": {
|
|
60
62
|
"skillType": "official",
|
|
61
63
|
"skillId": "image-gen-nanobanana"
|
|
@@ -63,18 +65,18 @@ ${AVAILABLE_TOOLS.join(', ')}
|
|
|
63
65
|
},
|
|
64
66
|
{
|
|
65
67
|
"type": "skill",
|
|
66
|
-
"label": "
|
|
67
|
-
"description": "커스텀 스킬 설명",
|
|
68
|
+
"label": "custom-skill-name",
|
|
69
|
+
"description": "커스텀 스킬 설명 (한글 가능)",
|
|
68
70
|
"config": {
|
|
69
71
|
"skillType": "custom",
|
|
70
72
|
"skillId": "my-custom-skill",
|
|
71
|
-
"skillContent": "---\\nname: my-custom-skill\\ndescription:
|
|
73
|
+
"skillContent": "---\\nname: my-custom-skill\\ndescription: Custom skill description\\n---\\n\\n# Skill Instructions\\n\\nSpecific instructions..."
|
|
72
74
|
}
|
|
73
75
|
},
|
|
74
76
|
{
|
|
75
77
|
"type": "output",
|
|
76
|
-
"label": "
|
|
77
|
-
"description": "출력 설명",
|
|
78
|
+
"label": "output-name-in-english",
|
|
79
|
+
"description": "출력 설명 (한글 가능)",
|
|
78
80
|
"config": {
|
|
79
81
|
"outputType": "auto|markdown|document|image"
|
|
80
82
|
}
|
|
@@ -94,30 +96,33 @@ ${AVAILABLE_TOOLS.join(', ')}
|
|
|
94
96
|
5. edges의 from/to는 nodes 배열의 인덱스 (0부터 시작)
|
|
95
97
|
6. 순차적으로 연결되지 않아도 됨 (병렬 처리, 합류 가능)
|
|
96
98
|
7. systemPrompt는 구체적이고 실행 가능한 지시사항으로 작성
|
|
99
|
+
8. **중요: label은 반드시 영어로, kebab-case 형식으로 작성 (예: blog-writer, data-analyzer)**
|
|
100
|
+
9. workflowName도 영어로 작성
|
|
101
|
+
10. **필수: input 노드의 config에 반드시 defaultValue 포함 - 워크플로우 목적에 맞는 구체적인 예시 값을 한글로 제공**
|
|
97
102
|
|
|
98
103
|
## 예시
|
|
99
104
|
|
|
100
105
|
### 예시 1: "이미지 3개 만들어줘"
|
|
101
106
|
{
|
|
102
|
-
"workflowName": "
|
|
107
|
+
"workflowName": "Image Generation",
|
|
103
108
|
"description": "3개의 이미지를 생성하는 워크플로우",
|
|
104
109
|
"nodes": [
|
|
105
|
-
{ "type": "input", "label": "
|
|
106
|
-
{ "type": "skill", "label": "
|
|
107
|
-
{ "type": "output", "label": "
|
|
110
|
+
{ "type": "input", "label": "image-prompt", "description": "생성할 이미지에 대한 설명", "config": { "inputType": "text", "placeholder": "이미지 프롬프트 입력", "defaultValue": "미래적인 도시 야경, 네온 사인이 빛나는 사이버펑크 스타일" } },
|
|
111
|
+
{ "type": "skill", "label": "image-generator", "description": "AI로 이미지 생성", "config": { "skillType": "official", "skillId": "image-gen-nanobanana" } },
|
|
112
|
+
{ "type": "output", "label": "generated-images", "description": "생성된 이미지 결과", "config": { "outputType": "image" } }
|
|
108
113
|
],
|
|
109
114
|
"edges": [{ "from": 0, "to": 1 }, { "from": 1, "to": 2 }]
|
|
110
115
|
}
|
|
111
116
|
|
|
112
117
|
### 예시 2: "데이터 분석해서 보고서 만들어줘"
|
|
113
118
|
{
|
|
114
|
-
"workflowName": "
|
|
119
|
+
"workflowName": "Data Analysis Report",
|
|
115
120
|
"description": "데이터를 분석하고 보고서를 작성하는 워크플로우",
|
|
116
121
|
"nodes": [
|
|
117
|
-
{ "type": "input", "label": "
|
|
118
|
-
{ "type": "agent", "label": "
|
|
119
|
-
{ "type": "agent", "label": "
|
|
120
|
-
{ "type": "output", "label": "
|
|
122
|
+
{ "type": "input", "label": "data-file", "description": "분석할 데이터 파일", "config": { "inputType": "file", "defaultValue": "2024년 1~3분기 매출 데이터: 1월 1200만, 2월 1350만, 3월 1100만, 4월 1500만, 5월 1650만, 6월 1400만, 7월 1800만, 8월 1750만, 9월 1900만" } },
|
|
123
|
+
{ "type": "agent", "label": "data-analyzer", "description": "데이터를 분석하고 인사이트 도출", "config": { "role": "analyst", "tools": ["Read", "Grep", "Glob"], "model": "sonnet", "systemPrompt": "주어진 데이터를 분석하여 핵심 인사이트를 도출하세요. 통계적 요약, 트렌드, 이상치를 파악하세요." } },
|
|
124
|
+
{ "type": "agent", "label": "report-writer", "description": "분석 결과로 보고서 작성", "config": { "role": "writer", "tools": ["Read", "Write"], "model": "opus", "systemPrompt": "분석 결과를 바탕으로 경영진을 위한 간결하고 명확한 보고서를 작성하세요." } },
|
|
125
|
+
{ "type": "output", "label": "analysis-report", "description": "최종 분석 보고서", "config": { "outputType": "document" } }
|
|
121
126
|
],
|
|
122
127
|
"edges": [{ "from": 0, "to": 1 }, { "from": 1, "to": 2 }, { "from": 2, "to": 3 }]
|
|
123
128
|
}
|
|
@@ -211,5 +216,146 @@ export class WorkflowAIService {
|
|
|
211
216
|
edge.to < result.nodes.length);
|
|
212
217
|
return result;
|
|
213
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* 워크플로우 생성 + 재귀적 노드 확장
|
|
221
|
+
* 각 custom 스킬과 에이전트에 대해 상세 내용 생성
|
|
222
|
+
*/
|
|
223
|
+
async generateWithExpansion(prompt, settings, onProgress) {
|
|
224
|
+
// 1. 워크플로우 구조 생성
|
|
225
|
+
onProgress?.({ step: 'workflow', message: '워크플로우 구조를 생성하고 있습니다...' });
|
|
226
|
+
const result = await this.generate(prompt, settings);
|
|
227
|
+
// 2. 확장이 필요한 노드 식별
|
|
228
|
+
const customSkills = result.nodes.filter((n) => n.type === 'skill' && n.config.skillType === 'custom');
|
|
229
|
+
const agents = result.nodes.filter((n) => n.type === 'agent');
|
|
230
|
+
const totalExpansions = customSkills.length + agents.length;
|
|
231
|
+
let current = 0;
|
|
232
|
+
// 3. 각 custom 스킬 확장
|
|
233
|
+
for (const skill of customSkills) {
|
|
234
|
+
current++;
|
|
235
|
+
onProgress?.({
|
|
236
|
+
step: 'skill',
|
|
237
|
+
message: `스킬 "${skill.label}" 상세 생성 중...`,
|
|
238
|
+
current,
|
|
239
|
+
total: totalExpansions,
|
|
240
|
+
});
|
|
241
|
+
try {
|
|
242
|
+
// skillGeneratorService를 사용하여 완전한 스킬 생성
|
|
243
|
+
const skillPrompt = this.buildSkillPrompt(skill, result);
|
|
244
|
+
const skillResult = await skillGeneratorService.generate(skillPrompt, settings);
|
|
245
|
+
if (skillResult.success && skillResult.skill) {
|
|
246
|
+
// 생성된 스킬 정보로 노드 업데이트
|
|
247
|
+
skill.config.skillId = skillResult.skill.skillId;
|
|
248
|
+
skill.config.skillContent = undefined; // 파일로 저장되었으므로 제거
|
|
249
|
+
// savedPath는 로그로만 출력 (타입에 없음)
|
|
250
|
+
console.log(`Skill saved to: ${skillResult.savedPath}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
console.error(`Failed to expand skill ${skill.label}:`, error);
|
|
255
|
+
// 실패해도 계속 진행
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// 4. 각 에이전트 확장 (상세 systemPrompt 생성)
|
|
259
|
+
for (const agent of agents) {
|
|
260
|
+
current++;
|
|
261
|
+
onProgress?.({
|
|
262
|
+
step: 'agent',
|
|
263
|
+
message: `에이전트 "${agent.label}" 상세 생성 중...`,
|
|
264
|
+
current,
|
|
265
|
+
total: totalExpansions,
|
|
266
|
+
});
|
|
267
|
+
try {
|
|
268
|
+
const expandedPrompt = await this.expandAgentPrompt(agent, result, settings);
|
|
269
|
+
agent.config.systemPrompt = expandedPrompt;
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
console.error(`Failed to expand agent ${agent.label}:`, error);
|
|
273
|
+
// 실패해도 계속 진행
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
onProgress?.({ step: 'completed', message: '워크플로우 생성 완료!' });
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* 스킬 생성을 위한 상세 프롬프트 빌드
|
|
281
|
+
*/
|
|
282
|
+
buildSkillPrompt(skill, workflow) {
|
|
283
|
+
// 워크플로우에서 이 스킬의 연결 관계 파악
|
|
284
|
+
const skillIndex = workflow.nodes.indexOf(skill);
|
|
285
|
+
const upstreamNodes = workflow.edges
|
|
286
|
+
.filter((e) => e.to === skillIndex)
|
|
287
|
+
.map((e) => workflow.nodes[e.from]);
|
|
288
|
+
const downstreamNodes = workflow.edges
|
|
289
|
+
.filter((e) => e.from === skillIndex)
|
|
290
|
+
.map((e) => workflow.nodes[e.to]);
|
|
291
|
+
const contextParts = [
|
|
292
|
+
`스킬 이름: ${skill.label}`,
|
|
293
|
+
`설명: ${skill.description}`,
|
|
294
|
+
`워크플로우: ${workflow.workflowName}`,
|
|
295
|
+
];
|
|
296
|
+
if (upstreamNodes.length > 0) {
|
|
297
|
+
contextParts.push(`이전 단계: ${upstreamNodes.map((n) => `${n.label} (${n.type})`).join(', ')}`);
|
|
298
|
+
}
|
|
299
|
+
if (downstreamNodes.length > 0) {
|
|
300
|
+
contextParts.push(`다음 단계: ${downstreamNodes.map((n) => `${n.label} (${n.type})`).join(', ')}`);
|
|
301
|
+
}
|
|
302
|
+
if (skill.config.skillContent) {
|
|
303
|
+
contextParts.push(`기본 내용:\n${skill.config.skillContent}`);
|
|
304
|
+
}
|
|
305
|
+
return `다음 스킬을 생성해주세요:\n\n${contextParts.join('\n')}`;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* 에이전트의 systemPrompt를 상세하게 확장
|
|
309
|
+
*/
|
|
310
|
+
async expandAgentPrompt(agent, workflow, settings) {
|
|
311
|
+
const client = this.getClient(settings);
|
|
312
|
+
// 워크플로우에서 이 에이전트의 연결 관계 파악
|
|
313
|
+
const agentIndex = workflow.nodes.indexOf(agent);
|
|
314
|
+
const upstreamNodes = workflow.edges
|
|
315
|
+
.filter((e) => e.to === agentIndex)
|
|
316
|
+
.map((e) => workflow.nodes[e.from]);
|
|
317
|
+
const downstreamNodes = workflow.edges
|
|
318
|
+
.filter((e) => e.from === agentIndex)
|
|
319
|
+
.map((e) => workflow.nodes[e.to]);
|
|
320
|
+
const systemPrompt = `You are an expert at writing detailed system prompts for AI agents.
|
|
321
|
+
Given an agent's context, generate a comprehensive system prompt that:
|
|
322
|
+
1. Clearly defines the agent's role and responsibilities
|
|
323
|
+
2. Specifies input/output expectations
|
|
324
|
+
3. Provides step-by-step instructions
|
|
325
|
+
4. Includes best practices and constraints
|
|
326
|
+
5. Is written in Korean for user-facing parts
|
|
327
|
+
|
|
328
|
+
Respond with ONLY the system prompt text, no explanations or formatting.`;
|
|
329
|
+
const userPrompt = `워크플로우: ${workflow.workflowName}
|
|
330
|
+
워크플로우 설명: ${workflow.description}
|
|
331
|
+
|
|
332
|
+
에이전트 정보:
|
|
333
|
+
- 이름: ${agent.label}
|
|
334
|
+
- 설명: ${agent.description}
|
|
335
|
+
- 역할: ${agent.config.role || 'custom'}
|
|
336
|
+
- 도구: ${(agent.config.tools || []).join(', ')}
|
|
337
|
+
- 모델: ${agent.config.model || 'sonnet'}
|
|
338
|
+
|
|
339
|
+
${upstreamNodes.length > 0 ? `이전 단계에서 받는 입력:\n${upstreamNodes.map((n) => `- ${n.label}: ${n.description}`).join('\n')}` : ''}
|
|
340
|
+
|
|
341
|
+
${downstreamNodes.length > 0 ? `다음 단계로 전달할 출력:\n${downstreamNodes.map((n) => `- ${n.label}: ${n.description}`).join('\n')}` : ''}
|
|
342
|
+
|
|
343
|
+
${agent.config.systemPrompt ? `기존 프롬프트 (확장 필요):\n${agent.config.systemPrompt}` : ''}
|
|
344
|
+
|
|
345
|
+
이 에이전트를 위한 상세하고 실행 가능한 system prompt를 작성해주세요.`;
|
|
346
|
+
const response = await client.messages.create({
|
|
347
|
+
model: 'claude-sonnet-4-20250514',
|
|
348
|
+
max_tokens: 2048,
|
|
349
|
+
system: systemPrompt,
|
|
350
|
+
messages: [{ role: 'user', content: userPrompt }],
|
|
351
|
+
});
|
|
352
|
+
let result = '';
|
|
353
|
+
for (const block of response.content) {
|
|
354
|
+
if (block.type === 'text') {
|
|
355
|
+
result += block.text;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return result.trim() || agent.config.systemPrompt || agent.description;
|
|
359
|
+
}
|
|
214
360
|
}
|
|
215
361
|
export const workflowAIService = new WorkflowAIService();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "makecc",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.19",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Visual workflow builder for Claude Code agents and skills",
|
|
6
6
|
"keywords": [
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"react": "^19.2.0",
|
|
56
56
|
"react-dom": "^19.2.0",
|
|
57
57
|
"react-markdown": "^10.1.0",
|
|
58
|
+
"react-router-dom": "^7.13.0",
|
|
58
59
|
"remark-gfm": "^4.0.1",
|
|
59
60
|
"sharp": "^0.34.5",
|
|
60
61
|
"socket.io": "^4.8.3",
|
package/server/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { configLoaderService } from './services/configLoaderService';
|
|
|
27
27
|
import { workflowExecutionService } from './services/workflowExecutionService';
|
|
28
28
|
import { executeInTerminal, getClaudeCommand } from './services/terminalService';
|
|
29
29
|
import { claudeMdService } from './services/claudeMdService';
|
|
30
|
+
import { projectService } from './services/projectService';
|
|
30
31
|
import type { WorkflowExecutionRequest, NodeExecutionUpdate } from './types';
|
|
31
32
|
import type { ClaudeConfigExport, SaveOptions } from './services/fileService';
|
|
32
33
|
|
|
@@ -70,6 +71,72 @@ app.get('/api/project-path', (req, res) => {
|
|
|
70
71
|
res.json({ path: fileService.getProjectPath() });
|
|
71
72
|
});
|
|
72
73
|
|
|
74
|
+
// ============================================
|
|
75
|
+
// Project Management API
|
|
76
|
+
// ============================================
|
|
77
|
+
|
|
78
|
+
// List all projects
|
|
79
|
+
app.get('/api/projects', async (req, res) => {
|
|
80
|
+
try {
|
|
81
|
+
const projects = await projectService.listProjects();
|
|
82
|
+
const gallery = projectService.getGalleryItems();
|
|
83
|
+
res.json({ projects, gallery });
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
86
|
+
console.error('List projects error:', errorMessage);
|
|
87
|
+
res.status(500).json({ message: errorMessage });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Create a new project
|
|
92
|
+
app.post('/api/projects', async (req, res) => {
|
|
93
|
+
try {
|
|
94
|
+
const { name, description } = req.body as { name: string; description: string };
|
|
95
|
+
|
|
96
|
+
if (!name || typeof name !== 'string') {
|
|
97
|
+
return res.status(400).json({ message: 'Project name is required' });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const project = await projectService.createProject(name, description || '');
|
|
101
|
+
res.json(project);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
104
|
+
console.error('Create project error:', errorMessage);
|
|
105
|
+
res.status(500).json({ message: errorMessage });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Get a specific project
|
|
110
|
+
app.get('/api/projects/:id', async (req, res) => {
|
|
111
|
+
try {
|
|
112
|
+
const project = await projectService.getProject(req.params.id);
|
|
113
|
+
if (!project) {
|
|
114
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
115
|
+
}
|
|
116
|
+
res.json(project);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
119
|
+
res.status(500).json({ message: errorMessage });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Delete a project
|
|
124
|
+
app.delete('/api/projects/:id', async (req, res) => {
|
|
125
|
+
try {
|
|
126
|
+
await projectService.deleteProject(req.params.id);
|
|
127
|
+
res.json({ success: true });
|
|
128
|
+
} catch (error) {
|
|
129
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
130
|
+
console.error('Delete project error:', errorMessage);
|
|
131
|
+
res.status(500).json({ message: errorMessage });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Get makecc home directory
|
|
136
|
+
app.get('/api/makecc-home', (req, res) => {
|
|
137
|
+
res.json({ path: projectService.getMakeccHome() });
|
|
138
|
+
});
|
|
139
|
+
|
|
73
140
|
// Save workflow as Claude Code configuration
|
|
74
141
|
app.post('/api/save/workflow', async (req, res) => {
|
|
75
142
|
try {
|
|
@@ -242,8 +309,7 @@ app.get('/api/load/claude-config', async (req, res) => {
|
|
|
242
309
|
try {
|
|
243
310
|
const config = await configLoaderService.loadAll();
|
|
244
311
|
console.log(
|
|
245
|
-
`Loaded config: ${config.skills.length} skills, ${config.agents.length} agents, `
|
|
246
|
-
`${config.commands.length} commands, ${config.hooks.length} hooks`
|
|
312
|
+
`Loaded config: ${config.skills.length} skills, ${config.agents.length} agents, ${config.hooks.length} hooks`
|
|
247
313
|
);
|
|
248
314
|
res.json(config);
|
|
249
315
|
} catch (error) {
|
|
@@ -256,7 +322,7 @@ app.get('/api/load/claude-config', async (req, res) => {
|
|
|
256
322
|
// Generate workflow using AI
|
|
257
323
|
app.post('/api/generate/workflow', async (req, res) => {
|
|
258
324
|
try {
|
|
259
|
-
const { prompt } = req.body as { prompt: string };
|
|
325
|
+
const { prompt, expand = true } = req.body as { prompt: string; expand?: boolean };
|
|
260
326
|
|
|
261
327
|
if (!prompt || typeof prompt !== 'string') {
|
|
262
328
|
return res.status(400).json({ message: 'Prompt is required' });
|
|
@@ -267,13 +333,21 @@ app.post('/api/generate/workflow', async (req, res) => {
|
|
|
267
333
|
const apiKey = req.headers['x-api-key'] as string;
|
|
268
334
|
const proxyUrl = req.headers['x-proxy-url'] as string;
|
|
269
335
|
|
|
270
|
-
console.log('Generating workflow for prompt:', prompt, 'mode:', apiMode);
|
|
336
|
+
console.log('Generating workflow for prompt:', prompt, 'mode:', apiMode, 'expand:', expand);
|
|
271
337
|
|
|
272
|
-
const
|
|
338
|
+
const settings = {
|
|
273
339
|
apiMode: apiMode as 'proxy' | 'direct',
|
|
274
340
|
apiKey,
|
|
275
341
|
proxyUrl,
|
|
276
|
-
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// expand=true (기본값)이면 재귀적으로 스킬/에이전트 상세 생성
|
|
345
|
+
const result = expand
|
|
346
|
+
? await workflowAIService.generateWithExpansion(prompt, settings, (event) => {
|
|
347
|
+
console.log(`[Workflow] ${event.step}: ${event.message}`);
|
|
348
|
+
})
|
|
349
|
+
: await workflowAIService.generate(prompt, settings);
|
|
350
|
+
|
|
277
351
|
console.log('Workflow generated:', result.workflowName);
|
|
278
352
|
|
|
279
353
|
res.json(result);
|
|
@@ -641,8 +715,16 @@ async function startServer() {
|
|
|
641
715
|
// 초기화 실패해도 서버는 시작
|
|
642
716
|
}
|
|
643
717
|
|
|
718
|
+
// 샘플 프로젝트 생성 (~/makecc에 없는 경우)
|
|
719
|
+
try {
|
|
720
|
+
await projectService.createSampleProjects();
|
|
721
|
+
} catch (error) {
|
|
722
|
+
console.error('Sample projects creation warning:', error);
|
|
723
|
+
}
|
|
724
|
+
|
|
644
725
|
httpServer.listen(PORT, () => {
|
|
645
726
|
console.log(`Server running on http://localhost:${PORT}`);
|
|
727
|
+
console.log(`makecc home: ${projectService.getMakeccHome()}`);
|
|
646
728
|
});
|
|
647
729
|
}
|
|
648
730
|
|
|
@@ -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
38
|
agents: LoadedSubagent[];
|
|
48
|
-
commands: LoadedCommand[];
|
|
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, agents,
|
|
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, agents,
|
|
59
|
+
return { skills, agents, hooks };
|
|
71
60
|
}
|
|
72
61
|
|
|
73
62
|
/**
|
|
@@ -108,9 +97,9 @@ 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
104
|
const agents: LoadedSubagent[] = [];
|
|
116
105
|
|
|
@@ -147,44 +136,6 @@ export class ConfigLoaderService {
|
|
|
147
136
|
return agents;
|
|
148
137
|
}
|
|
149
138
|
|
|
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;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
139
|
/**
|
|
189
140
|
* .claude/settings.json 에서 훅 로드
|
|
190
141
|
*/
|