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
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { promises as fs, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { nanoid } from 'nanoid';
|
|
5
|
+
|
|
6
|
+
export interface Project {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
thumbnail?: string;
|
|
11
|
+
skillCount: number;
|
|
12
|
+
agentCount: number;
|
|
13
|
+
path: string;
|
|
14
|
+
lastModified: string;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface GalleryItem {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
thumbnail: string;
|
|
23
|
+
author: string;
|
|
24
|
+
downloads: number;
|
|
25
|
+
tags: string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Default makecc home directory
|
|
29
|
+
const MAKECC_HOME = process.env.MAKECC_HOME || join(homedir(), 'makecc');
|
|
30
|
+
|
|
31
|
+
// Gallery items (static for now, could be fetched from a registry later)
|
|
32
|
+
const GALLERY_ITEMS: GalleryItem[] = [
|
|
33
|
+
{
|
|
34
|
+
id: 'gmail-assistant',
|
|
35
|
+
name: 'Gmail Assistant',
|
|
36
|
+
description: 'Read, write and organize emails using Claude',
|
|
37
|
+
thumbnail: '/gallery/gmail.png',
|
|
38
|
+
author: 'makecc',
|
|
39
|
+
downloads: 1234,
|
|
40
|
+
tags: ['email', 'productivity'],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'web-scraper',
|
|
44
|
+
name: 'Web Scraper',
|
|
45
|
+
description: 'Extract data from websites automatically',
|
|
46
|
+
thumbnail: '/gallery/scraper.png',
|
|
47
|
+
author: 'makecc',
|
|
48
|
+
downloads: 892,
|
|
49
|
+
tags: ['data', 'automation'],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'document-analyzer',
|
|
53
|
+
name: 'Document Analyzer',
|
|
54
|
+
description: 'Analyze and summarize PDF documents',
|
|
55
|
+
thumbnail: '/gallery/docs.png',
|
|
56
|
+
author: 'makecc',
|
|
57
|
+
downloads: 567,
|
|
58
|
+
tags: ['pdf', 'analysis'],
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
class ProjectService {
|
|
63
|
+
private makeccHome: string;
|
|
64
|
+
|
|
65
|
+
constructor() {
|
|
66
|
+
this.makeccHome = MAKECC_HOME;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getMakeccHome(): string {
|
|
70
|
+
return this.makeccHome;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async ensureMakeccHome(): Promise<void> {
|
|
74
|
+
if (!existsSync(this.makeccHome)) {
|
|
75
|
+
await fs.mkdir(this.makeccHome, { recursive: true });
|
|
76
|
+
console.log(`Created makecc home directory: ${this.makeccHome}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async listProjects(): Promise<Project[]> {
|
|
81
|
+
await this.ensureMakeccHome();
|
|
82
|
+
|
|
83
|
+
const entries = await fs.readdir(this.makeccHome, { withFileTypes: true });
|
|
84
|
+
const projects: Project[] = [];
|
|
85
|
+
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
if (!entry.isDirectory()) continue;
|
|
88
|
+
if (entry.name.startsWith('.')) continue;
|
|
89
|
+
|
|
90
|
+
const projectPath = join(this.makeccHome, entry.name);
|
|
91
|
+
const project = await this.loadProjectInfo(projectPath, entry.name);
|
|
92
|
+
if (project) {
|
|
93
|
+
projects.push(project);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Sort by lastModified (most recent first)
|
|
98
|
+
projects.sort((a, b) =>
|
|
99
|
+
new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime()
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return projects;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private async loadProjectInfo(projectPath: string, name: string): Promise<Project | null> {
|
|
106
|
+
try {
|
|
107
|
+
const stat = await fs.stat(projectPath);
|
|
108
|
+
|
|
109
|
+
// Read project.json if exists
|
|
110
|
+
const configPath = join(projectPath, 'project.json');
|
|
111
|
+
let config: { description?: string; id?: string; createdAt?: string } = {};
|
|
112
|
+
|
|
113
|
+
if (existsSync(configPath)) {
|
|
114
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
115
|
+
config = JSON.parse(content);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Count skills and agents
|
|
119
|
+
const claudePath = join(projectPath, '.claude');
|
|
120
|
+
let skillCount = 0;
|
|
121
|
+
let agentCount = 0;
|
|
122
|
+
|
|
123
|
+
if (existsSync(claudePath)) {
|
|
124
|
+
const skillsPath = join(claudePath, 'skills');
|
|
125
|
+
const agentsPath = join(claudePath, 'agents');
|
|
126
|
+
|
|
127
|
+
if (existsSync(skillsPath)) {
|
|
128
|
+
const skills = await fs.readdir(skillsPath, { withFileTypes: true });
|
|
129
|
+
skillCount = skills.filter(s => s.isDirectory()).length;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (existsSync(agentsPath)) {
|
|
133
|
+
const agents = await fs.readdir(agentsPath);
|
|
134
|
+
agentCount = agents.filter(a => a.endsWith('.md')).length;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
id: config.id || name,
|
|
140
|
+
name,
|
|
141
|
+
description: config.description || '',
|
|
142
|
+
skillCount,
|
|
143
|
+
agentCount,
|
|
144
|
+
path: projectPath,
|
|
145
|
+
lastModified: stat.mtime.toISOString(),
|
|
146
|
+
createdAt: config.createdAt || stat.birthtime.toISOString(),
|
|
147
|
+
};
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error(`Failed to load project info for ${name}:`, error);
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async createProject(name: string, description: string): Promise<Project> {
|
|
155
|
+
await this.ensureMakeccHome();
|
|
156
|
+
|
|
157
|
+
// Sanitize project name
|
|
158
|
+
const sanitizedName = name
|
|
159
|
+
.toLowerCase()
|
|
160
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
161
|
+
.replace(/-+/g, '-')
|
|
162
|
+
.replace(/^-|-$/g, '');
|
|
163
|
+
|
|
164
|
+
const projectPath = join(this.makeccHome, sanitizedName);
|
|
165
|
+
|
|
166
|
+
if (existsSync(projectPath)) {
|
|
167
|
+
throw new Error(`Project "${sanitizedName}" already exists`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Create project directory structure
|
|
171
|
+
await fs.mkdir(projectPath, { recursive: true });
|
|
172
|
+
await fs.mkdir(join(projectPath, '.claude', 'skills'), { recursive: true });
|
|
173
|
+
await fs.mkdir(join(projectPath, '.claude', 'agents'), { recursive: true });
|
|
174
|
+
|
|
175
|
+
// Create project.json
|
|
176
|
+
const projectId = nanoid(10);
|
|
177
|
+
const now = new Date().toISOString();
|
|
178
|
+
const config = {
|
|
179
|
+
id: projectId,
|
|
180
|
+
name: sanitizedName,
|
|
181
|
+
description,
|
|
182
|
+
createdAt: now,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
await fs.writeFile(
|
|
186
|
+
join(projectPath, 'project.json'),
|
|
187
|
+
JSON.stringify(config, null, 2)
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Create CLAUDE.md
|
|
191
|
+
const claudeMd = `# ${name}
|
|
192
|
+
|
|
193
|
+
${description}
|
|
194
|
+
|
|
195
|
+
## Project Structure
|
|
196
|
+
|
|
197
|
+
\`\`\`
|
|
198
|
+
${sanitizedName}/
|
|
199
|
+
├── .claude/
|
|
200
|
+
│ ├── skills/ # Claude Code skills
|
|
201
|
+
│ └── agents/ # Claude Code agents
|
|
202
|
+
└── project.json # Project configuration
|
|
203
|
+
\`\`\`
|
|
204
|
+
|
|
205
|
+
## Getting Started
|
|
206
|
+
|
|
207
|
+
1. Open this project in makecc
|
|
208
|
+
2. Create skills using natural language
|
|
209
|
+
3. Build workflows to automate tasks
|
|
210
|
+
`;
|
|
211
|
+
|
|
212
|
+
await fs.writeFile(join(projectPath, 'CLAUDE.md'), claudeMd);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
id: projectId,
|
|
216
|
+
name: sanitizedName,
|
|
217
|
+
description,
|
|
218
|
+
skillCount: 0,
|
|
219
|
+
agentCount: 0,
|
|
220
|
+
path: projectPath,
|
|
221
|
+
lastModified: now,
|
|
222
|
+
createdAt: now,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async deleteProject(projectId: string): Promise<void> {
|
|
227
|
+
const projects = await this.listProjects();
|
|
228
|
+
const project = projects.find(p => p.id === projectId || p.name === projectId);
|
|
229
|
+
|
|
230
|
+
if (!project) {
|
|
231
|
+
throw new Error(`Project "${projectId}" not found`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Move to trash instead of permanent delete
|
|
235
|
+
const trashPath = join(this.makeccHome, '.trash');
|
|
236
|
+
await fs.mkdir(trashPath, { recursive: true });
|
|
237
|
+
|
|
238
|
+
const trashName = `${project.name}_${Date.now()}`;
|
|
239
|
+
await fs.rename(project.path, join(trashPath, trashName));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async getProject(projectId: string): Promise<Project | null> {
|
|
243
|
+
const projects = await this.listProjects();
|
|
244
|
+
return projects.find(p => p.id === projectId || p.name === projectId) || null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
getGalleryItems(): GalleryItem[] {
|
|
248
|
+
return GALLERY_ITEMS;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async createSampleProjects(): Promise<void> {
|
|
252
|
+
await this.ensureMakeccHome();
|
|
253
|
+
|
|
254
|
+
// Check if samples already exist
|
|
255
|
+
const projects = await this.listProjects();
|
|
256
|
+
const hasGmail = projects.some(p => p.name === 'gmail-assistant');
|
|
257
|
+
const hasScraper = projects.some(p => p.name === 'web-scraper');
|
|
258
|
+
|
|
259
|
+
if (!hasGmail) {
|
|
260
|
+
await this.createGmailSampleProject();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!hasScraper) {
|
|
264
|
+
await this.createScraperSampleProject();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private async createGmailSampleProject(): Promise<void> {
|
|
269
|
+
const projectPath = join(this.makeccHome, 'gmail-assistant');
|
|
270
|
+
await fs.mkdir(projectPath, { recursive: true });
|
|
271
|
+
await fs.mkdir(join(projectPath, '.claude', 'skills', 'gmail'), { recursive: true });
|
|
272
|
+
await fs.mkdir(join(projectPath, '.claude', 'agents'), { recursive: true });
|
|
273
|
+
|
|
274
|
+
// project.json
|
|
275
|
+
await fs.writeFile(
|
|
276
|
+
join(projectPath, 'project.json'),
|
|
277
|
+
JSON.stringify({
|
|
278
|
+
id: 'gmail-sample',
|
|
279
|
+
name: 'gmail-assistant',
|
|
280
|
+
description: 'Read, write and organize emails using Claude',
|
|
281
|
+
createdAt: new Date().toISOString(),
|
|
282
|
+
}, null, 2)
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// Sample skill: SKILL.md
|
|
286
|
+
const skillMd = `---
|
|
287
|
+
name: gmail
|
|
288
|
+
description: Gmail 읽기/쓰기 스킬
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
# Gmail Skill
|
|
292
|
+
|
|
293
|
+
이 스킬은 Gmail API를 사용하여 이메일을 읽고 쓸 수 있게 합니다.
|
|
294
|
+
|
|
295
|
+
## 사용법
|
|
296
|
+
|
|
297
|
+
\`\`\`bash
|
|
298
|
+
python scripts/main.py read --count 10
|
|
299
|
+
python scripts/main.py send --to "user@example.com" --subject "Hello" --body "Hi there!"
|
|
300
|
+
\`\`\`
|
|
301
|
+
|
|
302
|
+
## 기능
|
|
303
|
+
|
|
304
|
+
- 받은편지함 읽기
|
|
305
|
+
- 이메일 검색
|
|
306
|
+
- 이메일 작성 및 전송
|
|
307
|
+
- 라벨 관리
|
|
308
|
+
`;
|
|
309
|
+
|
|
310
|
+
await fs.writeFile(
|
|
311
|
+
join(projectPath, '.claude', 'skills', 'gmail', 'SKILL.md'),
|
|
312
|
+
skillMd
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
// Sample Python script
|
|
316
|
+
const mainPy = `#!/usr/bin/env python3
|
|
317
|
+
"""Gmail skill for reading and writing emails."""
|
|
318
|
+
|
|
319
|
+
import argparse
|
|
320
|
+
import json
|
|
321
|
+
|
|
322
|
+
def read_emails(count: int = 10):
|
|
323
|
+
"""Read recent emails from inbox."""
|
|
324
|
+
print(json.dumps({
|
|
325
|
+
"status": "success",
|
|
326
|
+
"message": f"Would read {count} emails from Gmail",
|
|
327
|
+
"note": "This is a sample - configure Gmail API credentials to use"
|
|
328
|
+
}))
|
|
329
|
+
|
|
330
|
+
def send_email(to: str, subject: str, body: str):
|
|
331
|
+
"""Send an email."""
|
|
332
|
+
print(json.dumps({
|
|
333
|
+
"status": "success",
|
|
334
|
+
"message": f"Would send email to {to}",
|
|
335
|
+
"subject": subject,
|
|
336
|
+
"note": "This is a sample - configure Gmail API credentials to use"
|
|
337
|
+
}))
|
|
338
|
+
|
|
339
|
+
def main():
|
|
340
|
+
parser = argparse.ArgumentParser(description='Gmail Skill')
|
|
341
|
+
subparsers = parser.add_subparsers(dest='command', help='Commands')
|
|
342
|
+
|
|
343
|
+
# Read command
|
|
344
|
+
read_parser = subparsers.add_parser('read', help='Read emails')
|
|
345
|
+
read_parser.add_argument('--count', type=int, default=10, help='Number of emails')
|
|
346
|
+
|
|
347
|
+
# Send command
|
|
348
|
+
send_parser = subparsers.add_parser('send', help='Send email')
|
|
349
|
+
send_parser.add_argument('--to', required=True, help='Recipient')
|
|
350
|
+
send_parser.add_argument('--subject', required=True, help='Subject')
|
|
351
|
+
send_parser.add_argument('--body', required=True, help='Body')
|
|
352
|
+
|
|
353
|
+
args = parser.parse_args()
|
|
354
|
+
|
|
355
|
+
if args.command == 'read':
|
|
356
|
+
read_emails(args.count)
|
|
357
|
+
elif args.command == 'send':
|
|
358
|
+
send_email(args.to, args.subject, args.body)
|
|
359
|
+
else:
|
|
360
|
+
parser.print_help()
|
|
361
|
+
|
|
362
|
+
if __name__ == '__main__':
|
|
363
|
+
main()
|
|
364
|
+
`;
|
|
365
|
+
|
|
366
|
+
await fs.mkdir(join(projectPath, '.claude', 'skills', 'gmail', 'scripts'), { recursive: true });
|
|
367
|
+
await fs.writeFile(
|
|
368
|
+
join(projectPath, '.claude', 'skills', 'gmail', 'scripts', 'main.py'),
|
|
369
|
+
mainPy
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
console.log('Created sample project: gmail-assistant');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private async createScraperSampleProject(): Promise<void> {
|
|
376
|
+
const projectPath = join(this.makeccHome, 'web-scraper');
|
|
377
|
+
await fs.mkdir(projectPath, { recursive: true });
|
|
378
|
+
await fs.mkdir(join(projectPath, '.claude', 'agents'), { recursive: true });
|
|
379
|
+
|
|
380
|
+
// project.json
|
|
381
|
+
await fs.writeFile(
|
|
382
|
+
join(projectPath, 'project.json'),
|
|
383
|
+
JSON.stringify({
|
|
384
|
+
id: 'scraper-sample',
|
|
385
|
+
name: 'web-scraper',
|
|
386
|
+
description: 'Extract data from websites automatically',
|
|
387
|
+
createdAt: new Date().toISOString(),
|
|
388
|
+
}, null, 2)
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
// Sample agent
|
|
392
|
+
const agentMd = `---
|
|
393
|
+
name: web-scraper
|
|
394
|
+
description: 웹 페이지에서 데이터를 추출하는 에이전트
|
|
395
|
+
tools: WebFetch, Read, Write
|
|
396
|
+
model: sonnet
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
# Web Scraper Agent
|
|
400
|
+
|
|
401
|
+
이 에이전트는 웹 페이지에서 데이터를 추출하고 구조화합니다.
|
|
402
|
+
|
|
403
|
+
## 워크플로우
|
|
404
|
+
|
|
405
|
+
1. URL을 입력받습니다
|
|
406
|
+
2. WebFetch로 페이지 내용을 가져옵니다
|
|
407
|
+
3. 원하는 데이터를 추출합니다
|
|
408
|
+
4. JSON 또는 CSV로 결과를 저장합니다
|
|
409
|
+
|
|
410
|
+
## 사용 예시
|
|
411
|
+
|
|
412
|
+
"https://example.com에서 모든 제품 이름과 가격을 추출해줘"
|
|
413
|
+
`;
|
|
414
|
+
|
|
415
|
+
await fs.writeFile(
|
|
416
|
+
join(projectPath, '.claude', 'agents', 'web-scraper.md'),
|
|
417
|
+
agentMd
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
console.log('Created sample project: web-scraper');
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export const projectService = new ProjectService();
|
|
@@ -42,23 +42,25 @@ export interface SkillProgressEvent {
|
|
|
42
42
|
|
|
43
43
|
export type SkillProgressCallback = (event: SkillProgressEvent) => void;
|
|
44
44
|
|
|
45
|
-
const SYSTEM_PROMPT = `You are
|
|
45
|
+
const SYSTEM_PROMPT = `You are an expert Claude Code skill generator. Generate production-quality skills with complete, working code.
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
RESPOND WITH ONLY A VALID JSON OBJECT - NO MARKDOWN, NO CODE BLOCKS, NO EXPLANATIONS.
|
|
48
|
+
|
|
49
|
+
## JSON Schema
|
|
48
50
|
|
|
49
51
|
{
|
|
50
|
-
"skillName": "Human
|
|
52
|
+
"skillName": "Human Readable Skill Name",
|
|
51
53
|
"skillId": "skill-id-in-kebab-case",
|
|
52
|
-
"description": "
|
|
54
|
+
"description": "Comprehensive description including what it does AND when to use it. Include trigger phrases in Korean. Example: 'PDF 문서에서 텍스트 추출 및 분석. \"PDF 읽어줘\", \"PDF 분석해줘\", \"PDF에서 텍스트 추출\" 등의 요청 시 사용.'",
|
|
53
55
|
"files": [
|
|
54
56
|
{
|
|
55
57
|
"path": "SKILL.md",
|
|
56
|
-
"content": "Full SKILL.md content
|
|
58
|
+
"content": "Full SKILL.md content (see format below)",
|
|
57
59
|
"language": "markdown"
|
|
58
60
|
},
|
|
59
61
|
{
|
|
60
62
|
"path": "scripts/main.py",
|
|
61
|
-
"content": "Full Python script
|
|
63
|
+
"content": "Full Python script (150-300 lines, production quality)",
|
|
62
64
|
"language": "python"
|
|
63
65
|
},
|
|
64
66
|
{
|
|
@@ -69,36 +71,155 @@ Your response must follow this exact JSON schema:
|
|
|
69
71
|
]
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
SKILL.md
|
|
74
|
+
## SKILL.md Format (MUST follow exactly)
|
|
75
|
+
|
|
73
76
|
---
|
|
74
77
|
name: skill-id
|
|
75
|
-
description:
|
|
78
|
+
description: Detailed description with trigger phrases. Include what it does AND specific Korean trigger phrases like "~해줘", "~만들어줘". This is the PRIMARY mechanism for skill activation.
|
|
76
79
|
---
|
|
77
80
|
|
|
78
81
|
# Skill Name
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
Brief description of what this skill does.
|
|
84
|
+
|
|
85
|
+
## 실행 전 요구사항 (필수)
|
|
86
|
+
|
|
87
|
+
List any prerequisites:
|
|
88
|
+
- API keys needed (with instructions to check/request)
|
|
89
|
+
- Environment setup
|
|
90
|
+
- Dependencies
|
|
91
|
+
|
|
92
|
+
## 빠른 시작
|
|
93
|
+
|
|
94
|
+
\\\`\\\`\\\`bash
|
|
95
|
+
.venv/bin/python .claude/skills/skill-id/scripts/main.py \\\\
|
|
96
|
+
--required-arg "value" \\\\
|
|
97
|
+
--output output.ext
|
|
98
|
+
\\\`\\\`\\\`
|
|
99
|
+
|
|
100
|
+
## 스크립트 옵션
|
|
101
|
+
|
|
102
|
+
| 옵션 | 설명 | 기본값 |
|
|
103
|
+
|------|------|--------|
|
|
104
|
+
| \\\`--arg1\\\`, \\\`-a\\\` | Description | default |
|
|
105
|
+
| \\\`--output\\\`, \\\`-o\\\` | 출력 경로 (필수) | - |
|
|
83
106
|
|
|
84
|
-
##
|
|
85
|
-
\`\`\`bash
|
|
86
|
-
.venv/bin/python .claude/skills/skill-id/scripts/main.py [args]
|
|
87
|
-
\`\`\`
|
|
107
|
+
## 사용 예시
|
|
88
108
|
|
|
89
|
-
|
|
90
|
-
|
|
109
|
+
### 예시 1: Basic Usage
|
|
110
|
+
\\\`\\\`\\\`bash
|
|
111
|
+
.venv/bin/python .claude/skills/skill-id/scripts/main.py \\\\
|
|
112
|
+
--arg "value" --output result.ext
|
|
113
|
+
\\\`\\\`\\\`
|
|
91
114
|
|
|
92
|
-
|
|
93
|
-
|
|
115
|
+
### 예시 2: Advanced Usage
|
|
116
|
+
\\\`\\\`\\\`bash
|
|
117
|
+
.venv/bin/python .claude/skills/skill-id/scripts/main.py \\\\
|
|
118
|
+
--arg "value" --advanced-option
|
|
119
|
+
\\\`\\\`\\\`
|
|
94
120
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
121
|
+
## 제한사항
|
|
122
|
+
|
|
123
|
+
- List any limitations or constraints
|
|
124
|
+
|
|
125
|
+
## Python Script Requirements (scripts/main.py)
|
|
126
|
+
|
|
127
|
+
The script MUST:
|
|
128
|
+
1. Be 150-300 lines of COMPLETE, WORKING code
|
|
129
|
+
2. Use argparse with --help support
|
|
130
|
+
3. Include comprehensive error handling (try-except)
|
|
131
|
+
4. Print Korean status messages with emoji (✅ 완료, ❌ 오류, ⏳ 처리 중)
|
|
132
|
+
5. Check dependencies at startup with helpful install instructions
|
|
133
|
+
6. Support common use cases with sensible defaults
|
|
134
|
+
7. Include docstring with usage examples
|
|
135
|
+
|
|
136
|
+
## Script Template Structure
|
|
137
|
+
|
|
138
|
+
\\\`\\\`\\\`python
|
|
139
|
+
#!/usr/bin/env python3
|
|
140
|
+
"""
|
|
141
|
+
Skill Name - Brief description
|
|
142
|
+
|
|
143
|
+
Usage:
|
|
144
|
+
python main.py --arg "value" --output output.ext
|
|
145
|
+
|
|
146
|
+
Examples:
|
|
147
|
+
python main.py --input data.txt --output result.txt
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
import argparse
|
|
151
|
+
import sys
|
|
152
|
+
from pathlib import Path
|
|
153
|
+
|
|
154
|
+
def check_dependencies():
|
|
155
|
+
"""Check required packages"""
|
|
156
|
+
try:
|
|
157
|
+
import required_package
|
|
158
|
+
return True
|
|
159
|
+
except ImportError:
|
|
160
|
+
print("❌ required_package가 설치되어 있지 않습니다.")
|
|
161
|
+
print(" 설치: uv pip install --python .venv/bin/python required_package")
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
def main_function(arg1, arg2, output_path):
|
|
165
|
+
"""Main processing logic with error handling"""
|
|
166
|
+
print(f"⏳ 처리 중...")
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
# Processing logic here
|
|
170
|
+
result = process(arg1, arg2)
|
|
171
|
+
|
|
172
|
+
# Save output
|
|
173
|
+
output_file = Path(output_path)
|
|
174
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
175
|
+
|
|
176
|
+
with open(output_file, 'w') as f:
|
|
177
|
+
f.write(result)
|
|
178
|
+
|
|
179
|
+
print(f"✅ 완료!")
|
|
180
|
+
print(f" 파일: {output_file}")
|
|
181
|
+
return str(output_file)
|
|
182
|
+
|
|
183
|
+
except Exception as e:
|
|
184
|
+
print(f"❌ 오류 발생: {e}")
|
|
185
|
+
sys.exit(1)
|
|
186
|
+
|
|
187
|
+
def main():
|
|
188
|
+
parser = argparse.ArgumentParser(
|
|
189
|
+
description="Skill description",
|
|
190
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
191
|
+
epilog=\"\"\"
|
|
192
|
+
예시:
|
|
193
|
+
python main.py --input data.txt --output result.txt
|
|
194
|
+
python main.py --input data.txt --output result.txt --option
|
|
195
|
+
\"\"\"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
parser.add_argument("--input", "-i", required=True, help="입력 파일")
|
|
199
|
+
parser.add_argument("--output", "-o", required=True, help="출력 경로")
|
|
200
|
+
parser.add_argument("--option", action="store_true", help="옵션 설명")
|
|
201
|
+
|
|
202
|
+
args = parser.parse_args()
|
|
203
|
+
|
|
204
|
+
if not check_dependencies():
|
|
205
|
+
sys.exit(1)
|
|
206
|
+
|
|
207
|
+
main_function(args.input, args.option, args.output)
|
|
208
|
+
|
|
209
|
+
if __name__ == "__main__":
|
|
210
|
+
main()
|
|
211
|
+
\\\`\\\`\\\`
|
|
212
|
+
|
|
213
|
+
## Critical Rules
|
|
214
|
+
|
|
215
|
+
1. GENERATE COMPLETE, WORKING CODE - NO PLACEHOLDERS, NO "# TODO", NO "pass"
|
|
216
|
+
2. Scripts must be 150-300 lines with real implementation
|
|
217
|
+
3. Include ALL necessary imports and helper functions
|
|
218
|
+
4. Use Korean for user-facing messages, English for code/logs
|
|
219
|
+
5. description field MUST include Korean trigger phrases
|
|
220
|
+
6. SKILL.md MUST have complete usage examples with actual commands
|
|
221
|
+
7. Always include requirements.txt with specific packages needed
|
|
222
|
+
8. RESPOND WITH JSON ONLY - NO OTHER TEXT`;
|
|
102
223
|
|
|
103
224
|
export class SkillGeneratorService {
|
|
104
225
|
private projectRoot: string;
|