@codebakers/cli 3.8.1 → 3.8.3

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
+ }
@@ -1,4 +1 @@
1
- /**
2
- * Interactive init command - walks users through complete setup
3
- */
4
1
  export declare function init(): Promise<void>;