@codebakers/cli 3.8.1 → 3.8.2

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.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * CODEBAKERS BUILD COMMAND
3
+ *
4
+ * This is the zero-friction project builder.
5
+ * User describes what they want → AI builds actual files on their machine.
6
+ *
7
+ * Flow:
8
+ * 1. User runs: codebakers build "SaaS for invoicing"
9
+ * 2. CLI creates engineering session on server
10
+ * 3. Server runs AI agents to generate PRD, specs, code
11
+ * 4. CLI receives file contents and writes them to disk
12
+ * 5. User has a runnable project
13
+ */
14
+ interface BuildOptions {
15
+ description?: string;
16
+ output?: string;
17
+ verbose?: boolean;
18
+ }
19
+ /**
20
+ * Main build command
21
+ */
22
+ export declare function build(options?: BuildOptions): Promise<void>;
23
+ /**
24
+ * Check build status
25
+ */
26
+ export declare function buildStatus(): Promise<void>;
27
+ export {};
@@ -0,0 +1,325 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.build = build;
7
+ exports.buildStatus = buildStatus;
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const ora_1 = __importDefault(require("ora"));
10
+ const readline_1 = require("readline");
11
+ const fs_1 = require("fs");
12
+ const path_1 = require("path");
13
+ const config_js_1 = require("../config.js");
14
+ async function prompt(question) {
15
+ const rl = (0, readline_1.createInterface)({
16
+ input: process.stdin,
17
+ output: process.stdout,
18
+ });
19
+ return new Promise((resolve) => {
20
+ rl.question(question, (answer) => {
21
+ rl.close();
22
+ resolve(answer.trim());
23
+ });
24
+ });
25
+ }
26
+ /**
27
+ * Main build command
28
+ */
29
+ async function build(options = {}) {
30
+ console.log(chalk_1.default.blue(`
31
+ ╔═══════════════════════════════════════════════════════════╗
32
+ ║ ║
33
+ ║ ${chalk_1.default.bold('CodeBakers Build')} ║
34
+ ║ ║
35
+ ║ Describe your project → Get working code ║
36
+ ║ ║
37
+ ╚═══════════════════════════════════════════════════════════╝
38
+ `));
39
+ // Check authentication
40
+ if (!(0, config_js_1.hasValidAccess)()) {
41
+ console.log(chalk_1.default.red(' ✗ Not authenticated\n'));
42
+ console.log(chalk_1.default.gray(' Run: codebakers go (free trial) or codebakers setup (with API key)\n'));
43
+ process.exit(1);
44
+ }
45
+ // Get project description
46
+ let description = options.description;
47
+ if (!description) {
48
+ console.log(chalk_1.default.white(' What do you want to build?\n'));
49
+ console.log(chalk_1.default.gray(' Examples:'));
50
+ console.log(chalk_1.default.gray(' • "A SaaS for managing invoices with Stripe payments"'));
51
+ console.log(chalk_1.default.gray(' • "Todo app with user auth and real-time sync"'));
52
+ console.log(chalk_1.default.gray(' • "Blog platform with markdown support"\n'));
53
+ description = await prompt(' Describe your project: ');
54
+ if (!description.trim()) {
55
+ console.log(chalk_1.default.red('\n Please provide a project description.\n'));
56
+ process.exit(1);
57
+ }
58
+ }
59
+ const outputDir = options.output || process.cwd();
60
+ // Check if directory is empty
61
+ const files = (0, fs_1.existsSync)(outputDir) ?
62
+ require('fs').readdirSync(outputDir).filter((f) => !f.startsWith('.')) : [];
63
+ if (files.length > 0) {
64
+ console.log(chalk_1.default.yellow('\n ⚠️ This directory is not empty.'));
65
+ const proceed = await prompt(' Continue? (y/N): ');
66
+ if (proceed.toLowerCase() !== 'y') {
67
+ console.log(chalk_1.default.gray('\n Build cancelled.\n'));
68
+ return;
69
+ }
70
+ }
71
+ console.log(chalk_1.default.green(`\n Building: "${description}"\n`));
72
+ // Create engineering session
73
+ const spinner = (0, ora_1.default)(' Initializing build...').start();
74
+ try {
75
+ const apiUrl = (0, config_js_1.getApiUrl)();
76
+ const apiKey = (0, config_js_1.getApiKey)();
77
+ const trial = (0, config_js_1.getTrialState)();
78
+ let authHeader = '';
79
+ if (apiKey) {
80
+ authHeader = `Bearer ${apiKey}`;
81
+ }
82
+ else if (trial?.trialId) {
83
+ authHeader = `Trial ${trial.trialId}`;
84
+ }
85
+ if (!authHeader) {
86
+ spinner.fail('Authentication required');
87
+ console.log(chalk_1.default.gray('\n Run: codebakers go\n'));
88
+ return;
89
+ }
90
+ // Step 1: Create engineering session
91
+ spinner.text = ' Creating build session...';
92
+ const createResponse = await fetch(`${apiUrl}/api/engineering/sessions`, {
93
+ method: 'POST',
94
+ headers: {
95
+ 'Content-Type': 'application/json',
96
+ 'Authorization': authHeader,
97
+ },
98
+ body: JSON.stringify({
99
+ projectName: description,
100
+ projectDescription: description,
101
+ source: 'cli',
102
+ }),
103
+ });
104
+ if (!createResponse.ok) {
105
+ const error = await createResponse.json().catch(() => ({}));
106
+ throw new Error(error.error || 'Failed to create build session');
107
+ }
108
+ const { data: sessionData } = await createResponse.json();
109
+ const sessionId = sessionData.sessionId;
110
+ spinner.text = ' Session created, starting build...';
111
+ // Step 2: Auto-complete scoping
112
+ await fetch(`${apiUrl}/api/engineering/sessions/${sessionId}/scope`, {
113
+ method: 'POST',
114
+ headers: {
115
+ 'Content-Type': 'application/json',
116
+ 'Authorization': authHeader,
117
+ },
118
+ body: JSON.stringify({ stepId: 'auto', answer: description }),
119
+ });
120
+ // Step 3: Start the build with file generation enabled
121
+ const buildResponse = await fetch(`${apiUrl}/api/engineering/sessions/${sessionId}/build`, {
122
+ method: 'POST',
123
+ headers: {
124
+ 'Content-Type': 'application/json',
125
+ 'Authorization': authHeader,
126
+ },
127
+ body: JSON.stringify({
128
+ generateFiles: true, // Tell server to generate actual file contents
129
+ }),
130
+ });
131
+ if (!buildResponse.ok) {
132
+ const error = await buildResponse.json().catch(() => ({}));
133
+ throw new Error(error.error || 'Failed to start build');
134
+ }
135
+ spinner.succeed('Build started!');
136
+ console.log('');
137
+ // Step 4: Stream progress and receive files
138
+ await streamBuildProgress(apiUrl, authHeader, sessionId, outputDir, options.verbose);
139
+ }
140
+ catch (error) {
141
+ spinner.fail('Build failed');
142
+ const message = error instanceof Error ? error.message : 'Unknown error';
143
+ console.log(chalk_1.default.red(`\n Error: ${message}\n`));
144
+ process.exit(1);
145
+ }
146
+ }
147
+ /**
148
+ * Stream build progress and write files as they're generated
149
+ */
150
+ async function streamBuildProgress(apiUrl, authHeader, sessionId, outputDir, verbose) {
151
+ const phases = new Map();
152
+ let filesCreated = 0;
153
+ let currentSpinner = null;
154
+ // Display phase progress
155
+ function displayPhase(phase, status, displayName) {
156
+ const icon = status === 'completed' ? chalk_1.default.green('✓') :
157
+ status === 'in_progress' ? chalk_1.default.blue('●') :
158
+ status === 'failed' ? chalk_1.default.red('✗') : chalk_1.default.gray('○');
159
+ if (status === 'in_progress') {
160
+ if (currentSpinner)
161
+ currentSpinner.stop();
162
+ currentSpinner = (0, ora_1.default)(` ${displayName}...`).start();
163
+ }
164
+ else if (status === 'completed' && currentSpinner) {
165
+ currentSpinner.succeed(` ${displayName}`);
166
+ currentSpinner = null;
167
+ }
168
+ else if (status === 'failed' && currentSpinner) {
169
+ currentSpinner.fail(` ${displayName}`);
170
+ currentSpinner = null;
171
+ }
172
+ }
173
+ // Write a file to disk
174
+ function writeFile(file) {
175
+ const fullPath = (0, path_1.join)(outputDir, file.path);
176
+ const dir = (0, path_1.dirname)(fullPath);
177
+ if (!(0, fs_1.existsSync)(dir)) {
178
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
179
+ }
180
+ (0, fs_1.writeFileSync)(fullPath, file.content);
181
+ filesCreated++;
182
+ if (verbose) {
183
+ console.log(chalk_1.default.gray(` + ${file.path}`));
184
+ }
185
+ }
186
+ // Poll for updates (SSE would be better but this works for CLI)
187
+ let isComplete = false;
188
+ let pollCount = 0;
189
+ const maxPolls = 300; // 10 minutes max (2s intervals)
190
+ while (!isComplete && pollCount < maxPolls) {
191
+ try {
192
+ const response = await fetch(`${apiUrl}/api/engineering/sessions/${sessionId}/progress`, {
193
+ headers: {
194
+ 'Authorization': authHeader,
195
+ },
196
+ });
197
+ if (!response.ok) {
198
+ throw new Error('Failed to get build progress');
199
+ }
200
+ const data = await response.json();
201
+ const progress = data.data || data;
202
+ // Update phase displays
203
+ if (progress.phases) {
204
+ for (const phase of progress.phases) {
205
+ const existing = phases.get(phase.phase);
206
+ if (!existing || existing.status !== phase.status) {
207
+ phases.set(phase.phase, phase);
208
+ displayPhase(phase.phase, phase.status, phase.displayName || phase.phase);
209
+ }
210
+ }
211
+ }
212
+ // Write any new files
213
+ if (progress.newFiles && progress.newFiles.length > 0) {
214
+ for (const file of progress.newFiles) {
215
+ writeFile(file);
216
+ }
217
+ }
218
+ // Check if build is complete
219
+ if (progress.status === 'completed' || progress.status === 'failed' || progress.status === 'abandoned') {
220
+ isComplete = true;
221
+ if (currentSpinner !== null) {
222
+ currentSpinner.stop();
223
+ currentSpinner = null;
224
+ }
225
+ if (progress.status === 'completed') {
226
+ // Write final files if any
227
+ if (progress.files && progress.files.length > 0) {
228
+ console.log(chalk_1.default.white('\n Writing project files...\n'));
229
+ const fileSpinner = (0, ora_1.default)(' Creating files...').start();
230
+ for (const file of progress.files) {
231
+ writeFile(file);
232
+ }
233
+ fileSpinner.succeed(` Created ${filesCreated} files`);
234
+ }
235
+ // Success message
236
+ console.log(chalk_1.default.green(`
237
+ ╔═══════════════════════════════════════════════════════════╗
238
+ ║ ║
239
+ ║ ${chalk_1.default.bold('✓ Build complete!')} ║
240
+ ║ ║
241
+ ╚═══════════════════════════════════════════════════════════╝
242
+ `));
243
+ console.log(chalk_1.default.white(' Next steps:\n'));
244
+ console.log(chalk_1.default.cyan(' 1. ') + chalk_1.default.white('Install dependencies:'));
245
+ console.log(chalk_1.default.gray(' npm install\n'));
246
+ console.log(chalk_1.default.cyan(' 2. ') + chalk_1.default.white('Set up your database:'));
247
+ console.log(chalk_1.default.gray(' npx drizzle-kit db:push\n'));
248
+ console.log(chalk_1.default.cyan(' 3. ') + chalk_1.default.white('Start the dev server:'));
249
+ console.log(chalk_1.default.gray(' npm run dev\n'));
250
+ // Show summary
251
+ if (progress.summary) {
252
+ console.log(chalk_1.default.gray(` Summary: ${progress.summary.filesCreated || filesCreated} files, ${progress.summary.tokensUsed || 0} tokens used\n`));
253
+ }
254
+ }
255
+ else {
256
+ console.log(chalk_1.default.red(`\n Build ${progress.status}: ${progress.lastError || 'Unknown error'}\n`));
257
+ }
258
+ }
259
+ }
260
+ catch (error) {
261
+ if (verbose) {
262
+ console.log(chalk_1.default.gray(` Poll error: ${error}`));
263
+ }
264
+ }
265
+ if (!isComplete) {
266
+ await new Promise(resolve => setTimeout(resolve, 2000));
267
+ pollCount++;
268
+ }
269
+ }
270
+ if (!isComplete) {
271
+ if (currentSpinner !== null) {
272
+ currentSpinner.fail('Build timed out');
273
+ }
274
+ console.log(chalk_1.default.yellow('\n Build is taking longer than expected.'));
275
+ console.log(chalk_1.default.gray(' Check status: codebakers build-status\n'));
276
+ }
277
+ }
278
+ /**
279
+ * Check build status
280
+ */
281
+ async function buildStatus() {
282
+ console.log(chalk_1.default.blue('\n Checking recent builds...\n'));
283
+ const apiUrl = (0, config_js_1.getApiUrl)();
284
+ const apiKey = (0, config_js_1.getApiKey)();
285
+ const trial = (0, config_js_1.getTrialState)();
286
+ let authHeader = '';
287
+ if (apiKey) {
288
+ authHeader = `Bearer ${apiKey}`;
289
+ }
290
+ else if (trial?.trialId) {
291
+ authHeader = `Trial ${trial.trialId}`;
292
+ }
293
+ if (!authHeader) {
294
+ console.log(chalk_1.default.red(' Not authenticated. Run: codebakers go\n'));
295
+ return;
296
+ }
297
+ try {
298
+ const response = await fetch(`${apiUrl}/api/engineering/sessions`, {
299
+ headers: {
300
+ 'Authorization': authHeader,
301
+ },
302
+ });
303
+ if (!response.ok) {
304
+ throw new Error('Failed to get builds');
305
+ }
306
+ const { data } = await response.json();
307
+ const sessions = data.sessions || [];
308
+ if (sessions.length === 0) {
309
+ console.log(chalk_1.default.gray(' No builds found. Run: codebakers build "your project"\n'));
310
+ return;
311
+ }
312
+ console.log(chalk_1.default.white(' Recent builds:\n'));
313
+ for (const session of sessions.slice(0, 5)) {
314
+ const statusIcon = session.status === 'completed' ? chalk_1.default.green('✓') :
315
+ session.status === 'active' ? chalk_1.default.blue('●') :
316
+ session.status === 'failed' ? chalk_1.default.red('✗') : chalk_1.default.gray('○');
317
+ console.log(` ${statusIcon} ${session.projectName}`);
318
+ console.log(chalk_1.default.gray(` Status: ${session.status} | Phase: ${session.currentPhase} | Progress: ${session.progress}%\n`));
319
+ }
320
+ }
321
+ catch (error) {
322
+ const message = error instanceof Error ? error.message : 'Unknown error';
323
+ console.log(chalk_1.default.red(` Error: ${message}\n`));
324
+ }
325
+ }
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ const push_patterns_js_1 = require("./commands/push-patterns.js");
27
27
  const go_js_1 = require("./commands/go.js");
