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.
- package/dist/client/assets/{index-CdEIG4T7.js → index-c7WYmobg.js} +31 -31
- package/dist/client/index.html +1 -1
- package/dist/server/index.js +31 -0
- package/dist/server/services/skillGeneratorService.js +145 -0
- package/package.json +1 -1
- package/server/index.ts +35 -0
- package/server/services/skillGeneratorService.ts +186 -0
package/dist/client/index.html
CHANGED
|
@@ -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-
|
|
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>
|
package/dist/server/index.js
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 } 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
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();
|