@su-record/vibe 2.4.6 → 2.4.7
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/.claude/settings.local.json +28 -24
- package/README.md +32 -15
- package/dist/cli/auth.d.ts +13 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +118 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/collaborator.d.ts +8 -0
- package/dist/cli/collaborator.d.ts.map +1 -0
- package/dist/cli/collaborator.js +136 -0
- package/dist/cli/collaborator.js.map +1 -0
- package/dist/cli/detect.d.ts +35 -0
- package/dist/cli/detect.d.ts.map +1 -0
- package/dist/cli/detect.js +376 -0
- package/dist/cli/detect.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +201 -2001
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/llm.d.ts +49 -0
- package/dist/cli/llm.d.ts.map +1 -0
- package/dist/cli/llm.js +464 -0
- package/dist/cli/llm.js.map +1 -0
- package/dist/cli/mcp.d.ts +49 -0
- package/dist/cli/mcp.d.ts.map +1 -0
- package/dist/cli/mcp.js +169 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/cli/setup.d.ts +53 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +455 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/types.d.ts +83 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +5 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/utils.d.ts +40 -0
- package/dist/cli/utils.d.ts.map +1 -0
- package/dist/cli/utils.js +112 -0
- package/dist/cli/utils.js.map +1 -0
- package/dist/lib/MemoryManager.js +93 -93
- package/dist/lib/MemoryManager.js.map +1 -1
- package/dist/lib/ProjectCache.d.ts.map +1 -1
- package/dist/lib/ProjectCache.js +2 -1
- package/dist/lib/ProjectCache.js.map +1 -1
- package/dist/lib/PythonParser.js +109 -109
- package/dist/lib/PythonParser.js.map +1 -1
- package/dist/lib/constants.d.ts +31 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +36 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/gemini-api.d.ts.map +1 -1
- package/dist/lib/gemini-api.js +1 -6
- package/dist/lib/gemini-api.js.map +1 -1
- package/dist/lib/gemini-mcp.js +15 -15
- package/dist/lib/gemini-oauth.js +36 -36
- package/dist/lib/gemini-oauth.js.map +1 -1
- package/dist/lib/gpt-api.d.ts.map +1 -1
- package/dist/lib/gpt-api.js +6 -11
- package/dist/lib/gpt-api.js.map +1 -1
- package/dist/lib/gpt-mcp.js +17 -17
- package/dist/lib/gpt-oauth.js +45 -45
- package/dist/lib/gpt-oauth.js.map +1 -1
- package/dist/lib/utils.d.ts +21 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +51 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/orchestrator/agentDiscovery.d.ts.map +1 -1
- package/dist/orchestrator/agentDiscovery.js +3 -2
- package/dist/orchestrator/agentDiscovery.js.map +1 -1
- package/dist/orchestrator/backgroundAgent.d.ts.map +1 -1
- package/dist/orchestrator/backgroundAgent.js +4 -16
- package/dist/orchestrator/backgroundAgent.js.map +1 -1
- package/dist/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/orchestrator.js +17 -15
- package/dist/orchestrator/orchestrator.js.map +1 -1
- package/dist/orchestrator/parallelResearch.d.ts.map +1 -1
- package/dist/orchestrator/parallelResearch.js +30 -44
- package/dist/orchestrator/parallelResearch.js.map +1 -1
- package/dist/orchestrator/types.d.ts +3 -2
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/tools/analytics/getUsageAnalytics.js +13 -13
- package/dist/tools/analytics/getUsageAnalytics.js.map +1 -1
- package/dist/tools/convention/getCodingGuide.js +2 -2
- package/dist/tools/convention/getCodingGuide.js.map +1 -1
- package/dist/tools/memory/createMemoryTimeline.js +11 -11
- package/dist/tools/memory/createMemoryTimeline.js.map +1 -1
- package/dist/tools/memory/getMemoryGraph.js +12 -12
- package/dist/tools/memory/getSessionContext.js +10 -10
- package/dist/tools/memory/getSessionContext.js.map +1 -1
- package/dist/tools/memory/linkMemories.js +14 -14
- package/dist/tools/memory/listMemories.js +4 -4
- package/dist/tools/memory/recallMemory.js +4 -4
- package/dist/tools/memory/restoreSessionContext.js +2 -2
- package/dist/tools/memory/restoreSessionContext.js.map +1 -1
- package/dist/tools/memory/saveMemory.js +4 -4
- package/dist/tools/memory/searchMemoriesAdvanced.js +23 -23
- package/dist/tools/memory/searchMemoriesAdvanced.js.map +1 -1
- package/dist/tools/memory/startSession.js +3 -3
- package/dist/tools/memory/startSession.js.map +1 -1
- package/dist/tools/planning/generatePrd.js +46 -46
- package/dist/tools/prompt/enhancePromptGemini.js +160 -160
- package/dist/tools/reasoning/applyReasoningFramework.js +56 -56
- package/dist/tools/semantic/analyzeDependencyGraph.js +12 -12
- package/dist/tools/semantic/findReferences.d.ts.map +1 -1
- package/dist/tools/semantic/findReferences.js +2 -1
- package/dist/tools/semantic/findReferences.js.map +1 -1
- package/dist/tools/semantic/findSymbol.d.ts.map +1 -1
- package/dist/tools/semantic/findSymbol.js +2 -1
- package/dist/tools/semantic/findSymbol.js.map +1 -1
- package/hooks/hooks.json +10 -8
- package/package.json +4 -2
package/dist/cli/index.js
CHANGED
|
@@ -5,10 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import fs from 'fs';
|
|
8
|
-
import os from 'os';
|
|
9
8
|
import { execSync } from 'child_process';
|
|
10
9
|
import { fileURLToPath } from 'url';
|
|
11
10
|
import { createRequire } from 'module';
|
|
11
|
+
import { log, setSilentMode, ensureDir, removeDirRecursive, getPackageJson, compareVersions, } from './utils.js';
|
|
12
|
+
import { unregisterMcp } from './mcp.js';
|
|
13
|
+
import { detectTechStacks } from './detect.js';
|
|
14
|
+
import { formatLLMStatus } from './auth.js';
|
|
15
|
+
import { setupCollaboratorAutoInstall } from './collaborator.js';
|
|
16
|
+
import { setupExternalLLM, removeExternalLLM, gptAuth, gptStatus, gptLogout, geminiAuth, geminiStatus, geminiLogout, showAuthHelp, showLogoutHelp, } from './llm.js';
|
|
17
|
+
import { registerMcpServers, updateConstitution, updateClaudeMd, updateRules, installGlobalAssets, migrateLegacyVibe, updateGitignore, updateConfig, cleanupLegacy, removeLocalAssets, cleanupClaudeConfig, cleanupLegacyMcp, } from './setup.js';
|
|
12
18
|
const require = createRequire(import.meta.url);
|
|
13
19
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
20
|
const __dirname = path.dirname(__filename);
|
|
@@ -21,843 +27,8 @@ const options = {
|
|
|
21
27
|
silent: args.includes('--silent') || args.includes('-s')
|
|
22
28
|
};
|
|
23
29
|
const positionalArgs = args.filter(arg => !arg.startsWith('-'));
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* @returns 1 if a > b, -1 if a < b, 0 if equal
|
|
27
|
-
*/
|
|
28
|
-
function compareVersions(a, b) {
|
|
29
|
-
const partsA = a.replace(/^v/, '').split('.').map(Number);
|
|
30
|
-
const partsB = b.replace(/^v/, '').split('.').map(Number);
|
|
31
|
-
for (let i = 0; i < 3; i++) {
|
|
32
|
-
const numA = partsA[i] || 0;
|
|
33
|
-
const numB = partsB[i] || 0;
|
|
34
|
-
if (numA > numB)
|
|
35
|
-
return 1;
|
|
36
|
-
if (numA < numB)
|
|
37
|
-
return -1;
|
|
38
|
-
}
|
|
39
|
-
return 0;
|
|
40
|
-
}
|
|
41
|
-
const DEFAULT_MCPS = [
|
|
42
|
-
{ name: 'vibe', type: 'node', local: true },
|
|
43
|
-
{ name: 'context7', type: 'npx', package: '@upstash/context7-mcp@latest' }
|
|
44
|
-
];
|
|
45
|
-
const EXTERNAL_LLMS = {
|
|
46
|
-
gpt: {
|
|
47
|
-
name: 'vibe-gpt',
|
|
48
|
-
role: 'architecture',
|
|
49
|
-
description: '아키텍처/디버깅 (GPT 5.2)',
|
|
50
|
-
package: '@anthropics/openai-mcp',
|
|
51
|
-
envKey: 'OPENAI_API_KEY'
|
|
52
|
-
},
|
|
53
|
-
gemini: {
|
|
54
|
-
name: 'vibe-gemini',
|
|
55
|
-
role: 'ui-ux',
|
|
56
|
-
description: 'UI/UX 설계 (Gemini 3)',
|
|
57
|
-
package: '@anthropics/gemini-mcp',
|
|
58
|
-
envKey: 'GOOGLE_API_KEY'
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
// ============================================================================
|
|
62
|
-
// Utility Functions
|
|
63
|
-
// ============================================================================
|
|
64
|
-
/**
|
|
65
|
-
* Claude CLI 경로 찾기 (Windows/macOS/Linux 지원)
|
|
66
|
-
* 네이티브 설치와 npm 설치 모두 지원
|
|
67
|
-
*/
|
|
68
|
-
function getClaudePath() {
|
|
69
|
-
// 1. PATH에서 'claude' 찾기 (npm 설치 또는 PATH에 추가된 네이티브)
|
|
70
|
-
try {
|
|
71
|
-
execSync('claude --version', { stdio: 'pipe' });
|
|
72
|
-
return 'claude';
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
// PATH에 없으면 플랫폼별 기본 경로 확인
|
|
76
|
-
}
|
|
77
|
-
// 2. 플랫폼별 네이티브 설치 경로 확인
|
|
78
|
-
if (process.platform === 'win32') {
|
|
79
|
-
// Windows 네이티브 설치 경로
|
|
80
|
-
const possiblePaths = [
|
|
81
|
-
// 네이티브 설치 (claude install)
|
|
82
|
-
path.join(os.homedir(), '.local', 'bin', 'claude.exe'),
|
|
83
|
-
// npm 전역 설치
|
|
84
|
-
path.join(process.env.APPDATA || '', 'npm', 'claude.cmd'),
|
|
85
|
-
// 기타 가능한 경로
|
|
86
|
-
path.join(os.homedir(), 'AppData', 'Local', 'Programs', '@anthropic', 'claude-code', 'claude.exe'),
|
|
87
|
-
path.join(os.homedir(), 'AppData', 'Local', 'AnthropicClaude', 'claude.exe'),
|
|
88
|
-
path.join(os.homedir(), '.claude', 'local', 'claude.exe'),
|
|
89
|
-
'C:\\Program Files\\Anthropic\\Claude\\claude.exe',
|
|
90
|
-
'C:\\Program Files (x86)\\Anthropic\\Claude\\claude.exe',
|
|
91
|
-
];
|
|
92
|
-
for (const p of possiblePaths) {
|
|
93
|
-
if (p && fs.existsSync(p)) {
|
|
94
|
-
return `"${p}"`;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
// macOS/Linux 네이티브 설치 경로
|
|
100
|
-
const possiblePaths = [
|
|
101
|
-
// 네이티브 설치 (claude install)
|
|
102
|
-
path.join(os.homedir(), '.local', 'bin', 'claude'),
|
|
103
|
-
// npm 전역 설치 (일반적인 경로)
|
|
104
|
-
'/usr/local/bin/claude',
|
|
105
|
-
'/usr/bin/claude',
|
|
106
|
-
// macOS Homebrew
|
|
107
|
-
'/opt/homebrew/bin/claude',
|
|
108
|
-
];
|
|
109
|
-
for (const p of possiblePaths) {
|
|
110
|
-
if (fs.existsSync(p)) {
|
|
111
|
-
return p;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// 찾지 못하면 기본값 반환 (에러는 호출 측에서 처리)
|
|
116
|
-
return 'claude';
|
|
117
|
-
}
|
|
118
|
-
// Claude CLI 경로 캐시
|
|
119
|
-
let _claudePath = null;
|
|
120
|
-
function claudeCmd() {
|
|
121
|
-
if (_claudePath === null) {
|
|
122
|
-
_claudePath = getClaudePath();
|
|
123
|
-
}
|
|
124
|
-
return _claudePath;
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Claude CLI가 사용 가능한지 확인
|
|
128
|
-
*/
|
|
129
|
-
function isClaudeCliAvailable() {
|
|
130
|
-
const cmd = claudeCmd();
|
|
131
|
-
try {
|
|
132
|
-
execSync(`${cmd} --version`, { stdio: 'pipe' });
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Claude 설정 파일 경로 가져오기
|
|
141
|
-
*/
|
|
142
|
-
function getClaudeSettingsPath() {
|
|
143
|
-
return path.join(os.homedir(), '.claude', 'settings.json');
|
|
144
|
-
}
|
|
145
|
-
function readClaudeSettings() {
|
|
146
|
-
const settingsPath = getClaudeSettingsPath();
|
|
147
|
-
if (fs.existsSync(settingsPath)) {
|
|
148
|
-
try {
|
|
149
|
-
return JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
return {};
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return {};
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Claude 설정 파일 쓰기
|
|
159
|
-
*/
|
|
160
|
-
function writeClaudeSettings(settings) {
|
|
161
|
-
const settingsPath = getClaudeSettingsPath();
|
|
162
|
-
const dir = path.dirname(settingsPath);
|
|
163
|
-
if (!fs.existsSync(dir)) {
|
|
164
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
165
|
-
}
|
|
166
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* MCP 서버 직접 추가 (CLI 없이 설정 파일 수정)
|
|
170
|
-
*/
|
|
171
|
-
function addMcpServer(name, config) {
|
|
172
|
-
const settings = readClaudeSettings();
|
|
173
|
-
if (!settings.mcpServers) {
|
|
174
|
-
settings.mcpServers = {};
|
|
175
|
-
}
|
|
176
|
-
settings.mcpServers[name] = config;
|
|
177
|
-
writeClaudeSettings(settings);
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* MCP 서버 직접 제거 (CLI 없이 설정 파일 수정)
|
|
181
|
-
*/
|
|
182
|
-
function removeMcpServer(name) {
|
|
183
|
-
const settings = readClaudeSettings();
|
|
184
|
-
if (settings.mcpServers && settings.mcpServers[name]) {
|
|
185
|
-
delete settings.mcpServers[name];
|
|
186
|
-
writeClaudeSettings(settings);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* MCP 등록 (CLI 또는 직접 설정 파일 수정)
|
|
191
|
-
*/
|
|
192
|
-
function registerMcp(name, config) {
|
|
193
|
-
if (isClaudeCliAvailable()) {
|
|
194
|
-
// CLI 사용
|
|
195
|
-
const argsStr = config.args ? config.args.map(a => `"${a}"`).join(' ') : '';
|
|
196
|
-
const envStr = config.env ? Object.entries(config.env).map(([k, v]) => `-e ${k}=${v}`).join(' ') : '';
|
|
197
|
-
const cmd = `${claudeCmd()} mcp add ${name} -s user ${envStr} -- ${config.command} ${argsStr}`.trim();
|
|
198
|
-
try {
|
|
199
|
-
execSync(cmd, { stdio: 'pipe' });
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
// CLI 실패시 직접 설정 파일 수정
|
|
203
|
-
addMcpServer(name, config);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
// CLI 없으면 직접 설정 파일 수정
|
|
208
|
-
addMcpServer(name, config);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* MCP 제거 (CLI 또는 직접 설정 파일 수정)
|
|
213
|
-
*/
|
|
214
|
-
function unregisterMcp(name) {
|
|
215
|
-
if (isClaudeCliAvailable()) {
|
|
216
|
-
try {
|
|
217
|
-
execSync(`${claudeCmd()} mcp remove ${name}`, { stdio: 'pipe' });
|
|
218
|
-
}
|
|
219
|
-
catch { /* ignore */ }
|
|
220
|
-
try {
|
|
221
|
-
execSync(`${claudeCmd()} mcp remove ${name} -s user`, { stdio: 'pipe' });
|
|
222
|
-
}
|
|
223
|
-
catch { /* ignore */ }
|
|
224
|
-
}
|
|
225
|
-
// 항상 설정 파일에서도 제거 (중복 가능)
|
|
226
|
-
removeMcpServer(name);
|
|
227
|
-
}
|
|
228
|
-
function log(message) {
|
|
229
|
-
if (!options.silent) {
|
|
230
|
-
console.log(message);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
function ensureDir(dir) {
|
|
234
|
-
if (!fs.existsSync(dir)) {
|
|
235
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
function copyDirContents(sourceDir, targetDir) {
|
|
239
|
-
if (fs.existsSync(sourceDir)) {
|
|
240
|
-
fs.readdirSync(sourceDir).forEach(file => {
|
|
241
|
-
fs.copyFileSync(path.join(sourceDir, file), path.join(targetDir, file));
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
function copyDirRecursive(sourceDir, targetDir) {
|
|
246
|
-
if (!fs.existsSync(sourceDir))
|
|
247
|
-
return;
|
|
248
|
-
ensureDir(targetDir);
|
|
249
|
-
fs.readdirSync(sourceDir).forEach(item => {
|
|
250
|
-
const sourcePath = path.join(sourceDir, item);
|
|
251
|
-
const targetPath = path.join(targetDir, item);
|
|
252
|
-
if (fs.statSync(sourcePath).isDirectory()) {
|
|
253
|
-
copyDirRecursive(sourcePath, targetPath);
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
fs.copyFileSync(sourcePath, targetPath);
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
function removeDirRecursive(dirPath) {
|
|
261
|
-
if (!fs.existsSync(dirPath))
|
|
262
|
-
return;
|
|
263
|
-
fs.readdirSync(dirPath).forEach(item => {
|
|
264
|
-
const itemPath = path.join(dirPath, item);
|
|
265
|
-
if (fs.statSync(itemPath).isDirectory()) {
|
|
266
|
-
removeDirRecursive(itemPath);
|
|
267
|
-
}
|
|
268
|
-
else {
|
|
269
|
-
fs.unlinkSync(itemPath);
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
fs.rmdirSync(dirPath);
|
|
273
|
-
}
|
|
274
|
-
function getPackageJson() {
|
|
275
|
-
const pkgPath = path.join(__dirname, '../../package.json');
|
|
276
|
-
return JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
277
|
-
}
|
|
278
|
-
// ============================================================================
|
|
279
|
-
// LLM Auth Status
|
|
280
|
-
// ============================================================================
|
|
281
|
-
function getLLMAuthStatus() {
|
|
282
|
-
const status = { gpt: null, gemini: null };
|
|
283
|
-
// GPT 상태 확인
|
|
284
|
-
try {
|
|
285
|
-
const gptStoragePath = path.join(__dirname, '../lib/gpt-storage.js');
|
|
286
|
-
if (fs.existsSync(gptStoragePath)) {
|
|
287
|
-
const gptStorage = require(gptStoragePath);
|
|
288
|
-
const account = gptStorage.getActiveAccount();
|
|
289
|
-
if (account) {
|
|
290
|
-
const isExpired = gptStorage.isTokenExpired(account);
|
|
291
|
-
status.gpt = {
|
|
292
|
-
type: 'oauth',
|
|
293
|
-
email: account.email,
|
|
294
|
-
valid: !isExpired
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
catch (e) { }
|
|
300
|
-
// GPT API 키 확인 (프로젝트 config)
|
|
301
|
-
if (!status.gpt) {
|
|
302
|
-
try {
|
|
303
|
-
const configPath = path.join(process.cwd(), '.claude', 'vibe', 'config.json');
|
|
304
|
-
if (fs.existsSync(configPath)) {
|
|
305
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
306
|
-
if (config.models?.gpt?.enabled) {
|
|
307
|
-
status.gpt = { type: 'apikey', valid: true };
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
catch (e) { }
|
|
312
|
-
}
|
|
313
|
-
// Gemini 상태 확인
|
|
314
|
-
try {
|
|
315
|
-
// Windows: %APPDATA%/vibe, macOS/Linux: ~/.config/vibe
|
|
316
|
-
const geminiConfigDir = process.platform === 'win32'
|
|
317
|
-
? path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'vibe')
|
|
318
|
-
: path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'vibe');
|
|
319
|
-
const tokenPath = path.join(geminiConfigDir, 'gemini-auth.json');
|
|
320
|
-
if (fs.existsSync(tokenPath)) {
|
|
321
|
-
const tokenData = JSON.parse(fs.readFileSync(tokenPath, 'utf-8'));
|
|
322
|
-
if (tokenData.accounts && tokenData.accounts.length > 0) {
|
|
323
|
-
const activeAccount = tokenData.accounts.find((a) => a.active) || tokenData.accounts[0];
|
|
324
|
-
const isExpired = activeAccount.expires && Date.now() > activeAccount.expires;
|
|
325
|
-
status.gemini = {
|
|
326
|
-
type: 'oauth',
|
|
327
|
-
email: activeAccount.email || 'default',
|
|
328
|
-
valid: !isExpired || !!activeAccount.refreshToken
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
catch (e) { }
|
|
334
|
-
// Gemini API 키 확인 (프로젝트 config)
|
|
335
|
-
if (!status.gemini) {
|
|
336
|
-
try {
|
|
337
|
-
const configPath = path.join(process.cwd(), '.claude', 'vibe', 'config.json');
|
|
338
|
-
if (fs.existsSync(configPath)) {
|
|
339
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
340
|
-
if (config.models?.gemini?.enabled) {
|
|
341
|
-
status.gemini = { type: 'apikey', valid: true };
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
catch (e) { }
|
|
346
|
-
}
|
|
347
|
-
return status;
|
|
348
|
-
}
|
|
349
|
-
function formatLLMStatus() {
|
|
350
|
-
const status = getLLMAuthStatus();
|
|
351
|
-
const lines = [];
|
|
352
|
-
lines.push('외부 LLM:');
|
|
353
|
-
// GPT 상태
|
|
354
|
-
if (status.gpt) {
|
|
355
|
-
if (status.gpt.type === 'oauth') {
|
|
356
|
-
const icon = status.gpt.valid ? '✓' : '⚠';
|
|
357
|
-
lines.push(` GPT: ${icon} OAuth 인증됨 (${status.gpt.email})`);
|
|
358
|
-
}
|
|
359
|
-
else {
|
|
360
|
-
lines.push(' GPT: ✓ API 키 설정됨');
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
lines.push(' GPT: ✗ 미설정 (vibe gpt --auth 또는 vibe gpt <api-key>)');
|
|
365
|
-
}
|
|
366
|
-
// Gemini 상태
|
|
367
|
-
if (status.gemini) {
|
|
368
|
-
if (status.gemini.type === 'oauth') {
|
|
369
|
-
const icon = status.gemini.valid ? '✓' : '⚠';
|
|
370
|
-
lines.push(` Gemini: ${icon} OAuth 인증됨 (${status.gemini.email})`);
|
|
371
|
-
}
|
|
372
|
-
else {
|
|
373
|
-
lines.push(' Gemini: ✓ API 키 설정됨');
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
lines.push(' Gemini: ✗ 미설정 (vibe gemini --auth 또는 vibe gemini <api-key>)');
|
|
378
|
-
}
|
|
379
|
-
return lines.join('\n');
|
|
380
|
-
}
|
|
381
|
-
// ============================================================================
|
|
382
|
-
// Tech Stack Detection
|
|
383
|
-
// ============================================================================
|
|
384
|
-
function detectTechStacks(projectRoot) {
|
|
385
|
-
const stacks = [];
|
|
386
|
-
const details = { databases: [], stateManagement: [], hosting: [], cicd: [] };
|
|
387
|
-
const detectInDir = (dir, prefix = '') => {
|
|
388
|
-
const detected = [];
|
|
389
|
-
// Node.js / TypeScript
|
|
390
|
-
if (fs.existsSync(path.join(dir, 'package.json'))) {
|
|
391
|
-
try {
|
|
392
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
|
|
393
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
394
|
-
// 프레임워크 감지
|
|
395
|
-
if (deps['next'])
|
|
396
|
-
detected.push({ type: 'typescript-nextjs', path: prefix });
|
|
397
|
-
else if (deps['react-native'])
|
|
398
|
-
detected.push({ type: 'typescript-react-native', path: prefix });
|
|
399
|
-
else if (deps['react'])
|
|
400
|
-
detected.push({ type: 'typescript-react', path: prefix });
|
|
401
|
-
else if (deps['nuxt'] || deps['nuxt3'])
|
|
402
|
-
detected.push({ type: 'typescript-nuxt', path: prefix });
|
|
403
|
-
else if (deps['vue'])
|
|
404
|
-
detected.push({ type: 'typescript-vue', path: prefix });
|
|
405
|
-
else if (deps['express'] || deps['fastify'] || deps['koa'] || deps['nest'] || deps['@nestjs/core'])
|
|
406
|
-
detected.push({ type: 'typescript-node', path: prefix });
|
|
407
|
-
else if (pkg.name)
|
|
408
|
-
detected.push({ type: 'typescript-node', path: prefix });
|
|
409
|
-
// DB 감지
|
|
410
|
-
if (deps['pg'] || deps['postgres'] || deps['@prisma/client'])
|
|
411
|
-
details.databases.push('PostgreSQL');
|
|
412
|
-
if (deps['mysql'] || deps['mysql2'])
|
|
413
|
-
details.databases.push('MySQL');
|
|
414
|
-
if (deps['mongodb'] || deps['mongoose'])
|
|
415
|
-
details.databases.push('MongoDB');
|
|
416
|
-
if (deps['redis'] || deps['ioredis'])
|
|
417
|
-
details.databases.push('Redis');
|
|
418
|
-
if (deps['sqlite3'] || deps['better-sqlite3'])
|
|
419
|
-
details.databases.push('SQLite');
|
|
420
|
-
if (deps['typeorm'])
|
|
421
|
-
details.databases.push('TypeORM');
|
|
422
|
-
if (deps['prisma'] || deps['@prisma/client'])
|
|
423
|
-
details.databases.push('Prisma');
|
|
424
|
-
if (deps['drizzle-orm'])
|
|
425
|
-
details.databases.push('Drizzle');
|
|
426
|
-
if (deps['sequelize'])
|
|
427
|
-
details.databases.push('Sequelize');
|
|
428
|
-
// 상태관리 감지
|
|
429
|
-
if (deps['redux'] || deps['@reduxjs/toolkit'])
|
|
430
|
-
details.stateManagement.push('Redux');
|
|
431
|
-
if (deps['zustand'])
|
|
432
|
-
details.stateManagement.push('Zustand');
|
|
433
|
-
if (deps['jotai'])
|
|
434
|
-
details.stateManagement.push('Jotai');
|
|
435
|
-
if (deps['recoil'])
|
|
436
|
-
details.stateManagement.push('Recoil');
|
|
437
|
-
if (deps['mobx'])
|
|
438
|
-
details.stateManagement.push('MobX');
|
|
439
|
-
if (deps['@tanstack/react-query'] || deps['react-query'])
|
|
440
|
-
details.stateManagement.push('React Query');
|
|
441
|
-
if (deps['swr'])
|
|
442
|
-
details.stateManagement.push('SWR');
|
|
443
|
-
if (deps['pinia'])
|
|
444
|
-
details.stateManagement.push('Pinia');
|
|
445
|
-
if (deps['vuex'])
|
|
446
|
-
details.stateManagement.push('Vuex');
|
|
447
|
-
}
|
|
448
|
-
catch (e) { }
|
|
449
|
-
}
|
|
450
|
-
// Python
|
|
451
|
-
if (fs.existsSync(path.join(dir, 'pyproject.toml'))) {
|
|
452
|
-
try {
|
|
453
|
-
const content = fs.readFileSync(path.join(dir, 'pyproject.toml'), 'utf-8');
|
|
454
|
-
if (content.includes('fastapi'))
|
|
455
|
-
detected.push({ type: 'python-fastapi', path: prefix });
|
|
456
|
-
else if (content.includes('django'))
|
|
457
|
-
detected.push({ type: 'python-django', path: prefix });
|
|
458
|
-
else
|
|
459
|
-
detected.push({ type: 'python', path: prefix });
|
|
460
|
-
if (content.includes('psycopg') || content.includes('asyncpg'))
|
|
461
|
-
details.databases.push('PostgreSQL');
|
|
462
|
-
if (content.includes('pymongo'))
|
|
463
|
-
details.databases.push('MongoDB');
|
|
464
|
-
if (content.includes('sqlalchemy'))
|
|
465
|
-
details.databases.push('SQLAlchemy');
|
|
466
|
-
if (content.includes('prisma'))
|
|
467
|
-
details.databases.push('Prisma');
|
|
468
|
-
}
|
|
469
|
-
catch (e) { }
|
|
470
|
-
}
|
|
471
|
-
else if (fs.existsSync(path.join(dir, 'requirements.txt'))) {
|
|
472
|
-
try {
|
|
473
|
-
const content = fs.readFileSync(path.join(dir, 'requirements.txt'), 'utf-8');
|
|
474
|
-
if (content.includes('fastapi'))
|
|
475
|
-
detected.push({ type: 'python-fastapi', path: prefix });
|
|
476
|
-
else if (content.includes('django'))
|
|
477
|
-
detected.push({ type: 'python-django', path: prefix });
|
|
478
|
-
else
|
|
479
|
-
detected.push({ type: 'python', path: prefix });
|
|
480
|
-
if (content.includes('psycopg') || content.includes('asyncpg'))
|
|
481
|
-
details.databases.push('PostgreSQL');
|
|
482
|
-
if (content.includes('pymongo'))
|
|
483
|
-
details.databases.push('MongoDB');
|
|
484
|
-
if (content.includes('sqlalchemy'))
|
|
485
|
-
details.databases.push('SQLAlchemy');
|
|
486
|
-
}
|
|
487
|
-
catch (e) { }
|
|
488
|
-
}
|
|
489
|
-
// Flutter / Dart
|
|
490
|
-
if (fs.existsSync(path.join(dir, 'pubspec.yaml'))) {
|
|
491
|
-
detected.push({ type: 'dart-flutter', path: prefix });
|
|
492
|
-
try {
|
|
493
|
-
const content = fs.readFileSync(path.join(dir, 'pubspec.yaml'), 'utf-8');
|
|
494
|
-
if (content.includes('flutter_riverpod') || content.includes('riverpod'))
|
|
495
|
-
details.stateManagement.push('Riverpod');
|
|
496
|
-
else if (content.includes('provider'))
|
|
497
|
-
details.stateManagement.push('Provider');
|
|
498
|
-
if (content.includes('bloc'))
|
|
499
|
-
details.stateManagement.push('BLoC');
|
|
500
|
-
if (content.includes('getx') || content.includes('get:'))
|
|
501
|
-
details.stateManagement.push('GetX');
|
|
502
|
-
}
|
|
503
|
-
catch (e) { }
|
|
504
|
-
}
|
|
505
|
-
// Go
|
|
506
|
-
if (fs.existsSync(path.join(dir, 'go.mod'))) {
|
|
507
|
-
detected.push({ type: 'go', path: prefix });
|
|
508
|
-
try {
|
|
509
|
-
const content = fs.readFileSync(path.join(dir, 'go.mod'), 'utf-8');
|
|
510
|
-
if (content.includes('pgx') || content.includes('pq'))
|
|
511
|
-
details.databases.push('PostgreSQL');
|
|
512
|
-
if (content.includes('go-redis'))
|
|
513
|
-
details.databases.push('Redis');
|
|
514
|
-
if (content.includes('mongo-driver'))
|
|
515
|
-
details.databases.push('MongoDB');
|
|
516
|
-
}
|
|
517
|
-
catch (e) { }
|
|
518
|
-
}
|
|
519
|
-
// Rust
|
|
520
|
-
if (fs.existsSync(path.join(dir, 'Cargo.toml'))) {
|
|
521
|
-
detected.push({ type: 'rust', path: prefix });
|
|
522
|
-
try {
|
|
523
|
-
const content = fs.readFileSync(path.join(dir, 'Cargo.toml'), 'utf-8');
|
|
524
|
-
if (content.includes('sqlx') || content.includes('diesel'))
|
|
525
|
-
details.databases.push('PostgreSQL');
|
|
526
|
-
if (content.includes('mongodb'))
|
|
527
|
-
details.databases.push('MongoDB');
|
|
528
|
-
}
|
|
529
|
-
catch (e) { }
|
|
530
|
-
}
|
|
531
|
-
// Java / Kotlin
|
|
532
|
-
if (fs.existsSync(path.join(dir, 'build.gradle')) || fs.existsSync(path.join(dir, 'build.gradle.kts'))) {
|
|
533
|
-
try {
|
|
534
|
-
const gradleFile = fs.existsSync(path.join(dir, 'build.gradle.kts'))
|
|
535
|
-
? path.join(dir, 'build.gradle.kts')
|
|
536
|
-
: path.join(dir, 'build.gradle');
|
|
537
|
-
const content = fs.readFileSync(gradleFile, 'utf-8');
|
|
538
|
-
if (content.includes('com.android'))
|
|
539
|
-
detected.push({ type: 'kotlin-android', path: prefix });
|
|
540
|
-
else if (content.includes('kotlin'))
|
|
541
|
-
detected.push({ type: 'kotlin', path: prefix });
|
|
542
|
-
else if (content.includes('spring'))
|
|
543
|
-
detected.push({ type: 'java-spring', path: prefix });
|
|
544
|
-
else
|
|
545
|
-
detected.push({ type: 'java', path: prefix });
|
|
546
|
-
if (content.includes('postgresql'))
|
|
547
|
-
details.databases.push('PostgreSQL');
|
|
548
|
-
if (content.includes('mysql'))
|
|
549
|
-
details.databases.push('MySQL');
|
|
550
|
-
if (content.includes('jpa') || content.includes('hibernate'))
|
|
551
|
-
details.databases.push('JPA/Hibernate');
|
|
552
|
-
}
|
|
553
|
-
catch (e) { }
|
|
554
|
-
}
|
|
555
|
-
else if (fs.existsSync(path.join(dir, 'pom.xml'))) {
|
|
556
|
-
try {
|
|
557
|
-
const content = fs.readFileSync(path.join(dir, 'pom.xml'), 'utf-8');
|
|
558
|
-
if (content.includes('spring'))
|
|
559
|
-
detected.push({ type: 'java-spring', path: prefix });
|
|
560
|
-
else
|
|
561
|
-
detected.push({ type: 'java', path: prefix });
|
|
562
|
-
if (content.includes('postgresql'))
|
|
563
|
-
details.databases.push('PostgreSQL');
|
|
564
|
-
if (content.includes('mysql'))
|
|
565
|
-
details.databases.push('MySQL');
|
|
566
|
-
}
|
|
567
|
-
catch (e) { }
|
|
568
|
-
}
|
|
569
|
-
// Swift / iOS
|
|
570
|
-
if (fs.existsSync(path.join(dir, 'Package.swift')) ||
|
|
571
|
-
fs.readdirSync(dir).some(f => f.endsWith('.xcodeproj') || f.endsWith('.xcworkspace'))) {
|
|
572
|
-
detected.push({ type: 'swift-ios', path: prefix });
|
|
573
|
-
}
|
|
574
|
-
return detected;
|
|
575
|
-
};
|
|
576
|
-
// CI/CD 감지
|
|
577
|
-
if (fs.existsSync(path.join(projectRoot, '.github', 'workflows'))) {
|
|
578
|
-
details.cicd.push('GitHub Actions');
|
|
579
|
-
}
|
|
580
|
-
if (fs.existsSync(path.join(projectRoot, '.gitlab-ci.yml'))) {
|
|
581
|
-
details.cicd.push('GitLab CI');
|
|
582
|
-
}
|
|
583
|
-
if (fs.existsSync(path.join(projectRoot, 'Jenkinsfile'))) {
|
|
584
|
-
details.cicd.push('Jenkins');
|
|
585
|
-
}
|
|
586
|
-
if (fs.existsSync(path.join(projectRoot, '.circleci'))) {
|
|
587
|
-
details.cicd.push('CircleCI');
|
|
588
|
-
}
|
|
589
|
-
// Hosting 감지
|
|
590
|
-
if (fs.existsSync(path.join(projectRoot, 'vercel.json')) ||
|
|
591
|
-
fs.existsSync(path.join(projectRoot, '.vercel'))) {
|
|
592
|
-
details.hosting.push('Vercel');
|
|
593
|
-
}
|
|
594
|
-
if (fs.existsSync(path.join(projectRoot, 'netlify.toml'))) {
|
|
595
|
-
details.hosting.push('Netlify');
|
|
596
|
-
}
|
|
597
|
-
if (fs.existsSync(path.join(projectRoot, 'app.yaml')) ||
|
|
598
|
-
fs.existsSync(path.join(projectRoot, 'cloudbuild.yaml'))) {
|
|
599
|
-
details.hosting.push('Google Cloud');
|
|
600
|
-
}
|
|
601
|
-
if (fs.existsSync(path.join(projectRoot, 'Dockerfile')) ||
|
|
602
|
-
fs.existsSync(path.join(projectRoot, 'docker-compose.yml'))) {
|
|
603
|
-
details.hosting.push('Docker');
|
|
604
|
-
}
|
|
605
|
-
if (fs.existsSync(path.join(projectRoot, 'fly.toml'))) {
|
|
606
|
-
details.hosting.push('Fly.io');
|
|
607
|
-
}
|
|
608
|
-
if (fs.existsSync(path.join(projectRoot, 'railway.json'))) {
|
|
609
|
-
details.hosting.push('Railway');
|
|
610
|
-
}
|
|
611
|
-
// 루트 디렉토리 검사
|
|
612
|
-
stacks.push(...detectInDir(projectRoot));
|
|
613
|
-
// 1레벨 하위 폴더 검사
|
|
614
|
-
const subDirs = ['backend', 'frontend', 'server', 'client', 'api', 'web', 'mobile', 'app', 'packages', 'apps'];
|
|
615
|
-
for (const subDir of subDirs) {
|
|
616
|
-
const subPath = path.join(projectRoot, subDir);
|
|
617
|
-
if (fs.existsSync(subPath) && fs.statSync(subPath).isDirectory()) {
|
|
618
|
-
stacks.push(...detectInDir(subPath, subDir));
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
// packages/* 또는 apps/* 내부 검사 (monorepo)
|
|
622
|
-
for (const monoDir of ['packages', 'apps']) {
|
|
623
|
-
const monoPath = path.join(projectRoot, monoDir);
|
|
624
|
-
if (fs.existsSync(monoPath) && fs.statSync(monoPath).isDirectory()) {
|
|
625
|
-
const subPackages = fs.readdirSync(monoPath).filter(f => {
|
|
626
|
-
const fullPath = path.join(monoPath, f);
|
|
627
|
-
return fs.statSync(fullPath).isDirectory() && !f.startsWith('.');
|
|
628
|
-
});
|
|
629
|
-
for (const pkg of subPackages) {
|
|
630
|
-
stacks.push(...detectInDir(path.join(monoPath, pkg), `${monoDir}/${pkg}`));
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
// 중복 제거
|
|
635
|
-
details.databases = [...new Set(details.databases)];
|
|
636
|
-
details.stateManagement = [...new Set(details.stateManagement)];
|
|
637
|
-
details.hosting = [...new Set(details.hosting)];
|
|
638
|
-
details.cicd = [...new Set(details.cicd)];
|
|
639
|
-
return { stacks, details };
|
|
640
|
-
}
|
|
641
|
-
// ============================================================================
|
|
642
|
-
// Collaborator Setup
|
|
643
|
-
// ============================================================================
|
|
644
|
-
function setupCollaboratorAutoInstall(projectRoot) {
|
|
645
|
-
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
646
|
-
const vibeDir = path.join(projectRoot, '.claude', 'vibe');
|
|
647
|
-
const vibeVersion = getPackageJson().version;
|
|
648
|
-
// 1. Node.js 프로젝트: package.json 정리
|
|
649
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
650
|
-
try {
|
|
651
|
-
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
652
|
-
let modified = false;
|
|
653
|
-
// 기존 devDependencies에서 @su-record/vibe 제거
|
|
654
|
-
if (pkg.devDependencies?.['@su-record/vibe']) {
|
|
655
|
-
delete pkg.devDependencies['@su-record/vibe'];
|
|
656
|
-
modified = true;
|
|
657
|
-
}
|
|
658
|
-
// 기존 postinstall/prepare에서 vibe update 제거
|
|
659
|
-
if (pkg.scripts) {
|
|
660
|
-
const oldPatterns = [
|
|
661
|
-
/\s*&&\s*npx @su-record\/vibe update[^&|;]*/g,
|
|
662
|
-
/npx @su-record\/vibe update[^&|;]*\s*&&\s*/g,
|
|
663
|
-
/npx @su-record\/vibe update[^&|;]*/g,
|
|
664
|
-
/\s*&&\s*node_modules\/\.bin\/vibe update[^&|;]*/g,
|
|
665
|
-
/node_modules\/\.bin\/vibe update[^&|;]*\s*&&\s*/g,
|
|
666
|
-
/node_modules\/\.bin\/vibe update[^&|;]*/g
|
|
667
|
-
];
|
|
668
|
-
['postinstall', 'prepare'].forEach(script => {
|
|
669
|
-
if (pkg.scripts[script]?.includes('vibe update')) {
|
|
670
|
-
let cleaned = pkg.scripts[script];
|
|
671
|
-
oldPatterns.forEach(p => { cleaned = cleaned.replace(p, ''); });
|
|
672
|
-
cleaned = cleaned.trim();
|
|
673
|
-
if (cleaned) {
|
|
674
|
-
pkg.scripts[script] = cleaned;
|
|
675
|
-
}
|
|
676
|
-
else {
|
|
677
|
-
delete pkg.scripts[script];
|
|
678
|
-
}
|
|
679
|
-
modified = true;
|
|
680
|
-
}
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
if (modified) {
|
|
684
|
-
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
685
|
-
log(' ✅ package.json 정리 완료 (레거시 vibe 설정 제거)\n');
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
catch (e) {
|
|
689
|
-
log(' ⚠️ package.json 수정 실패: ' + e.message + '\n');
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
// 2. .claude/vibe/setup.sh 생성
|
|
693
|
-
const setupShPath = path.join(vibeDir, 'setup.sh');
|
|
694
|
-
if (!fs.existsSync(vibeDir)) {
|
|
695
|
-
fs.mkdirSync(vibeDir, { recursive: true });
|
|
696
|
-
}
|
|
697
|
-
if (!fs.existsSync(setupShPath)) {
|
|
698
|
-
const setupScript = `#!/bin/bash
|
|
699
|
-
# Vibe 협업자 자동 설치 스크립트
|
|
700
|
-
# 사용법: ./.claude/vibe/setup.sh
|
|
701
|
-
|
|
702
|
-
set -e
|
|
703
|
-
|
|
704
|
-
echo "🔧 Vibe 설치 확인 중..."
|
|
705
|
-
|
|
706
|
-
# npm/npx 확인
|
|
707
|
-
if ! command -v npx &> /dev/null; then
|
|
708
|
-
echo "❌ Node.js/npm이 설치되어 있지 않습니다."
|
|
709
|
-
echo " https://nodejs.org 에서 설치해주세요."
|
|
710
|
-
exit 1
|
|
711
|
-
fi
|
|
712
|
-
|
|
713
|
-
# vibe 설치 확인 및 업데이트
|
|
714
|
-
if command -v vibe &> /dev/null; then
|
|
715
|
-
echo "✅ Vibe가 이미 설치되어 있습니다."
|
|
716
|
-
vibe update --silent
|
|
717
|
-
echo "✅ Vibe 업데이트 완료!"
|
|
718
|
-
else
|
|
719
|
-
echo "📦 Vibe 설치 중..."
|
|
720
|
-
npm install -g @su-record/vibe
|
|
721
|
-
vibe update --silent
|
|
722
|
-
echo "✅ Vibe 설치 및 설정 완료!"
|
|
723
|
-
fi
|
|
724
|
-
|
|
725
|
-
echo ""
|
|
726
|
-
echo "다음 명령어로 시작하세요:"
|
|
727
|
-
echo " /vibe.spec \\"기능명\\" SPEC 작성"
|
|
728
|
-
echo " /vibe.run \\"기능명\\" 구현 실행"
|
|
729
|
-
`;
|
|
730
|
-
fs.writeFileSync(setupShPath, setupScript);
|
|
731
|
-
fs.chmodSync(setupShPath, '755');
|
|
732
|
-
log(' ✅ 협업자 설치 스크립트 생성 (.claude/vibe/setup.sh)\n');
|
|
733
|
-
}
|
|
734
|
-
// 3. README.md에 협업자 안내 추가
|
|
735
|
-
const readmePath = path.join(projectRoot, 'README.md');
|
|
736
|
-
const vibeSetupSection = `
|
|
737
|
-
## Vibe Setup (AI Coding)
|
|
738
|
-
|
|
739
|
-
이 프로젝트는 [Vibe](https://github.com/su-record/vibe) AI 코딩 프레임워크를 사용합니다.
|
|
740
|
-
|
|
741
|
-
### 협업자 설치
|
|
742
|
-
|
|
743
|
-
\`\`\`bash
|
|
744
|
-
# 전역 설치 (권장)
|
|
745
|
-
npm install -g @su-record/vibe
|
|
746
|
-
vibe update
|
|
747
|
-
|
|
748
|
-
# 또는 setup 스크립트 실행
|
|
749
|
-
./.claude/vibe/setup.sh
|
|
750
|
-
\`\`\`
|
|
751
|
-
|
|
752
|
-
### 사용법
|
|
753
|
-
|
|
754
|
-
Claude Code에서 슬래시 커맨드 사용:
|
|
755
|
-
- \`/vibe.spec "기능명"\` - SPEC 문서 작성
|
|
756
|
-
- \`/vibe.run "기능명"\` - 구현 실행
|
|
757
|
-
`;
|
|
758
|
-
if (fs.existsSync(readmePath)) {
|
|
759
|
-
const readme = fs.readFileSync(readmePath, 'utf-8');
|
|
760
|
-
if (!readme.includes('## Vibe Setup')) {
|
|
761
|
-
fs.appendFileSync(readmePath, vibeSetupSection);
|
|
762
|
-
log(' ✅ README.md에 협업자 안내 추가\n');
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
else {
|
|
766
|
-
fs.writeFileSync(readmePath, `# Project\n${vibeSetupSection}`);
|
|
767
|
-
log(' ✅ README.md 생성 (협업자 안내 포함)\n');
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
// ============================================================================
|
|
771
|
-
// Stack Name Mapping
|
|
772
|
-
// ============================================================================
|
|
773
|
-
const STACK_NAMES = {
|
|
774
|
-
'python-fastapi': { lang: 'Python 3.11+', framework: 'FastAPI' },
|
|
775
|
-
'python-django': { lang: 'Python 3.11+', framework: 'Django' },
|
|
776
|
-
'python': { lang: 'Python 3.11+', framework: '-' },
|
|
777
|
-
'typescript-node': { lang: 'TypeScript/Node.js', framework: 'Express/Fastify' },
|
|
778
|
-
'typescript-nextjs': { lang: 'TypeScript', framework: 'Next.js' },
|
|
779
|
-
'typescript-react': { lang: 'TypeScript', framework: 'React' },
|
|
780
|
-
'typescript-vue': { lang: 'TypeScript', framework: 'Vue.js' },
|
|
781
|
-
'typescript-nuxt': { lang: 'TypeScript', framework: 'Nuxt 3' },
|
|
782
|
-
'typescript-react-native': { lang: 'TypeScript', framework: 'React Native' },
|
|
783
|
-
'dart-flutter': { lang: 'Dart', framework: 'Flutter' },
|
|
784
|
-
'go': { lang: 'Go', framework: '-' },
|
|
785
|
-
'rust': { lang: 'Rust', framework: '-' },
|
|
786
|
-
'java-spring': { lang: 'Java 17+', framework: 'Spring Boot' },
|
|
787
|
-
'kotlin-android': { lang: 'Kotlin', framework: 'Android' },
|
|
788
|
-
'swift-ios': { lang: 'Swift', framework: 'iOS/SwiftUI' }
|
|
789
|
-
};
|
|
790
|
-
// 언어별 CLAUDE.md 규칙
|
|
791
|
-
const LANGUAGE_RULES = {
|
|
792
|
-
typescript: `### TypeScript 규칙
|
|
793
|
-
- \`any\` 타입 사용 금지 → \`unknown\` + 타입 가드 사용
|
|
794
|
-
- \`as any\` 캐스팅 금지 → 적절한 인터페이스 정의
|
|
795
|
-
- \`@ts-ignore\` 금지 → 타입 문제 근본 해결
|
|
796
|
-
- 모든 함수에 반환 타입 명시`,
|
|
797
|
-
python: `### Python 규칙
|
|
798
|
-
- 타입 힌트 필수 (함수 매개변수, 반환값)
|
|
799
|
-
- \`# type: ignore\` 금지 → 타입 문제 근본 해결
|
|
800
|
-
- f-string 사용 권장 (format() 대신)
|
|
801
|
-
- 리스트 컴프리헨션 적절히 활용`,
|
|
802
|
-
go: `### Go 규칙
|
|
803
|
-
- 에러 반환 즉시 처리 (if err != nil)
|
|
804
|
-
- 명시적 에러 래핑 (fmt.Errorf with %w)
|
|
805
|
-
- 인터페이스는 사용처에서 정의
|
|
806
|
-
- 고루틴 누수 방지 (context 활용)`,
|
|
807
|
-
rust: `### Rust 규칙
|
|
808
|
-
- unwrap()/expect() 프로덕션 코드 금지 → Result/Option 처리
|
|
809
|
-
- unsafe 블록 최소화
|
|
810
|
-
- 명시적 에러 타입 정의
|
|
811
|
-
- 소유권/수명 주석 명확히`,
|
|
812
|
-
java: `### Java 규칙
|
|
813
|
-
- Optional 적극 활용 (null 대신)
|
|
814
|
-
- 불변 객체 선호 (final 필드)
|
|
815
|
-
- 체크 예외 적절히 처리
|
|
816
|
-
- 스트림 API 활용`,
|
|
817
|
-
kotlin: `### Kotlin 규칙
|
|
818
|
-
- nullable 타입 명시 (?)
|
|
819
|
-
- !! 연산자 금지 → safe call (?.) 사용
|
|
820
|
-
- data class 적극 활용
|
|
821
|
-
- 확장 함수로 유틸리티 구현`,
|
|
822
|
-
dart: `### Dart 규칙
|
|
823
|
-
- null safety 준수 (? 및 ! 적절히 사용)
|
|
824
|
-
- late 키워드 남용 금지
|
|
825
|
-
- const 생성자 활용
|
|
826
|
-
- 비동기 코드에 async/await 사용`,
|
|
827
|
-
swift: `### Swift 규칙
|
|
828
|
-
- 옵셔널 강제 언래핑 금지 → guard let / if let 사용
|
|
829
|
-
- 프로토콜 지향 프로그래밍 권장
|
|
830
|
-
- 값 타입(struct) 우선 사용
|
|
831
|
-
- @escaping 클로저 메모리 관리 주의`
|
|
832
|
-
};
|
|
833
|
-
function getLanguageRulesForStacks(stacks) {
|
|
834
|
-
const addedRules = new Set();
|
|
835
|
-
const rules = [];
|
|
836
|
-
for (const stack of stacks) {
|
|
837
|
-
let langKey = '';
|
|
838
|
-
if (stack.type.startsWith('typescript'))
|
|
839
|
-
langKey = 'typescript';
|
|
840
|
-
else if (stack.type.startsWith('python'))
|
|
841
|
-
langKey = 'python';
|
|
842
|
-
else if (stack.type === 'go')
|
|
843
|
-
langKey = 'go';
|
|
844
|
-
else if (stack.type === 'rust')
|
|
845
|
-
langKey = 'rust';
|
|
846
|
-
else if (stack.type.startsWith('java'))
|
|
847
|
-
langKey = 'java';
|
|
848
|
-
else if (stack.type.startsWith('kotlin'))
|
|
849
|
-
langKey = 'kotlin';
|
|
850
|
-
else if (stack.type.startsWith('dart'))
|
|
851
|
-
langKey = 'dart';
|
|
852
|
-
else if (stack.type.startsWith('swift'))
|
|
853
|
-
langKey = 'swift';
|
|
854
|
-
if (langKey && !addedRules.has(langKey) && LANGUAGE_RULES[langKey]) {
|
|
855
|
-
addedRules.add(langKey);
|
|
856
|
-
rules.push(LANGUAGE_RULES[langKey]);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
return rules.join('\n\n');
|
|
860
|
-
}
|
|
30
|
+
// Silent 모드 설정
|
|
31
|
+
setSilentMode(options.silent);
|
|
861
32
|
// ============================================================================
|
|
862
33
|
// Main Commands
|
|
863
34
|
// ============================================================================
|
|
@@ -875,7 +46,8 @@ async function init(projectName) {
|
|
|
875
46
|
fs.mkdirSync(projectRoot, { recursive: true });
|
|
876
47
|
isNewProject = true;
|
|
877
48
|
}
|
|
878
|
-
const
|
|
49
|
+
const claudeDir = path.join(projectRoot, '.claude');
|
|
50
|
+
const vibeDir = path.join(claudeDir, 'vibe');
|
|
879
51
|
if (fs.existsSync(vibeDir)) {
|
|
880
52
|
log('❌ .claude/vibe/ 폴더가 이미 존재합니다.');
|
|
881
53
|
return;
|
|
@@ -883,105 +55,17 @@ async function init(projectName) {
|
|
|
883
55
|
ensureDir(vibeDir);
|
|
884
56
|
// MCP 서버 등록
|
|
885
57
|
log('🔧 Claude Code MCP 서버 등록 중 (전역)...\n');
|
|
886
|
-
|
|
887
|
-
const gptMcpPath = path.join(__dirname, '../lib/gpt-mcp.js');
|
|
888
|
-
// 0. 기존 hi-ai/vibe MCP 제거 (마이그레이션 - 내장 도구로 전환)
|
|
889
|
-
unregisterMcp('vibe');
|
|
890
|
-
// 1. vibe-gemini MCP
|
|
891
|
-
if (fs.existsSync(geminiMcpPath)) {
|
|
892
|
-
try {
|
|
893
|
-
registerMcp('vibe-gemini', { command: 'node', args: [geminiMcpPath] });
|
|
894
|
-
log(' ✅ vibe-gemini MCP 등록 완료 (전역)\n');
|
|
895
|
-
}
|
|
896
|
-
catch (e) {
|
|
897
|
-
if (e.message?.includes('already exists')) {
|
|
898
|
-
log(' ℹ️ vibe-gemini MCP 이미 등록됨\n');
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
// 3. vibe-gpt MCP
|
|
903
|
-
if (fs.existsSync(gptMcpPath)) {
|
|
904
|
-
try {
|
|
905
|
-
registerMcp('vibe-gpt', { command: 'node', args: [gptMcpPath] });
|
|
906
|
-
log(' ✅ vibe-gpt MCP 등록 완료 (전역)\n');
|
|
907
|
-
}
|
|
908
|
-
catch (e) {
|
|
909
|
-
if (e.message?.includes('already exists')) {
|
|
910
|
-
log(' ℹ️ vibe-gpt MCP 이미 등록됨\n');
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
// 4. Context7 MCP
|
|
915
|
-
try {
|
|
916
|
-
registerMcp('context7', { command: 'npx', args: ['-y', '@upstash/context7-mcp@latest'] });
|
|
917
|
-
log(' ✅ Context7 MCP 등록 완료 (라이브러리 문서 검색)\n');
|
|
918
|
-
}
|
|
919
|
-
catch (e) {
|
|
920
|
-
if (e.message?.includes('already exists')) {
|
|
921
|
-
log(' ℹ️ Context7 MCP 이미 등록됨\n');
|
|
922
|
-
}
|
|
923
|
-
else {
|
|
924
|
-
log(' ⚠️ Context7 MCP 수동 등록 필요\n');
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
// Agent SDK 설치 (오케스트레이터용)
|
|
928
|
-
log(' 🤖 Agent SDK 설치 중 (오케스트레이터용)...\n');
|
|
929
|
-
try {
|
|
930
|
-
const { execSync } = await import('child_process');
|
|
931
|
-
execSync('npm install @anthropic-ai/claude-agent-sdk --save-dev', {
|
|
932
|
-
cwd: projectRoot,
|
|
933
|
-
stdio: 'pipe'
|
|
934
|
-
});
|
|
935
|
-
log(' ✅ Agent SDK 설치 완료\n');
|
|
936
|
-
}
|
|
937
|
-
catch (e) {
|
|
938
|
-
log(' ⚠️ Agent SDK 설치 실패 - 수동 설치: npm i -D @anthropic-ai/claude-agent-sdk\n');
|
|
939
|
-
}
|
|
58
|
+
registerMcpServers(false);
|
|
940
59
|
// .claude/vibe 폴더 구조 생성
|
|
941
60
|
['specs', 'features'].forEach(dir => {
|
|
942
61
|
ensureDir(path.join(vibeDir, dir));
|
|
943
62
|
});
|
|
944
|
-
//
|
|
945
|
-
|
|
946
|
-
if (fs.existsSync(legacyVibeDir)) {
|
|
947
|
-
log(' 🔄 레거시 .vibe/ 폴더 마이그레이션 중...\n');
|
|
948
|
-
try {
|
|
949
|
-
// specs, features 등 데이터 폴더 이동
|
|
950
|
-
['specs', 'features', 'solutions', 'todos', 'memory'].forEach(dir => {
|
|
951
|
-
const legacySrc = path.join(legacyVibeDir, dir);
|
|
952
|
-
const newDest = path.join(vibeDir, dir);
|
|
953
|
-
if (fs.existsSync(legacySrc) && !fs.existsSync(newDest)) {
|
|
954
|
-
copyDirRecursive(legacySrc, newDest);
|
|
955
|
-
}
|
|
956
|
-
});
|
|
957
|
-
removeDirRecursive(legacyVibeDir);
|
|
958
|
-
log(' ✅ .vibe/ → .claude/vibe/ 마이그레이션 완료\n');
|
|
959
|
-
}
|
|
960
|
-
catch (e) {
|
|
961
|
-
log(' ⚠️ 마이그레이션 실패 - .vibe/ 폴더 수동 삭제 필요\n');
|
|
962
|
-
}
|
|
963
|
-
}
|
|
63
|
+
// 레거시 마이그레이션
|
|
64
|
+
migrateLegacyVibe(projectRoot, vibeDir);
|
|
964
65
|
// .gitignore 업데이트
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
let gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
969
|
-
if (!gitignore.includes(mcpIgnore)) {
|
|
970
|
-
gitignore += `\n# vibe MCP\n${mcpIgnore}\n`;
|
|
971
|
-
fs.writeFileSync(gitignorePath, gitignore);
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
else {
|
|
975
|
-
fs.writeFileSync(gitignorePath, `# vibe MCP\n${mcpIgnore}\n`);
|
|
976
|
-
}
|
|
977
|
-
// .claude/commands 복사
|
|
978
|
-
const claudeDir = path.join(projectRoot, '.claude');
|
|
979
|
-
const commandsDir = path.join(claudeDir, 'commands');
|
|
980
|
-
ensureDir(claudeDir);
|
|
981
|
-
ensureDir(commandsDir);
|
|
982
|
-
const sourceDir = path.join(__dirname, '../../commands');
|
|
983
|
-
copyDirRecursive(sourceDir, commandsDir);
|
|
984
|
-
log(' ✅ 슬래시 커맨드 설치 완료 (7개)\n');
|
|
66
|
+
updateGitignore(projectRoot);
|
|
67
|
+
// 전역 assets 설치
|
|
68
|
+
installGlobalAssets(false);
|
|
985
69
|
// 기술 스택 감지
|
|
986
70
|
const { stacks: detectedStacks, details: stackDetails } = detectTechStacks(projectRoot);
|
|
987
71
|
if (detectedStacks.length > 0) {
|
|
@@ -997,171 +81,59 @@ async function init(projectName) {
|
|
|
997
81
|
}
|
|
998
82
|
}
|
|
999
83
|
// constitution.md 생성
|
|
1000
|
-
|
|
1001
|
-
const constitutionPath = path.join(vibeDir, 'constitution.md');
|
|
1002
|
-
if (fs.existsSync(templatePath)) {
|
|
1003
|
-
let constitution = fs.readFileSync(templatePath, 'utf-8');
|
|
1004
|
-
const backendStack = detectedStacks.find(s => s.type.includes('python') || s.type.includes('node') ||
|
|
1005
|
-
s.type.includes('go') || s.type.includes('java') || s.type.includes('rust'));
|
|
1006
|
-
const frontendStack = detectedStacks.find(s => s.type.includes('react') || s.type.includes('vue') ||
|
|
1007
|
-
s.type.includes('flutter') || s.type.includes('swift') || s.type.includes('android'));
|
|
1008
|
-
if (backendStack && STACK_NAMES[backendStack.type]) {
|
|
1009
|
-
const info = STACK_NAMES[backendStack.type];
|
|
1010
|
-
constitution = constitution.replace('- Language: {Python 3.11+ / Node.js / etc.}', `- Language: ${info.lang}`);
|
|
1011
|
-
constitution = constitution.replace('- Framework: {FastAPI / Express / etc.}', `- Framework: ${info.framework}`);
|
|
1012
|
-
}
|
|
1013
|
-
if (frontendStack && STACK_NAMES[frontendStack.type]) {
|
|
1014
|
-
const info = STACK_NAMES[frontendStack.type];
|
|
1015
|
-
constitution = constitution.replace('- Framework: {Flutter / React / etc.}', `- Framework: ${info.framework}`);
|
|
1016
|
-
}
|
|
1017
|
-
constitution = constitution.replace('- Database: {PostgreSQL / MongoDB / etc.}', stackDetails.databases.length > 0 ? `- Database: ${stackDetails.databases.join(', ')}` : '- Database: (프로젝트에 맞게 설정)');
|
|
1018
|
-
constitution = constitution.replace('- State Management: {Provider / Redux / etc.}', stackDetails.stateManagement.length > 0 ? `- State Management: ${stackDetails.stateManagement.join(', ')}` : '- State Management: (프로젝트에 맞게 설정)');
|
|
1019
|
-
constitution = constitution.replace('- Hosting: {Cloud Run / Vercel / etc.}', stackDetails.hosting.length > 0 ? `- Hosting: ${stackDetails.hosting.join(', ')}` : '- Hosting: (프로젝트에 맞게 설정)');
|
|
1020
|
-
constitution = constitution.replace('- CI/CD: {GitHub Actions / etc.}', stackDetails.cicd.length > 0 ? `- CI/CD: ${stackDetails.cicd.join(', ')}` : '- CI/CD: (프로젝트에 맞게 설정)');
|
|
1021
|
-
fs.writeFileSync(constitutionPath, constitution);
|
|
1022
|
-
}
|
|
84
|
+
updateConstitution(vibeDir, detectedStacks, stackDetails);
|
|
1023
85
|
// config.json 생성
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
};
|
|
1030
|
-
fs.writeFileSync(path.join(vibeDir, 'config.json'), JSON.stringify(config, null, 2));
|
|
1031
|
-
// CLAUDE.md 병합 (언어별 규칙 동적 추가)
|
|
1032
|
-
const vibeClaudeMd = path.join(__dirname, '../../CLAUDE.md');
|
|
1033
|
-
const projectClaudeMd = path.join(projectRoot, 'CLAUDE.md');
|
|
1034
|
-
let vibeContent = fs.readFileSync(vibeClaudeMd, 'utf-8');
|
|
1035
|
-
// 감지된 기술 스택에 따라 언어별 규칙 추가
|
|
1036
|
-
const languageRules = getLanguageRulesForStacks(detectedStacks);
|
|
1037
|
-
if (languageRules) {
|
|
1038
|
-
// "### 에러 처리 필수" 앞에 언어별 규칙 삽입
|
|
1039
|
-
vibeContent = vibeContent.replace('### 에러 처리 필수', languageRules + '\n\n### 에러 처리 필수');
|
|
1040
|
-
}
|
|
1041
|
-
if (fs.existsSync(projectClaudeMd)) {
|
|
1042
|
-
const existingContent = fs.readFileSync(projectClaudeMd, 'utf-8');
|
|
1043
|
-
if (!existingContent.includes('/vibe.spec')) {
|
|
1044
|
-
const mergedContent = existingContent.trim() + '\n\n---\n\n' + vibeContent;
|
|
1045
|
-
fs.writeFileSync(projectClaudeMd, mergedContent);
|
|
1046
|
-
log(' ✅ CLAUDE.md에 vibe 섹션 추가\n');
|
|
1047
|
-
}
|
|
1048
|
-
else {
|
|
1049
|
-
log(' ℹ️ CLAUDE.md에 vibe 섹션 이미 존재\n');
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
else {
|
|
1053
|
-
fs.writeFileSync(projectClaudeMd, vibeContent);
|
|
1054
|
-
log(' ✅ CLAUDE.md 생성\n');
|
|
1055
|
-
}
|
|
1056
|
-
// .claude/vibe/rules/ 복사
|
|
1057
|
-
const rulesSource = path.join(__dirname, '../../.claude/vibe/rules');
|
|
1058
|
-
const rulesTarget = path.join(vibeDir, 'rules');
|
|
1059
|
-
const coreDirs = ['core', 'quality', 'standards', 'tools'];
|
|
1060
|
-
coreDirs.forEach(dir => {
|
|
1061
|
-
const src = path.join(rulesSource, dir);
|
|
1062
|
-
const dst = path.join(rulesTarget, dir);
|
|
1063
|
-
if (fs.existsSync(src)) {
|
|
1064
|
-
copyDirRecursive(src, dst);
|
|
1065
|
-
}
|
|
1066
|
-
});
|
|
1067
|
-
const langSource = path.join(rulesSource, 'languages');
|
|
1068
|
-
const langTarget = path.join(rulesTarget, 'languages');
|
|
1069
|
-
ensureDir(langTarget);
|
|
1070
|
-
// 감지된 스택 타입을 그대로 파일명으로 매칭 (typescript-nextjs -> typescript-nextjs.md)
|
|
1071
|
-
const detectedTypes = new Set(detectedStacks.map(s => s.type));
|
|
1072
|
-
if (fs.existsSync(langSource)) {
|
|
1073
|
-
const langFiles = fs.readdirSync(langSource);
|
|
1074
|
-
langFiles.forEach(file => {
|
|
1075
|
-
const langType = file.replace('.md', '');
|
|
1076
|
-
if (detectedTypes.has(langType)) {
|
|
1077
|
-
fs.copyFileSync(path.join(langSource, file), path.join(langTarget, file));
|
|
1078
|
-
}
|
|
1079
|
-
});
|
|
1080
|
-
}
|
|
1081
|
-
log(' ✅ 코딩 규칙 설치 완료 (.claude/vibe/rules/)\n');
|
|
1082
|
-
// .claude/agents/ 복사
|
|
1083
|
-
const agentsDir = path.join(claudeDir, 'agents');
|
|
1084
|
-
ensureDir(agentsDir);
|
|
1085
|
-
const agentsSourceDir = path.join(__dirname, '../../agents');
|
|
1086
|
-
copyDirRecursive(agentsSourceDir, agentsDir);
|
|
1087
|
-
log(' ✅ 서브에이전트 설치 완료 (.claude/agents/)\n');
|
|
1088
|
-
// .claude/skills/ 복사
|
|
1089
|
-
const skillsDir = path.join(claudeDir, 'skills');
|
|
1090
|
-
ensureDir(skillsDir);
|
|
1091
|
-
const skillsSourceDir = path.join(__dirname, '../../skills');
|
|
1092
|
-
if (fs.existsSync(skillsSourceDir)) {
|
|
1093
|
-
copyDirRecursive(skillsSourceDir, skillsDir);
|
|
1094
|
-
log(' ✅ 스킬 설치 완료 (.claude/skills/)\n');
|
|
1095
|
-
}
|
|
1096
|
-
// .claude/settings.json 설정
|
|
1097
|
-
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
1098
|
-
const hooksTemplate = path.join(__dirname, '../../hooks/hooks.json');
|
|
1099
|
-
if (fs.existsSync(hooksTemplate)) {
|
|
1100
|
-
const vibeHooks = JSON.parse(fs.readFileSync(hooksTemplate, 'utf-8'));
|
|
1101
|
-
if (fs.existsSync(settingsPath)) {
|
|
1102
|
-
const existingSettings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
1103
|
-
existingSettings.hooks = vibeHooks.hooks;
|
|
1104
|
-
fs.writeFileSync(settingsPath, JSON.stringify(existingSettings, null, 2));
|
|
1105
|
-
log(' ✅ Hooks 설정 업데이트 완료\n');
|
|
1106
|
-
}
|
|
1107
|
-
else {
|
|
1108
|
-
fs.copyFileSync(hooksTemplate, settingsPath);
|
|
1109
|
-
log(' ✅ Hooks 설정 설치 완료\n');
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
// .gitignore에서 settings.local.json 제거
|
|
1113
|
-
if (fs.existsSync(gitignorePath)) {
|
|
1114
|
-
let gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
1115
|
-
if (gitignore.includes('settings.local.json')) {
|
|
1116
|
-
gitignore = gitignore.replace(/\.claude\/settings\.local\.json\n?/g, '');
|
|
1117
|
-
gitignore = gitignore.replace(/settings\.local\.json\n?/g, '');
|
|
1118
|
-
fs.writeFileSync(gitignorePath, gitignore);
|
|
1119
|
-
log(' ✅ .gitignore에서 settings.local.json 제거\n');
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
86
|
+
updateConfig(vibeDir, detectedStacks, stackDetails, false);
|
|
87
|
+
// CLAUDE.md 병합
|
|
88
|
+
updateClaudeMd(projectRoot, detectedStacks, false);
|
|
89
|
+
// 규칙 복사
|
|
90
|
+
updateRules(vibeDir, detectedStacks, false);
|
|
1122
91
|
// 협업자 자동 설치 설정
|
|
1123
92
|
setupCollaboratorAutoInstall(projectRoot);
|
|
1124
93
|
// 완료 메시지
|
|
1125
|
-
log(`
|
|
1126
|
-
✅ vibe 초기화 완료!
|
|
1127
|
-
|
|
1128
|
-
${isNewProject ? `프로젝트 위치:
|
|
1129
|
-
${projectRoot}/
|
|
1130
|
-
|
|
1131
|
-
` : ''}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
├──
|
|
1135
|
-
├──
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
/vibe.
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
94
|
+
log(`
|
|
95
|
+
✅ vibe 초기화 완료!
|
|
96
|
+
|
|
97
|
+
${isNewProject ? `프로젝트 위치:
|
|
98
|
+
${projectRoot}/
|
|
99
|
+
|
|
100
|
+
` : ''}전역 설치 (~/.claude/):
|
|
101
|
+
~/.claude/
|
|
102
|
+
├── commands/ # 슬래시 커맨드 (7개)
|
|
103
|
+
├── agents/ # 서브에이전트
|
|
104
|
+
├── skills/ # 스킬 (7개)
|
|
105
|
+
└── settings.json # Hooks + MCP 설정
|
|
106
|
+
|
|
107
|
+
프로젝트 설정 (.claude/vibe/):
|
|
108
|
+
CLAUDE.md # 프로젝트 컨텍스트
|
|
109
|
+
.claude/vibe/
|
|
110
|
+
├── config.json # 프로젝트 설정
|
|
111
|
+
├── constitution.md # 프로젝트 원칙
|
|
112
|
+
├── setup.sh # 협업자 설치 스크립트
|
|
113
|
+
├── rules/ # 코딩 규칙
|
|
114
|
+
│ ├── core/ # 핵심 원칙
|
|
115
|
+
│ ├── quality/ # 품질 체크리스트
|
|
116
|
+
│ └── languages/ # 언어별 규칙
|
|
117
|
+
├── specs/ # SPEC 문서들
|
|
118
|
+
└── features/ # BDD Feature 파일들
|
|
119
|
+
|
|
120
|
+
내장 도구: ✓ (35+)
|
|
121
|
+
협업자 자동 설치: ✓
|
|
122
|
+
|
|
123
|
+
${formatLLMStatus()}
|
|
124
|
+
|
|
125
|
+
사용법:
|
|
126
|
+
/vibe.spec "기능명" SPEC 작성 (대화형)
|
|
127
|
+
/vibe.run "기능명" 구현 실행
|
|
128
|
+
/vibe.verify "기능명" 검증
|
|
129
|
+
|
|
130
|
+
다음 단계:
|
|
131
|
+
${isNewProject ? `cd ${projectName}\n ` : ''}/vibe.spec "기능명" 으로 시작하세요!
|
|
1161
132
|
`);
|
|
1162
133
|
}
|
|
1163
134
|
catch (error) {
|
|
1164
|
-
|
|
135
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
136
|
+
console.error('❌ 초기화 실패:', message);
|
|
1165
137
|
process.exit(1);
|
|
1166
138
|
}
|
|
1167
139
|
}
|
|
@@ -1172,7 +144,6 @@ async function checkAndUpgradeVibe() {
|
|
|
1172
144
|
encoding: 'utf-8',
|
|
1173
145
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
1174
146
|
}).trim();
|
|
1175
|
-
// 버전 비교: 실제로 새 버전인 경우에만 업그레이드
|
|
1176
147
|
const isNewer = compareVersions(latestVersion, currentVersion) > 0;
|
|
1177
148
|
if (isNewer) {
|
|
1178
149
|
log(` 📦 새 버전 발견: v${currentVersion} → v${latestVersion}\n`);
|
|
@@ -1193,7 +164,7 @@ async function checkAndUpgradeVibe() {
|
|
|
1193
164
|
return false;
|
|
1194
165
|
}
|
|
1195
166
|
}
|
|
1196
|
-
catch
|
|
167
|
+
catch { /* ignore: optional operation */
|
|
1197
168
|
log(` ℹ️ 버전 확인 스킵 (오프라인 또는 네트워크 오류)\n`);
|
|
1198
169
|
return false;
|
|
1199
170
|
}
|
|
@@ -1208,29 +179,9 @@ async function update() {
|
|
|
1208
179
|
if (process.env.NODE_ENV === 'production' || process.env.CI === 'true') {
|
|
1209
180
|
return;
|
|
1210
181
|
}
|
|
1211
|
-
// 레거시
|
|
182
|
+
// 레거시 마이그레이션
|
|
1212
183
|
if (fs.existsSync(legacyVibeDir) && !fs.existsSync(vibeDir)) {
|
|
1213
|
-
|
|
1214
|
-
ensureDir(vibeDir);
|
|
1215
|
-
['specs', 'features', 'solutions', 'todos', 'memory', 'rules', 'config.json', 'constitution.md'].forEach(item => {
|
|
1216
|
-
const src = path.join(legacyVibeDir, item);
|
|
1217
|
-
const dst = path.join(vibeDir, item);
|
|
1218
|
-
if (fs.existsSync(src) && !fs.existsSync(dst)) {
|
|
1219
|
-
if (fs.statSync(src).isDirectory()) {
|
|
1220
|
-
copyDirRecursive(src, dst);
|
|
1221
|
-
}
|
|
1222
|
-
else {
|
|
1223
|
-
fs.copyFileSync(src, dst);
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
});
|
|
1227
|
-
try {
|
|
1228
|
-
removeDirRecursive(legacyVibeDir);
|
|
1229
|
-
log(' ✅ 마이그레이션 완료\n');
|
|
1230
|
-
}
|
|
1231
|
-
catch (e) {
|
|
1232
|
-
log(' ⚠️ .vibe/ 폴더 수동 삭제 필요\n');
|
|
1233
|
-
}
|
|
184
|
+
migrateLegacyVibe(projectRoot, vibeDir);
|
|
1234
185
|
}
|
|
1235
186
|
if (!fs.existsSync(vibeDir) && !fs.existsSync(legacyVibeDir)) {
|
|
1236
187
|
if (!options.silent) {
|
|
@@ -1238,7 +189,6 @@ async function update() {
|
|
|
1238
189
|
}
|
|
1239
190
|
return;
|
|
1240
191
|
}
|
|
1241
|
-
// vibeDir이 없으면 생성
|
|
1242
192
|
ensureDir(vibeDir);
|
|
1243
193
|
log('🔄 vibe 업데이트 중...\n');
|
|
1244
194
|
// 최신 버전 확인
|
|
@@ -1247,360 +197,53 @@ async function update() {
|
|
|
1247
197
|
if (wasUpgraded)
|
|
1248
198
|
return;
|
|
1249
199
|
}
|
|
1250
|
-
//
|
|
1251
|
-
|
|
1252
|
-
const oldAgentDir = path.join(projectRoot, '.agent');
|
|
1253
|
-
if (fs.existsSync(oldRulesDir)) {
|
|
1254
|
-
log(' 🔄 마이그레이션: .agent/rules/ → .claude/vibe/rules/\n');
|
|
1255
|
-
removeDirRecursive(oldRulesDir);
|
|
1256
|
-
if (fs.existsSync(oldAgentDir) && fs.readdirSync(oldAgentDir).length === 0) {
|
|
1257
|
-
fs.rmdirSync(oldAgentDir);
|
|
1258
|
-
}
|
|
1259
|
-
log(' ✅ 기존 .agent/rules/ 폴더 정리 완료\n');
|
|
1260
|
-
}
|
|
1261
|
-
// .claude/commands 업데이트
|
|
1262
|
-
const commandsDir = path.join(claudeDir, 'commands');
|
|
1263
|
-
ensureDir(commandsDir);
|
|
1264
|
-
// 레거시 커맨드 파일 정리 (v2.2.7 이전 버전에서 제거된 커맨드들)
|
|
1265
|
-
const legacyCommands = [
|
|
1266
|
-
'vibe.analyze.md', // agents/analyzer.md로 통합 후 다시 commands로 복원
|
|
1267
|
-
'vibe.compound.md', // hooks 자동 트리거로 변경
|
|
1268
|
-
'vibe.continue.md', // SessionStart hook으로 변경
|
|
1269
|
-
'vibe.diagram.md', // vibe.utils --diagram으로 변경
|
|
1270
|
-
'vibe.e2e.md', // vibe.utils --e2e로 변경
|
|
1271
|
-
'vibe.reason.md', // agents/reasoner.md로 통합 후 다시 commands로 복원
|
|
1272
|
-
'vibe.setup.md', // 제거됨
|
|
1273
|
-
'vibe.ui.md', // vibe.utils --ui로 변경
|
|
1274
|
-
];
|
|
1275
|
-
legacyCommands.forEach(cmd => {
|
|
1276
|
-
const cmdPath = path.join(commandsDir, cmd);
|
|
1277
|
-
if (fs.existsSync(cmdPath)) {
|
|
1278
|
-
fs.unlinkSync(cmdPath);
|
|
1279
|
-
}
|
|
1280
|
-
});
|
|
1281
|
-
const sourceDir = path.join(__dirname, '../../commands');
|
|
1282
|
-
copyDirRecursive(sourceDir, commandsDir);
|
|
1283
|
-
log(' ✅ 슬래시 커맨드 업데이트 완료 (7개)\n');
|
|
1284
|
-
// 레거시 에이전트 파일 정리 (commands에 통합된 에이전트들)
|
|
1285
|
-
const agentsDir = path.join(claudeDir, 'agents');
|
|
1286
|
-
const legacyAgents = ['reviewer.md', 'analyzer.md', 'reasoner.md'];
|
|
1287
|
-
legacyAgents.forEach(agent => {
|
|
1288
|
-
const agentPath = path.join(agentsDir, agent);
|
|
1289
|
-
if (fs.existsSync(agentPath)) {
|
|
1290
|
-
fs.unlinkSync(agentPath);
|
|
1291
|
-
}
|
|
1292
|
-
});
|
|
1293
|
-
// Agent SDK 설치 확인 (오케스트레이터용)
|
|
1294
|
-
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
1295
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
1296
|
-
try {
|
|
1297
|
-
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
1298
|
-
const hasAgentSdk = pkg.dependencies?.['@anthropic-ai/claude-agent-sdk'] ||
|
|
1299
|
-
pkg.devDependencies?.['@anthropic-ai/claude-agent-sdk'];
|
|
1300
|
-
if (!hasAgentSdk) {
|
|
1301
|
-
log(' 🤖 Agent SDK 설치 중 (오케스트레이터용)...\n');
|
|
1302
|
-
try {
|
|
1303
|
-
const { execSync } = await import('child_process');
|
|
1304
|
-
execSync('npm install @anthropic-ai/claude-agent-sdk --save-dev', {
|
|
1305
|
-
cwd: projectRoot,
|
|
1306
|
-
stdio: 'pipe'
|
|
1307
|
-
});
|
|
1308
|
-
log(' ✅ Agent SDK 설치 완료\n');
|
|
1309
|
-
}
|
|
1310
|
-
catch (e) {
|
|
1311
|
-
log(' ⚠️ Agent SDK 설치 실패 - 수동 설치: npm i -D @anthropic-ai/claude-agent-sdk\n');
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
catch (e) { }
|
|
1316
|
-
}
|
|
200
|
+
// 레거시 정리
|
|
201
|
+
cleanupLegacy(projectRoot, claudeDir);
|
|
1317
202
|
// 기술 스택 감지
|
|
1318
203
|
const { stacks: detectedStacks, details: stackDetails } = detectTechStacks(projectRoot);
|
|
1319
204
|
// config.json 업데이트
|
|
1320
|
-
|
|
1321
|
-
if (fs.existsSync(configPath)) {
|
|
1322
|
-
try {
|
|
1323
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
1324
|
-
config.stacks = detectedStacks;
|
|
1325
|
-
config.details = stackDetails;
|
|
1326
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1327
|
-
}
|
|
1328
|
-
catch (e) { }
|
|
1329
|
-
}
|
|
205
|
+
updateConfig(vibeDir, detectedStacks, stackDetails, true);
|
|
1330
206
|
// constitution.md 업데이트
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
const frontendStack = detectedStacks.find(s => s.type.includes('react') || s.type.includes('vue') ||
|
|
1338
|
-
s.type.includes('flutter') || s.type.includes('swift') || s.type.includes('android'));
|
|
1339
|
-
if (backendStack && STACK_NAMES[backendStack.type]) {
|
|
1340
|
-
const info = STACK_NAMES[backendStack.type];
|
|
1341
|
-
constitution = constitution.replace('- Language: {Python 3.11+ / Node.js / etc.}', `- Language: ${info.lang}`);
|
|
1342
|
-
constitution = constitution.replace('- Framework: {FastAPI / Express / etc.}', `- Framework: ${info.framework}`);
|
|
1343
|
-
}
|
|
1344
|
-
if (frontendStack && STACK_NAMES[frontendStack.type]) {
|
|
1345
|
-
const info = STACK_NAMES[frontendStack.type];
|
|
1346
|
-
constitution = constitution.replace('- Framework: {Flutter / React / etc.}', `- Framework: ${info.framework}`);
|
|
1347
|
-
}
|
|
1348
|
-
constitution = constitution.replace('- Database: {PostgreSQL / MongoDB / etc.}', stackDetails.databases.length > 0 ? `- Database: ${stackDetails.databases.join(', ')}` : '- Database: (프로젝트에 맞게 설정)');
|
|
1349
|
-
constitution = constitution.replace('- State Management: {Provider / Redux / etc.}', stackDetails.stateManagement.length > 0 ? `- State Management: ${stackDetails.stateManagement.join(', ')}` : '- State Management: (프로젝트에 맞게 설정)');
|
|
1350
|
-
constitution = constitution.replace('- Hosting: {Cloud Run / Vercel / etc.}', stackDetails.hosting.length > 0 ? `- Hosting: ${stackDetails.hosting.join(', ')}` : '- Hosting: (프로젝트에 맞게 설정)');
|
|
1351
|
-
constitution = constitution.replace('- CI/CD: {GitHub Actions / etc.}', stackDetails.cicd.length > 0 ? `- CI/CD: ${stackDetails.cicd.join(', ')}` : '- CI/CD: (프로젝트에 맞게 설정)');
|
|
1352
|
-
fs.writeFileSync(constitutionPath, constitution);
|
|
1353
|
-
log(' ✅ constitution.md 업데이트 완료\n');
|
|
1354
|
-
}
|
|
1355
|
-
// CLAUDE.md 업데이트 (언어별 규칙 동적 추가)
|
|
1356
|
-
const vibeClaudeMd = path.join(__dirname, '../../CLAUDE.md');
|
|
1357
|
-
const projectClaudeMd = path.join(projectRoot, 'CLAUDE.md');
|
|
1358
|
-
if (fs.existsSync(vibeClaudeMd)) {
|
|
1359
|
-
let vibeContent = fs.readFileSync(vibeClaudeMd, 'utf-8');
|
|
1360
|
-
// 감지된 기술 스택에 따라 언어별 규칙 추가
|
|
1361
|
-
const languageRules = getLanguageRulesForStacks(detectedStacks);
|
|
1362
|
-
if (languageRules) {
|
|
1363
|
-
vibeContent = vibeContent.replace('### 에러 처리 필수', languageRules + '\n\n### 에러 처리 필수');
|
|
1364
|
-
}
|
|
1365
|
-
if (fs.existsSync(projectClaudeMd)) {
|
|
1366
|
-
const existingContent = fs.readFileSync(projectClaudeMd, 'utf-8');
|
|
1367
|
-
// vibe 섹션 찾아서 교체 (# VIBE 부터 다음 --- 또는 끝까지)
|
|
1368
|
-
const vibeStartMarker = '# VIBE';
|
|
1369
|
-
const sectionSeparator = '\n---\n';
|
|
1370
|
-
if (existingContent.includes(vibeStartMarker)) {
|
|
1371
|
-
// 기존 vibe 섹션 교체
|
|
1372
|
-
const vibeStartIdx = existingContent.indexOf(vibeStartMarker);
|
|
1373
|
-
const beforeVibe = existingContent.substring(0, vibeStartIdx).trimEnd();
|
|
1374
|
-
// vibe 섹션 뒤에 다른 섹션이 있는지 확인
|
|
1375
|
-
const afterVibeStart = existingContent.substring(vibeStartIdx);
|
|
1376
|
-
const nextSeparatorIdx = afterVibeStart.indexOf(sectionSeparator);
|
|
1377
|
-
let afterVibe = '';
|
|
1378
|
-
if (nextSeparatorIdx !== -1) {
|
|
1379
|
-
afterVibe = afterVibeStart.substring(nextSeparatorIdx);
|
|
1380
|
-
}
|
|
1381
|
-
const newContent = beforeVibe + (beforeVibe ? '\n\n---\n\n' : '') + vibeContent + afterVibe;
|
|
1382
|
-
fs.writeFileSync(projectClaudeMd, newContent);
|
|
1383
|
-
log(' ✅ CLAUDE.md vibe 섹션 업데이트 완료\n');
|
|
1384
|
-
}
|
|
1385
|
-
else if (!existingContent.includes('/vibe.spec')) {
|
|
1386
|
-
// vibe 섹션이 없으면 추가
|
|
1387
|
-
const mergedContent = existingContent.trim() + '\n\n---\n\n' + vibeContent;
|
|
1388
|
-
fs.writeFileSync(projectClaudeMd, mergedContent);
|
|
1389
|
-
log(' ✅ CLAUDE.md에 vibe 섹션 추가\n');
|
|
1390
|
-
}
|
|
1391
|
-
else {
|
|
1392
|
-
log(' ℹ️ CLAUDE.md vibe 섹션 유지\n');
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
else {
|
|
1396
|
-
fs.writeFileSync(projectClaudeMd, vibeContent);
|
|
1397
|
-
log(' ✅ CLAUDE.md 생성\n');
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
// .claude/vibe/rules/ 업데이트
|
|
1401
|
-
const rulesSource = path.join(__dirname, '../../.claude/vibe/rules');
|
|
1402
|
-
const rulesTarget = path.join(vibeDir, 'rules');
|
|
1403
|
-
const coreDirs = ['core', 'quality', 'standards', 'tools'];
|
|
1404
|
-
coreDirs.forEach(dir => {
|
|
1405
|
-
const src = path.join(rulesSource, dir);
|
|
1406
|
-
const dst = path.join(rulesTarget, dir);
|
|
1407
|
-
if (fs.existsSync(src)) {
|
|
1408
|
-
copyDirRecursive(src, dst);
|
|
1409
|
-
}
|
|
1410
|
-
});
|
|
1411
|
-
const langSource = path.join(rulesSource, 'languages');
|
|
1412
|
-
const langTarget = path.join(rulesTarget, 'languages');
|
|
1413
|
-
if (fs.existsSync(langTarget)) {
|
|
1414
|
-
removeDirRecursive(langTarget);
|
|
1415
|
-
}
|
|
1416
|
-
ensureDir(langTarget);
|
|
1417
|
-
// 감지된 스택 타입을 그대로 파일명으로 매칭 (typescript-nextjs -> typescript-nextjs.md)
|
|
1418
|
-
const detectedTypes = new Set(detectedStacks.map(s => s.type));
|
|
1419
|
-
if (fs.existsSync(langSource)) {
|
|
1420
|
-
const langFiles = fs.readdirSync(langSource);
|
|
1421
|
-
langFiles.forEach(file => {
|
|
1422
|
-
const langType = file.replace('.md', '');
|
|
1423
|
-
if (detectedTypes.has(langType)) {
|
|
1424
|
-
fs.copyFileSync(path.join(langSource, file), path.join(langTarget, file));
|
|
1425
|
-
}
|
|
1426
|
-
});
|
|
1427
|
-
}
|
|
207
|
+
updateConstitution(vibeDir, detectedStacks, stackDetails);
|
|
208
|
+
log(' ✅ constitution.md 업데이트 완료\n');
|
|
209
|
+
// CLAUDE.md 업데이트
|
|
210
|
+
updateClaudeMd(projectRoot, detectedStacks, true);
|
|
211
|
+
// 규칙 업데이트
|
|
212
|
+
updateRules(vibeDir, detectedStacks, true);
|
|
1428
213
|
if (detectedStacks.length > 0) {
|
|
214
|
+
const detectedTypes = new Set(detectedStacks.map(s => s.type));
|
|
1429
215
|
log(` 🔍 감지된 기술 스택: ${Array.from(detectedTypes).join(', ')}\n`);
|
|
1430
216
|
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
// .claude/skills/ 업데이트
|
|
1438
|
-
const skillsDir = path.join(claudeDir, 'skills');
|
|
1439
|
-
ensureDir(skillsDir);
|
|
1440
|
-
const skillsSourceDir = path.join(__dirname, '../../skills');
|
|
1441
|
-
if (fs.existsSync(skillsSourceDir)) {
|
|
1442
|
-
copyDirRecursive(skillsSourceDir, skillsDir);
|
|
1443
|
-
log(' ✅ 스킬 업데이트 완료 (.claude/skills/)\n');
|
|
1444
|
-
}
|
|
1445
|
-
// settings.json 업데이트
|
|
1446
|
-
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
1447
|
-
const hooksTemplate = path.join(__dirname, '../../hooks/hooks.json');
|
|
1448
|
-
if (fs.existsSync(hooksTemplate)) {
|
|
1449
|
-
const vibeHooks = JSON.parse(fs.readFileSync(hooksTemplate, 'utf-8'));
|
|
1450
|
-
if (fs.existsSync(settingsPath)) {
|
|
1451
|
-
const existingSettings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
1452
|
-
existingSettings.hooks = vibeHooks.hooks;
|
|
1453
|
-
fs.writeFileSync(settingsPath, JSON.stringify(existingSettings, null, 2));
|
|
1454
|
-
log(' ✅ Hooks 설정 업데이트 완료\n');
|
|
1455
|
-
}
|
|
1456
|
-
else {
|
|
1457
|
-
fs.copyFileSync(hooksTemplate, settingsPath);
|
|
1458
|
-
log(' ✅ Hooks 설정 생성 완료\n');
|
|
1459
|
-
}
|
|
1460
|
-
// settings.local.json도 업데이트
|
|
1461
|
-
const settingsLocalPath = path.join(claudeDir, 'settings.local.json');
|
|
1462
|
-
if (fs.existsSync(settingsLocalPath)) {
|
|
1463
|
-
try {
|
|
1464
|
-
const localSettings = JSON.parse(fs.readFileSync(settingsLocalPath, 'utf-8'));
|
|
1465
|
-
if (localSettings.hooks) {
|
|
1466
|
-
localSettings.hooks = vibeHooks.hooks;
|
|
1467
|
-
fs.writeFileSync(settingsLocalPath, JSON.stringify(localSettings, null, 2));
|
|
1468
|
-
log(' ✅ 로컬 Hooks 설정 업데이트 완료\n');
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
catch (e) { }
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
// .gitignore에서 settings.local.json 제거
|
|
1475
|
-
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
1476
|
-
if (fs.existsSync(gitignorePath)) {
|
|
1477
|
-
let gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
1478
|
-
if (gitignore.includes('settings.local.json')) {
|
|
1479
|
-
gitignore = gitignore.replace(/\.claude\/settings\.local\.json\n?/g, '');
|
|
1480
|
-
gitignore = gitignore.replace(/settings\.local\.json\n?/g, '');
|
|
1481
|
-
fs.writeFileSync(gitignorePath, gitignore);
|
|
1482
|
-
log(' ✅ .gitignore에서 settings.local.json 제거\n');
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
217
|
+
// 전역 assets 업데이트
|
|
218
|
+
installGlobalAssets(true);
|
|
219
|
+
// 프로젝트 로컬 자산 제거
|
|
220
|
+
removeLocalAssets(claudeDir);
|
|
221
|
+
// .gitignore 업데이트
|
|
222
|
+
updateGitignore(projectRoot);
|
|
1485
223
|
// 협업자 자동 설치 설정
|
|
1486
224
|
setupCollaboratorAutoInstall(projectRoot);
|
|
1487
|
-
// MCP 서버 등록
|
|
1488
|
-
const geminiMcpPath = path.join(__dirname, '../lib/gemini-mcp.js');
|
|
1489
|
-
const gptMcpPath = path.join(__dirname, '../lib/gpt-mcp.js');
|
|
1490
225
|
// ~/.claude.json 정리
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
if (claudeConfig.projects) {
|
|
1497
|
-
for (const [projectPath, projectConfig] of Object.entries(claudeConfig.projects)) {
|
|
1498
|
-
if (projectConfig.mcpServers) {
|
|
1499
|
-
if (projectConfig.mcpServers.vibe) {
|
|
1500
|
-
const vibeArgs = projectConfig.mcpServers.vibe.args || [];
|
|
1501
|
-
const isLocalPath = vibeArgs.some((arg) => arg.includes('.vibe/mcp/') || arg.includes('.vibe\\mcp\\'));
|
|
1502
|
-
if (isLocalPath) {
|
|
1503
|
-
delete projectConfig.mcpServers.vibe;
|
|
1504
|
-
configModified = true;
|
|
1505
|
-
log(` 🧹 ${projectPath}: 로컬 vibe MCP 제거\n`);
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
if (projectConfig.mcpServers['vibe-gemini']) {
|
|
1509
|
-
const geminiArgs = projectConfig.mcpServers['vibe-gemini'].args || [];
|
|
1510
|
-
const isLocalPath = geminiArgs.some((arg) => arg.includes('.vibe/') || arg.includes('.vibe\\'));
|
|
1511
|
-
if (isLocalPath) {
|
|
1512
|
-
delete projectConfig.mcpServers['vibe-gemini'];
|
|
1513
|
-
configModified = true;
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
if (projectConfig.mcpServers.context7) {
|
|
1517
|
-
delete projectConfig.mcpServers.context7;
|
|
1518
|
-
configModified = true;
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
if (configModified) {
|
|
1524
|
-
fs.writeFileSync(claudeConfigPath, JSON.stringify(claudeConfig, null, 2));
|
|
1525
|
-
log(' ✅ ~/.claude.json 로컬 MCP 설정 정리 완료\n');
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
catch (e) {
|
|
1529
|
-
log(' ⚠️ ~/.claude.json 정리 실패: ' + e.message + '\n');
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
// MCP 등록 (hi-ai는 내장 도구로 전환됨)
|
|
1533
|
-
try {
|
|
1534
|
-
// 기존 vibe MCP 제거 (hi-ai 기반 → 내장 도구로 마이그레이션)
|
|
1535
|
-
unregisterMcp('vibe');
|
|
1536
|
-
// vibe-gemini MCP 등록
|
|
1537
|
-
unregisterMcp('vibe-gemini');
|
|
1538
|
-
if (fs.existsSync(geminiMcpPath)) {
|
|
1539
|
-
try {
|
|
1540
|
-
registerMcp('vibe-gemini', { command: 'node', args: [geminiMcpPath] });
|
|
1541
|
-
log(' ✅ vibe-gemini MCP 전역 등록 완료\n');
|
|
1542
|
-
}
|
|
1543
|
-
catch (e) {
|
|
1544
|
-
if (e.message?.includes('already exists')) {
|
|
1545
|
-
log(' ℹ️ vibe-gemini MCP 이미 등록됨\n');
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
// vibe-gpt MCP 등록
|
|
1550
|
-
unregisterMcp('vibe-gpt');
|
|
1551
|
-
if (fs.existsSync(gptMcpPath)) {
|
|
1552
|
-
try {
|
|
1553
|
-
registerMcp('vibe-gpt', { command: 'node', args: [gptMcpPath] });
|
|
1554
|
-
log(' ✅ vibe-gpt MCP 전역 등록 완료\n');
|
|
1555
|
-
}
|
|
1556
|
-
catch (e) {
|
|
1557
|
-
if (e.message?.includes('already exists')) {
|
|
1558
|
-
log(' ℹ️ vibe-gpt MCP 이미 등록됨\n');
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
// context7 MCP 등록
|
|
1563
|
-
unregisterMcp('context7');
|
|
1564
|
-
try {
|
|
1565
|
-
registerMcp('context7', { command: 'npx', args: ['-y', '@upstash/context7-mcp@latest'] });
|
|
1566
|
-
log(' ✅ context7 MCP 전역 등록 완료\n');
|
|
1567
|
-
}
|
|
1568
|
-
catch (e) {
|
|
1569
|
-
if (e.message?.includes('already exists')) {
|
|
1570
|
-
log(' ℹ️ context7 MCP 이미 등록됨\n');
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
catch (e) {
|
|
1575
|
-
log(' ⚠️ MCP 등록 실패\n');
|
|
1576
|
-
}
|
|
1577
|
-
// 기존 mcp/ 폴더 정리 (레거시)
|
|
1578
|
-
const oldMcpDir = path.join(vibeDir, 'mcp');
|
|
1579
|
-
if (fs.existsSync(oldMcpDir)) {
|
|
1580
|
-
log(' 🧹 기존 mcp/ 폴더 정리 중...\n');
|
|
1581
|
-
try {
|
|
1582
|
-
removeDirRecursive(oldMcpDir);
|
|
1583
|
-
log(' ✅ mcp/ 폴더 삭제 완료\n');
|
|
1584
|
-
}
|
|
1585
|
-
catch (e) {
|
|
1586
|
-
log(' ⚠️ mcp/ 폴더 수동 삭제 필요\n');
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
226
|
+
cleanupClaudeConfig();
|
|
227
|
+
// MCP 서버 등록
|
|
228
|
+
registerMcpServers(true);
|
|
229
|
+
// 레거시 mcp 폴더 정리
|
|
230
|
+
cleanupLegacyMcp(vibeDir);
|
|
1589
231
|
const packageJson = getPackageJson();
|
|
1590
|
-
log(`
|
|
1591
|
-
✅ vibe 업데이트 완료! (v${packageJson.version})
|
|
1592
|
-
|
|
1593
|
-
업데이트된 항목:
|
|
1594
|
-
- 슬래시 커맨드 (7개)
|
|
1595
|
-
- 코딩 규칙 (.claude/vibe/rules/)
|
|
1596
|
-
- 서브에이전트 (.claude/agents/)
|
|
1597
|
-
- Hooks 설정
|
|
1598
|
-
|
|
1599
|
-
${formatLLMStatus()}
|
|
232
|
+
log(`
|
|
233
|
+
✅ vibe 업데이트 완료! (v${packageJson.version})
|
|
234
|
+
|
|
235
|
+
업데이트된 항목:
|
|
236
|
+
- 슬래시 커맨드 (7개)
|
|
237
|
+
- 코딩 규칙 (.claude/vibe/rules/)
|
|
238
|
+
- 서브에이전트 (.claude/agents/)
|
|
239
|
+
- Hooks 설정
|
|
240
|
+
|
|
241
|
+
${formatLLMStatus()}
|
|
1600
242
|
`);
|
|
1601
243
|
}
|
|
1602
244
|
catch (error) {
|
|
1603
|
-
|
|
245
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
246
|
+
console.error('❌ 업데이트 실패:', message);
|
|
1604
247
|
process.exit(1);
|
|
1605
248
|
}
|
|
1606
249
|
}
|
|
@@ -1665,511 +308,72 @@ function remove() {
|
|
|
1665
308
|
console.log(' ✅ Hooks 설정 제거 완료\n');
|
|
1666
309
|
}
|
|
1667
310
|
}
|
|
1668
|
-
catch
|
|
1669
|
-
}
|
|
1670
|
-
console.log(`
|
|
1671
|
-
✅ vibe 제거 완료!
|
|
1672
|
-
|
|
1673
|
-
제거된 항목:
|
|
1674
|
-
- MCP 서버 (vibe, context7)
|
|
1675
|
-
- .claude/vibe/ 폴더
|
|
1676
|
-
- 슬래시 커맨드 (7개)
|
|
1677
|
-
- 서브에이전트 (5개)
|
|
1678
|
-
- Hooks 설정
|
|
1679
|
-
|
|
1680
|
-
다시 설치하려면: vibe init
|
|
1681
|
-
`);
|
|
1682
|
-
}
|
|
1683
|
-
// ============================================================================
|
|
1684
|
-
// External LLM Commands
|
|
1685
|
-
// ============================================================================
|
|
1686
|
-
function setupExternalLLM(llmType, apiKey) {
|
|
1687
|
-
if (!apiKey) {
|
|
1688
|
-
console.log(`
|
|
1689
|
-
❌ API 키가 필요합니다.
|
|
1690
|
-
|
|
1691
|
-
사용법:
|
|
1692
|
-
vibe ${llmType} <api-key>
|
|
1693
|
-
|
|
1694
|
-
${llmType === 'gpt' ? 'OpenAI API 키: https://platform.openai.com/api-keys' : 'Google API 키: https://aistudio.google.com/apikey'}
|
|
1695
|
-
`);
|
|
1696
|
-
return;
|
|
1697
|
-
}
|
|
1698
|
-
const projectRoot = process.cwd();
|
|
1699
|
-
const vibeDir = path.join(projectRoot, '.claude', 'vibe');
|
|
1700
|
-
const configPath = path.join(vibeDir, 'config.json');
|
|
1701
|
-
if (!fs.existsSync(vibeDir)) {
|
|
1702
|
-
console.log('❌ vibe 프로젝트가 아닙니다. 먼저 vibe init을 실행하세요.');
|
|
1703
|
-
return;
|
|
1704
|
-
}
|
|
1705
|
-
let config = {};
|
|
1706
|
-
if (fs.existsSync(configPath)) {
|
|
1707
|
-
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
1708
|
-
}
|
|
1709
|
-
if (!config.models) {
|
|
1710
|
-
config.models = {};
|
|
1711
|
-
}
|
|
1712
|
-
const llmConfig = EXTERNAL_LLMS[llmType];
|
|
1713
|
-
config.models[llmType] = {
|
|
1714
|
-
enabled: true,
|
|
1715
|
-
role: llmConfig.role,
|
|
1716
|
-
description: llmConfig.description
|
|
1717
|
-
};
|
|
1718
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1719
|
-
const envKey = llmConfig.envKey;
|
|
1720
|
-
try {
|
|
1721
|
-
unregisterMcp(llmConfig.name);
|
|
1722
|
-
registerMcp(llmConfig.name, {
|
|
1723
|
-
command: 'npx',
|
|
1724
|
-
args: ['-y', llmConfig.package],
|
|
1725
|
-
env: { [envKey]: apiKey }
|
|
1726
|
-
});
|
|
1727
|
-
console.log(`
|
|
1728
|
-
✅ ${llmType.toUpperCase()} 활성화 완료! (전역)
|
|
1729
|
-
|
|
1730
|
-
역할: ${llmConfig.description}
|
|
1731
|
-
MCP: ${llmConfig.name}
|
|
1732
|
-
|
|
1733
|
-
모든 프로젝트에서 /vibe.run 실행 시 자동으로 활용됩니다.
|
|
1734
|
-
|
|
1735
|
-
비활성화: vibe remove ${llmType}
|
|
1736
|
-
`);
|
|
1737
|
-
}
|
|
1738
|
-
catch (e) {
|
|
1739
|
-
console.log(`
|
|
1740
|
-
⚠️ MCP 등록 실패. ~/.claude/settings.json에 수동으로 추가하세요.
|
|
1741
|
-
`);
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
function removeExternalLLM(llmType) {
|
|
1745
|
-
const projectRoot = process.cwd();
|
|
1746
|
-
const vibeDir = path.join(projectRoot, '.claude', 'vibe');
|
|
1747
|
-
const configPath = path.join(vibeDir, 'config.json');
|
|
1748
|
-
if (!fs.existsSync(vibeDir)) {
|
|
1749
|
-
console.log('❌ vibe 프로젝트가 아닙니다.');
|
|
1750
|
-
return;
|
|
1751
|
-
}
|
|
1752
|
-
if (fs.existsSync(configPath)) {
|
|
1753
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
1754
|
-
if (config.models?.[llmType]) {
|
|
1755
|
-
config.models[llmType].enabled = false;
|
|
1756
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
const llmConfig = EXTERNAL_LLMS[llmType];
|
|
1760
|
-
unregisterMcp(llmConfig.name);
|
|
1761
|
-
console.log(`✅ ${llmType.toUpperCase()} 비활성화 완료`);
|
|
1762
|
-
}
|
|
1763
|
-
// ============================================================================
|
|
1764
|
-
// GPT OAuth Commands
|
|
1765
|
-
// ============================================================================
|
|
1766
|
-
async function gptAuth() {
|
|
1767
|
-
console.log(`
|
|
1768
|
-
🔐 GPT Plus/Pro 인증 (OAuth)
|
|
1769
|
-
|
|
1770
|
-
ChatGPT Plus 또는 Pro 구독이 있으면 Codex API를 사용할 수 있습니다.
|
|
1771
|
-
브라우저에서 OpenAI 계정으로 로그인하세요.
|
|
1772
|
-
`);
|
|
1773
|
-
try {
|
|
1774
|
-
const gptOAuthPath = path.join(__dirname, '../lib/gpt-oauth.js');
|
|
1775
|
-
const gptStoragePath = path.join(__dirname, '../lib/gpt-storage.js');
|
|
1776
|
-
const { startOAuthFlow } = require(gptOAuthPath);
|
|
1777
|
-
const storage = require(gptStoragePath);
|
|
1778
|
-
const tokens = await startOAuthFlow();
|
|
1779
|
-
storage.addAccount({
|
|
1780
|
-
email: tokens.email,
|
|
1781
|
-
accessToken: tokens.accessToken,
|
|
1782
|
-
refreshToken: tokens.refreshToken,
|
|
1783
|
-
idToken: tokens.idToken,
|
|
1784
|
-
expires: tokens.expires,
|
|
1785
|
-
accountId: tokens.accountId,
|
|
1786
|
-
});
|
|
1787
|
-
console.log(`
|
|
1788
|
-
✅ GPT 인증 완료!
|
|
1789
|
-
|
|
1790
|
-
계정: ${tokens.email}
|
|
1791
|
-
계정 ID: ${tokens.accountId || '(자동 감지)'}
|
|
1792
|
-
|
|
1793
|
-
⚠️ 참고: ChatGPT Plus/Pro 구독이 있어야 API 호출이 가능합니다.
|
|
1794
|
-
구독이 없으면 인증은 성공하지만 API 호출 시 오류가 발생합니다.
|
|
1795
|
-
|
|
1796
|
-
상태 확인: vibe status gpt
|
|
1797
|
-
로그아웃: vibe logout gpt
|
|
1798
|
-
`);
|
|
1799
|
-
// config.json 업데이트
|
|
1800
|
-
const projectRoot = process.cwd();
|
|
1801
|
-
const vibeDir = path.join(projectRoot, '.claude', 'vibe');
|
|
1802
|
-
const configPath = path.join(vibeDir, 'config.json');
|
|
1803
|
-
if (fs.existsSync(configPath)) {
|
|
1804
|
-
try {
|
|
1805
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
1806
|
-
if (!config.models)
|
|
1807
|
-
config.models = {};
|
|
1808
|
-
config.models.gpt = {
|
|
1809
|
-
enabled: true,
|
|
1810
|
-
authType: 'oauth',
|
|
1811
|
-
email: tokens.email,
|
|
1812
|
-
role: 'architecture',
|
|
1813
|
-
description: 'GPT (ChatGPT Plus/Pro)',
|
|
1814
|
-
};
|
|
1815
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1816
|
-
}
|
|
1817
|
-
catch (e) { }
|
|
1818
|
-
}
|
|
1819
|
-
// MCP 서버 등록
|
|
1820
|
-
try {
|
|
1821
|
-
const mcpPath = path.join(__dirname, '../lib/gpt-mcp.js');
|
|
1822
|
-
unregisterMcp('vibe-gpt');
|
|
1823
|
-
registerMcp('vibe-gpt', { command: 'node', args: [mcpPath] });
|
|
1824
|
-
console.log(`
|
|
1825
|
-
✅ vibe-gpt MCP 서버 등록 완료! (전역)
|
|
1826
|
-
|
|
1827
|
-
이제 모든 프로젝트에서 다음 도구를 사용할 수 있습니다:
|
|
1828
|
-
- gpt_chat: GPT에 질문
|
|
1829
|
-
- gpt_analyze_architecture: 아키텍처 분석
|
|
1830
|
-
- gpt_debug: 디버깅
|
|
1831
|
-
- gpt_quick_ask: 빠른 질문
|
|
1832
|
-
`);
|
|
1833
|
-
}
|
|
1834
|
-
catch (mcpError) {
|
|
1835
|
-
console.log(`
|
|
1836
|
-
⚠️ MCP 서버 등록 실패. ~/.claude/settings.json에 수동으로 추가하세요.
|
|
1837
|
-
`);
|
|
1838
|
-
}
|
|
1839
|
-
process.exit(0);
|
|
1840
|
-
}
|
|
1841
|
-
catch (error) {
|
|
1842
|
-
console.error(`
|
|
1843
|
-
❌ GPT 인증 실패
|
|
1844
|
-
|
|
1845
|
-
오류: ${error.message}
|
|
1846
|
-
|
|
1847
|
-
다시 시도하려면: vibe gpt --auth
|
|
1848
|
-
`);
|
|
1849
|
-
process.exit(1);
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
function gptStatus() {
|
|
1853
|
-
try {
|
|
1854
|
-
const gptStoragePath = path.join(__dirname, '../lib/gpt-storage.js');
|
|
1855
|
-
const storage = require(gptStoragePath);
|
|
1856
|
-
const accounts = storage.getAllAccounts();
|
|
1857
|
-
if (accounts.length === 0) {
|
|
1858
|
-
console.log(`
|
|
1859
|
-
📊 GPT 인증 상태
|
|
1860
|
-
|
|
1861
|
-
인증된 계정 없음
|
|
1862
|
-
|
|
1863
|
-
로그인: vibe gpt --auth
|
|
1864
|
-
`);
|
|
1865
|
-
return;
|
|
1866
|
-
}
|
|
1867
|
-
const activeAccount = storage.getActiveAccount();
|
|
1868
|
-
const isExpired = storage.isTokenExpired(activeAccount);
|
|
1869
|
-
console.log(`
|
|
1870
|
-
📊 GPT 인증 상태
|
|
1871
|
-
|
|
1872
|
-
활성 계정: ${activeAccount.email}
|
|
1873
|
-
계정 ID: ${activeAccount.accountId || '(없음)'}
|
|
1874
|
-
토큰 상태: ${isExpired ? '⚠️ 만료됨 (자동 갱신됨)' : '✅ 유효'}
|
|
1875
|
-
마지막 사용: ${new Date(activeAccount.lastUsed).toLocaleString()}
|
|
1876
|
-
|
|
1877
|
-
등록된 계정 (${accounts.length}개):
|
|
1878
|
-
${accounts.map((acc, i) => ` ${i === storage.loadAccounts()?.activeIndex ? '→' : ' '} ${acc.email}`).join('\n')}
|
|
1879
|
-
|
|
1880
|
-
⚠️ 참고: ChatGPT Plus/Pro 구독이 있어야 API 호출이 가능합니다.
|
|
1881
|
-
|
|
1882
|
-
로그아웃: vibe logout gpt
|
|
1883
|
-
`);
|
|
1884
|
-
}
|
|
1885
|
-
catch (error) {
|
|
1886
|
-
console.error('상태 확인 실패:', error.message);
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
function gptLogout() {
|
|
1890
|
-
try {
|
|
1891
|
-
const gptStoragePath = path.join(__dirname, '../lib/gpt-storage.js');
|
|
1892
|
-
const storage = require(gptStoragePath);
|
|
1893
|
-
const activeAccount = storage.getActiveAccount();
|
|
1894
|
-
if (!activeAccount) {
|
|
1895
|
-
console.log('로그인된 계정이 없습니다.');
|
|
1896
|
-
return;
|
|
1897
|
-
}
|
|
1898
|
-
storage.clearAccounts();
|
|
1899
|
-
console.log(`
|
|
1900
|
-
✅ GPT 로그아웃 완료
|
|
1901
|
-
|
|
1902
|
-
${activeAccount.email} 계정이 제거되었습니다.
|
|
1903
|
-
|
|
1904
|
-
다시 로그인: vibe gpt --auth
|
|
1905
|
-
`);
|
|
1906
|
-
// config.json 업데이트
|
|
1907
|
-
const projectRoot = process.cwd();
|
|
1908
|
-
const vibeDir = path.join(projectRoot, '.claude', 'vibe');
|
|
1909
|
-
const configPath = path.join(vibeDir, 'config.json');
|
|
1910
|
-
if (fs.existsSync(configPath)) {
|
|
1911
|
-
try {
|
|
1912
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
1913
|
-
if (config.models?.gpt) {
|
|
1914
|
-
config.models.gpt.enabled = false;
|
|
1915
|
-
config.models.gpt.authType = undefined;
|
|
1916
|
-
config.models.gpt.email = undefined;
|
|
1917
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1920
|
-
catch (e) { }
|
|
1921
|
-
}
|
|
311
|
+
catch { /* ignore: optional operation */ }
|
|
1922
312
|
}
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
vibe auth gemini Gemini 구독 OAuth 인증 (권장)
|
|
1935
|
-
vibe auth gemini --key <key> Gemini API 키로 설정
|
|
1936
|
-
|
|
1937
|
-
예시:
|
|
1938
|
-
vibe auth gpt OpenAI 로그인 (Plus/Pro 구독 필요)
|
|
1939
|
-
vibe auth gemini Google 로그인 (Gemini Advanced 구독 시 무료)
|
|
1940
|
-
vibe auth gpt --key sk-xxx API 키로 설정 (사용량 과금)
|
|
313
|
+
console.log(`
|
|
314
|
+
✅ vibe 제거 완료!
|
|
315
|
+
|
|
316
|
+
제거된 항목:
|
|
317
|
+
- MCP 서버 (vibe, context7)
|
|
318
|
+
- .claude/vibe/ 폴더
|
|
319
|
+
- 슬래시 커맨드 (7개)
|
|
320
|
+
- 서브에이전트 (5개)
|
|
321
|
+
- Hooks 설정
|
|
322
|
+
|
|
323
|
+
다시 설치하려면: vibe init
|
|
1941
324
|
`);
|
|
1942
325
|
}
|
|
1943
|
-
function showLogoutHelp() {
|
|
1944
|
-
console.log(`
|
|
1945
|
-
🚪 vibe logout - LLM 로그아웃
|
|
1946
|
-
|
|
1947
|
-
사용법:
|
|
1948
|
-
vibe logout gpt GPT 로그아웃
|
|
1949
|
-
vibe logout gemini Gemini 로그아웃
|
|
1950
|
-
`);
|
|
1951
|
-
}
|
|
1952
|
-
// ============================================================================
|
|
1953
|
-
// Gemini OAuth Commands
|
|
1954
|
-
// ============================================================================
|
|
1955
|
-
async function geminiAuth() {
|
|
1956
|
-
console.log(`
|
|
1957
|
-
🔐 Gemini 구독 인증 (OAuth)
|
|
1958
|
-
|
|
1959
|
-
Gemini Advanced 구독이 있으면 추가 비용 없이 사용할 수 있습니다.
|
|
1960
|
-
브라우저에서 Google 계정으로 로그인하세요.
|
|
1961
|
-
`);
|
|
1962
|
-
try {
|
|
1963
|
-
const geminiOAuthPath = path.join(__dirname, '../lib/gemini-oauth.js');
|
|
1964
|
-
const geminiStoragePath = path.join(__dirname, '../lib/gemini-storage.js');
|
|
1965
|
-
const { startOAuthFlow } = require(geminiOAuthPath);
|
|
1966
|
-
const storage = require(geminiStoragePath);
|
|
1967
|
-
const tokens = await startOAuthFlow();
|
|
1968
|
-
storage.addAccount({
|
|
1969
|
-
email: tokens.email,
|
|
1970
|
-
accessToken: tokens.accessToken,
|
|
1971
|
-
refreshToken: tokens.refreshToken,
|
|
1972
|
-
expires: tokens.expires,
|
|
1973
|
-
projectId: tokens.projectId,
|
|
1974
|
-
});
|
|
1975
|
-
console.log(`
|
|
1976
|
-
✅ Gemini 인증 완료!
|
|
1977
|
-
|
|
1978
|
-
계정: ${tokens.email}
|
|
1979
|
-
프로젝트: ${tokens.projectId || '(자동 감지)'}
|
|
1980
|
-
|
|
1981
|
-
사용 가능한 모델:
|
|
1982
|
-
- Gemini 3 Flash (빠른 응답, 탐색/검색)
|
|
1983
|
-
- Gemini 3 Pro (높은 정확도)
|
|
1984
|
-
|
|
1985
|
-
/vibe.run 실행 시 자동으로 Gemini가 보조 모델로 활용됩니다.
|
|
1986
|
-
|
|
1987
|
-
상태 확인: vibe status gemini
|
|
1988
|
-
로그아웃: vibe logout gemini
|
|
1989
|
-
`);
|
|
1990
|
-
// config.json 업데이트
|
|
1991
|
-
const projectRoot = process.cwd();
|
|
1992
|
-
const vibeDir = path.join(projectRoot, '.claude', 'vibe');
|
|
1993
|
-
const configPath = path.join(vibeDir, 'config.json');
|
|
1994
|
-
if (fs.existsSync(configPath)) {
|
|
1995
|
-
try {
|
|
1996
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
1997
|
-
if (!config.models)
|
|
1998
|
-
config.models = {};
|
|
1999
|
-
config.models.gemini = {
|
|
2000
|
-
enabled: true,
|
|
2001
|
-
authType: 'oauth',
|
|
2002
|
-
email: tokens.email,
|
|
2003
|
-
role: 'exploration',
|
|
2004
|
-
description: 'Gemini 3 Flash/Pro (탐색, UI/UX)',
|
|
2005
|
-
};
|
|
2006
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
2007
|
-
}
|
|
2008
|
-
catch (e) { }
|
|
2009
|
-
}
|
|
2010
|
-
// MCP 서버 등록
|
|
2011
|
-
try {
|
|
2012
|
-
const mcpPath = path.join(__dirname, '../lib/gemini-mcp.js');
|
|
2013
|
-
unregisterMcp('vibe-gemini');
|
|
2014
|
-
registerMcp('vibe-gemini', { command: 'node', args: [mcpPath] });
|
|
2015
|
-
console.log(`
|
|
2016
|
-
✅ vibe-gemini MCP 서버 등록 완료! (전역)
|
|
2017
|
-
|
|
2018
|
-
이제 모든 프로젝트에서 다음 도구를 사용할 수 있습니다:
|
|
2019
|
-
- gemini_chat: Gemini에 질문
|
|
2020
|
-
- gemini_analyze_code: 코드 분석
|
|
2021
|
-
- gemini_review_ui: UI/UX 리뷰
|
|
2022
|
-
- gemini_quick_ask: 빠른 질문
|
|
2023
|
-
`);
|
|
2024
|
-
}
|
|
2025
|
-
catch (mcpError) {
|
|
2026
|
-
console.log(`
|
|
2027
|
-
⚠️ MCP 서버 등록 실패. ~/.claude/settings.json에 수동으로 추가하세요.
|
|
2028
|
-
`);
|
|
2029
|
-
}
|
|
2030
|
-
process.exit(0);
|
|
2031
|
-
}
|
|
2032
|
-
catch (error) {
|
|
2033
|
-
console.error(`
|
|
2034
|
-
❌ Gemini 인증 실패
|
|
2035
|
-
|
|
2036
|
-
오류: ${error.message}
|
|
2037
|
-
|
|
2038
|
-
다시 시도하려면: vibe gemini --auth
|
|
2039
|
-
`);
|
|
2040
|
-
process.exit(1);
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
function geminiStatus() {
|
|
2044
|
-
try {
|
|
2045
|
-
const geminiStoragePath = path.join(__dirname, '../lib/gemini-storage.js');
|
|
2046
|
-
const geminiApiPath = path.join(__dirname, '../lib/gemini-api.js');
|
|
2047
|
-
const storage = require(geminiStoragePath);
|
|
2048
|
-
const { GEMINI_MODELS } = require(geminiApiPath);
|
|
2049
|
-
const accounts = storage.getAllAccounts();
|
|
2050
|
-
if (accounts.length === 0) {
|
|
2051
|
-
console.log(`
|
|
2052
|
-
📊 Gemini 인증 상태
|
|
2053
|
-
|
|
2054
|
-
인증된 계정 없음
|
|
2055
|
-
|
|
2056
|
-
로그인: vibe gemini --auth
|
|
2057
|
-
`);
|
|
2058
|
-
return;
|
|
2059
|
-
}
|
|
2060
|
-
const activeAccount = storage.getActiveAccount();
|
|
2061
|
-
const isExpired = storage.isTokenExpired(activeAccount);
|
|
2062
|
-
console.log(`
|
|
2063
|
-
📊 Gemini 인증 상태
|
|
2064
|
-
|
|
2065
|
-
활성 계정: ${activeAccount.email}
|
|
2066
|
-
프로젝트: ${activeAccount.projectId || '(자동)'}
|
|
2067
|
-
토큰 상태: ${isExpired ? '⚠️ 만료됨 (자동 갱신됨)' : '✅ 유효'}
|
|
2068
|
-
마지막 사용: ${new Date(activeAccount.lastUsed).toLocaleString()}
|
|
2069
|
-
|
|
2070
|
-
등록된 계정 (${accounts.length}개):
|
|
2071
|
-
${accounts.map((acc, i) => ` ${i === storage.loadAccounts()?.activeIndex ? '→' : ' '} ${acc.email}`).join('\n')}
|
|
2072
|
-
|
|
2073
|
-
사용 가능한 모델:
|
|
2074
|
-
${Object.entries(GEMINI_MODELS).map(([id, info]) => ` - ${id}: ${info.description}`).join('\n')}
|
|
2075
|
-
|
|
2076
|
-
로그아웃: vibe logout gemini
|
|
2077
|
-
`);
|
|
2078
|
-
}
|
|
2079
|
-
catch (error) {
|
|
2080
|
-
console.error('상태 확인 실패:', error.message);
|
|
2081
|
-
}
|
|
2082
|
-
}
|
|
2083
|
-
function geminiLogout() {
|
|
2084
|
-
try {
|
|
2085
|
-
const geminiStoragePath = path.join(__dirname, '../lib/gemini-storage.js');
|
|
2086
|
-
const storage = require(geminiStoragePath);
|
|
2087
|
-
const activeAccount = storage.getActiveAccount();
|
|
2088
|
-
if (!activeAccount) {
|
|
2089
|
-
console.log('로그인된 계정이 없습니다.');
|
|
2090
|
-
return;
|
|
2091
|
-
}
|
|
2092
|
-
storage.clearAccounts();
|
|
2093
|
-
console.log(`
|
|
2094
|
-
✅ Gemini 로그아웃 완료
|
|
2095
|
-
|
|
2096
|
-
${activeAccount.email} 계정이 제거되었습니다.
|
|
2097
|
-
|
|
2098
|
-
다시 로그인: vibe gemini --auth
|
|
2099
|
-
`);
|
|
2100
|
-
// config.json 업데이트
|
|
2101
|
-
const projectRoot = process.cwd();
|
|
2102
|
-
const vibeDir = path.join(projectRoot, '.claude', 'vibe');
|
|
2103
|
-
const configPath = path.join(vibeDir, 'config.json');
|
|
2104
|
-
if (fs.existsSync(configPath)) {
|
|
2105
|
-
try {
|
|
2106
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
2107
|
-
if (config.models?.gemini) {
|
|
2108
|
-
config.models.gemini.enabled = false;
|
|
2109
|
-
config.models.gemini.authType = undefined;
|
|
2110
|
-
config.models.gemini.email = undefined;
|
|
2111
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
2112
|
-
}
|
|
2113
|
-
}
|
|
2114
|
-
catch (e) { }
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
catch (error) {
|
|
2118
|
-
console.error('로그아웃 실패:', error.message);
|
|
2119
|
-
}
|
|
2120
|
-
}
|
|
2121
|
-
// showGeminiHelp 제거됨 - showAuthHelp로 통합
|
|
2122
326
|
// ============================================================================
|
|
2123
327
|
// Info Commands
|
|
2124
328
|
// ============================================================================
|
|
2125
329
|
function showHelp() {
|
|
2126
|
-
console.log(`
|
|
2127
|
-
📖 Vibe - SPEC-driven AI coding framework (Claude Code 전용)
|
|
2128
|
-
|
|
2129
|
-
기본 명령어:
|
|
2130
|
-
vibe init [project] 프로젝트 초기화
|
|
2131
|
-
vibe update 설정 업데이트
|
|
2132
|
-
vibe status 현재 설정 상태
|
|
2133
|
-
vibe help 도움말
|
|
2134
|
-
vibe version 버전 정보
|
|
2135
|
-
|
|
2136
|
-
외부 LLM 인증:
|
|
2137
|
-
vibe auth gpt GPT Plus/Pro OAuth 인증
|
|
2138
|
-
vibe auth gemini Gemini 구독 OAuth 인증 (권장)
|
|
2139
|
-
vibe auth gpt --key <key> GPT API 키 설정
|
|
2140
|
-
vibe auth gemini --key <key> Gemini API 키 설정
|
|
2141
|
-
|
|
2142
|
-
상태 및 관리:
|
|
2143
|
-
vibe status 전체 상태 확인
|
|
2144
|
-
vibe status gpt GPT 인증 상태 확인
|
|
2145
|
-
vibe status gemini Gemini 인증 상태 확인
|
|
2146
|
-
vibe logout gpt GPT 로그아웃
|
|
2147
|
-
vibe logout gemini Gemini 로그아웃
|
|
2148
|
-
vibe remove gpt GPT 제거
|
|
2149
|
-
vibe remove gemini Gemini 제거
|
|
2150
|
-
vibe remove vibe 전체 제거 (MCP, 설정, 패키지)
|
|
2151
|
-
|
|
2152
|
-
Claude Code 슬래시 커맨드:
|
|
2153
|
-
/vibe.spec "기능명" SPEC 작성 (PTCF 구조)
|
|
2154
|
-
/vibe.run "기능명" 구현 실행
|
|
2155
|
-
/vibe.verify "기능명" 검증
|
|
2156
|
-
/vibe.reason "문제" 체계적 추론
|
|
2157
|
-
/vibe.analyze 프로젝트 분석
|
|
2158
|
-
/vibe.ui "설명" UI 미리보기
|
|
2159
|
-
/vibe.diagram 다이어그램 생성
|
|
2160
|
-
|
|
2161
|
-
모델 오케스트레이션:
|
|
2162
|
-
Opus 4.5 오케스트레이터 (메인)
|
|
2163
|
-
Sonnet 4 구현
|
|
2164
|
-
Haiku 4.5 코드 탐색
|
|
2165
|
-
GPT 5.2 아키텍처/디버깅 (선택적)
|
|
2166
|
-
Gemini 3 UI/UX 설계 (선택적)
|
|
2167
|
-
|
|
2168
|
-
Workflow:
|
|
2169
|
-
/vibe.spec → /vibe.run → /vibe.verify
|
|
2170
|
-
|
|
2171
|
-
문서:
|
|
2172
|
-
https://github.com/su-record/vibe
|
|
330
|
+
console.log(`
|
|
331
|
+
📖 Vibe - SPEC-driven AI coding framework (Claude Code 전용)
|
|
332
|
+
|
|
333
|
+
기본 명령어:
|
|
334
|
+
vibe init [project] 프로젝트 초기화
|
|
335
|
+
vibe update 설정 업데이트
|
|
336
|
+
vibe status 현재 설정 상태
|
|
337
|
+
vibe help 도움말
|
|
338
|
+
vibe version 버전 정보
|
|
339
|
+
|
|
340
|
+
외부 LLM 인증:
|
|
341
|
+
vibe auth gpt GPT Plus/Pro OAuth 인증
|
|
342
|
+
vibe auth gemini Gemini 구독 OAuth 인증 (권장)
|
|
343
|
+
vibe auth gpt --key <key> GPT API 키 설정
|
|
344
|
+
vibe auth gemini --key <key> Gemini API 키 설정
|
|
345
|
+
|
|
346
|
+
상태 및 관리:
|
|
347
|
+
vibe status 전체 상태 확인
|
|
348
|
+
vibe status gpt GPT 인증 상태 확인
|
|
349
|
+
vibe status gemini Gemini 인증 상태 확인
|
|
350
|
+
vibe logout gpt GPT 로그아웃
|
|
351
|
+
vibe logout gemini Gemini 로그아웃
|
|
352
|
+
vibe remove gpt GPT 제거
|
|
353
|
+
vibe remove gemini Gemini 제거
|
|
354
|
+
vibe remove vibe 전체 제거 (MCP, 설정, 패키지)
|
|
355
|
+
|
|
356
|
+
Claude Code 슬래시 커맨드:
|
|
357
|
+
/vibe.spec "기능명" SPEC 작성 (PTCF 구조)
|
|
358
|
+
/vibe.run "기능명" 구현 실행
|
|
359
|
+
/vibe.verify "기능명" 검증
|
|
360
|
+
/vibe.reason "문제" 체계적 추론
|
|
361
|
+
/vibe.analyze 프로젝트 분석
|
|
362
|
+
/vibe.ui "설명" UI 미리보기
|
|
363
|
+
/vibe.diagram 다이어그램 생성
|
|
364
|
+
|
|
365
|
+
모델 오케스트레이션:
|
|
366
|
+
Opus 4.5 오케스트레이터 (메인)
|
|
367
|
+
Sonnet 4 구현
|
|
368
|
+
Haiku 4.5 코드 탐색
|
|
369
|
+
GPT 5.2 아키텍처/디버깅 (선택적)
|
|
370
|
+
Gemini 3 UI/UX 설계 (선택적)
|
|
371
|
+
|
|
372
|
+
Workflow:
|
|
373
|
+
/vibe.spec → /vibe.run → /vibe.verify
|
|
374
|
+
|
|
375
|
+
문서:
|
|
376
|
+
https://github.com/su-record/vibe
|
|
2173
377
|
`);
|
|
2174
378
|
}
|
|
2175
379
|
function showStatus() {
|
|
@@ -2185,35 +389,35 @@ function showStatus() {
|
|
|
2185
389
|
if (fs.existsSync(configPath)) {
|
|
2186
390
|
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
2187
391
|
}
|
|
2188
|
-
const
|
|
2189
|
-
const
|
|
2190
|
-
console.log(`
|
|
2191
|
-
📊 Vibe 상태 (v${packageJson.version})
|
|
2192
|
-
|
|
2193
|
-
프로젝트: ${projectRoot}
|
|
2194
|
-
언어: ${config.language || 'ko'}
|
|
2195
|
-
|
|
2196
|
-
모델 오케스트레이션:
|
|
2197
|
-
┌─────────────────────────────────────────┐
|
|
2198
|
-
│ Opus 4.5 오케스트레이터 │
|
|
2199
|
-
├─────────────────────────────────────────┤
|
|
2200
|
-
│ Sonnet 4 구현 │
|
|
2201
|
-
│ Haiku 4.5 코드 탐색 │
|
|
2202
|
-
├─────────────────────────────────────────┤
|
|
2203
|
-
│ GPT 5.2 ${
|
|
2204
|
-
│ Gemini 3 ${
|
|
2205
|
-
└─────────────────────────────────────────┘
|
|
2206
|
-
|
|
2207
|
-
MCP 서버:
|
|
2208
|
-
vibe-gemini Gemini API
|
|
2209
|
-
vibe-gpt GPT API
|
|
2210
|
-
context7 라이브러리 문서 검색
|
|
2211
|
-
|
|
2212
|
-
외부 LLM 설정:
|
|
2213
|
-
vibe auth gpt GPT 활성화 (OAuth)
|
|
2214
|
-
vibe auth gemini Gemini 활성화 (OAuth)
|
|
2215
|
-
vibe remove gpt GPT 제거
|
|
2216
|
-
vibe remove gemini Gemini 제거
|
|
392
|
+
const gptStatusText = config.models?.gpt?.enabled ? '✅ 활성' : '⬚ 비활성';
|
|
393
|
+
const geminiStatusText = config.models?.gemini?.enabled ? '✅ 활성' : '⬚ 비활성';
|
|
394
|
+
console.log(`
|
|
395
|
+
📊 Vibe 상태 (v${packageJson.version})
|
|
396
|
+
|
|
397
|
+
프로젝트: ${projectRoot}
|
|
398
|
+
언어: ${config.language || 'ko'}
|
|
399
|
+
|
|
400
|
+
모델 오케스트레이션:
|
|
401
|
+
┌─────────────────────────────────────────┐
|
|
402
|
+
│ Opus 4.5 오케스트레이터 │
|
|
403
|
+
├─────────────────────────────────────────┤
|
|
404
|
+
│ Sonnet 4 구현 │
|
|
405
|
+
│ Haiku 4.5 코드 탐색 │
|
|
406
|
+
├─────────────────────────────────────────┤
|
|
407
|
+
│ GPT 5.2 ${gptStatusText} 아키텍처/디버깅 │
|
|
408
|
+
│ Gemini 3 ${geminiStatusText} UI/UX 설계 │
|
|
409
|
+
└─────────────────────────────────────────┘
|
|
410
|
+
|
|
411
|
+
MCP 서버:
|
|
412
|
+
vibe-gemini Gemini API
|
|
413
|
+
vibe-gpt GPT API
|
|
414
|
+
context7 라이브러리 문서 검색
|
|
415
|
+
|
|
416
|
+
외부 LLM 설정:
|
|
417
|
+
vibe auth gpt GPT 활성화 (OAuth)
|
|
418
|
+
vibe auth gemini Gemini 활성화 (OAuth)
|
|
419
|
+
vibe remove gpt GPT 제거
|
|
420
|
+
vibe remove gemini Gemini 제거
|
|
2217
421
|
`);
|
|
2218
422
|
}
|
|
2219
423
|
function showVersion() {
|
|
@@ -2276,7 +480,6 @@ switch (command) {
|
|
|
2276
480
|
break;
|
|
2277
481
|
case 'remove':
|
|
2278
482
|
case 'uninstall':
|
|
2279
|
-
// vibe remove gpt / vibe remove gemini
|
|
2280
483
|
if (positionalArgs[1] === 'gpt' || positionalArgs[1] === 'gemini') {
|
|
2281
484
|
removeExternalLLM(positionalArgs[1]);
|
|
2282
485
|
}
|
|
@@ -2285,7 +488,6 @@ switch (command) {
|
|
|
2285
488
|
}
|
|
2286
489
|
break;
|
|
2287
490
|
case 'auth':
|
|
2288
|
-
// vibe auth gpt / vibe auth gemini
|
|
2289
491
|
if (positionalArgs[1] === 'gpt') {
|
|
2290
492
|
const keyIndex = args.indexOf('--key');
|
|
2291
493
|
if (keyIndex !== -1 && args[keyIndex + 1]) {
|
|
@@ -2309,7 +511,6 @@ switch (command) {
|
|
|
2309
511
|
}
|
|
2310
512
|
break;
|
|
2311
513
|
case 'logout':
|
|
2312
|
-
// vibe logout gpt / vibe logout gemini
|
|
2313
514
|
if (positionalArgs[1] === 'gpt') {
|
|
2314
515
|
gptLogout();
|
|
2315
516
|
}
|
|
@@ -2321,7 +522,6 @@ switch (command) {
|
|
|
2321
522
|
}
|
|
2322
523
|
break;
|
|
2323
524
|
case 'status':
|
|
2324
|
-
// vibe status / vibe status gpt / vibe status gemini
|
|
2325
525
|
if (positionalArgs[1] === 'gpt') {
|
|
2326
526
|
gptStatus();
|
|
2327
527
|
}
|
|
@@ -2344,20 +544,20 @@ switch (command) {
|
|
|
2344
544
|
showHelp();
|
|
2345
545
|
break;
|
|
2346
546
|
default:
|
|
2347
|
-
console.log(`
|
|
2348
|
-
❌ 알 수 없는 명령어: ${command}
|
|
2349
|
-
|
|
2350
|
-
사용 가능한 명령어:
|
|
2351
|
-
vibe init 프로젝트 초기화
|
|
2352
|
-
vibe update 설정 업데이트
|
|
2353
|
-
vibe auth LLM 인증 (gpt, gemini)
|
|
2354
|
-
vibe status 상태 확인
|
|
2355
|
-
vibe logout 로그아웃
|
|
2356
|
-
vibe remove 제거
|
|
2357
|
-
vibe help 도움말
|
|
2358
|
-
vibe version 버전 정보
|
|
2359
|
-
|
|
2360
|
-
사용법: vibe help
|
|
547
|
+
console.log(`
|
|
548
|
+
❌ 알 수 없는 명령어: ${command}
|
|
549
|
+
|
|
550
|
+
사용 가능한 명령어:
|
|
551
|
+
vibe init 프로젝트 초기화
|
|
552
|
+
vibe update 설정 업데이트
|
|
553
|
+
vibe auth LLM 인증 (gpt, gemini)
|
|
554
|
+
vibe status 상태 확인
|
|
555
|
+
vibe logout 로그아웃
|
|
556
|
+
vibe remove 제거
|
|
557
|
+
vibe help 도움말
|
|
558
|
+
vibe version 버전 정보
|
|
559
|
+
|
|
560
|
+
사용법: vibe help
|
|
2361
561
|
`);
|
|
2362
562
|
process.exit(1);
|
|
2363
563
|
}
|