@nclamvn/vibecode-cli 1.1.0 → 2.0.0
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/bin/vibecode.js +14 -0
- package/package.json +1 -1
- package/src/commands/build.js +165 -3
- package/src/commands/config.js +149 -0
- package/src/config/constants.js +1 -1
- package/src/index.js +7 -0
- package/src/providers/claude-code.js +159 -0
- package/src/providers/index.js +45 -0
package/bin/vibecode.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
buildCommand,
|
|
17
17
|
reviewCommand,
|
|
18
18
|
snapshotCommand,
|
|
19
|
+
configCommand,
|
|
19
20
|
VERSION
|
|
20
21
|
} from '../src/index.js';
|
|
21
22
|
|
|
@@ -77,6 +78,8 @@ program
|
|
|
77
78
|
.option('-s, --start', 'Start the build process')
|
|
78
79
|
.option('-c, --complete', 'Mark build as complete')
|
|
79
80
|
.option('-e, --evidence', 'Capture evidence snapshot')
|
|
81
|
+
.option('-a, --auto', 'Auto-build with Claude Code (--dangerously-skip-permissions)')
|
|
82
|
+
.option('--provider <name>', 'Provider to use: claude-code', 'claude-code')
|
|
80
83
|
.action(buildCommand);
|
|
81
84
|
|
|
82
85
|
program
|
|
@@ -93,6 +96,17 @@ program
|
|
|
93
96
|
.option('--major', 'Major version bump')
|
|
94
97
|
.action(snapshotCommand);
|
|
95
98
|
|
|
99
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
100
|
+
// Phase C Commands - AI Integration
|
|
101
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
program
|
|
104
|
+
.command('config')
|
|
105
|
+
.description('Manage Vibecode configuration')
|
|
106
|
+
.option('--show', 'Show current configuration')
|
|
107
|
+
.option('--provider <name>', 'Set default AI provider')
|
|
108
|
+
.action(configCommand);
|
|
109
|
+
|
|
96
110
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
97
111
|
// Parse
|
|
98
112
|
// ─────────────────────────────────────────────────────────────────────────────
|
package/package.json
CHANGED
package/src/commands/build.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
2
|
// VIBECODE CLI - Build Command
|
|
3
|
+
// "Claude/LLM là PIPELINE, là KIẾN TRÚC SƯ"
|
|
3
4
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4
5
|
|
|
5
6
|
import chalk from 'chalk';
|
|
@@ -12,14 +13,21 @@ import {
|
|
|
12
13
|
getCurrentSessionId,
|
|
13
14
|
getCurrentSessionPath,
|
|
14
15
|
writeSessionFile,
|
|
15
|
-
sessionFileExists
|
|
16
|
+
sessionFileExists,
|
|
17
|
+
readSessionFile
|
|
16
18
|
} from '../core/session.js';
|
|
17
19
|
import { getCurrentState, transitionTo } from '../core/state-machine.js';
|
|
18
20
|
import { getSpecHash } from '../core/contract.js';
|
|
19
21
|
import { STATES } from '../config/constants.js';
|
|
20
22
|
import { getBuildReportTemplate } from '../config/templates.js';
|
|
21
|
-
import { ensureDir, pathExists, appendToFile } from '../utils/files.js';
|
|
23
|
+
import { ensureDir, pathExists, appendToFile, readMarkdown } from '../utils/files.js';
|
|
22
24
|
import { printBox, printError, printSuccess, printWarning, printNextStep } from '../ui/output.js';
|
|
25
|
+
import {
|
|
26
|
+
spawnClaudeCode,
|
|
27
|
+
isClaudeCodeAvailable,
|
|
28
|
+
buildPromptWithContext,
|
|
29
|
+
getProviderInfo
|
|
30
|
+
} from '../providers/index.js';
|
|
23
31
|
|
|
24
32
|
const execAsync = promisify(exec);
|
|
25
33
|
|
|
@@ -38,7 +46,9 @@ export async function buildCommand(options = {}) {
|
|
|
38
46
|
const specHash = await getSpecHash();
|
|
39
47
|
|
|
40
48
|
// Handle different build modes
|
|
41
|
-
if (options.
|
|
49
|
+
if (options.auto) {
|
|
50
|
+
await handleAutoBuild(currentState, projectName, sessionId, sessionPath, specHash, options);
|
|
51
|
+
} else if (options.start) {
|
|
42
52
|
await handleBuildStart(currentState, projectName, sessionId, sessionPath, specHash);
|
|
43
53
|
} else if (options.complete) {
|
|
44
54
|
await handleBuildComplete(currentState, projectName, sessionId, sessionPath, specHash);
|
|
@@ -54,6 +64,158 @@ export async function buildCommand(options = {}) {
|
|
|
54
64
|
}
|
|
55
65
|
}
|
|
56
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Handle --auto mode: Spawn Claude Code with optimal config
|
|
69
|
+
* "Contract LOCKED = License to build"
|
|
70
|
+
*/
|
|
71
|
+
async function handleAutoBuild(currentState, projectName, sessionId, sessionPath, specHash, options) {
|
|
72
|
+
// Check state - must be PLAN_CREATED or BUILD_IN_PROGRESS or REVIEW_FAILED
|
|
73
|
+
const validStates = [STATES.PLAN_CREATED, STATES.BUILD_IN_PROGRESS, STATES.REVIEW_FAILED];
|
|
74
|
+
if (!validStates.includes(currentState)) {
|
|
75
|
+
printError(`Cannot auto-build in state: ${currentState}`);
|
|
76
|
+
console.log('Run `vibecode plan` first to create execution plan.');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check if Claude Code is available
|
|
81
|
+
const available = await isClaudeCodeAvailable();
|
|
82
|
+
if (!available) {
|
|
83
|
+
printError('Claude Code CLI not found.');
|
|
84
|
+
console.log(chalk.gray('Install with: npm install -g @anthropic-ai/claude-code'));
|
|
85
|
+
console.log(chalk.gray('Or use manual build: vibecode build --start'));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check coder_pack.md exists
|
|
90
|
+
if (!await sessionFileExists('coder_pack.md')) {
|
|
91
|
+
printError('coder_pack.md not found. Run `vibecode plan` first.');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Setup evidence directory
|
|
96
|
+
const evidencePath = path.join(sessionPath, 'evidence');
|
|
97
|
+
await ensureDir(evidencePath);
|
|
98
|
+
await ensureDir(path.join(evidencePath, 'screenshots'));
|
|
99
|
+
const logPath = path.join(evidencePath, 'build.log');
|
|
100
|
+
|
|
101
|
+
// Save build start time
|
|
102
|
+
const stateData = await loadState();
|
|
103
|
+
const startTime = new Date().toISOString();
|
|
104
|
+
stateData.build_started = startTime;
|
|
105
|
+
stateData.build_provider = 'claude-code';
|
|
106
|
+
stateData.build_mode = 'auto';
|
|
107
|
+
await saveState(stateData);
|
|
108
|
+
|
|
109
|
+
// Transition to BUILD_IN_PROGRESS if not already
|
|
110
|
+
if (currentState !== STATES.BUILD_IN_PROGRESS) {
|
|
111
|
+
await transitionTo(STATES.BUILD_IN_PROGRESS, 'auto_build_started');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Load coder pack
|
|
115
|
+
const coderPackContent = await readSessionFile('coder_pack.md');
|
|
116
|
+
|
|
117
|
+
// Build full prompt with CLAUDE.md injection if exists
|
|
118
|
+
const fullPrompt = await buildPromptWithContext(coderPackContent, process.cwd());
|
|
119
|
+
|
|
120
|
+
// Log start
|
|
121
|
+
await appendToFile(logPath, `\n${'='.repeat(60)}\n`);
|
|
122
|
+
await appendToFile(logPath, `AUTO BUILD STARTED: ${startTime}\n`);
|
|
123
|
+
await appendToFile(logPath, `Provider: Claude Code (--dangerously-skip-permissions)\n`);
|
|
124
|
+
await appendToFile(logPath, `${'='.repeat(60)}\n\n`);
|
|
125
|
+
|
|
126
|
+
// Show starting message
|
|
127
|
+
const providerInfo = getProviderInfo();
|
|
128
|
+
|
|
129
|
+
const content = `🤖 AUTO BUILD
|
|
130
|
+
|
|
131
|
+
Project: ${projectName}
|
|
132
|
+
Session: ${sessionId}
|
|
133
|
+
Spec Hash: ${specHash}
|
|
134
|
+
|
|
135
|
+
Provider: ${providerInfo.name}
|
|
136
|
+
Mode: ${providerInfo.mode}
|
|
137
|
+
|
|
138
|
+
Starting AI build session...
|
|
139
|
+
Contract LOCKED = License to build`;
|
|
140
|
+
|
|
141
|
+
console.log();
|
|
142
|
+
printBox(content, { borderColor: 'magenta' });
|
|
143
|
+
console.log();
|
|
144
|
+
console.log(chalk.magenta('─'.repeat(60)));
|
|
145
|
+
console.log(chalk.magenta('│ CLAUDE CODE OUTPUT'));
|
|
146
|
+
console.log(chalk.magenta('─'.repeat(60)));
|
|
147
|
+
console.log();
|
|
148
|
+
|
|
149
|
+
// Spawn Claude Code
|
|
150
|
+
try {
|
|
151
|
+
const result = await spawnClaudeCode(fullPrompt, {
|
|
152
|
+
cwd: process.cwd(),
|
|
153
|
+
logPath: logPath
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
console.log();
|
|
157
|
+
console.log(chalk.magenta('─'.repeat(60)));
|
|
158
|
+
console.log();
|
|
159
|
+
|
|
160
|
+
// Capture evidence
|
|
161
|
+
await captureGitDiff(evidencePath);
|
|
162
|
+
|
|
163
|
+
const endTime = new Date().toISOString();
|
|
164
|
+
await appendToFile(logPath, `\n${'='.repeat(60)}\n`);
|
|
165
|
+
await appendToFile(logPath, `AUTO BUILD COMPLETED: ${endTime}\n`);
|
|
166
|
+
await appendToFile(logPath, `Exit code: ${result.code}\n`);
|
|
167
|
+
await appendToFile(logPath, `${'='.repeat(60)}\n`);
|
|
168
|
+
|
|
169
|
+
// Check evidence
|
|
170
|
+
const evidence = await checkEvidence(evidencePath);
|
|
171
|
+
|
|
172
|
+
// Generate build report
|
|
173
|
+
const reportContent = getBuildReportTemplate(
|
|
174
|
+
projectName,
|
|
175
|
+
sessionId,
|
|
176
|
+
specHash,
|
|
177
|
+
startTime,
|
|
178
|
+
endTime,
|
|
179
|
+
evidence
|
|
180
|
+
);
|
|
181
|
+
await writeSessionFile('build_report.md', reportContent);
|
|
182
|
+
|
|
183
|
+
// Update state
|
|
184
|
+
stateData.build_completed = endTime;
|
|
185
|
+
await saveState(stateData);
|
|
186
|
+
|
|
187
|
+
if (result.success) {
|
|
188
|
+
await transitionTo(STATES.BUILD_DONE, 'auto_build_completed');
|
|
189
|
+
|
|
190
|
+
const duration = Math.round((new Date(endTime) - new Date(startTime)) / 1000 / 60);
|
|
191
|
+
|
|
192
|
+
const successContent = `✅ AUTO BUILD COMPLETED
|
|
193
|
+
|
|
194
|
+
Project: ${projectName}
|
|
195
|
+
Duration: ${duration} minutes
|
|
196
|
+
Provider: Claude Code
|
|
197
|
+
|
|
198
|
+
Evidence:
|
|
199
|
+
${evidence.hasDiff ? ' ✅ changes.diff' : ' ⬜ changes.diff'}
|
|
200
|
+
${evidence.hasLog ? ' ✅ build.log' : ' ⬜ build.log'}
|
|
201
|
+
${evidence.screenshots > 0 ? ` ✅ ${evidence.screenshots} screenshots` : ' ⬜ No screenshots'}`;
|
|
202
|
+
|
|
203
|
+
printBox(successContent, { borderColor: 'green' });
|
|
204
|
+
printNextStep('Run `vibecode review` to validate your build');
|
|
205
|
+
|
|
206
|
+
} else {
|
|
207
|
+
printWarning(`Claude Code exited with code: ${result.code}`);
|
|
208
|
+
console.log(chalk.gray('Check build.log for details.'));
|
|
209
|
+
console.log(chalk.gray('Run `vibecode build --auto` to retry.'));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
} catch (error) {
|
|
213
|
+
await appendToFile(logPath, `\nERROR: ${error.message}\n`);
|
|
214
|
+
printError(`Auto build failed: ${error.message}`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
57
219
|
async function handleBuildStart(currentState, projectName, sessionId, sessionPath, specHash) {
|
|
58
220
|
const spinner = ora('Starting build...').start();
|
|
59
221
|
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Config Command
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { workspaceExists, getWorkspacePath } from '../core/workspace.js';
|
|
8
|
+
import { pathExists, readJson, writeJson } from '../utils/files.js';
|
|
9
|
+
import { PROVIDERS, getDefaultProvider } from '../providers/index.js';
|
|
10
|
+
import { printBox, printError, printSuccess, printInfo } from '../ui/output.js';
|
|
11
|
+
|
|
12
|
+
const CONFIG_FILE = 'config.json';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get config file path
|
|
16
|
+
*/
|
|
17
|
+
function getConfigPath() {
|
|
18
|
+
return path.join(getWorkspacePath(), CONFIG_FILE);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load config
|
|
23
|
+
*/
|
|
24
|
+
async function loadConfig() {
|
|
25
|
+
const configPath = getConfigPath();
|
|
26
|
+
if (await pathExists(configPath)) {
|
|
27
|
+
return await readJson(configPath);
|
|
28
|
+
}
|
|
29
|
+
return getDefaultConfig();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Save config
|
|
34
|
+
*/
|
|
35
|
+
async function saveConfig(config) {
|
|
36
|
+
const configPath = getConfigPath();
|
|
37
|
+
await writeJson(configPath, config);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get default config
|
|
42
|
+
*/
|
|
43
|
+
function getDefaultConfig() {
|
|
44
|
+
return {
|
|
45
|
+
provider: 'claude-code',
|
|
46
|
+
claudeCode: {
|
|
47
|
+
flags: ['--dangerously-skip-permissions'],
|
|
48
|
+
timeout: 30 // minutes
|
|
49
|
+
},
|
|
50
|
+
autoEvidence: true,
|
|
51
|
+
verbose: false
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function configCommand(options = {}) {
|
|
56
|
+
try {
|
|
57
|
+
// Check workspace for set operations
|
|
58
|
+
if (options.provider || options.set) {
|
|
59
|
+
if (!await workspaceExists()) {
|
|
60
|
+
printError('No Vibecode workspace found. Run `vibecode init` first.');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Show current config
|
|
66
|
+
if (options.show || (!options.provider && !options.set)) {
|
|
67
|
+
await showConfig();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Set provider
|
|
72
|
+
if (options.provider) {
|
|
73
|
+
await setProvider(options.provider);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
} catch (error) {
|
|
78
|
+
printError(error.message);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function showConfig() {
|
|
84
|
+
console.log();
|
|
85
|
+
|
|
86
|
+
// Check if workspace exists
|
|
87
|
+
if (!await workspaceExists()) {
|
|
88
|
+
// Show default config
|
|
89
|
+
console.log(chalk.cyan('Default Configuration (no workspace):'));
|
|
90
|
+
console.log();
|
|
91
|
+
const defaultConfig = getDefaultConfig();
|
|
92
|
+
console.log(chalk.gray('Provider: ') + chalk.white(defaultConfig.provider));
|
|
93
|
+
console.log(chalk.gray('Flags: ') + chalk.white(defaultConfig.claudeCode.flags.join(', ')));
|
|
94
|
+
console.log(chalk.gray('Timeout: ') + chalk.white(`${defaultConfig.claudeCode.timeout} minutes`));
|
|
95
|
+
console.log();
|
|
96
|
+
console.log(chalk.gray('Run `vibecode init` to create a workspace.'));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const config = await loadConfig();
|
|
101
|
+
|
|
102
|
+
const content = `Current Configuration
|
|
103
|
+
|
|
104
|
+
Provider: ${config.provider}
|
|
105
|
+
Flags: ${config.claudeCode?.flags?.join(', ') || 'none'}
|
|
106
|
+
Timeout: ${config.claudeCode?.timeout || 30} minutes
|
|
107
|
+
Auto Evidence: ${config.autoEvidence ? 'enabled' : 'disabled'}
|
|
108
|
+
Verbose: ${config.verbose ? 'enabled' : 'disabled'}`;
|
|
109
|
+
|
|
110
|
+
printBox(content, { borderColor: 'cyan' });
|
|
111
|
+
|
|
112
|
+
// Show available providers
|
|
113
|
+
console.log();
|
|
114
|
+
console.log(chalk.cyan('Available Providers:'));
|
|
115
|
+
Object.entries(PROVIDERS).forEach(([key, provider]) => {
|
|
116
|
+
const status = provider.available ? chalk.green('available') : chalk.gray('coming soon');
|
|
117
|
+
const current = key === config.provider ? chalk.yellow(' (current)') : '';
|
|
118
|
+
console.log(chalk.gray(` • ${key}: ${provider.description} [${status}]${current}`));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
console.log();
|
|
122
|
+
console.log(chalk.gray('To change: vibecode config --provider <name>'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function setProvider(providerName) {
|
|
126
|
+
// Validate provider
|
|
127
|
+
if (!PROVIDERS[providerName]) {
|
|
128
|
+
printError(`Unknown provider: ${providerName}`);
|
|
129
|
+
console.log();
|
|
130
|
+
console.log(chalk.cyan('Available providers:'));
|
|
131
|
+
Object.keys(PROVIDERS).forEach(key => {
|
|
132
|
+
console.log(chalk.gray(` • ${key}`));
|
|
133
|
+
});
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!PROVIDERS[providerName].available) {
|
|
138
|
+
printError(`Provider "${providerName}" is not yet available.`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Load and update config
|
|
143
|
+
const config = await loadConfig();
|
|
144
|
+
config.provider = providerName;
|
|
145
|
+
await saveConfig(config);
|
|
146
|
+
|
|
147
|
+
printSuccess(`Provider set to: ${providerName}`);
|
|
148
|
+
console.log(chalk.gray(`Config saved to: ${getConfigPath()}`));
|
|
149
|
+
}
|
package/src/config/constants.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Spec Hash: 0fe43335f5a325e3279a079ce616c052
|
|
4
4
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
5
|
|
|
6
|
-
export const VERSION = '
|
|
6
|
+
export const VERSION = '2.0.0';
|
|
7
7
|
export const SPEC_HASH = '0fe43335f5a325e3279a079ce616c052';
|
|
8
8
|
|
|
9
9
|
// ─────────────────────────────────────────────────────────────────────────────
|
package/src/index.js
CHANGED
|
@@ -16,4 +16,11 @@ export { buildCommand } from './commands/build.js';
|
|
|
16
16
|
export { reviewCommand } from './commands/review.js';
|
|
17
17
|
export { snapshotCommand } from './commands/snapshot.js';
|
|
18
18
|
|
|
19
|
+
// Phase C Commands
|
|
20
|
+
export { configCommand } from './commands/config.js';
|
|
21
|
+
|
|
22
|
+
// Constants
|
|
19
23
|
export { VERSION, SPEC_HASH, STATES } from './config/constants.js';
|
|
24
|
+
|
|
25
|
+
// Providers
|
|
26
|
+
export { PROVIDERS, getProvider, getDefaultProvider } from './providers/index.js';
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Claude Code Provider
|
|
3
|
+
// "Claude/LLM là PIPELINE, là KIẾN TRÚC SƯ"
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { pathExists, appendToFile } from '../utils/files.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Claude Code optimal configuration
|
|
12
|
+
* Contract LOCKED = License to build (không cần hỏi thêm)
|
|
13
|
+
*/
|
|
14
|
+
export const CLAUDE_CODE_CONFIG = {
|
|
15
|
+
command: 'claude',
|
|
16
|
+
flags: [
|
|
17
|
+
'--dangerously-skip-permissions', // Trust the AI - Contract đã locked
|
|
18
|
+
],
|
|
19
|
+
timeout: 30 * 60 * 1000, // 30 minutes max
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if Claude Code CLI is available
|
|
24
|
+
*/
|
|
25
|
+
export async function isClaudeCodeAvailable() {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
const proc = spawn('which', ['claude'], { shell: true });
|
|
28
|
+
proc.on('close', (code) => {
|
|
29
|
+
resolve(code === 0);
|
|
30
|
+
});
|
|
31
|
+
proc.on('error', () => {
|
|
32
|
+
resolve(false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Spawn Claude Code with optimal settings
|
|
39
|
+
*
|
|
40
|
+
* @param {string} prompt - The coder pack / prompt to send
|
|
41
|
+
* @param {object} options - Configuration options
|
|
42
|
+
* @param {string} options.cwd - Working directory
|
|
43
|
+
* @param {string} options.logPath - Path to write build logs
|
|
44
|
+
* @param {function} options.onOutput - Callback for output
|
|
45
|
+
* @returns {Promise<{success: boolean, code: number}>}
|
|
46
|
+
*/
|
|
47
|
+
export async function spawnClaudeCode(prompt, options = {}) {
|
|
48
|
+
const { cwd, logPath, onOutput } = options;
|
|
49
|
+
const fs = await import('fs-extra');
|
|
50
|
+
const os = await import('os');
|
|
51
|
+
|
|
52
|
+
// Check if Claude Code is available
|
|
53
|
+
const available = await isClaudeCodeAvailable();
|
|
54
|
+
if (!available) {
|
|
55
|
+
throw new Error('Claude Code CLI not found. Install with: npm install -g @anthropic/claude-code');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Write prompt to temp file to avoid shell escaping issues
|
|
59
|
+
const tempDir = os.default.tmpdir();
|
|
60
|
+
const promptFile = path.join(tempDir, `vibecode-prompt-${Date.now()}.md`);
|
|
61
|
+
await fs.default.writeFile(promptFile, prompt, 'utf-8');
|
|
62
|
+
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
// Use cat to pipe the prompt file content to claude
|
|
65
|
+
const command = `cat "${promptFile}" | claude ${CLAUDE_CODE_CONFIG.flags.join(' ')}`;
|
|
66
|
+
|
|
67
|
+
// Log the command being run
|
|
68
|
+
if (logPath) {
|
|
69
|
+
appendToFile(logPath, `\n[${new Date().toISOString()}] Running: claude with prompt from ${promptFile}\n`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const proc = spawn(command, [], {
|
|
73
|
+
cwd: cwd || process.cwd(),
|
|
74
|
+
stdio: 'inherit', // Stream directly to terminal
|
|
75
|
+
shell: true,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
let timeoutId = setTimeout(() => {
|
|
79
|
+
proc.kill();
|
|
80
|
+
// Cleanup temp file
|
|
81
|
+
fs.default.remove(promptFile).catch(() => {});
|
|
82
|
+
reject(new Error('Claude Code process timed out'));
|
|
83
|
+
}, CLAUDE_CODE_CONFIG.timeout);
|
|
84
|
+
|
|
85
|
+
proc.on('close', async (code) => {
|
|
86
|
+
clearTimeout(timeoutId);
|
|
87
|
+
|
|
88
|
+
// Cleanup temp file
|
|
89
|
+
await fs.default.remove(promptFile).catch(() => {});
|
|
90
|
+
|
|
91
|
+
const result = {
|
|
92
|
+
success: code === 0,
|
|
93
|
+
code: code || 0,
|
|
94
|
+
timestamp: new Date().toISOString()
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (logPath) {
|
|
98
|
+
const status = result.success ? 'SUCCESS' : 'FAILED';
|
|
99
|
+
appendToFile(logPath, `\n[${result.timestamp}] Claude Code ${status} (exit code: ${code})\n`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
resolve(result);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
proc.on('error', async (error) => {
|
|
106
|
+
clearTimeout(timeoutId);
|
|
107
|
+
// Cleanup temp file
|
|
108
|
+
await fs.default.remove(promptFile).catch(() => {});
|
|
109
|
+
|
|
110
|
+
if (logPath) {
|
|
111
|
+
appendToFile(logPath, `\n[${new Date().toISOString()}] ERROR: ${error.message}\n`);
|
|
112
|
+
}
|
|
113
|
+
reject(error);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Build prompt with optional CLAUDE.md injection
|
|
120
|
+
*
|
|
121
|
+
* @param {string} coderPackContent - Content of coder_pack.md
|
|
122
|
+
* @param {string} projectRoot - Project root directory
|
|
123
|
+
* @returns {Promise<string>} - Final prompt
|
|
124
|
+
*/
|
|
125
|
+
export async function buildPromptWithContext(coderPackContent, projectRoot) {
|
|
126
|
+
let fullPrompt = coderPackContent;
|
|
127
|
+
|
|
128
|
+
// Check for CLAUDE.md in project root
|
|
129
|
+
const claudeMdPath = path.join(projectRoot, 'CLAUDE.md');
|
|
130
|
+
if (await pathExists(claudeMdPath)) {
|
|
131
|
+
const fs = await import('fs-extra');
|
|
132
|
+
const claudeMd = await fs.default.readFile(claudeMdPath, 'utf-8');
|
|
133
|
+
|
|
134
|
+
// Inject CLAUDE.md rules before coder pack
|
|
135
|
+
fullPrompt = `# PROJECT RULES (from CLAUDE.md)
|
|
136
|
+
|
|
137
|
+
${claudeMd}
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
# BUILD INSTRUCTIONS
|
|
142
|
+
|
|
143
|
+
${coderPackContent}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return fullPrompt;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get provider info for status display
|
|
151
|
+
*/
|
|
152
|
+
export function getProviderInfo() {
|
|
153
|
+
return {
|
|
154
|
+
name: 'Claude Code',
|
|
155
|
+
command: CLAUDE_CODE_CONFIG.command,
|
|
156
|
+
mode: '--dangerously-skip-permissions',
|
|
157
|
+
description: 'AI coding with guardrails disabled (contract-approved)'
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - AI Provider Manager
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
spawnClaudeCode,
|
|
7
|
+
isClaudeCodeAvailable,
|
|
8
|
+
buildPromptWithContext,
|
|
9
|
+
getProviderInfo,
|
|
10
|
+
CLAUDE_CODE_CONFIG
|
|
11
|
+
} from './claude-code.js';
|
|
12
|
+
|
|
13
|
+
// Future providers:
|
|
14
|
+
// export { callAnthropicAPI } from './anthropic-api.js';
|
|
15
|
+
// export { spawnCursor } from './cursor.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Available providers
|
|
19
|
+
*/
|
|
20
|
+
export const PROVIDERS = {
|
|
21
|
+
'claude-code': {
|
|
22
|
+
name: 'Claude Code',
|
|
23
|
+
description: 'Official Claude CLI for coding',
|
|
24
|
+
available: true
|
|
25
|
+
},
|
|
26
|
+
'anthropic-api': {
|
|
27
|
+
name: 'Anthropic API',
|
|
28
|
+
description: 'Direct API calls (coming soon)',
|
|
29
|
+
available: false
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get provider by name
|
|
35
|
+
*/
|
|
36
|
+
export function getProvider(name) {
|
|
37
|
+
return PROVIDERS[name] || null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get default provider
|
|
42
|
+
*/
|
|
43
|
+
export function getDefaultProvider() {
|
|
44
|
+
return 'claude-code';
|
|
45
|
+
}
|