makecc 0.2.3 → 0.2.4

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.
@@ -5,7 +5,7 @@
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>vite-temp</title>
8
- <script type="module" crossorigin src="/assets/index-CdEIG4T7.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-c7WYmobg.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-DLT8bQFx.css">
10
10
  </head>
11
11
  <body>
@@ -9,6 +9,7 @@ import { existsSync } from 'fs';
9
9
  import { ClaudeService } from './services/claudeService';
10
10
  import { fileService } from './services/fileService';
11
11
  import { workflowAIService } from './services/workflowAIService';
12
+ import { skillGeneratorService } from './services/skillGeneratorService';
12
13
  import { workflowExecutionService } from './services/workflowExecutionService';
13
14
  import { executeInTerminal } from './services/terminalService';
14
15
  const __filename = fileURLToPath(import.meta.url);
@@ -89,6 +90,36 @@ app.get('/api/settings/api-key', async (req, res) => {
89
90
  res.status(500).json({ message: errorMessage });
90
91
  }
91
92
  });
93
+ // Generate skill using AI
94
+ app.post('/api/generate/skill', async (req, res) => {
95
+ try {
96
+ const { prompt } = req.body;
97
+ if (!prompt || typeof prompt !== 'string') {
98
+ return res.status(400).json({ message: 'Prompt is required' });
99
+ }
100
+ const apiMode = req.headers['x-api-mode'] || 'proxy';
101
+ const apiKey = req.headers['x-api-key'];
102
+ const proxyUrl = req.headers['x-proxy-url'];
103
+ console.log('Generating skill for prompt:', prompt);
104
+ const result = await skillGeneratorService.generate(prompt, {
105
+ apiMode: apiMode,
106
+ apiKey,
107
+ proxyUrl,
108
+ });
109
+ if (result.success) {
110
+ console.log('Skill generated:', result.skill?.skillName, 'at', result.savedPath);
111
+ res.json(result);
112
+ }
113
+ else {
114
+ res.status(500).json({ message: result.error });
115
+ }
116
+ }
117
+ catch (error) {
118
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
119
+ console.error('Generate skill error:', errorMessage);
120
+ res.status(500).json({ message: errorMessage });
121
+ }
122
+ });
92
123
  // Generate workflow using AI
