@nclamvn/vibecode-cli 1.0.0 → 1.0.1

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 CHANGED
@@ -12,6 +12,11 @@ import {
12
12
  statusCommand,
13
13
  lockCommand,
14
14
  doctorCommand,
15
+ planCommand,
16
+ buildCommand,
17
+ reviewCommand,
18
+ snapshotCommand,
19
+ configCommand,
15
20
  VERSION
16
21
  } from '../src/index.js';
17
22
 
@@ -23,7 +28,7 @@ program
23
28
  .version(VERSION);
24
29
 
25
30
  // ─────────────────────────────────────────────────────────────────────────────
26
- // Commands
31
+ // Phase A Commands - Planning
27
32
  // ─────────────────────────────────────────────────────────────────────────────
28
33
 
29
34
  program
@@ -58,6 +63,50 @@ program
58
63
  .description('Check configuration and diagnose issues')
59
64
  .action(doctorCommand);
60
65
 
66
+ // ─────────────────────────────────────────────────────────────────────────────
67
+ // Phase B Commands - Execution
68
+ // ─────────────────────────────────────────────────────────────────────────────
69
+
70
+ program
71
+ .command('plan')
72
+ .description('Create execution plan from locked contract')
73
+ .action(planCommand);
74
+
75
+ program
76
+ .command('build')
77
+ .description('Manage build process and capture evidence')
78
+ .option('-s, --start', 'Start the build process')
79
+ .option('-c, --complete', 'Mark build as complete')
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')
83
+ .action(buildCommand);
84
+
85
+ program
86
+ .command('review')
87
+ .description('Review build against acceptance criteria')
88
+ .option('--skip-manual', 'Skip manual verification prompts')
89
+ .action(reviewCommand);
90
+
91
+ program
92
+ .command('snapshot')
93
+ .description('Create release snapshot with version bump')
94
+ .option('--patch', 'Patch version bump (default)')
95
+ .option('--minor', 'Minor version bump')
96
+ .option('--major', 'Major version bump')
97
+ .action(snapshotCommand);
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
+
61
110
  // ─────────────────────────────────────────────────────────────────────────────
62
111
  // Parse
63
112
  // ─────────────────────────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nclamvn/vibecode-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Build software with discipline - AI coding with guardrails",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -0,0 +1,470 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Build Command
