makecc 0.2.10 → 0.2.12
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-B9j5oIcH.js → index-oO8rpFoq.js} +2 -2
- package/dist/client/index.html +1 -1
- package/dist/server/index.js +32 -8
- package/dist/server/services/claudeMdService.js +243 -0
- package/dist/server/services/skillGeneratorService.js +50 -24
- package/dist/server/services/workflowAIService.js +21 -16
- package/package.json +1 -1
- package/server/index.ts +33 -8
- package/server/services/claudeMdService.ts +270 -0
- package/server/services/skillGeneratorService.ts +55 -25
- package/server/services/workflowAIService.ts +24 -16
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-oO8rpFoq.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-v9IFpWkA.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/dist/server/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import 'dotenv
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
2
|
import express from 'express';
|
|
3
3
|
import { createServer } from 'http';
|
|
4
4
|
import { Server } from 'socket.io';
|
|
@@ -6,6 +6,18 @@ import cors from 'cors';
|
|
|
6
6
|
import { join, dirname } from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { existsSync, promises as fs } from 'fs';
|
|
9
|
+
// 프로젝트 경로의 .env 파일을 명시적으로 로드
|
|
10
|
+
// npx makecc 실행 시 MAKECC_PROJECT_PATH가 사용자 프로젝트 경로를 가리킴
|
|
11
|
+
const projectPath = process.env.MAKECC_PROJECT_PATH || process.cwd();
|
|
12
|
+
const envPath = join(projectPath, '.env');
|
|
13
|
+
if (existsSync(envPath)) {
|
|
14
|
+
dotenv.config({ path: envPath });
|
|
15
|
+
console.log(`Loaded .env from: ${envPath}`);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
// 폴백: 현재 디렉토리에서 로드 시도
|
|
19
|
+
dotenv.config();
|
|
20
|
+
}
|
|
9
21
|
import { ClaudeService } from './services/claudeService';
|
|
10
22
|
import { fileService } from './services/fileService';
|
|
11
23
|
import { workflowAIService } from './services/workflowAIService';
|
|
@@ -14,6 +26,7 @@ import { nodeSyncService } from './services/nodeSyncService';
|
|
|
14
26
|
import { configLoaderService } from './services/configLoaderService';
|
|
15
27
|
import { workflowExecutionService } from './services/workflowExecutionService';
|
|
16
28
|
import { executeInTerminal } from './services/terminalService';
|
|
29
|
+
import { claudeMdService } from './services/claudeMdService';
|
|
17
30
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
31
|
const __dirname = dirname(__filename);
|
|
19
32
|
const app = express();
|
|
@@ -259,10 +272,9 @@ app.post('/api/skill/test', async (req, res) => {
|
|
|
259
272
|
if (!existsSync(mainPyPath)) {
|
|
260
273
|
return res.status(404).json({ message: 'main.py not found' });
|
|
261
274
|
}
|
|
262
|
-
//
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
const command = existsSync(pythonPath) ? pythonPath : 'python3';
|
|
275
|
+
// 로컬 프로젝트의 .venv 사용
|
|
276
|
+
const localPythonPath = join(projectRoot, '.venv', 'bin', 'python');
|
|
277
|
+
const command = existsSync(localPythonPath) ? localPythonPath : 'python3';
|
|
266
278
|
console.log(`Testing skill: ${command} ${mainPyPath}`);
|
|
267
279
|
return new Promise((resolve) => {
|
|
268
280
|
const proc = spawn(command, [mainPyPath, ...(args || [])], {
|
|
@@ -497,6 +509,18 @@ if (isProduction) {
|
|
|
497
509
|
});
|
|
498
510
|
}
|
|
499
511
|
const PORT = process.env.PORT || 3001;
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
512
|
+
// 서버 시작 시 프로젝트 초기화
|
|
513
|
+
async function startServer() {
|
|
514
|
+
try {
|
|
515
|
+
// CLAUDE.md, .venv, uv 초기화
|
|
516
|
+
await claudeMdService.setup();
|
|
517
|
+
}
|
|
518
|
+
catch (error) {
|
|
519
|
+
console.error('Project setup warning:', error);
|
|
520
|
+
// 초기화 실패해도 서버는 시작
|
|
521
|
+
}
|
|
522
|
+
httpServer.listen(PORT, () => {
|
|
523
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
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
|
-
|
|
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
|
|
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 {
|
|
@@ -66,20 +67,33 @@ export class SkillGeneratorService {
|
|
|
66
67
|
this.projectRoot = projectRoot || process.env.MAKECC_PROJECT_PATH || process.cwd();
|
|
67
68
|
}
|
|
68
69
|
getClient(settings) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (settings?.
|
|
70
|
+
const DEFAULT_ANTHROPIC_URL = 'https://api.anthropic.com';
|
|
71
|
+
// 1. 프록시 우선: proxyUrl이 설정되어 있고, 기본 Anthropic URL이 아닌 경우
|
|
72
|
+
// 프록시 서버가 API 키를 관리하므로 클라이언트는 키 불필요
|
|
73
|
+
if (settings?.proxyUrl && settings.proxyUrl !== DEFAULT_ANTHROPIC_URL) {
|
|
74
|
+
console.log(`Using proxy server: ${settings.proxyUrl}`);
|
|
73
75
|
return new Anthropic({
|
|
74
76
|
baseURL: settings.proxyUrl,
|
|
75
|
-
apiKey:
|
|
77
|
+
apiKey: 'proxy-mode', // 프록시 서버가 실제 키를 관리
|
|
76
78
|
});
|
|
77
79
|
}
|
|
80
|
+
// 2. 환경변수에 API 키가 있으면 직접 호출
|
|
78
81
|
const envApiKey = process.env.ANTHROPIC_API_KEY;
|
|
79
|
-
if (
|
|
80
|
-
|
|
82
|
+
if (envApiKey) {
|
|
83
|
+
console.log('Using API key from environment variable');
|
|
84
|
+
return new Anthropic({ apiKey: envApiKey });
|
|
85
|
+
}
|
|
86
|
+
// 3. UI에서 direct 모드로 API 키를 직접 입력한 경우
|
|
87
|
+
if (settings?.apiMode === 'direct' && settings.apiKey) {
|
|
88
|
+
console.log('Using API key from UI settings');
|
|
89
|
+
return new Anthropic({ apiKey: settings.apiKey });
|
|
81
90
|
}
|
|
82
|
-
|
|
91
|
+
// 4. 아무것도 없으면 에러
|
|
92
|
+
throw new Error('API 키가 설정되지 않았습니다.\n' +
|
|
93
|
+
'해결 방법:\n' +
|
|
94
|
+
'1. 프록시 서버 URL을 설정하거나\n' +
|
|
95
|
+
'2. 프로젝트 .env 파일에 ANTHROPIC_API_KEY를 추가하거나\n' +
|
|
96
|
+
'3. 설정에서 Direct 모드로 API 키를 직접 입력하세요.');
|
|
83
97
|
}
|
|
84
98
|
async generate(prompt, settings, onProgress) {
|
|
85
99
|
const progress = onProgress || (() => { });
|
|
@@ -94,9 +108,20 @@ export class SkillGeneratorService {
|
|
|
94
108
|
message: '🔍 요청을 분석하고 있어요',
|
|
95
109
|
detail: '어떤 스킬이 필요한지 파악 중...',
|
|
96
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
|
+
}
|
|
97
122
|
const userPrompt = `Create a skill for: "${prompt}"
|
|
98
123
|
|
|
99
|
-
Generate complete, working code. Respond with JSON only
|
|
124
|
+
Generate complete, working code. Respond with JSON only.${claudeMdContext}`;
|
|
100
125
|
try {
|
|
101
126
|
progress({
|
|
102
127
|
step: 'designing',
|
|
@@ -211,40 +236,41 @@ Generate complete, working code. Respond with JSON only.`;
|
|
|
211
236
|
}
|
|
212
237
|
async installDependencies(requirementsPath, progress) {
|
|
213
238
|
return new Promise((resolve, reject) => {
|
|
214
|
-
|
|
215
|
-
const
|
|
239
|
+
// 로컬 프로젝트의 .venv 사용
|
|
240
|
+
const localVenvPythonPath = path.join(this.projectRoot, '.venv', 'bin', 'python');
|
|
241
|
+
const localVenvPipPath = path.join(this.projectRoot, '.venv', 'bin', 'pip');
|
|
216
242
|
let command;
|
|
217
243
|
let args;
|
|
218
244
|
// uv를 우선 사용 (10-100x 빠름)
|
|
219
|
-
// uv pip install --python <venv-python> -r requirements.txt
|
|
220
245
|
const useUv = this.checkCommandExists('uv');
|
|
221
|
-
if (useUv && existsSync(
|
|
246
|
+
if (useUv && existsSync(localVenvPythonPath)) {
|
|
222
247
|
command = 'uv';
|
|
223
|
-
args = ['pip', 'install', '--python',
|
|
248
|
+
args = ['pip', 'install', '--python', localVenvPythonPath, '-r', requirementsPath];
|
|
224
249
|
progress({
|
|
225
250
|
step: 'installing',
|
|
226
251
|
message: '⚡ uv로 패키지 설치 중 (고속)',
|
|
227
|
-
detail: 'uv pip install
|
|
252
|
+
detail: 'uv pip install → .venv/',
|
|
228
253
|
});
|
|
229
254
|
}
|
|
230
|
-
else if (existsSync(
|
|
231
|
-
// fallback:
|
|
232
|
-
command =
|
|
255
|
+
else if (existsSync(localVenvPipPath)) {
|
|
256
|
+
// fallback: 로컬 venv pip 사용
|
|
257
|
+
command = localVenvPipPath;
|
|
233
258
|
args = ['install', '-r', requirementsPath];
|
|
234
259
|
progress({
|
|
235
260
|
step: 'installing',
|
|
236
261
|
message: '📦 pip으로 패키지 설치 중',
|
|
237
|
-
detail: 'pip install
|
|
262
|
+
detail: 'pip install → .venv/',
|
|
238
263
|
});
|
|
239
264
|
}
|
|
240
265
|
else {
|
|
241
|
-
// fallback: 시스템 pip 사용
|
|
266
|
+
// fallback: 시스템 pip 사용 (경고)
|
|
267
|
+
console.warn('Warning: .venv not found, using system pip');
|
|
242
268
|
command = 'pip3';
|
|
243
269
|
args = ['install', '-r', requirementsPath];
|
|
244
270
|
progress({
|
|
245
271
|
step: 'installing',
|
|
246
|
-
message: '
|
|
247
|
-
detail: '
|
|
272
|
+
message: '⚠️ 시스템 pip 사용 중',
|
|
273
|
+
detail: '.venv가 없어 시스템 pip 사용',
|
|
248
274
|
});
|
|
249
275
|
}
|
|
250
276
|
console.log(`Installing dependencies: ${command} ${args.join(' ')}`);
|
|
@@ -124,28 +124,33 @@ ${AVAILABLE_TOOLS.join(', ')}
|
|
|
124
124
|
`;
|
|
125
125
|
export class WorkflowAIService {
|
|
126
126
|
getClient(settings) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
// 2. Proxy mode with custom base URL
|
|
134
|
-
if (settings?.apiMode === 'proxy' && settings.proxyUrl) {
|
|
127
|
+
const DEFAULT_ANTHROPIC_URL = 'https://api.anthropic.com';
|
|
128
|
+
// 1. 프록시 우선: proxyUrl이 설정되어 있고, 기본 Anthropic URL이 아닌 경우
|
|
129
|
+
// 프록시 서버가 API 키를 관리하므로 클라이언트는 키 불필요
|
|
130
|
+
if (settings?.proxyUrl && settings.proxyUrl !== DEFAULT_ANTHROPIC_URL) {
|
|
131
|
+
console.log(`Using proxy server: ${settings.proxyUrl}`);
|
|
135
132
|
return new Anthropic({
|
|
136
133
|
baseURL: settings.proxyUrl,
|
|
137
|
-
//
|
|
138
|
-
apiKey: process.env.ANTHROPIC_API_KEY || 'proxy-mode',
|
|
134
|
+
apiKey: 'proxy-mode', // 프록시 서버가 실제 키를 관리
|
|
139
135
|
});
|
|
140
136
|
}
|
|
141
|
-
//
|
|
137
|
+
// 2. 환경변수에 API 키가 있으면 직접 호출
|
|
142
138
|
const envApiKey = process.env.ANTHROPIC_API_KEY;
|
|
143
|
-
if (
|
|
144
|
-
|
|
139
|
+
if (envApiKey) {
|
|
140
|
+
console.log('Using API key from environment variable');
|
|
141
|
+
return new Anthropic({ apiKey: envApiKey });
|
|
145
142
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
// 3. UI에서 direct 모드로 API 키를 직접 입력한 경우
|
|
144
|
+
if (settings?.apiMode === 'direct' && settings.apiKey) {
|
|
145
|
+
console.log('Using API key from UI settings');
|
|
146
|
+
return new Anthropic({ apiKey: settings.apiKey });
|
|
147
|
+
}
|
|
148
|
+
// 4. 아무것도 없으면 에러
|
|
149
|
+
throw new Error('API 키가 설정되지 않았습니다.\n' +
|
|
150
|
+
'해결 방법:\n' +
|
|
151
|
+
'1. 프록시 서버 URL을 설정하거나\n' +
|
|
152
|
+
'2. 프로젝트 .env 파일에 ANTHROPIC_API_KEY를 추가하거나\n' +
|
|
153
|
+
'3. 설정에서 Direct 모드로 API 키를 직접 입력하세요.');
|
|
149
154
|
}
|
|
150
155
|
async generate(prompt, settings) {
|
|
151
156
|
const client = this.getClient(settings);
|
package/package.json
CHANGED
package/server/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import 'dotenv
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
2
|
import express from 'express';
|
|
3
3
|
import { createServer } from 'http';
|
|
4
4
|
import { Server } from 'socket.io';
|
|
@@ -6,6 +6,18 @@ import cors from 'cors';
|
|
|
6
6
|
import { join, dirname } from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { existsSync, promises as fs } from 'fs';
|
|
9
|
+
|
|
10
|
+
// 프로젝트 경로의 .env 파일을 명시적으로 로드
|
|
11
|
+
// npx makecc 실행 시 MAKECC_PROJECT_PATH가 사용자 프로젝트 경로를 가리킴
|
|
12
|
+
const projectPath = process.env.MAKECC_PROJECT_PATH || process.cwd();
|
|
13
|
+
const envPath = join(projectPath, '.env');
|
|
14
|
+
if (existsSync(envPath)) {
|
|
15
|
+
dotenv.config({ path: envPath });
|
|
16
|
+
console.log(`Loaded .env from: ${envPath}`);
|
|
17
|
+
} else {
|
|
18
|
+
// 폴백: 현재 디렉토리에서 로드 시도
|
|
19
|
+
dotenv.config();
|
|
20
|
+
}
|
|
9
21
|
import { ClaudeService } from './services/claudeService';
|
|
10
22
|
import { fileService } from './services/fileService';
|
|
11
23
|
import { workflowAIService } from './services/workflowAIService';
|
|
@@ -14,6 +26,7 @@ import { nodeSyncService } from './services/nodeSyncService';
|
|
|
14
26
|
import { configLoaderService } from './services/configLoaderService';
|
|
15
27
|
import { workflowExecutionService } from './services/workflowExecutionService';
|
|
16
28
|
import { executeInTerminal, getClaudeCommand } from './services/terminalService';
|
|
29
|
+
import { claudeMdService } from './services/claudeMdService';
|
|
17
30
|
import type { WorkflowExecutionRequest, NodeExecutionUpdate } from './types';
|
|
18
31
|
import type { ClaudeConfigExport, SaveOptions } from './services/fileService';
|
|
19
32
|
|
|
@@ -293,10 +306,9 @@ app.post('/api/skill/test', async (req, res) => {
|
|
|
293
306
|
return res.status(404).json({ message: 'main.py not found' });
|
|
294
307
|
}
|
|
295
308
|
|
|
296
|
-
//
|
|
297
|
-
const
|
|
298
|
-
const
|
|
299
|
-
const command = existsSync(pythonPath) ? pythonPath : 'python3';
|
|
309
|
+
// 로컬 프로젝트의 .venv 사용
|
|
310
|
+
const localPythonPath = join(projectRoot, '.venv', 'bin', 'python');
|
|
311
|
+
const command = existsSync(localPythonPath) ? localPythonPath : 'python3';
|
|
300
312
|
|
|
301
313
|
console.log(`Testing skill: ${command} ${mainPyPath}`);
|
|
302
314
|
|
|
@@ -580,6 +592,19 @@ if (isProduction) {
|
|
|
580
592
|
|
|
581
593
|
const PORT = process.env.PORT || 3001;
|
|
582
594
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
595
|
+
// 서버 시작 시 프로젝트 초기화
|
|
596
|
+
async function startServer() {
|
|
597
|
+
try {
|
|
598
|
+
// CLAUDE.md, .venv, uv 초기화
|
|
599
|
+
await claudeMdService.setup();
|
|
600
|
+
} catch (error) {
|
|
601
|
+
console.error('Project setup warning:', error);
|
|
602
|
+
// 초기화 실패해도 서버는 시작
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
httpServer.listen(PORT, () => {
|
|
606
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
startServer();
|