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.
@@ -4,9 +4,9 @@
4
4
  <meta charset="UTF-8" />
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
- <title>vite-temp</title>
8
- <script type="module" crossorigin src="/assets/index-lpZin4sA.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-v9IFpWkA.css">
7
+ <title>makecc</title>
8
+ <script type="module" crossorigin src="/assets/index-PJfSWqWr.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-CXXgu628.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
@@ -26,6 +26,7 @@ import { nodeSyncService } from './services/nodeSyncService';
26
26
  import { configLoaderService } from './services/configLoaderService';
27
27
  import { workflowExecutionService } from './services/workflowExecutionService';
28
28
  import { executeInTerminal } from './services/terminalService';
29
+ import { claudeMdService } from './services/claudeMdService';
29
30
  const __filename = fileURLToPath(import.meta.url);
30
31
  const __dirname = dirname(__filename);
31
32
  const app = express();
@@ -271,10 +272,9 @@ app.post('/api/skill/test', async (req, res) => {
271
272
  if (!existsSync(mainPyPath)) {
272
273
  return res.status(404).json({ message: 'main.py not found' });
273
274
  }
274
- // 전역 venv python 사용
275
- const homeDir = process.env.HOME || process.env.USERPROFILE || '';
276
- const pythonPath = join(homeDir, '.claude', 'venv', 'bin', 'python');
277
- const command = existsSync(pythonPath) ? pythonPath : 'python3';
275
+ // 로컬 프로젝트의 .venv 사용
276
+ const localPythonPath = join(projectRoot, '.venv', 'bin', 'python');
277
+ const command = existsSync(localPythonPath) ? localPythonPath : 'python3';
278
278
  console.log(`Testing skill: ${command} ${mainPyPath}`);
279
279
  return new Promise((resolve) => {
280
280
  const proc = spawn(command, [mainPyPath, ...(args || [])], {
@@ -314,6 +314,36 @@ app.post('/api/skill/test', async (req, res) => {
314
314
  res.status(500).json({ message: errorMessage });
315
315
  }
316
316
  });
317
+ // Save workflow output to local project
318
+ app.post('/api/save/workflow-output', async (req, res) => {
319
+ try {
320
+ const { workflowName, files } = req.body;
321
+ if (!workflowName || !files || files.length === 0) {
322
+ return res.status(400).json({ message: 'Workflow name and files are required' });
323
+ }
324
+ // output/워크플로우명/ 폴더 생성
325
+ const sanitizedName = workflowName.replace(/[/\\?%*:|"<>]/g, '_').replace(/\s+/g, '_');
326
+ const outputDir = join(fileService.getProjectPath(), 'output', sanitizedName);
327
+ await fs.mkdir(outputDir, { recursive: true });
328
+ const savedFiles = [];
329
+ for (const file of files) {
330
+ const filePath = join(outputDir, file.name);
331
+ await fs.writeFile(filePath, file.content, 'utf-8');
332
+ savedFiles.push({ name: file.name, path: filePath });
333
+ console.log(`Saved workflow output: ${filePath}`);
334
+ }
335
+ res.json({
336
+ success: true,
337
+ outputDir,
338
+ files: savedFiles,
339
+ });
340
+ }
341
+ catch (error) {
342
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
343
+ console.error('Save workflow output error:', errorMessage);
344
+ res.status(500).json({ message: errorMessage });
345
+ }
346
+ });
317
347
  // Read skill files for preview
318
348
  app.get('/api/skill/files', async (req, res) => {
319
349
  try {
@@ -509,6 +539,18 @@ if (isProduction) {
509
539
  });
510
540
  }
511
541
  const PORT = process.env.PORT || 3001;
512
- httpServer.listen(PORT, () => {
513
- console.log(`Server running on http://localhost:${PORT}`);
514
- });
542
+ // 서버 시작 시 프로젝트 초기화
543
+ async function startServer() {
544
+ try {
545
+ // CLAUDE.md, .venv, uv 초기화
546
+ await claudeMdService.setup();
547
+ }
548
+ catch (error) {
549
+ console.error('Project setup warning:', error);
550
+ // 초기화 실패해도 서버는 시작
551
+ }
552
+ httpServer.listen(PORT, () => {
553
+ console.log(`Server running on http://localhost:${PORT}`);
554
+ });
555
+ }
556
+ startServer();
@@ -0,0 +1,243 @@
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
+ const MAKECC_SECTION_MARKER = '<!-- makecc-managed-section -->';
6
+ const MAKECC_SECTION_END = '<!-- /makecc-managed-section -->';
7
+ /**
8
+ * makecc가 로컬 프로젝트에 추가할 CLAUDE.md 기본 템플릿
9
+ */
10
+ const MAKECC_CLAUDE_MD_TEMPLATE = `${MAKECC_SECTION_MARKER}
11
+ ## makecc 스킬/워크플로우 규칙
12
+
13
+ 이 섹션은 makecc에 의해 자동 생성되었습니다.
14
+
15
+ ### Python 가상환경
16
+
17
+ **모든 스킬은 프로젝트 로컬 가상환경을 사용합니다:**
18
+
19
+ \`\`\`
20
+ .venv/
21
+ \`\`\`
22
+
23
+ **사용 패턴:**
24
+
25
+ \`\`\`bash
26
+ # 로컬 venv Python 직접 실행 (권장)
27
+ .venv/bin/python script.py
28
+
29
+ # 또는 활성화 후 실행
30
+ source .venv/bin/activate && python script.py
31
+ \`\`\`
32
+
33
+ ### 패키지 관리
34
+
35
+ **uv를 우선 사용합니다 (pip보다 10-100x 빠름):**
36
+
37
+ \`\`\`bash
38
+ # uv로 패키지 설치 (권장)
39
+ uv pip install --python .venv/bin/python package_name
40
+
41
+ # requirements.txt 설치
42
+ uv pip install --python .venv/bin/python -r requirements.txt
43
+
44
+ # uv가 없으면 pip 폴백
45
+ .venv/bin/pip install package_name
46
+ \`\`\`
47
+
48
+ ### 스킬 저장 경로
49
+
50
+ | 항목 | 경로 |
51
+ |------|------|
52
+ | 스킬 | \`.claude/skills/[skill-name]/\` |
53
+ | 에이전트 | \`.claude/agents/[agent-name].md\` |
54
+ | 워크플로우 | \`.claude/agents/[workflow-name].md\` |
55
+
56
+ ### 스킬 구조
57
+
58
+ \`\`\`
59
+ .claude/skills/[skill-name]/
60
+ ├── SKILL.md # 스킬 정의 (필수)
61
+ ├── scripts/
62
+ │ └── main.py # 메인 스크립트
63
+ └── requirements.txt # 의존성 목록
64
+ \`\`\`
65
+
66
+ ### 스킬 실행 규칙
67
+
68
+ 1. **가상환경 사용**: 항상 \`.venv/bin/python\` 사용
69
+ 2. **의존성 설치**: \`uv pip install\` 우선, 실패 시 \`pip\` 폴백
70
+ 3. **경로 참조**: 상대 경로 대신 절대 경로 사용 권장
71
+ 4. **한글 지원**: 사용자 메시지는 한글로 출력
72
+
73
+ ${MAKECC_SECTION_END}
74
+ `;
75
+ export class ClaudeMdService {
76
+ projectRoot;
77
+ constructor(projectRoot) {
78
+ this.projectRoot = projectRoot || process.env.MAKECC_PROJECT_PATH || process.cwd();
79
+ }
80
+ /**
81
+ * CLAUDE.md 파일 경로
82
+ */
83
+ getClaudeMdPath() {
84
+ return path.join(this.projectRoot, 'CLAUDE.md');
85
+ }
86
+ /**
87
+ * CLAUDE.md 내용 읽기
88
+ */
89
+ async read() {
90
+ const claudeMdPath = this.getClaudeMdPath();
91
+ if (!existsSync(claudeMdPath)) {
92
+ return null;
93
+ }
94
+ return fs.readFile(claudeMdPath, 'utf-8');
95
+ }
96
+ /**
97
+ * makecc 섹션이 있는지 확인
98
+ */
99
+ hasMakeccSection(content) {
100
+ return content.includes(MAKECC_SECTION_MARKER);
101
+ }
102
+ /**
103
+ * CLAUDE.md 초기화 - 없으면 생성, 있으면 섹션 추가
104
+ */
105
+ async initialize() {
106
+ const claudeMdPath = this.getClaudeMdPath();
107
+ const result = { created: false, updated: false, path: claudeMdPath };
108
+ const existingContent = await this.read();
109
+ if (existingContent === null) {
110
+ // 파일이 없으면 새로 생성
111
+ const newContent = `# CLAUDE.md
112
+
113
+ 이 파일은 Claude Code가 이 프로젝트의 코드를 다룰 때 참고하는 가이드입니다.
114
+
115
+ ${MAKECC_CLAUDE_MD_TEMPLATE}
116
+ `;
117
+ await fs.writeFile(claudeMdPath, newContent, 'utf-8');
118
+ result.created = true;
119
+ console.log(`Created CLAUDE.md at ${claudeMdPath}`);
120
+ }
121
+ else if (!this.hasMakeccSection(existingContent)) {
122
+ // 파일은 있지만 makecc 섹션이 없으면 추가
123
+ const updatedContent = existingContent.trimEnd() + '\n\n' + MAKECC_CLAUDE_MD_TEMPLATE;
124
+ await fs.writeFile(claudeMdPath, updatedContent, 'utf-8');
125
+ result.updated = true;
126
+ console.log(`Added makecc section to CLAUDE.md at ${claudeMdPath}`);
127
+ }
128
+ else {
129
+ console.log(`CLAUDE.md already has makecc section at ${claudeMdPath}`);
130
+ }
131
+ return result;
132
+ }
133
+ /**
134
+ * 프로젝트 로컬 .venv 초기화
135
+ */
136
+ async initializeVenv() {
137
+ const venvPath = path.join(this.projectRoot, '.venv');
138
+ const result = { created: false, path: venvPath };
139
+ if (existsSync(venvPath)) {
140
+ console.log(`Virtual environment already exists at ${venvPath}`);
141
+ return result;
142
+ }
143
+ console.log(`Creating virtual environment at ${venvPath}...`);
144
+ try {
145
+ // uv가 있으면 uv venv 사용 (더 빠름)
146
+ if (this.checkCommandExists('uv')) {
147
+ execSync(`uv venv "${venvPath}"`, { cwd: this.projectRoot, stdio: 'inherit' });
148
+ }
149
+ else {
150
+ // python -m venv 사용
151
+ execSync(`python3 -m venv "${venvPath}"`, { cwd: this.projectRoot, stdio: 'inherit' });
152
+ }
153
+ result.created = true;
154
+ console.log(`Created virtual environment at ${venvPath}`);
155
+ }
156
+ catch (error) {
157
+ console.error('Failed to create virtual environment:', error);
158
+ throw error;
159
+ }
160
+ return result;
161
+ }
162
+ /**
163
+ * uv 설치 확인 및 자동 설치
164
+ */
165
+ async ensureUvInstalled() {
166
+ const result = { installed: false, alreadyExists: false };
167
+ if (this.checkCommandExists('uv')) {
168
+ result.alreadyExists = true;
169
+ console.log('uv is already installed');
170
+ return result;
171
+ }
172
+ console.log('uv not found, attempting to install...');
173
+ try {
174
+ // curl 스크립트로 설치
175
+ await this.runCommand('curl', ['-LsSf', 'https://astral.sh/uv/install.sh', '-o', '/tmp/uv-install.sh']);
176
+ await this.runCommand('sh', ['/tmp/uv-install.sh']);
177
+ // PATH에 추가된 uv 확인
178
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '';
179
+ const uvPath = path.join(homeDir, '.local', 'bin', 'uv');
180
+ if (existsSync(uvPath)) {
181
+ // PATH에 추가
182
+ process.env.PATH = `${path.dirname(uvPath)}:${process.env.PATH}`;
183
+ result.installed = true;
184
+ console.log('uv installed successfully');
185
+ }
186
+ else {
187
+ console.warn('uv installation may have failed, falling back to pip');
188
+ }
189
+ }
190
+ catch (error) {
191
+ console.error('Failed to install uv:', error);
192
+ console.log('Will fall back to pip for package management');
193
+ }
194
+ return result;
195
+ }
196
+ /**
197
+ * 전체 초기화 실행
198
+ */
199
+ async setup() {
200
+ console.log('\n=== makecc Project Setup ===\n');
201
+ console.log(`Project root: ${this.projectRoot}\n`);
202
+ // 1. uv 설치 확인
203
+ const uvResult = await this.ensureUvInstalled();
204
+ // 2. CLAUDE.md 초기화
205
+ const claudeMdResult = await this.initialize();
206
+ // 3. .venv 초기화
207
+ const venvResult = await this.initializeVenv();
208
+ console.log('\n=== Setup Complete ===\n');
209
+ return {
210
+ claudeMd: claudeMdResult,
211
+ venv: venvResult,
212
+ uv: uvResult,
213
+ };
214
+ }
215
+ /**
216
+ * 명령어 존재 확인
217
+ */
218
+ checkCommandExists(cmd) {
219
+ try {
220
+ execSync(`which ${cmd}`, { stdio: 'ignore' });
221
+ return true;
222
+ }
223
+ catch {
224
+ return false;
225
+ }
226
+ }
227
+ /**
228
+ * 명령어 실행 (Promise)
229
+ */
230
+ runCommand(command, args) {
231
+ return new Promise((resolve, reject) => {
232
+ const proc = spawn(command, args, { stdio: 'inherit' });
233
+ proc.on('close', (code) => {
234
+ if (code === 0)
235
+ resolve();
236
+ else
237
+ reject(new Error(`Command failed with code ${code}`));
238
+ });
239
+ proc.on('error', reject);
240
+ });
241
+ }
242
+ }
243
+ export const claudeMdService = new ClaudeMdService();
@@ -3,6 +3,7 @@ import * as fs from 'fs/promises';
3
3
  import * as path from 'path';
4
4
  import { existsSync } from 'fs';
5
5
  import { spawn } from 'child_process';
6
+ import { claudeMdService } from './claudeMdService';
6
7
  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.
7
8
 
8
9
  Your response must follow this exact JSON schema:
@@ -44,7 +45,7 @@ description: One line description
44
45
 
45
46
  ## Usage
46
47
  \`\`\`bash
47
- ~/.claude/venv/bin/python ~/.claude/skills/skill-id/scripts/main.py [args]
48
+ .venv/bin/python .claude/skills/skill-id/scripts/main.py [args]
48
49
  \`\`\`
49
50
 
50
51
  ## Parameters
@@ -57,7 +58,7 @@ RULES:
57
58
  1. Generate COMPLETE, WORKING code - no placeholders
58
59
  2. Include proper error handling with try-except
59
60
  3. Use Korean for user-facing messages
60
- 4. Scripts run with ~/.claude/venv/bin/python
61
+ 4. Scripts run with .venv/bin/python (project local virtual environment)
61
62
  5. List all required packages in requirements.txt
62
63
  6. RESPOND WITH JSON ONLY - NO OTHER TEXT`;
63
64
  export class SkillGeneratorService {
@@ -107,9 +108,20 @@ export class SkillGeneratorService {
107
108
  message: '🔍 요청을 분석하고 있어요',
108
109
  detail: '어떤 스킬이 필요한지 파악 중...',
109
110
  });
111
+ // CLAUDE.md 내용 읽기
112
+ let claudeMdContext = '';
113
+ const claudeMdContent = await claudeMdService.read();
114
+ if (claudeMdContent) {
115
+ claudeMdContext = `\n\n<project_context>
116
+ The following is the project's CLAUDE.md file. Follow these guidelines when generating the skill:
117
+
118
+ ${claudeMdContent}
119
+ </project_context>`;
120
+ console.log('Including CLAUDE.md in API context');
121
+ }
110
122
  const userPrompt = `Create a skill for: "${prompt}"
111
123
 
112
- Generate complete, working code. Respond with JSON only.`;
124
+ Generate complete, working code. Respond with JSON only.${claudeMdContext}`;
113
125
  try {
114
126
  progress({
115
127
  step: 'designing',
@@ -224,40 +236,41 @@ Generate complete, working code. Respond with JSON only.`;
224
236
  }
225
237
  async installDependencies(requirementsPath, progress) {
226
238
  return new Promise((resolve, reject) => {
227
- const homeDir = process.env.HOME || process.env.USERPROFILE || '';
228
- const venvPythonPath = path.join(homeDir, '.claude', 'venv', 'bin', 'python');
239
+ // 로컬 프로젝트의 .venv 사용
240
+ const localVenvPythonPath = path.join(this.projectRoot, '.venv', 'bin', 'python');
241
+ const localVenvPipPath = path.join(this.projectRoot, '.venv', 'bin', 'pip');
229
242
  let command;
230
243
  let args;
231
244
  // uv를 우선 사용 (10-100x 빠름)
232
- // uv pip install --python <venv-python> -r requirements.txt
233
245
  const useUv = this.checkCommandExists('uv');
234
- if (useUv && existsSync(venvPythonPath)) {
246
+ if (useUv && existsSync(localVenvPythonPath)) {
235
247
  command = 'uv';
236
- args = ['pip', 'install', '--python', venvPythonPath, '-r', requirementsPath];
248
+ args = ['pip', 'install', '--python', localVenvPythonPath, '-r', requirementsPath];
237
249
  progress({
238
250
  step: 'installing',
239
251
  message: '⚡ uv로 패키지 설치 중 (고속)',
240
- detail: 'uv pip install 실행 중...',
252
+ detail: 'uv pip install .venv/',
241
253
  });
242
254
  }
243
- else if (existsSync(path.join(homeDir, '.claude', 'venv', 'bin', 'pip'))) {
244
- // fallback: 전역 venv pip 사용
245
- command = path.join(homeDir, '.claude', 'venv', 'bin', 'pip');
255
+ else if (existsSync(localVenvPipPath)) {
256
+ // fallback: 로컬 venv pip 사용
257
+ command = localVenvPipPath;
246
258
  args = ['install', '-r', requirementsPath];
247
259
  progress({
248
260
  step: 'installing',
249
261
  message: '📦 pip으로 패키지 설치 중',
250
- detail: 'pip install 실행 중...',
262
+ detail: 'pip install .venv/',
251
263
  });
252
264
  }
253
265
  else {
254
- // fallback: 시스템 pip 사용
266
+ // fallback: 시스템 pip 사용 (경고)
267
+ console.warn('Warning: .venv not found, using system pip');
255
268
  command = 'pip3';
256
269
  args = ['install', '-r', requirementsPath];
257
270
  progress({
258
271
  step: 'installing',
259
- message: '📦 pip으로 패키지 설치 중',
260
- detail: 'pip install 실행 중...',
272
+ message: '⚠️ 시스템 pip 사용 중',
273
+ detail: '.venv가 없어 시스템 pip 사용',
261
274
  });
262
275
  }
263
276
  console.log(`Installing dependencies: ${command} ${args.join(' ')}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makecc",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "type": "module",
5
5
  "description": "Visual workflow builder for Claude Code agents and skills",
6
6
  "keywords": [
package/server/index.ts CHANGED
@@ -26,6 +26,7 @@ import { nodeSyncService } from './services/nodeSyncService';
26
26
  import { configLoaderService } from './services/configLoaderService';
27
27
  import { workflowExecutionService } from './services/workflowExecutionService';
28
28
  import { executeInTerminal, getClaudeCommand } from './services/terminalService';
29
+ import { claudeMdService } from './services/claudeMdService';
29
30
  import type { WorkflowExecutionRequest, NodeExecutionUpdate } from './types';
30
31
  import type { ClaudeConfigExport, SaveOptions } from './services/fileService';
31
32
 
@@ -305,10 +306,9 @@ app.post('/api/skill/test', async (req, res) => {
305
306
  return res.status(404).json({ message: 'main.py not found' });
306
307
  }
307
308
 
308
- // 전역 venv python 사용
309
- const homeDir = process.env.HOME || process.env.USERPROFILE || '';
310
- const pythonPath = join(homeDir, '.claude', 'venv', 'bin', 'python');
311
- const command = existsSync(pythonPath) ? pythonPath : 'python3';
309
+ // 로컬 프로젝트의 .venv 사용
310
+ const localPythonPath = join(projectRoot, '.venv', 'bin', 'python');
311
+ const command = existsSync(localPythonPath) ? localPythonPath : 'python3';
312
312
 
313
313
  console.log(`Testing skill: ${command} ${mainPyPath}`);
314
314
 
@@ -355,6 +355,45 @@ app.post('/api/skill/test', async (req, res) => {
355
355
  }
356
356
  });
357
357
 
358
+ // Save workflow output to local project
359
+ app.post('/api/save/workflow-output', async (req, res) => {
360
+ try {
361
+ const { workflowName, files } = req.body as {
362
+ workflowName: string;
363
+ files: Array<{ name: string; content: string }>;
364
+ };
365
+
366
+ if (!workflowName || !files || files.length === 0) {
367
+ return res.status(400).json({ message: 'Workflow name and files are required' });
368
+ }
369
+
370
+ // output/워크플로우명/ 폴더 생성
371
+ const sanitizedName = workflowName.replace(/[/\\?%*:|"<>]/g, '_').replace(/\s+/g, '_');
372
+ const outputDir = join(fileService.getProjectPath(), 'output', sanitizedName);
373
+
374
+ await fs.mkdir(outputDir, { recursive: true });
375
+
376
+ const savedFiles: Array<{ name: string; path: string }> = [];
377
+
378
+ for (const file of files) {
379
+ const filePath = join(outputDir, file.name);
380
+ await fs.writeFile(filePath, file.content, 'utf-8');
381
+ savedFiles.push({ name: file.name, path: filePath });
382
+ console.log(`Saved workflow output: ${filePath}`);
383
+ }
384
+
385
+ res.json({
386
+ success: true,
387
+ outputDir,
388
+ files: savedFiles,
389
+ });
390
+ } catch (error) {
391
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
392
+ console.error('Save workflow output error:', errorMessage);
393
+ res.status(500).json({ message: errorMessage });
394
+ }
395
+ });
396
+
358
397
  // Read skill files for preview
359
398
  app.get('/api/skill/files', async (req, res) => {
360
399
  try {
@@ -592,6 +631,19 @@ if (isProduction) {
592
631
 
593
632
  const PORT = process.env.PORT || 3001;
594
633
 
595
- httpServer.listen(PORT, () => {
596
- console.log(`Server running on http://localhost:${PORT}`);
597
- });
634
+ // 서버 시작 시 프로젝트 초기화
635
+ async function startServer() {
636
+ try {
637
+ // CLAUDE.md, .venv, uv 초기화
638
+ await claudeMdService.setup();
639
+ } catch (error) {
640
+ console.error('Project setup warning:', error);
641
+ // 초기화 실패해도 서버는 시작
642
+ }
643
+
644
+ httpServer.listen(PORT, () => {
645
+ console.log(`Server running on http://localhost:${PORT}`);
646
+ });
647
+ }
648
+
649
+ startServer();