93
124
  app.post('/api/generate/workflow', async (req, res) => {
94
125
  try {
@@ -0,0 +1,168 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ const SYSTEM_PROMPT = `당신은 Claude Code 스킬 생성 전문가입니다.
5
+
6
+ 사용자의 요청을 분석하여 완전히 작동하는 Claude Code 스킬을 생성합니다.
7
+
8
+ ## Claude Code 스킬 구조
9
+
10
+ 스킬은 다음 구조로 생성됩니다:
11
+ \`\`\`
12
+ .claude/skills/[skill-name]/
13
+ ├── SKILL.md # 스킬 정의 (필수)
14
+ ├── scripts/ # 스크립트 폴더
15
+ │ └── main.py # 메인 스크립트
16
+ └── requirements.txt # Python 의존성 (필요시)
17
+ \`\`\`
18
+
19
+ ## SKILL.md 형식
20
+
21
+ \`\`\`markdown
22
+ ---
23
+ name: skill-name
24
+ description: 스킬 설명 (한 줄)
25
+ ---
26
+
27
+ # 스킬 이름
28
+
29
+ ## 사용 시점
30
+ 이 스킬은 다음 상황에서 사용됩니다:
31
+ - 상황 1
32
+ - 상황 2
33
+
34
+ ## 사용 방법
35
+
36
+ \\\`\\\`\\\`bash
37
+ ~/.claude/venv/bin/python ~/.claude/skills/[skill-name]/scripts/main.py [인자들]
38
+ \\\`\\\`\\\`
39
+
40
+ ## 파라미터
41
+ - \`param1\`: 설명
42
+ - \`param2\`: 설명
43
+
44
+ ## 예시
45
+ [사용 예시]
46
+ \`\`\`
47
+
48
+ ## 응답 형식 (JSON)
49
+
50
+ 반드시 아래 형식의 JSON으로 응답하세요:
51
+
52
+ {
53
+ "skillName": "스킬 이름 (한글 가능)",
54
+ "skillId": "skill-id-kebab-case",
55
+ "description": "스킬 설명",
56
+ "files": [
57
+ {
58
+ "path": "SKILL.md",
59
+ "content": "SKILL.md 전체 내용",
60
+ "language": "markdown"
61
+ },
62
+ {
63
+ "path": "scripts/main.py",
64
+ "content": "Python 스크립트 전체 내용",
65
+ "language": "python"
66
+ },
67
+ {
68
+ "path": "requirements.txt",
69
+ "content": "의존성 목록 (필요한 경우)",
70
+ "language": "text"
71
+ }
72
+ ]
73
+ }
74
+
75
+ ## 중요 규칙
76
+
77
+ 1. **완전한 코드 생성**: 실제로 동작하는 완전한 코드를 작성하세요
78
+ 2. **에러 처리 포함**: try-except로 에러 처리를 포함하세요
79
+ 3. **한글 지원**: 출력 메시지는 한글로 작성하세요
80
+ 4. **환경 고려**:
81
+ - Python 스크립트는 \`~/.claude/venv/bin/python\`으로 실행됩니다
82
+ - 필요한 패키지는 requirements.txt에 명시하세요
83
+ 5. **JSON만 반환**: 설명 없이 JSON만 반환하세요
84
+ `;
85
+ export class SkillGeneratorService {
86
+ projectRoot;
87
+ constructor(projectRoot) {
88
+ this.projectRoot = projectRoot || process.env.MAKECC_PROJECT_PATH || process.cwd();
89
+ }
90
+ getClient(settings) {
91
+ if (settings?.apiMode === 'direct' && settings.apiKey) {
92
+ return new Anthropic({ apiKey: settings.apiKey });
93
+ }
94
+ if (settings?.apiMode === 'proxy' && settings.proxyUrl) {
95
+ return new Anthropic({
96
+ baseURL: settings.proxyUrl,
97
+ apiKey: process.env.ANTHROPIC_API_KEY || 'proxy-mode',
98
+ });
99
+ }
100
+ const envApiKey = process.env.ANTHROPIC_API_KEY;
101
+ if (!envApiKey) {
102
+ throw new Error('API 키가 설정되지 않았습니다. 설정에서 API 키를 입력하세요.');
103
+ }
104
+ return new Anthropic({ apiKey: envApiKey });
105
+ }
106
+ async generate(prompt, settings) {
107
+ const client = this.getClient(settings);
108
+ const userPrompt = `다음 스킬을 생성해주세요: "${prompt}"
109
+
110
+ 완전히 동작하는 코드를 포함한 스킬을 생성하세요.
111
+ 반드시 JSON만 반환하세요.`;
112
+ try {
113
+ const response = await client.messages.create({
114
+ model: 'claude-sonnet-4-20250514',
115
+ max_tokens: 8192,
116
+ system: SYSTEM_PROMPT,
117
+ messages: [{ role: 'user', content: userPrompt }],
118
+ });
119
+ let responseText = '';
120
+ for (const block of response.content) {
121
+ if (block.type === 'text') {
122
+ responseText += block.text;
123
+ }
124
+ }
125
+ if (!responseText) {
126
+ return { success: false, error: 'AI 응답을 받지 못했습니다.' };
127
+ }
128
+ // JSON 추출
129
+ const jsonMatch = responseText.match(/```(?:json)?\s*([\s\S]*?)```/);
130
+ const rawJson = jsonMatch ? jsonMatch[1].trim() : responseText.trim();
131
+ let skill;
132
+ try {
133
+ skill = JSON.parse(rawJson);
134
+ }
135
+ catch {
136
+ console.error('Failed to parse skill response:', rawJson.slice(0, 500));
137
+ return { success: false, error: 'AI 응답을 파싱할 수 없습니다. 다시 시도해주세요.' };
138
+ }
139
+ // 파일 저장
140
+ const skillPath = path.join(this.projectRoot, '.claude', 'skills', skill.skillId);
141
+ await this.saveSkillFiles(skillPath, skill.files);
142
+ return {
143
+ success: true,
144
+ skill,
145
+ savedPath: skillPath,
146
+ };
147
+ }
148
+ catch (error) {
149
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
150
+ console.error('Skill generation error:', errorMessage);
151
+ return { success: false, error: errorMessage };
152
+ }
153
+ }
154
+ async saveSkillFiles(skillPath, files) {
155
+ // 스킬 디렉토리 생성
156
+ await fs.mkdir(skillPath, { recursive: true });
157
+ for (const file of files) {
158
+ const filePath = path.join(skillPath, file.path);
159
+ const dirPath = path.dirname(filePath);
160
+ // 하위 디렉토리 생성
161
+ await fs.mkdir(dirPath, { recursive: true });
162
+ // 파일 저장
163
+ await fs.writeFile(filePath, file.content, 'utf-8');
164
+ console.log(`Saved: ${filePath}`);
165
+ }
166
+ }
167
+ }
168
+ export const skillGeneratorService = new SkillGeneratorService();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makecc",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "description": "Visual workflow builder for Claude Code agents and skills",
6
6
  "keywords": [
package/server/index.ts CHANGED
@@ -9,6 +9,7 @@ import { existsSync } from 'fs';
9
9
  import { ClaudeService } from './services/claudeService';
10
10
  import { fileService } from './services/fileService';
11
11
  import { workflowAIService } from './services/workflowAIService';
12
+ import { skillGeneratorService } from './services/skillGeneratorService';
12
13
  import { workflowExecutionService } from './services/workflowExecutionService';
13
14
  import { executeInTerminal, getClaudeCommand } from './services/terminalService';
14
15
  import type { WorkflowExecutionRequest, NodeExecutionUpdate } from './types';
@@ -107,6 +108,40 @@ app.get('/api/settings/api-key', async (req, res) => {
107
108
  }
108
109
  });
109
110
 
111
+ // Generate skill using AI
112
+ app.post('/api/generate/skill', async (req, res) => {
113
+ try {
114
+ const { prompt } = req.body as { prompt: string };
115
+
116
+ if (!prompt || typeof prompt !== 'string') {
117
+ return res.status(400).json({ message: 'Prompt is required' });
118
+ }
119
+
120
+ const apiMode = req.headers['x-api-mode'] as string || 'proxy';
121
+ const apiKey = req.headers['x-api-key'] as string;
122
+ const proxyUrl = req.headers['x-proxy-url'] as string;
123
+
124
+ console.log('Generating skill for prompt:', prompt);
125
+
126
+ const result = await skillGeneratorService.generate(prompt, {
127
+ apiMode: apiMode as 'proxy' | 'direct',
128
+ apiKey,
129
+ proxyUrl,
130
+ });
131
+
132
+ if (result.success) {
133
+ console.log('Skill generated:', result.skill?.skillName, 'at', result.savedPath);
134
+ res.json(result);
135
+ } else {
136
+ res.status(500).json({ message: result.error });
137
+ }
138
+ } catch (error) {
139
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
140
+ console.error('Generate skill error:', errorMessage);
141
+ res.status(500).json({ message: errorMessage });
142
+ }
143
+ });
144
+
110
145
  // Generate workflow using AI
111
146
  app.post('/api/generate/workflow', async (req, res) => {
112
147
  try {
@@ -0,0 +1,209 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ import type { ApiSettings } from './workflowAIService';
5
+
6
+ export interface GeneratedSkill {
7
+ skillName: string;
8
+ skillId: string;
9
+ description: string;
10
+ files: Array<{
11
+ path: string;
12
+ content: string;
13
+ language: string;
14
+ }>;
15
+ }
16
+
17
+ export interface SkillGenerationResult {
18
+ success: boolean;
19
+ skill?: GeneratedSkill;
20
+ savedPath?: string;
21
+ error?: string;
22
+ }
23
+
24
+ const SYSTEM_PROMPT = `당신은 Claude Code 스킬 생성 전문가입니다.
25
+
26
+ 사용자의 요청을 분석하여 완전히 작동하는 Claude Code 스킬을 생성합니다.
27
+
28
+ ## Claude Code 스킬 구조
29
+
30
+ 스킬은 다음 구조로 생성됩니다:
31
+ \`\`\`
32
+ .claude/skills/[skill-name]/
33
+ ├── SKILL.md # 스킬 정의 (필수)
34
+ ├── scripts/ # 스크립트 폴더
35
+ │ └── main.py # 메인 스크립트
36
+ └── requirements.txt # Python 의존성 (필요시)
37
+ \`\`\`
38
+
39
+ ## SKILL.md 형식
40
+
41
+ \`\`\`markdown
42
+ ---
43
+ name: skill-name
44
+ description: 스킬 설명 (한 줄)
45
+ ---
46
+
47
+ # 스킬 이름
48
+
49
+ ## 사용 시점
50
+ 이 스킬은 다음 상황에서 사용됩니다:
51
+ - 상황 1
52
+ - 상황 2
53
+
54
+ ## 사용 방법
55
+
56
+ \\\`\\\`\\\`bash
57
+ ~/.claude/venv/bin/python ~/.claude/skills/[skill-name]/scripts/main.py [인자들]
58
+ \\\`\\\`\\\`
59
+
60
+ ## 파라미터
61
+ - \`param1\`: 설명
62
+ - \`param2\`: 설명
63
+
64
+ ## 예시
65
+ [사용 예시]
66
+ \`\`\`
67
+
68
+ ## 응답 형식 (JSON)
69
+
70
+ 반드시 아래 형식의 JSON으로 응답하세요:
71
+
72
+ {
73
+ "skillName": "스킬 이름 (한글 가능)",
74
+ "skillId": "skill-id-kebab-case",
75
+ "description": "스킬 설명",
76
+ "files": [
77
+ {
78
+ "path": "SKILL.md",
79
+ "content": "SKILL.md 전체 내용",
80
+ "language": "markdown"
81
+ },
82
+ {
83
+ "path": "scripts/main.py",
84
+ "content": "Python 스크립트 전체 내용",
85
+ "language": "python"
86
+ },
87
+ {
88
+ "path": "requirements.txt",
89
+ "content": "의존성 목록 (필요한 경우)",
90
+ "language": "text"
91
+ }
92
+ ]
93
+ }
94
+
95
+ ## 중요 규칙
96
+
97
+ 1. **완전한 코드 생성**: 실제로 동작하는 완전한 코드를 작성하세요
98
+ 2. **에러 처리 포함**: try-except로 에러 처리를 포함하세요
99
+ 3. **한글 지원**: 출력 메시지는 한글로 작성하세요
100
+ 4. **환경 고려**:
101
+ - Python 스크립트는 \`~/.claude/venv/bin/python\`으로 실행됩니다
102
+ - 필요한 패키지는 requirements.txt에 명시하세요
103
+ 5. **JSON만 반환**: 설명 없이 JSON만 반환하세요
104
+ `;
105
+
106
+ export class SkillGeneratorService {
107
+ private projectRoot: string;
108
+
109
+ constructor(projectRoot?: string) {
110
+ this.projectRoot = projectRoot || process.env.MAKECC_PROJECT_PATH || process.cwd();
111
+ }
112
+
113
+ private getClient(settings?: ApiSettings): Anthropic {
114
+ if (settings?.apiMode === 'direct' && settings.apiKey) {
115
+ return new Anthropic({ apiKey: settings.apiKey });
116
+ }
117
+
118
+ if (settings?.apiMode === 'proxy' && settings.proxyUrl) {
119
+ return new Anthropic({
120
+ baseURL: settings.proxyUrl,
121
+ apiKey: process.env.ANTHROPIC_API_KEY || 'proxy-mode',
122
+ });
123
+ }
124
+
125
+ const envApiKey = process.env.ANTHROPIC_API_KEY;
126
+ if (!envApiKey) {
127
+ throw new Error('API 키가 설정되지 않았습니다. 설정에서 API 키를 입력하세요.');
128
+ }
129
+
130
+ return new Anthropic({ apiKey: envApiKey });
131
+ }
132
+
133
+ async generate(prompt: string, settings?: ApiSettings): Promise<SkillGenerationResult> {
134
+ const client = this.getClient(settings);
135
+
136
+ const userPrompt = `다음 스킬을 생성해주세요: "${prompt}"
137
+
138
+ 완전히 동작하는 코드를 포함한 스킬을 생성하세요.
139
+ 반드시 JSON만 반환하세요.`;
140
+
141
+ try {
142
+ const response = await client.messages.create({
143
+ model: 'claude-sonnet-4-20250514',
144
+ max_tokens: 8192,
145
+ system: SYSTEM_PROMPT,
146
+ messages: [{ role: 'user', content: userPrompt }],
147
+ });
148
+
149
+ let responseText = '';
150
+ for (const block of response.content) {
151
+ if (block.type === 'text') {
152
+ responseText += block.text;
153
+ }
154
+ }
155
+
156
+ if (!responseText) {
157
+ return { success: false, error: 'AI 응답을 받지 못했습니다.' };
158
+ }
159
+
160
+ // JSON 추출
161
+ const jsonMatch = responseText.match(/```(?:json)?\s*([\s\S]*?)```/);
162
+ const rawJson = jsonMatch ? jsonMatch[1].trim() : responseText.trim();
163
+
164
+ let skill: GeneratedSkill;
165
+ try {
166
+ skill = JSON.parse(rawJson);
167
+ } catch {
168
+ console.error('Failed to parse skill response:', rawJson.slice(0, 500));
169
+ return { success: false, error: 'AI 응답을 파싱할 수 없습니다. 다시 시도해주세요.' };
170
+ }
171
+
172
+ // 파일 저장
173
+ const skillPath = path.join(this.projectRoot, '.claude', 'skills', skill.skillId);
174
+ await this.saveSkillFiles(skillPath, skill.files);
175
+
176
+ return {
177
+ success: true,
178
+ skill,
179
+ savedPath: skillPath,
180
+ };
181
+ } catch (error) {
182
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
183
+ console.error('Skill generation error:', errorMessage);
184
+ return { success: false, error: errorMessage };
185
+ }
186
+ }
187
+
188
+ private async saveSkillFiles(
189
+ skillPath: string,
190
+ files: Array<{ path: string; content: string }>
191
+ ): Promise<void> {
192
+ // 스킬 디렉토리 생성
193
+ await fs.mkdir(skillPath, { recursive: true });
194
+
195
+ for (const file of files) {
196
+ const filePath = path.join(skillPath, file.path);
197
+ const dirPath = path.dirname(filePath);
198
+
199
+ // 하위 디렉토리 생성
200
+ await fs.mkdir(dirPath, { recursive: true });
201
+
202
+ // 파일 저장
203
+ await fs.writeFile(filePath, file.content, 'utf-8');
204
+ console.log(`Saved: ${filePath}`);
205
+ }
206
+ }
207
+ }
208
+
209
+ export const skillGeneratorService = new SkillGeneratorService();