makecc 0.2.3 → 0.2.5

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,145 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ const SYSTEM_PROMPT = `You are a Claude Code skill generator. You MUST respond with ONLY a valid JSON object. No markdown, no code blocks, no explanations - just pure JSON.
5
+
6
+ Your response must follow this exact JSON schema:
7
+
8
+ {
9
+ "skillName": "Human readable skill name",
10
+ "skillId": "skill-id-in-kebab-case",
11
+ "description": "Brief description of what this skill does",
12
+ "files": [
13
+ {
14
+ "path": "SKILL.md",
15
+ "content": "Full SKILL.md content here",
16
+ "language": "markdown"
17
+ },
18
+ {
19
+ "path": "scripts/main.py",
20
+ "content": "Full Python script content here",
21
+ "language": "python"
22
+ },
23
+ {
24
+ "path": "requirements.txt",
25
+ "content": "package1\\npackage2",
26
+ "language": "text"
27
+ }
28
+ ]
29
+ }
30
+
31
+ SKILL.md must follow this format:
32
+ ---
33
+ name: skill-id
34
+ description: One line description
35
+ ---
36
+
37
+ # Skill Name
38
+
39
+ ## When to use
40
+ - Use case 1
41
+ - Use case 2
42
+
43
+ ## Usage
44
+ \`\`\`bash
45
+ ~/.claude/venv/bin/python ~/.claude/skills/skill-id/scripts/main.py [args]
46
+ \`\`\`
47
+
48
+ ## Parameters
49
+ - \`--param1\`: Description
50
+
51
+ ## Example
52
+ [Usage example]
53
+
54
+ RULES:
55
+ 1. Generate COMPLETE, WORKING code - no placeholders
56
+ 2. Include proper error handling with try-except
57
+ 3. Use Korean for user-facing messages
58
+ 4. Scripts run with ~/.claude/venv/bin/python
59
+ 5. List all required packages in requirements.txt
60
+ 6. RESPOND WITH JSON ONLY - NO OTHER TEXT`;
61
+ export class SkillGeneratorService {
62
+ projectRoot;
63
+ constructor(projectRoot) {
64
+ this.projectRoot = projectRoot || process.env.MAKECC_PROJECT_PATH || process.cwd();
65
+ }
66
+ getClient(settings) {
67
+ if (settings?.apiMode === 'direct' && settings.apiKey) {
68
+ return new Anthropic({ apiKey: settings.apiKey });
69
+ }
70
+ if (settings?.apiMode === 'proxy' && settings.proxyUrl) {
71
+ return new Anthropic({
72
+ baseURL: settings.proxyUrl,
73
+ apiKey: process.env.ANTHROPIC_API_KEY || 'proxy-mode',
74
+ });
75
+ }
76
+ const envApiKey = process.env.ANTHROPIC_API_KEY;
77
+ if (!envApiKey) {
78
+ throw new Error('API 키가 설정되지 않았습니다. 설정에서 API 키를 입력하세요.');
79
+ }
80
+ return new Anthropic({ apiKey: envApiKey });
81
+ }
82
+ async generate(prompt, settings) {
83
+ const client = this.getClient(settings);
84
+ const userPrompt = `Create a skill for: "${prompt}"
85
+
86
+ Generate complete, working code. Respond with JSON only.`;
87
+ try {
88
+ const response = await client.messages.create({
89
+ model: 'claude-sonnet-4-20250514',
90
+ max_tokens: 8192,
91
+ system: SYSTEM_PROMPT,
92
+ messages: [
93
+ { role: 'user', content: userPrompt },
94
+ { role: 'assistant', content: '{' } // Prefill to force JSON
95
+ ],
96
+ });
97
+ let responseText = '';
98
+ for (const block of response.content) {
99
+ if (block.type === 'text') {
100
+ responseText += block.text;
101
+ }
102
+ }
103
+ if (!responseText) {
104
+ return { success: false, error: 'AI 응답을 받지 못했습니다.' };
105
+ }
106
+ // Prefill로 '{'를 보냈으니 응답 앞에 '{'를 붙임
107
+ const fullJson = '{' + responseText;
108
+ let skill;
109
+ try {
110
+ skill = JSON.parse(fullJson);
111
+ }
112
+ catch {
113
+ console.error('Failed to parse skill response:', fullJson.slice(0, 500));
114
+ return { success: false, error: 'AI 응답을 파싱할 수 없습니다. 다시 시도해주세요.' };
115
+ }
116
+ // 파일 저장
117
+ const skillPath = path.join(this.projectRoot, '.claude', 'skills', skill.skillId);
118
+ await this.saveSkillFiles(skillPath, skill.files);
119
+ return {
120
+ success: true,
121
+ skill,
122
+ savedPath: skillPath,
123
+ };
124
+ }
125
+ catch (error) {
126
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
127
+ console.error('Skill generation error:', errorMessage);
128
+ return { success: false, error: errorMessage };
129
+ }
130
+ }
131
+ async saveSkillFiles(skillPath, files) {
132
+ // 스킬 디렉토리 생성
133
+ await fs.mkdir(skillPath, { recursive: true });
134
+ for (const file of files) {
135
+ const filePath = path.join(skillPath, file.path);
136
+ const dirPath = path.dirname(filePath);
137
+ // 하위 디렉토리 생성
138
+ await fs.mkdir(dirPath, { recursive: true });
139
+ // 파일 저장
140
+ await fs.writeFile(filePath, file.content, 'utf-8');
141
+ console.log(`Saved: ${filePath}`);
142
+ }
143
+ }
144
+ }
145
+ 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.5",
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,186 @@
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 = `You are a Claude Code skill generator. You MUST respond with ONLY a valid JSON object. No markdown, no code blocks, no explanations - just pure JSON.
25
+
26
+ Your response must follow this exact JSON schema:
27
+
28
+ {
29
+ "skillName": "Human readable skill name",
30
+ "skillId": "skill-id-in-kebab-case",
31
+ "description": "Brief description of what this skill does",
32
+ "files": [
33
+ {
34
+ "path": "SKILL.md",
35
+ "content": "Full SKILL.md content here",
36
+ "language": "markdown"
37
+ },
38
+ {
39
+ "path": "scripts/main.py",
40
+ "content": "Full Python script content here",
41
+ "language": "python"
42
+ },
43
+ {
44
+ "path": "requirements.txt",
45
+ "content": "package1\\npackage2",
46
+ "language": "text"
47
+ }
48
+ ]
49
+ }
50
+
51
+ SKILL.md must follow this format:
52
+ ---
53
+ name: skill-id
54
+ description: One line description
55
+ ---
56
+
57
+ # Skill Name
58
+
59
+ ## When to use
60
+ - Use case 1
61
+ - Use case 2
62
+
63
+ ## Usage
64
+ \`\`\`bash
65
+ ~/.claude/venv/bin/python ~/.claude/skills/skill-id/scripts/main.py [args]
66
+ \`\`\`
67
+
68
+ ## Parameters
69
+ - \`--param1\`: Description
70
+
71
+ ## Example
72
+ [Usage example]
73
+
74
+ RULES:
75
+ 1. Generate COMPLETE, WORKING code - no placeholders
76
+ 2. Include proper error handling with try-except
77
+ 3. Use Korean for user-facing messages
78
+ 4. Scripts run with ~/.claude/venv/bin/python
79
+ 5. List all required packages in requirements.txt
80
+ 6. RESPOND WITH JSON ONLY - NO OTHER TEXT`;
81
+
82
+ export class SkillGeneratorService {
83
+ private projectRoot: string;
84
+
85
+ constructor(projectRoot?: string) {
86
+ this.projectRoot = projectRoot || process.env.MAKECC_PROJECT_PATH || process.cwd();
87
+ }
88
+
89
+ private getClient(settings?: ApiSettings): Anthropic {
90
+ if (settings?.apiMode === 'direct' && settings.apiKey) {
91
+ return new Anthropic({ apiKey: settings.apiKey });
92
+ }
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
+
101
+ const envApiKey = process.env.ANTHROPIC_API_KEY;
102
+ if (!envApiKey) {
103
+ throw new Error('API 키가 설정되지 않았습니다. 설정에서 API 키를 입력하세요.');
104
+ }
105
+
106
+ return new Anthropic({ apiKey: envApiKey });
107
+ }
108
+
109
+ async generate(prompt: string, settings?: ApiSettings): Promise<SkillGenerationResult> {
110
+ const client = this.getClient(settings);
111
+
112
+ const userPrompt = `Create a skill for: "${prompt}"
113
+
114
+ Generate complete, working code. Respond with JSON only.`;
115
+
116
+ try {
117
+ const response = await client.messages.create({
118
+ model: 'claude-sonnet-4-20250514',
119
+ max_tokens: 8192,
120
+ system: SYSTEM_PROMPT,
121
+ messages: [
122
+ { role: 'user', content: userPrompt },
123
+ { role: 'assistant', content: '{' } // Prefill to force JSON
124
+ ],
125
+ });
126
+
127
+ let responseText = '';
128
+ for (const block of response.content) {
129
+ if (block.type === 'text') {
130
+ responseText += block.text;
131
+ }
132
+ }
133
+
134
+ if (!responseText) {
135
+ return { success: false, error: 'AI 응답을 받지 못했습니다.' };
136
+ }
137
+
138
+ // Prefill로 '{'를 보냈으니 응답 앞에 '{'를 붙임
139
+ const fullJson = '{' + responseText;
140
+
141
+ let skill: GeneratedSkill;
142
+ try {
143
+ skill = JSON.parse(fullJson);
144
+ } catch {
145
+ console.error('Failed to parse skill response:', fullJson.slice(0, 500));
146
+ return { success: false, error: 'AI 응답을 파싱할 수 없습니다. 다시 시도해주세요.' };
147
+ }
148
+
149
+ // 파일 저장
150
+ const skillPath = path.join(this.projectRoot, '.claude', 'skills', skill.skillId);
151
+ await this.saveSkillFiles(skillPath, skill.files);
152
+
153
+ return {
154
+ success: true,
155
+ skill,
156
+ savedPath: skillPath,
157
+ };
158
+ } catch (error) {
159
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
160
+ console.error('Skill generation error:', errorMessage);
161
+ return { success: false, error: errorMessage };
162
+ }
163
+ }
164
+
165
+ private async saveSkillFiles(
166
+ skillPath: string,
167
+ files: Array<{ path: string; content: string }>
168
+ ): Promise<void> {
169
+ // 스킬 디렉토리 생성
170
+ await fs.mkdir(skillPath, { recursive: true });
171
+
172
+ for (const file of files) {
173
+ const filePath = path.join(skillPath, file.path);
174
+ const dirPath = path.dirname(filePath);
175
+
176
+ // 하위 디렉토리 생성
177
+ await fs.mkdir(dirPath, { recursive: true });
178
+
179
+ // 파일 저장
180
+ await fs.writeFile(filePath, file.content, 'utf-8');
181
+ console.log(`Saved: ${filePath}`);
182
+ }
183
+ }
184
+ }
185
+
186
+ export const skillGeneratorService = new SkillGeneratorService();