3
+ // "Claude/LLM là PIPELINE, là KIẾN TRÚC SƯ"
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ import chalk from 'chalk';
7
+ import ora from 'ora';
8
+ import path from 'path';
9
+ import { exec } from 'child_process';
10
+ import { promisify } from 'util';
11
+ import { workspaceExists, getProjectName, loadState, saveState } from '../core/workspace.js';
12
+ import {
13
+ getCurrentSessionId,
14
+ getCurrentSessionPath,
15
+ writeSessionFile,
16
+ sessionFileExists,
17
+ readSessionFile
18
+ } from '../core/session.js';
19
+ import { getCurrentState, transitionTo } from '../core/state-machine.js';
20
+ import { getSpecHash } from '../core/contract.js';
21
+ import { STATES } from '../config/constants.js';
22
+ import { getBuildReportTemplate } from '../config/templates.js';
23
+ import { ensureDir, pathExists, appendToFile, readMarkdown } from '../utils/files.js';
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';
31
+
32
+ const execAsync = promisify(exec);
33
+
34
+ export async function buildCommand(options = {}) {
35
+ try {
36
+ // Check workspace
37
+ if (!await workspaceExists()) {
38
+ printError('No Vibecode workspace found. Run `vibecode init` first.');
39
+ process.exit(1);
40
+ }
41
+
42
+ const currentState = await getCurrentState();
43
+ const projectName = await getProjectName();
44
+ const sessionId = await getCurrentSessionId();
45
+ const sessionPath = await getCurrentSessionPath();
46
+ const specHash = await getSpecHash();
47
+
48
+ // Handle different build modes
49
+ if (options.auto) {
50
+ await handleAutoBuild(currentState, projectName, sessionId, sessionPath, specHash, options);
51
+ } else if (options.start) {
52
+ await handleBuildStart(currentState, projectName, sessionId, sessionPath, specHash);
53
+ } else if (options.complete) {
54
+ await handleBuildComplete(currentState, projectName, sessionId, sessionPath, specHash);
55
+ } else if (options.evidence) {
56
+ await handleCaptureEvidence(currentState, sessionPath);
57
+ } else {
58
+ await handleBuildStatus(currentState, projectName, sessionId, sessionPath, specHash);
59
+ }
60
+
61
+ } catch (error) {
62
+ printError(error.message);
63
+ process.exit(1);
64
+ }
65
+ }
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
+
219
+ async function handleBuildStart(currentState, projectName, sessionId, sessionPath, specHash) {
220
+ const spinner = ora('Starting build...').start();
221
+
222
+ // Check state
223
+ if (currentState !== STATES.PLAN_CREATED && currentState !== STATES.REVIEW_FAILED) {
224
+ spinner.fail();
225
+ printError(`Cannot start build in state: ${currentState}`);
226
+ console.log('Run `vibecode plan` first to create execution plan.');
227
+ process.exit(1);
228
+ }
229
+
230
+ // Create evidence directory
231
+ const evidencePath = path.join(sessionPath, 'evidence');
232
+ await ensureDir(evidencePath);
233
+ await ensureDir(path.join(evidencePath, 'screenshots'));
234
+
235
+ // Save build start time
236
+ const stateData = await loadState();
237
+ stateData.build_started = new Date().toISOString();
238
+ await saveState(stateData);
239
+
240
+ // Initialize build log
241
+ const logPath = path.join(evidencePath, 'build.log');
242
+ await appendToFile(logPath, `=== BUILD STARTED: ${stateData.build_started} ===\n`);
243
+
244
+ // Transition state
245
+ await transitionTo(STATES.BUILD_IN_PROGRESS, 'build_started');
246
+
247
+ spinner.succeed('Build started!');
248
+
249
+ const content = `🏗️ BUILD IN PROGRESS
250
+
251
+ Project: ${projectName}
252
+ Session: ${sessionId}
253
+ Spec Hash: ${specHash}
254
+ Started: ${stateData.build_started}
255
+
256
+ Evidence folder ready:
257
+ ${evidencePath}/
258
+ ├── build.log
259
+ └── screenshots/`;
260
+
261
+ console.log();
262
+ printBox(content, { borderColor: 'yellow' });
263
+
264
+ console.log();
265
+ console.log(chalk.cyan('📝 While building:'));
266
+ console.log(chalk.gray(' • Follow coder_pack.md instructions'));
267
+ console.log(chalk.gray(' • Capture evidence with `vibecode build --evidence`'));
268
+ console.log(chalk.gray(' • Complete with `vibecode build --complete`'));
269
+
270
+ printNextStep('Build your deliverables and run `vibecode build --complete` when done');
271
+ }
272
+
273
+ async function handleBuildComplete(currentState, projectName, sessionId, sessionPath, specHash) {
274
+ const spinner = ora('Completing build...').start();
275
+
276
+ // Check state
277
+ if (currentState !== STATES.BUILD_IN_PROGRESS) {
278
+ spinner.fail();
279
+ printError(`Cannot complete build in state: ${currentState}`);
280
+ console.log('Run `vibecode build --start` first.');
281
+ process.exit(1);
282
+ }
283
+
284
+ const stateData = await loadState();
285
+ const startTime = stateData.build_started;
286
+ const endTime = new Date().toISOString();
287
+
288
+ // Capture final evidence
289
+ spinner.text = 'Capturing evidence...';
290
+ const evidencePath = path.join(sessionPath, 'evidence');
291
+ await captureGitDiff(evidencePath);
292
+
293
+ // Append to build log
294
+ const logPath = path.join(evidencePath, 'build.log');
295
+ await appendToFile(logPath, `\n=== BUILD COMPLETED: ${endTime} ===`);
296
+
297
+ // Check evidence collected
298
+ const evidence = await checkEvidence(evidencePath);
299
+
300
+ // Generate build report
301
+ spinner.text = 'Generating build report...';
302
+ const reportContent = getBuildReportTemplate(
303
+ projectName,
304
+ sessionId,
305
+ specHash,
306
+ startTime,
307
+ endTime,
308
+ evidence
309
+ );
310
+ await writeSessionFile('build_report.md', reportContent);
311
+
312
+ // Save end time
313
+ stateData.build_completed = endTime;
314
+ await saveState(stateData);
315
+
316
+ // Transition state
317
+ await transitionTo(STATES.BUILD_DONE, 'build_completed');
318
+
319
+ spinner.succeed('Build completed!');
320
+
321
+ const duration = Math.round((new Date(endTime) - new Date(startTime)) / 1000 / 60);
322
+
323
+ const content = `✅ BUILD COMPLETED
324
+
325
+ Project: ${projectName}
326
+ Duration: ${duration} minutes
327
+ Evidence collected:
328
+ ${evidence.hasDiff ? ' ✅ changes.diff' : ' ⬜ changes.diff'}
329
+ ${evidence.hasLog ? ' ✅ build.log' : ' ⬜ build.log'}
330
+ ${evidence.screenshots > 0 ? ` ✅ ${evidence.screenshots} screenshots` : ' ⬜ No screenshots'}`;
331
+
332
+ console.log();
333
+ printBox(content, { borderColor: 'green' });
334
+
335
+ printNextStep('Run `vibecode review` to validate your build');
336
+ }
337
+
338
+ async function handleCaptureEvidence(currentState, sessionPath) {
339
+ const spinner = ora('Capturing evidence...').start();
340
+
341
+ if (currentState !== STATES.BUILD_IN_PROGRESS) {
342
+ spinner.fail();
343
+ printError('Can only capture evidence during BUILD_IN_PROGRESS state');
344
+ process.exit(1);
345
+ }
346
+
347
+ const evidencePath = path.join(sessionPath, 'evidence');
348
+ await ensureDir(evidencePath);
349
+
350
+ // Capture git diff
351
+ await captureGitDiff(evidencePath);
352
+
353
+ // Append to build log
354
+ const timestamp = new Date().toISOString();
355
+ await appendToFile(
356
+ path.join(evidencePath, 'build.log'),
357
+ `\n[${timestamp}] Evidence snapshot captured\n`
358
+ );
359
+
360
+ spinner.succeed('Evidence captured!');
361
+
362
+ const evidence = await checkEvidence(evidencePath);
363
+ console.log();
364
+ console.log(chalk.cyan('📁 Evidence collected:'));
365
+ console.log(chalk.gray(` ${evidence.hasDiff ? '✅' : '⬜'} changes.diff`));
366
+ console.log(chalk.gray(` ${evidence.hasLog ? '✅' : '⬜'} build.log`));
367
+ console.log(chalk.gray(` ${evidence.screenshots > 0 ? `✅ ${evidence.screenshots}` : '⬜ 0'} screenshots`));
368
+ }
369
+
370
+ async function handleBuildStatus(currentState, projectName, sessionId, sessionPath, specHash) {
371
+ if (currentState === STATES.BUILD_IN_PROGRESS) {
372
+ const stateData = await loadState();
373
+ const evidencePath = path.join(sessionPath, 'evidence');
374
+ const evidence = await checkEvidence(evidencePath);
375
+
376
+ const elapsed = Math.round((new Date() - new Date(stateData.build_started)) / 1000 / 60);
377
+
378
+ const content = `🏗️ BUILD IN PROGRESS
379
+
380
+ Project: ${projectName}
381
+ Session: ${sessionId}
382
+ Started: ${stateData.build_started}
383
+ Elapsed: ${elapsed} minutes
384
+
385
+ Evidence:
386
+ ${evidence.hasDiff ? ' ✅ changes.diff' : ' ⬜ changes.diff'}
387
+ ${evidence.hasLog ? ' ✅ build.log' : ' ⬜ build.log'}
388
+ ${evidence.screenshots > 0 ? ` ✅ ${evidence.screenshots} screenshots` : ' ⬜ No screenshots'}`;
389
+
390
+ console.log();
391
+ printBox(content, { borderColor: 'yellow' });
392
+
393
+ console.log();
394
+ console.log(chalk.cyan('Commands:'));
395
+ console.log(chalk.gray(' --evidence Capture current git diff'));
396
+ console.log(chalk.gray(' --complete Mark build as done'));
397
+
398
+ } else if (currentState === STATES.BUILD_DONE) {
399
+ printSuccess('Build already completed!');
400
+ printNextStep('Run `vibecode review` to validate');
401
+
402
+ } else {
403
+ printWarning(`Current state: ${currentState}`);
404
+ console.log('Run `vibecode build --start` to begin building.');
405
+ }
406
+ }
407
+
408
+ async function captureGitDiff(evidencePath) {
409
+ try {
410
+ const { stdout } = await execAsync('git diff HEAD', { maxBuffer: 10 * 1024 * 1024 });
411
+ const diffPath = path.join(evidencePath, 'changes.diff');
412
+
413
+ if (stdout.trim()) {
414
+ const fs = await import('fs-extra');
415
+ await fs.default.writeFile(diffPath, stdout, 'utf-8');
416
+ } else {
417
+ // Try staged changes
418
+ const { stdout: stagedDiff } = await execAsync('git diff --cached', { maxBuffer: 10 * 1024 * 1024 });
419
+ if (stagedDiff.trim()) {
420
+ const fs = await import('fs-extra');
421
+ await fs.default.writeFile(diffPath, stagedDiff, 'utf-8');
422
+ }
423
+ }
424
+ } catch (error) {
425
+ // Git might not be available, that's okay
426
+ }
427
+ }
428
+
429
+ async function checkEvidence(evidencePath) {
430
+ const result = {
431
+ hasDiff: false,
432
+ hasLog: false,
433
+ screenshots: 0,
434
+ filesChanged: 0,
435
+ linesAdded: 0,
436
+ linesRemoved: 0
437
+ };
438
+
439
+ try {
440
+ result.hasDiff = await pathExists(path.join(evidencePath, 'changes.diff'));
441
+ result.hasLog = await pathExists(path.join(evidencePath, 'build.log'));
442
+
443
+ const screenshotsPath = path.join(evidencePath, 'screenshots');
444
+ if (await pathExists(screenshotsPath)) {
445
+ const fs = await import('fs-extra');
446
+ const files = await fs.default.readdir(screenshotsPath);
447
+ result.screenshots = files.filter(f => /\.(png|jpg|jpeg|gif)$/i.test(f)).length;
448
+ }
449
+
450
+ // Parse git diff stats
451
+ if (result.hasDiff) {
452
+ try {
453
+ const { stdout } = await execAsync('git diff --stat HEAD');
454
+ const statsLine = stdout.split('\n').pop();
455
+ const match = statsLine?.match(/(\d+) files? changed(?:, (\d+) insertions?)?(?:, (\d+) deletions?)?/);
456
+ if (match) {
457
+ result.filesChanged = parseInt(match[1]) || 0;
458
+ result.linesAdded = parseInt(match[2]) || 0;
459
+ result.linesRemoved = parseInt(match[3]) || 0;
460
+ }
461
+ } catch (e) {
462
+ // Ignore git errors
463
+ }
464
+ }
465
+ } catch (error) {
466
+ // Ignore errors
467
+ }
468
+
469
+ return result;
470
+ }
@@ -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
+ }