28
28
  const extend_js_1 = require("./commands/extend.js");
29
29
  const billing_js_1 = require("./commands/billing.js");
30
+ const build_js_1 = require("./commands/build.js");
30
31
  const config_js_2 = require("./config.js");
31
32
  const child_process_1 = require("child_process");
32
33
  const api_js_1 = require("./lib/api.js");
@@ -305,6 +306,7 @@ function showWelcome() {
305
306
  `));
306
307
  console.log(chalk_1.default.white(' Getting Started:\n'));
307
308
  console.log(chalk_1.default.cyan(' codebakers go') + chalk_1.default.gray(' Start free trial instantly (no signup!)'));
309
+ console.log(chalk_1.default.cyan(' codebakers build') + chalk_1.default.gray(' Describe your project → Get working code'));
308
310
  console.log(chalk_1.default.cyan(' codebakers scaffold') + chalk_1.default.gray(' Create a new project from scratch'));
309
311
  console.log(chalk_1.default.cyan(' codebakers init') + chalk_1.default.gray(' Add patterns to existing project\n'));
310
312
  console.log(chalk_1.default.white(' Development:\n'));
@@ -313,20 +315,20 @@ function showWelcome() {
313
315
  console.log(chalk_1.default.cyan(' codebakers status') + chalk_1.default.gray(' Check what\'s installed'));
314
316
  console.log(chalk_1.default.cyan(' codebakers config') + chalk_1.default.gray(' View or modify configuration\n'));
315
317
  console.log(chalk_1.default.white(' Examples:\n'));
318
+ console.log(chalk_1.default.gray(' $ ') + chalk_1.default.cyan('codebakers build "SaaS for invoicing"'));
319
+ console.log(chalk_1.default.gray(' AI generates full project with auth, payments, dashboard\n'));
316
320
  console.log(chalk_1.default.gray(' $ ') + chalk_1.default.cyan('codebakers scaffold'));
317
321
  console.log(chalk_1.default.gray(' Create a new Next.js + Supabase + Drizzle project\n'));
318
322
  console.log(chalk_1.default.gray(' $ ') + chalk_1.default.cyan('codebakers generate component Button'));
319
323
  console.log(chalk_1.default.gray(' Generate a React component with TypeScript\n'));
320
- console.log(chalk_1.default.gray(' $ ') + chalk_1.default.cyan('codebakers g api users'));
321
- console.log(chalk_1.default.gray(' Generate a Next.js API route with validation\n'));
322
324
  console.log(chalk_1.default.white(' Quality:\n'));
323
325
  console.log(chalk_1.default.cyan(' codebakers audit') + chalk_1.default.gray(' Run automated code quality checks'));
324
326
  console.log(chalk_1.default.cyan(' codebakers heal') + chalk_1.default.gray(' Auto-detect and fix common issues'));
325
327
  console.log(chalk_1.default.cyan(' codebakers doctor') + chalk_1.default.gray(' Check CodeBakers setup\n'));
326
328
  console.log(chalk_1.default.white(' All Commands:\n'));
327
- console.log(chalk_1.default.gray(' go, extend, billing, setup, scaffold, init, generate, upgrade, status'));
328
- console.log(chalk_1.default.gray(' audit, heal, doctor, config, login, install, uninstall'));
329
- console.log(chalk_1.default.gray(' install-hook, uninstall-hook, serve, mcp-config, mcp-uninstall\n'));
329
+ console.log(chalk_1.default.gray(' go, extend, billing, build, build-status, setup, scaffold, init'));
330
+ console.log(chalk_1.default.gray(' generate, upgrade, status, audit, heal, doctor, config'));
331
+ console.log(chalk_1.default.gray(' login, install, uninstall, serve, mcp-config, mcp-uninstall\n'));
330
332
  console.log(chalk_1.default.gray(' Run ') + chalk_1.default.cyan('codebakers <command> --help') + chalk_1.default.gray(' for more info\n'));
331
333
  }
332
334
  const program = new commander_1.Command();
@@ -350,6 +352,21 @@ program
350
352
  .alias('subscribe')
351
353
  .description('Manage subscription or upgrade to paid plan')
352
354
  .action(billing_js_1.billing);
355
+ // AI Build command - describe what you want, get working code
356
+ program
357
+ .command('build [description]')
358
+ .description('Build a project from description - AI generates actual files')
359
+ .option('-o, --output <dir>', 'Output directory (default: current directory)')
360
+ .option('-v, --verbose', 'Show detailed progress')
361
+ .action((description, options) => (0, build_js_1.build)({
362
+ description,
363
+ output: options.output,
364
+ verbose: options.verbose,
365
+ }));
366
+ program
367
+ .command('build-status')
368
+ .description('Check status of recent builds')
369
+ .action(build_js_1.buildStatus);
353
370
  // Primary command - one-time setup (for paid users)
354
371
  program
355
372
  .command('setup')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "3.8.1",
3
+ "version": "3.8.2",
4
4
  "description": "CodeBakers CLI - Production patterns for AI-assisted development",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -0,0 +1,425 @@
1
+ import chalk from 'chalk';
2
+ import ora, { type Ora } from 'ora';
3
+ import { createInterface } from 'readline';
4
+ import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'fs';
5
+ import { join, dirname } from 'path';
6
+ import { getApiKey, getApiUrl, getTrialState, hasValidAccess } from '../config.js';
7
+
8
+ /**
9
+ * CODEBAKERS BUILD COMMAND
10
+ *
11
+ * This is the zero-friction project builder.
12
+ * User describes what they want → AI builds actual files on their machine.
13
+ *
14
+ * Flow:
15
+ * 1. User runs: codebakers build "SaaS for invoicing"
16
+ * 2. CLI creates engineering session on server
17
+ * 3. Server runs AI agents to generate PRD, specs, code
18
+ * 4. CLI receives file contents and writes them to disk
19
+ * 5. User has a runnable project
20
+ */
21
+
22
+ interface BuildOptions {
23
+ description?: string;
24
+ output?: string;
25
+ verbose?: boolean;
26
+ }
27
+
28
+ interface FileToCreate {
29
+ path: string;
30
+ content: string;
31
+ type: 'code' | 'config' | 'doc';
32
+ }
33
+
34
+ interface BuildPhase {
35
+ phase: string;
36
+ status: 'pending' | 'in_progress' | 'completed' | 'failed';
37
+ displayName: string;
38
+ files?: FileToCreate[];
39
+ }
40
+
41
+ interface BuildStreamEvent {
42
+ type: 'phase_start' | 'phase_complete' | 'file_create' | 'message' | 'error' | 'complete';
43
+ phase?: string;
44
+ displayName?: string;
45
+ message?: string;
46
+ file?: FileToCreate;
47
+ files?: FileToCreate[];
48
+ error?: string;
49
+ summary?: {
50
+ filesCreated: number;
51
+ phases: number;
52
+ tokensUsed: number;
53
+ };
54
+ }
55
+
56
+ async function prompt(question: string): Promise<string> {
57
+ const rl = createInterface({
58
+ input: process.stdin,
59
+ output: process.stdout,
60
+ });
61
+
62
+ return new Promise((resolve) => {
63
+ rl.question(question, (answer) => {
64
+ rl.close();
65
+ resolve(answer.trim());
66
+ });
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Main build command
72
+ */
73
+ export async function build(options: BuildOptions = {}): Promise<void> {
74
+ console.log(chalk.blue(`
75
+ ╔═══════════════════════════════════════════════════════════╗
76
+ ║ ║
77
+ ║ ${chalk.bold('CodeBakers Build')} ║
78
+ ║ ║
79
+ ║ Describe your project → Get working code ║
80
+ ║ ║
81
+ ╚═══════════════════════════════════════════════════════════╝
82
+ `));
83
+
84
+ // Check authentication
85
+ if (!hasValidAccess()) {
86
+ console.log(chalk.red(' ✗ Not authenticated\n'));
87
+ console.log(chalk.gray(' Run: codebakers go (free trial) or codebakers setup (with API key)\n'));
88
+ process.exit(1);
89
+ }
90
+
91
+ // Get project description
92
+ let description = options.description;
93
+ if (!description) {
94
+ console.log(chalk.white(' What do you want to build?\n'));
95
+ console.log(chalk.gray(' Examples:'));
96
+ console.log(chalk.gray(' • "A SaaS for managing invoices with Stripe payments"'));
97
+ console.log(chalk.gray(' • "Todo app with user auth and real-time sync"'));
98
+ console.log(chalk.gray(' • "Blog platform with markdown support"\n'));
99
+
100
+ description = await prompt(' Describe your project: ');
101
+
102
+ if (!description.trim()) {
103
+ console.log(chalk.red('\n Please provide a project description.\n'));
104
+ process.exit(1);
105
+ }
106
+ }
107
+
108
+ const outputDir = options.output || process.cwd();
109
+
110
+ // Check if directory is empty
111
+ const files = existsSync(outputDir) ?
112
+ require('fs').readdirSync(outputDir).filter((f: string) => !f.startsWith('.')) : [];
113
+
114
+ if (files.length > 0) {
115
+ console.log(chalk.yellow('\n ⚠️ This directory is not empty.'));
116
+ const proceed = await prompt(' Continue? (y/N): ');
117
+ if (proceed.toLowerCase() !== 'y') {
118
+ console.log(chalk.gray('\n Build cancelled.\n'));
119
+ return;
120
+ }
121
+ }
122
+
123
+ console.log(chalk.green(`\n Building: "${description}"\n`));
124
+
125
+ // Create engineering session
126
+ const spinner = ora(' Initializing build...').start();
127
+
128
+ try {
129
+ const apiUrl = getApiUrl();
130
+ const apiKey = getApiKey();
131
+ const trial = getTrialState();
132
+
133
+ let authHeader = '';
134
+ if (apiKey) {
135
+ authHeader = `Bearer ${apiKey}`;
136
+ } else if (trial?.trialId) {
137
+ authHeader = `Trial ${trial.trialId}`;
138
+ }
139
+
140
+ if (!authHeader) {
141
+ spinner.fail('Authentication required');
142
+ console.log(chalk.gray('\n Run: codebakers go\n'));
143
+ return;
144
+ }
145
+
146
+ // Step 1: Create engineering session
147
+ spinner.text = ' Creating build session...';
148
+
149
+ const createResponse = await fetch(`${apiUrl}/api/engineering/sessions`, {
150
+ method: 'POST',
151
+ headers: {
152
+ 'Content-Type': 'application/json',
153
+ 'Authorization': authHeader,
154
+ },
155
+ body: JSON.stringify({
156
+ projectName: description,
157
+ projectDescription: description,
158
+ source: 'cli',
159
+ }),
160
+ });
161
+
162
+ if (!createResponse.ok) {
163
+ const error = await createResponse.json().catch(() => ({}));
164
+ throw new Error(error.error || 'Failed to create build session');
165
+ }
166
+
167
+ const { data: sessionData } = await createResponse.json();
168
+ const sessionId = sessionData.sessionId;
169
+
170
+ spinner.text = ' Session created, starting build...';
171
+
172
+ // Step 2: Auto-complete scoping
173
+ await fetch(`${apiUrl}/api/engineering/sessions/${sessionId}/scope`, {
174
+ method: 'POST',
175
+ headers: {
176
+ 'Content-Type': 'application/json',
177
+ 'Authorization': authHeader,
178
+ },
179
+ body: JSON.stringify({ stepId: 'auto', answer: description }),
180
+ });
181
+
182
+ // Step 3: Start the build with file generation enabled
183
+ const buildResponse = await fetch(`${apiUrl}/api/engineering/sessions/${sessionId}/build`, {
184
+ method: 'POST',
185
+ headers: {
186
+ 'Content-Type': 'application/json',
187
+ 'Authorization': authHeader,
188
+ },
189
+ body: JSON.stringify({
190
+ generateFiles: true, // Tell server to generate actual file contents
191
+ }),
192
+ });
193
+
194
+ if (!buildResponse.ok) {
195
+ const error = await buildResponse.json().catch(() => ({}));
196
+ throw new Error(error.error || 'Failed to start build');
197
+ }
198
+
199
+ spinner.succeed('Build started!');
200
+ console.log('');
201
+
202
+ // Step 4: Stream progress and receive files
203
+ await streamBuildProgress(apiUrl, authHeader, sessionId, outputDir, options.verbose);
204
+
205
+ } catch (error) {
206
+ spinner.fail('Build failed');
207
+ const message = error instanceof Error ? error.message : 'Unknown error';
208
+ console.log(chalk.red(`\n Error: ${message}\n`));
209
+ process.exit(1);
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Stream build progress and write files as they're generated
215
+ */
216
+ async function streamBuildProgress(
217
+ apiUrl: string,
218
+ authHeader: string,
219
+ sessionId: string,
220
+ outputDir: string,
221
+ verbose?: boolean
222
+ ): Promise<void> {
223
+ const phases: Map<string, BuildPhase> = new Map();
224
+ let filesCreated = 0;
225
+ let currentSpinner: Ora | null = null;
226
+
227
+ // Display phase progress
228
+ function displayPhase(phase: string, status: string, displayName: string): void {
229
+ const icon = status === 'completed' ? chalk.green('✓') :
230
+ status === 'in_progress' ? chalk.blue('●') :
231
+ status === 'failed' ? chalk.red('✗') : chalk.gray('○');
232
+
233
+ if (status === 'in_progress') {
234
+ if (currentSpinner) currentSpinner.stop();
235
+ currentSpinner = ora(` ${displayName}...`).start();
236
+ } else if (status === 'completed' && currentSpinner) {
237
+ currentSpinner.succeed(` ${displayName}`);
238
+ currentSpinner = null;
239
+ } else if (status === 'failed' && currentSpinner) {
240
+ currentSpinner.fail(` ${displayName}`);
241
+ currentSpinner = null;
242
+ }
243
+ }
244
+
245
+ // Write a file to disk
246
+ function writeFile(file: FileToCreate): void {
247
+ const fullPath = join(outputDir, file.path);
248
+ const dir = dirname(fullPath);
249
+
250
+ if (!existsSync(dir)) {
251
+ mkdirSync(dir, { recursive: true });
252
+ }
253
+
254
+ writeFileSync(fullPath, file.content);
255
+ filesCreated++;
256
+
257
+ if (verbose) {
258
+ console.log(chalk.gray(` + ${file.path}`));
259
+ }
260
+ }
261
+
262
+ // Poll for updates (SSE would be better but this works for CLI)
263
+ let isComplete = false;
264
+ let pollCount = 0;
265
+ const maxPolls = 300; // 10 minutes max (2s intervals)
266
+
267
+ while (!isComplete && pollCount < maxPolls) {
268
+ try {
269
+ const response = await fetch(`${apiUrl}/api/engineering/sessions/${sessionId}/progress`, {
270
+ headers: {
271
+ 'Authorization': authHeader,
272
+ },
273
+ });
274
+
275
+ if (!response.ok) {
276
+ throw new Error('Failed to get build progress');
277
+ }
278
+
279
+ const data = await response.json();
280
+ const progress = data.data || data;
281
+
282
+ // Update phase displays
283
+ if (progress.phases) {
284
+ for (const phase of progress.phases) {
285
+ const existing = phases.get(phase.phase);
286
+ if (!existing || existing.status !== phase.status) {
287
+ phases.set(phase.phase, phase);
288
+ displayPhase(phase.phase, phase.status, phase.displayName || phase.phase);
289
+ }
290
+ }
291
+ }
292
+
293
+ // Write any new files
294
+ if (progress.newFiles && progress.newFiles.length > 0) {
295
+ for (const file of progress.newFiles) {
296
+ writeFile(file);
297
+ }
298
+ }
299
+
300
+ // Check if build is complete
301
+ if (progress.status === 'completed' || progress.status === 'failed' || progress.status === 'abandoned') {
302
+ isComplete = true;
303
+
304
+ if (currentSpinner !== null) {
305
+ (currentSpinner as Ora).stop();
306
+ currentSpinner = null;
307
+ }
308
+
309
+ if (progress.status === 'completed') {
310
+ // Write final files if any
311
+ if (progress.files && progress.files.length > 0) {
312
+ console.log(chalk.white('\n Writing project files...\n'));
313
+ const fileSpinner = ora(' Creating files...').start();
314
+
315
+ for (const file of progress.files) {
316
+ writeFile(file);
317
+ }
318
+
319
+ fileSpinner.succeed(` Created ${filesCreated} files`);
320
+ }
321
+
322
+ // Success message
323
+ console.log(chalk.green(`
324
+ ╔═══════════════════════════════════════════════════════════╗
325
+ ║ ║
326
+ ║ ${chalk.bold('✓ Build complete!')} ║
327
+ ║ ║
328
+ ╚═══════════════════════════════════════════════════════════╝
329
+ `));
330
+
331
+ console.log(chalk.white(' Next steps:\n'));
332
+ console.log(chalk.cyan(' 1. ') + chalk.white('Install dependencies:'));
333
+ console.log(chalk.gray(' npm install\n'));
334
+ console.log(chalk.cyan(' 2. ') + chalk.white('Set up your database:'));
335
+ console.log(chalk.gray(' npx drizzle-kit db:push\n'));
336
+ console.log(chalk.cyan(' 3. ') + chalk.white('Start the dev server:'));
337
+ console.log(chalk.gray(' npm run dev\n'));
338
+
339
+ // Show summary
340
+ if (progress.summary) {
341
+ console.log(chalk.gray(` Summary: ${progress.summary.filesCreated || filesCreated} files, ${progress.summary.tokensUsed || 0} tokens used\n`));
342
+ }
343
+
344
+ } else {
345
+ console.log(chalk.red(`\n Build ${progress.status}: ${progress.lastError || 'Unknown error'}\n`));
346
+ }
347
+ }
348
+
349
+ } catch (error) {
350
+ if (verbose) {
351
+ console.log(chalk.gray(` Poll error: ${error}`));
352
+ }
353
+ }
354
+
355
+ if (!isComplete) {
356
+ await new Promise(resolve => setTimeout(resolve, 2000));
357
+ pollCount++;
358
+ }
359
+ }
360
+
361
+ if (!isComplete) {
362
+ if (currentSpinner !== null) {
363
+ (currentSpinner as Ora).fail('Build timed out');
364
+ }
365
+ console.log(chalk.yellow('\n Build is taking longer than expected.'));
366
+ console.log(chalk.gray(' Check status: codebakers build-status\n'));
367
+ }
368
+ }
369
+
370
+ /**
371
+ * Check build status
372
+ */
373
+ export async function buildStatus(): Promise<void> {
374
+ console.log(chalk.blue('\n Checking recent builds...\n'));
375
+
376
+ const apiUrl = getApiUrl();
377
+ const apiKey = getApiKey();
378
+ const trial = getTrialState();
379
+
380
+ let authHeader = '';
381
+ if (apiKey) {
382
+ authHeader = `Bearer ${apiKey}`;
383
+ } else if (trial?.trialId) {
384
+ authHeader = `Trial ${trial.trialId}`;
385
+ }
386
+
387
+ if (!authHeader) {
388
+ console.log(chalk.red(' Not authenticated. Run: codebakers go\n'));
389
+ return;
390
+ }
391
+
392
+ try {
393
+ const response = await fetch(`${apiUrl}/api/engineering/sessions`, {
394
+ headers: {
395
+ 'Authorization': authHeader,
396
+ },
397
+ });
398
+
399
+ if (!response.ok) {
400
+ throw new Error('Failed to get builds');
401
+ }
402
+
403
+ const { data } = await response.json();
404
+ const sessions = data.sessions || [];
405
+
406
+ if (sessions.length === 0) {
407
+ console.log(chalk.gray(' No builds found. Run: codebakers build "your project"\n'));
408
+ return;
409
+ }
410
+
411
+ console.log(chalk.white(' Recent builds:\n'));
412
+ for (const session of sessions.slice(0, 5)) {
413
+ const statusIcon = session.status === 'completed' ? chalk.green('✓') :
414
+ session.status === 'active' ? chalk.blue('●') :
415
+ session.status === 'failed' ? chalk.red('✗') : chalk.gray('○');
416
+
417
+ console.log(` ${statusIcon} ${session.projectName}`);
418
+ console.log(chalk.gray(` Status: ${session.status} | Phase: ${session.currentPhase} | Progress: ${session.progress}%\n`));
419
+ }
420
+
421
+ } catch (error) {
422
+ const message = error instanceof Error ? error.message : 'Unknown error';
423
+ console.log(chalk.red(` Error: ${message}\n`));
424
+ }
425
+ }
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ import { pushPatterns, pushPatternsInteractive } from './commands/push-patterns.
23
23
  import { go } from './commands/go.js';
24
24
  import { extend } from './commands/extend.js';
25
25
  import { billing } from './commands/billing.js';
26
+ import { build, buildStatus } from './commands/build.js';
26
27
  import { getCachedUpdateInfo, setCachedUpdateInfo, getCliVersion, getCachedPatternInfo, setCachedPatternInfo, getApiKey, getApiUrl, getTrialState, hasValidAccess, shouldAttemptCliUpdate, setCliUpdateAttempt, isCliAutoUpdateDisabled } from './config.js';
27
28
  import { execSync } from 'child_process';
28
29
  import { checkForUpdates } from './lib/api.js';
@@ -353,6 +354,7 @@ function showWelcome(): void {
353
354
 
354
355
  console.log(chalk.white(' Getting Started:\n'));
355
356
  console.log(chalk.cyan(' codebakers go') + chalk.gray(' Start free trial instantly (no signup!)'));
357
+ console.log(chalk.cyan(' codebakers build') + chalk.gray(' Describe your project → Get working code'));
356
358
  console.log(chalk.cyan(' codebakers scaffold') + chalk.gray(' Create a new project from scratch'));
357
359
  console.log(chalk.cyan(' codebakers init') + chalk.gray(' Add patterns to existing project\n'));
358
360
 
@@ -363,12 +365,12 @@ function showWelcome(): void {
363
365
  console.log(chalk.cyan(' codebakers config') + chalk.gray(' View or modify configuration\n'));
364
366
 
365
367
  console.log(chalk.white(' Examples:\n'));
368
+ console.log(chalk.gray(' $ ') + chalk.cyan('codebakers build "SaaS for invoicing"'));
369
+ console.log(chalk.gray(' AI generates full project with auth, payments, dashboard\n'));
366
370
  console.log(chalk.gray(' $ ') + chalk.cyan('codebakers scaffold'));
367
371
  console.log(chalk.gray(' Create a new Next.js + Supabase + Drizzle project\n'));
368
372
  console.log(chalk.gray(' $ ') + chalk.cyan('codebakers generate component Button'));
369
373
  console.log(chalk.gray(' Generate a React component with TypeScript\n'));
370
- console.log(chalk.gray(' $ ') + chalk.cyan('codebakers g api users'));
371
- console.log(chalk.gray(' Generate a Next.js API route with validation\n'));
372
374
 
373
375
  console.log(chalk.white(' Quality:\n'));
374
376
  console.log(chalk.cyan(' codebakers audit') + chalk.gray(' Run automated code quality checks'));
@@ -376,9 +378,9 @@ function showWelcome(): void {
376
378
  console.log(chalk.cyan(' codebakers doctor') + chalk.gray(' Check CodeBakers setup\n'));
377
379
 
378
380
  console.log(chalk.white(' All Commands:\n'));
379
- console.log(chalk.gray(' go, extend, billing, setup, scaffold, init, generate, upgrade, status'));
380
- console.log(chalk.gray(' audit, heal, doctor, config, login, install, uninstall'));
381
- console.log(chalk.gray(' install-hook, uninstall-hook, serve, mcp-config, mcp-uninstall\n'));
381
+ console.log(chalk.gray(' go, extend, billing, build, build-status, setup, scaffold, init'));
382
+ console.log(chalk.gray(' generate, upgrade, status, audit, heal, doctor, config'));
383
+ console.log(chalk.gray(' login, install, uninstall, serve, mcp-config, mcp-uninstall\n'));
382
384
 
383
385
  console.log(chalk.gray(' Run ') + chalk.cyan('codebakers <command> --help') + chalk.gray(' for more info\n'));
384
386
  }
@@ -409,6 +411,23 @@ program
409
411
  .description('Manage subscription or upgrade to paid plan')
410
412
  .action(billing);
411
413
 
414
+ // AI Build command - describe what you want, get working code
415
+ program
416
+ .command('build [description]')
417
+ .description('Build a project from description - AI generates actual files')
418
+ .option('-o, --output <dir>', 'Output directory (default: current directory)')
419
+ .option('-v, --verbose', 'Show detailed progress')
420
+ .action((description, options) => build({
421
+ description,
422
+ output: options.output,
423
+ verbose: options.verbose,
424
+ }));
425
+
426
+ program
427
+ .command('build-status')
428
+ .description('Check status of recent builds')
429
+ .action(buildStatus);
430
+
412
431
  // Primary command - one-time setup (for paid users)
413
432
  program
414
433
  .command('setup')