makecc 0.2.11 → 0.2.13
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-CXXgu628.css +1 -0
- package/dist/client/assets/index-PJfSWqWr.js +150 -0
- package/dist/client/index.html +3 -3
- package/dist/server/index.js +49 -7
- package/dist/server/services/claudeMdService.js +243 -0
- package/dist/server/services/skillGeneratorService.js +29 -16
- package/package.json +1 -1
- package/server/index.ts +59 -7
- package/server/services/claudeMdService.ts +270 -0
- package/server/services/skillGeneratorService.ts +31 -17
- package/dist/client/assets/index-lpZin4sA.js +0 -150
- package/dist/client/assets/index-v9IFpWkA.css +0 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { execSync, spawn } from 'child_process';
|
|
5
|
+
|
|
6
|
+
const MAKECC_SECTION_MARKER = '<!-- makecc-managed-section -->';
|
|
7
|
+
const MAKECC_SECTION_END = '<!-- /makecc-managed-section -->';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* makecc가 로컬 프로젝트에 추가할 CLAUDE.md 기본 템플릿
|
|
11
|
+
*/
|
|
12
|
+
const MAKECC_CLAUDE_MD_TEMPLATE = `${MAKECC_SECTION_MARKER}
|
|
13
|
+
## makecc 스킬/워크플로우 규칙
|
|
14
|
+
|
|
15
|
+
이 섹션은 makecc에 의해 자동 생성되었습니다.
|
|
16
|
+
|
|
17
|
+
### Python 가상환경
|
|
18
|
+
|
|
19
|
+
**모든 스킬은 프로젝트 로컬 가상환경을 사용합니다:**
|
|
20
|
+
|
|
21
|
+
\`\`\`
|
|
22
|
+
.venv/
|
|
23
|
+
\`\`\`
|
|
24
|
+
|
|
25
|
+
**사용 패턴:**
|
|
26
|
+
|
|
27
|
+
\`\`\`bash
|
|
28
|
+
# 로컬 venv Python 직접 실행 (권장)
|
|
29
|
+
.venv/bin/python script.py
|
|
30
|
+
|
|
31
|
+
# 또는 활성화 후 실행
|
|
32
|
+
source .venv/bin/activate && python script.py
|
|
33
|
+
\`\`\`
|
|
34
|
+
|
|
35
|
+
### 패키지 관리
|
|
36
|
+
|
|
37
|
+
**uv를 우선 사용합니다 (pip보다 10-100x 빠름):**
|
|
38
|
+
|
|
39
|
+
\`\`\`bash
|
|
40
|
+
# uv로 패키지 설치 (권장)
|
|
41
|
+
uv pip install --python .venv/bin/python package_name
|
|
42
|
+
|
|
43
|
+
# requirements.txt 설치
|
|
44
|
+
uv pip install --python .venv/bin/python -r requirements.txt
|
|
45
|
+
|
|
46
|
+
# uv가 없으면 pip 폴백
|
|
47
|
+
.venv/bin/pip install package_name
|
|
48
|
+
\`\`\`
|
|
49
|
+
|
|
50
|
+
### 스킬 저장 경로
|
|
51
|
+
|
|
52
|
+
| 항목 | 경로 |
|
|
53
|
+
|------|------|
|
|
54
|
+
| 스킬 | \`.claude/skills/[skill-name]/\` |
|
|
55
|
+
| 에이전트 | \`.claude/agents/[agent-name].md\` |
|
|
56
|
+
| 워크플로우 | \`.claude/agents/[workflow-name].md\` |
|
|
57
|
+
|
|
58
|
+
### 스킬 구조
|
|
59
|
+
|
|
60
|
+
\`\`\`
|
|
61
|
+
.claude/skills/[skill-name]/
|
|
62
|
+
├── SKILL.md # 스킬 정의 (필수)
|
|
63
|
+
├── scripts/
|
|
64
|
+
│ └── main.py # 메인 스크립트
|
|
65
|
+
└── requirements.txt # 의존성 목록
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
### 스킬 실행 규칙
|
|
69
|
+
|
|
70
|
+
1. **가상환경 사용**: 항상 \`.venv/bin/python\` 사용
|
|
71
|
+
2. **의존성 설치**: \`uv pip install\` 우선, 실패 시 \`pip\` 폴백
|
|
72
|
+
3. **경로 참조**: 상대 경로 대신 절대 경로 사용 권장
|
|
73
|
+
4. **한글 지원**: 사용자 메시지는 한글로 출력
|
|
74
|
+
|
|
75
|
+
${MAKECC_SECTION_END}
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
export class ClaudeMdService {
|
|
79
|
+
private projectRoot: string;
|
|
80
|
+
|
|
81
|
+
constructor(projectRoot?: string) {
|
|
82
|
+
this.projectRoot = projectRoot || process.env.MAKECC_PROJECT_PATH || process.cwd();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* CLAUDE.md 파일 경로
|
|
87
|
+
*/
|
|
88
|
+
private getClaudeMdPath(): string {
|
|
89
|
+
return path.join(this.projectRoot, 'CLAUDE.md');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* CLAUDE.md 내용 읽기
|
|
94
|
+
*/
|
|
95
|
+
async read(): Promise<string | null> {
|
|
96
|
+
const claudeMdPath = this.getClaudeMdPath();
|
|
97
|
+
if (!existsSync(claudeMdPath)) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return fs.readFile(claudeMdPath, 'utf-8');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* makecc 섹션이 있는지 확인
|
|
105
|
+
*/
|
|
106
|
+
hasMakeccSection(content: string): boolean {
|
|
107
|
+
return content.includes(MAKECC_SECTION_MARKER);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* CLAUDE.md 초기화 - 없으면 생성, 있으면 섹션 추가
|
|
112
|
+
*/
|
|
113
|
+
async initialize(): Promise<{ created: boolean; updated: boolean; path: string }> {
|
|
114
|
+
const claudeMdPath = this.getClaudeMdPath();
|
|
115
|
+
const result = { created: false, updated: false, path: claudeMdPath };
|
|
116
|
+
|
|
117
|
+
const existingContent = await this.read();
|
|
118
|
+
|
|
119
|
+
if (existingContent === null) {
|
|
120
|
+
// 파일이 없으면 새로 생성
|
|
121
|
+
const newContent = `# CLAUDE.md
|
|
122
|
+
|
|
123
|
+
이 파일은 Claude Code가 이 프로젝트의 코드를 다룰 때 참고하는 가이드입니다.
|
|
124
|
+
|
|
125
|
+
${MAKECC_CLAUDE_MD_TEMPLATE}
|
|
126
|
+
`;
|
|
127
|
+
await fs.writeFile(claudeMdPath, newContent, 'utf-8');
|
|
128
|
+
result.created = true;
|
|
129
|
+
console.log(`Created CLAUDE.md at ${claudeMdPath}`);
|
|
130
|
+
} else if (!this.hasMakeccSection(existingContent)) {
|
|
131
|
+
// 파일은 있지만 makecc 섹션이 없으면 추가
|
|
132
|
+
const updatedContent = existingContent.trimEnd() + '\n\n' + MAKECC_CLAUDE_MD_TEMPLATE;
|
|
133
|
+
await fs.writeFile(claudeMdPath, updatedContent, 'utf-8');
|
|
134
|
+
result.updated = true;
|
|
135
|
+
console.log(`Added makecc section to CLAUDE.md at ${claudeMdPath}`);
|
|
136
|
+
} else {
|
|
137
|
+
console.log(`CLAUDE.md already has makecc section at ${claudeMdPath}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 프로젝트 로컬 .venv 초기화
|
|
145
|
+
*/
|
|
146
|
+
async initializeVenv(): Promise<{ created: boolean; path: string }> {
|
|
147
|
+
const venvPath = path.join(this.projectRoot, '.venv');
|
|
148
|
+
const result = { created: false, path: venvPath };
|
|
149
|
+
|
|
150
|
+
if (existsSync(venvPath)) {
|
|
151
|
+
console.log(`Virtual environment already exists at ${venvPath}`);
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(`Creating virtual environment at ${venvPath}...`);
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
// uv가 있으면 uv venv 사용 (더 빠름)
|
|
159
|
+
if (this.checkCommandExists('uv')) {
|
|
160
|
+
execSync(`uv venv "${venvPath}"`, { cwd: this.projectRoot, stdio: 'inherit' });
|
|
161
|
+
} else {
|
|
162
|
+
// python -m venv 사용
|
|
163
|
+
execSync(`python3 -m venv "${venvPath}"`, { cwd: this.projectRoot, stdio: 'inherit' });
|
|
164
|
+
}
|
|
165
|
+
result.created = true;
|
|
166
|
+
console.log(`Created virtual environment at ${venvPath}`);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error('Failed to create virtual environment:', error);
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* uv 설치 확인 및 자동 설치
|
|
177
|
+
*/
|
|
178
|
+
async ensureUvInstalled(): Promise<{ installed: boolean; alreadyExists: boolean }> {
|
|
179
|
+
const result = { installed: false, alreadyExists: false };
|
|
180
|
+
|
|
181
|
+
if (this.checkCommandExists('uv')) {
|
|
182
|
+
result.alreadyExists = true;
|
|
183
|
+
console.log('uv is already installed');
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log('uv not found, attempting to install...');
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
// curl 스크립트로 설치
|
|
191
|
+
await this.runCommand('curl', ['-LsSf', 'https://astral.sh/uv/install.sh', '-o', '/tmp/uv-install.sh']);
|
|
192
|
+
await this.runCommand('sh', ['/tmp/uv-install.sh']);
|
|
193
|
+
|
|
194
|
+
// PATH에 추가된 uv 확인
|
|
195
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
196
|
+
const uvPath = path.join(homeDir, '.local', 'bin', 'uv');
|
|
197
|
+
|
|
198
|
+
if (existsSync(uvPath)) {
|
|
199
|
+
// PATH에 추가
|
|
200
|
+
process.env.PATH = `${path.dirname(uvPath)}:${process.env.PATH}`;
|
|
201
|
+
result.installed = true;
|
|
202
|
+
console.log('uv installed successfully');
|
|
203
|
+
} else {
|
|
204
|
+
console.warn('uv installation may have failed, falling back to pip');
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error('Failed to install uv:', error);
|
|
208
|
+
console.log('Will fall back to pip for package management');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 전체 초기화 실행
|
|
216
|
+
*/
|
|
217
|
+
async setup(): Promise<{
|
|
218
|
+
claudeMd: { created: boolean; updated: boolean; path: string };
|
|
219
|
+
venv: { created: boolean; path: string };
|
|
220
|
+
uv: { installed: boolean; alreadyExists: boolean };
|
|
221
|
+
}> {
|
|
222
|
+
console.log('\n=== makecc Project Setup ===\n');
|
|
223
|
+
console.log(`Project root: ${this.projectRoot}\n`);
|
|
224
|
+
|
|
225
|
+
// 1. uv 설치 확인
|
|
226
|
+
const uvResult = await this.ensureUvInstalled();
|
|
227
|
+
|
|
228
|
+
// 2. CLAUDE.md 초기화
|
|
229
|
+
const claudeMdResult = await this.initialize();
|
|
230
|
+
|
|
231
|
+
// 3. .venv 초기화
|
|
232
|
+
const venvResult = await this.initializeVenv();
|
|
233
|
+
|
|
234
|
+
console.log('\n=== Setup Complete ===\n');
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
claudeMd: claudeMdResult,
|
|
238
|
+
venv: venvResult,
|
|
239
|
+
uv: uvResult,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* 명령어 존재 확인
|
|
245
|
+
*/
|
|
246
|
+
private checkCommandExists(cmd: string): boolean {
|
|
247
|
+
try {
|
|
248
|
+
execSync(`which ${cmd}`, { stdio: 'ignore' });
|
|
249
|
+
return true;
|
|
250
|
+
} catch {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 명령어 실행 (Promise)
|
|
257
|
+
*/
|
|
258
|
+
private runCommand(command: string, args: string[]): Promise<void> {
|
|
259
|
+
return new Promise((resolve, reject) => {
|
|
260
|
+
const proc = spawn(command, args, { stdio: 'inherit' });
|
|
261
|
+
proc.on('close', (code) => {
|
|
262
|
+
if (code === 0) resolve();
|
|
263
|
+
else reject(new Error(`Command failed with code ${code}`));
|
|
264
|
+
});
|
|
265
|
+
proc.on('error', reject);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export const claudeMdService = new ClaudeMdService();
|
|
@@ -2,8 +2,9 @@ import Anthropic from '@anthropic-ai/sdk';
|
|
|
2
2
|
import * as fs from 'fs/promises';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import { existsSync } from 'fs';
|
|
5
|
-
import { spawn } from 'child_process';
|
|
5
|
+
import { spawn, execSync } from 'child_process';
|
|
6
6
|
import type { ApiSettings } from './workflowAIService';
|
|
7
|
+
import { claudeMdService } from './claudeMdService';
|
|
7
8
|
|
|
8
9
|
export interface GeneratedSkill {
|
|
9
10
|
skillName: string;
|
|
@@ -82,7 +83,7 @@ description: One line description
|
|
|
82
83
|
|
|
83
84
|
## Usage
|
|
84
85
|
\`\`\`bash
|
|
85
|
-
|
|
86
|
+
.venv/bin/python .claude/skills/skill-id/scripts/main.py [args]
|
|
86
87
|
\`\`\`
|
|
87
88
|
|
|
88
89
|
## Parameters
|
|
@@ -95,7 +96,7 @@ RULES:
|
|
|
95
96
|
1. Generate COMPLETE, WORKING code - no placeholders
|
|
96
97
|
2. Include proper error handling with try-except
|
|
97
98
|
3. Use Korean for user-facing messages
|
|
98
|
-
4. Scripts run with
|
|
99
|
+
4. Scripts run with .venv/bin/python (project local virtual environment)
|
|
99
100
|
5. List all required packages in requirements.txt
|
|
100
101
|
6. RESPOND WITH JSON ONLY - NO OTHER TEXT`;
|
|
101
102
|
|
|
@@ -163,9 +164,21 @@ export class SkillGeneratorService {
|
|
|
163
164
|
detail: '어떤 스킬이 필요한지 파악 중...',
|
|
164
165
|
});
|
|
165
166
|
|
|
167
|
+
// CLAUDE.md 내용 읽기
|
|
168
|
+
let claudeMdContext = '';
|
|
169
|
+
const claudeMdContent = await claudeMdService.read();
|
|
170
|
+
if (claudeMdContent) {
|
|
171
|
+
claudeMdContext = `\n\n<project_context>
|
|
172
|
+
The following is the project's CLAUDE.md file. Follow these guidelines when generating the skill:
|
|
173
|
+
|
|
174
|
+
${claudeMdContent}
|
|
175
|
+
</project_context>`;
|
|
176
|
+
console.log('Including CLAUDE.md in API context');
|
|
177
|
+
}
|
|
178
|
+
|
|
166
179
|
const userPrompt = `Create a skill for: "${prompt}"
|
|
167
180
|
|
|
168
|
-
Generate complete, working code. Respond with JSON only
|
|
181
|
+
Generate complete, working code. Respond with JSON only.${claudeMdContext}`;
|
|
169
182
|
|
|
170
183
|
try {
|
|
171
184
|
progress({
|
|
@@ -301,41 +314,42 @@ Generate complete, working code. Respond with JSON only.`;
|
|
|
301
314
|
progress: SkillProgressCallback
|
|
302
315
|
): Promise<void> {
|
|
303
316
|
return new Promise((resolve, reject) => {
|
|
304
|
-
|
|
305
|
-
const
|
|
317
|
+
// 로컬 프로젝트의 .venv 사용
|
|
318
|
+
const localVenvPythonPath = path.join(this.projectRoot, '.venv', 'bin', 'python');
|
|
319
|
+
const localVenvPipPath = path.join(this.projectRoot, '.venv', 'bin', 'pip');
|
|
306
320
|
|
|
307
321
|
let command: string;
|
|
308
322
|
let args: string[];
|
|
309
323
|
|
|
310
324
|
// uv를 우선 사용 (10-100x 빠름)
|
|
311
|
-
// uv pip install --python <venv-python> -r requirements.txt
|
|
312
325
|
const useUv = this.checkCommandExists('uv');
|
|
313
326
|
|
|
314
|
-
if (useUv && existsSync(
|
|
327
|
+
if (useUv && existsSync(localVenvPythonPath)) {
|
|
315
328
|
command = 'uv';
|
|
316
|
-
args = ['pip', 'install', '--python',
|
|
329
|
+
args = ['pip', 'install', '--python', localVenvPythonPath, '-r', requirementsPath];
|
|
317
330
|
progress({
|
|
318
331
|
step: 'installing',
|
|
319
332
|
message: '⚡ uv로 패키지 설치 중 (고속)',
|
|
320
|
-
detail: 'uv pip install
|
|
333
|
+
detail: 'uv pip install → .venv/',
|
|
321
334
|
});
|
|
322
|
-
} else if (existsSync(
|
|
323
|
-
// fallback:
|
|
324
|
-
command =
|
|
335
|
+
} else if (existsSync(localVenvPipPath)) {
|
|
336
|
+
// fallback: 로컬 venv pip 사용
|
|
337
|
+
command = localVenvPipPath;
|
|
325
338
|
args = ['install', '-r', requirementsPath];
|
|
326
339
|
progress({
|
|
327
340
|
step: 'installing',
|
|
328
341
|
message: '📦 pip으로 패키지 설치 중',
|
|
329
|
-
detail: 'pip install
|
|
342
|
+
detail: 'pip install → .venv/',
|
|
330
343
|
});
|
|
331
344
|
} else {
|
|
332
|
-
// fallback: 시스템 pip 사용
|
|
345
|
+
// fallback: 시스템 pip 사용 (경고)
|
|
346
|
+
console.warn('Warning: .venv not found, using system pip');
|
|
333
347
|
command = 'pip3';
|
|
334
348
|
args = ['install', '-r', requirementsPath];
|
|
335
349
|
progress({
|
|
336
350
|
step: 'installing',
|
|
337
|
-
message: '
|
|
338
|
-
detail: '
|
|
351
|
+
message: '⚠️ 시스템 pip 사용 중',
|
|
352
|
+
detail: '.venv가 없어 시스템 pip 사용',
|
|
339
353
|
});
|
|
340
354
|
}
|
|
341
355
|
|