@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.
- package/dist/commands/build.d.ts +27 -0
- package/dist/commands/build.js +325 -0
- package/dist/commands/init.d.ts +0 -3
- package/dist/commands/init.js +578 -550
- package/dist/commands/install.js +14 -15
- package/dist/commands/upgrade.d.ts +1 -1
- package/dist/commands/upgrade.js +57 -313
- package/dist/index.js +27 -154
- package/package.json +1 -1
- package/src/commands/build.ts +425 -0
- package/src/commands/init.ts +606 -559
- package/src/commands/install.ts +14 -15
- package/src/commands/upgrade.ts +60 -369
- package/src/index.ts +29 -188
package/dist/index.js
CHANGED
|
@@ -27,11 +27,10 @@ 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");
|
|
33
|
-
const fs_1 = require("fs");
|
|
34
|
-
const path_1 = require("path");
|
|
35
34
|
// ============================================
|
|
36
35
|
// Automatic Update Notification
|
|
37
36
|
// ============================================
|
|
@@ -154,144 +153,6 @@ async function autoUpdateCli() {
|
|
|
154
153
|
// Silently fail - don't block CLI for update check
|
|
155
154
|
}
|
|
156
155
|
}
|
|
157
|
-
function getLocalPatternVersion() {
|
|
158
|
-
const cwd = process.cwd();
|
|
159
|
-
const versionFile = (0, path_1.join)(cwd, '.claude', '.version.json');
|
|
160
|
-
if (!(0, fs_1.existsSync)(versionFile))
|
|
161
|
-
return null;
|
|
162
|
-
try {
|
|
163
|
-
const content = (0, fs_1.readFileSync)(versionFile, 'utf-8');
|
|
164
|
-
const info = JSON.parse(content);
|
|
165
|
-
return info.version;
|
|
166
|
-
}
|
|
167
|
-
catch {
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
function isCodeBakersProject() {
|
|
172
|
-
const cwd = process.cwd();
|
|
173
|
-
return (0, fs_1.existsSync)((0, path_1.join)(cwd, 'CLAUDE.md')) || (0, fs_1.existsSync)((0, path_1.join)(cwd, '.claude'));
|
|
174
|
-
}
|
|
175
|
-
async function autoUpdatePatterns() {
|
|
176
|
-
// Only auto-update if this is a CodeBakers project
|
|
177
|
-
if (!isCodeBakersProject())
|
|
178
|
-
return;
|
|
179
|
-
// Only auto-update if user has valid access
|
|
180
|
-
if (!(0, config_js_2.hasValidAccess)())
|
|
181
|
-
return;
|
|
182
|
-
const localVersion = getLocalPatternVersion();
|
|
183
|
-
// Check if we have a valid cached result first (fast path)
|
|
184
|
-
const cached = (0, config_js_2.getCachedPatternInfo)();
|
|
185
|
-
if (cached) {
|
|
186
|
-
// If local matches latest, nothing to do
|
|
187
|
-
if (localVersion === cached.latestVersion)
|
|
188
|
-
return;
|
|
189
|
-
// If we know there's an update but haven't updated yet, do it now
|
|
190
|
-
if (localVersion !== cached.latestVersion) {
|
|
191
|
-
await performPatternUpdate(cached.latestVersion);
|
|
192
|
-
}
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
// Fetch from server to check for updates (with timeout)
|
|
196
|
-
try {
|
|
197
|
-
const controller = new AbortController();
|
|
198
|
-
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
199
|
-
const apiUrl = (0, config_js_2.getApiUrl)();
|
|
200
|
-
const apiKey = (0, config_js_2.getApiKey)();
|
|
201
|
-
const trial = (0, config_js_2.getTrialState)();
|
|
202
|
-
// Build authorization header
|
|
203
|
-
let authHeader = '';
|
|
204
|
-
if (apiKey) {
|
|
205
|
-
authHeader = `Bearer ${apiKey}`;
|
|
206
|
-
}
|
|
207
|
-
else if (trial?.trialId) {
|
|
208
|
-
authHeader = `Trial ${trial.trialId}`;
|
|
209
|
-
}
|
|
210
|
-
if (!authHeader)
|
|
211
|
-
return;
|
|
212
|
-
// First, check the version endpoint (lightweight)
|
|
213
|
-
const versionResponse = await fetch(`${apiUrl}/api/content/version`, {
|
|
214
|
-
method: 'GET',
|
|
215
|
-
headers: {
|
|
216
|
-
'Authorization': authHeader,
|
|
217
|
-
},
|
|
218
|
-
signal: controller.signal,
|
|
219
|
-
});
|
|
220
|
-
clearTimeout(timeout);
|
|
221
|
-
if (versionResponse.ok) {
|
|
222
|
-
const versionData = await versionResponse.json();
|
|
223
|
-
const serverVersion = versionData.version;
|
|
224
|
-
// Cache the version info
|
|
225
|
-
(0, config_js_2.setCachedPatternInfo)(serverVersion);
|
|
226
|
-
// If local version is different, update
|
|
227
|
-
if (localVersion !== serverVersion) {
|
|
228
|
-
await performPatternUpdate(serverVersion);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
catch {
|
|
233
|
-
// Silently fail - don't block CLI for pattern check
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
async function performPatternUpdate(targetVersion) {
|
|
237
|
-
const cwd = process.cwd();
|
|
238
|
-
const claudeMdPath = (0, path_1.join)(cwd, 'CLAUDE.md');
|
|
239
|
-
const claudeDir = (0, path_1.join)(cwd, '.claude');
|
|
240
|
-
try {
|
|
241
|
-
const apiUrl = (0, config_js_2.getApiUrl)();
|
|
242
|
-
const apiKey = (0, config_js_2.getApiKey)();
|
|
243
|
-
const trial = (0, config_js_2.getTrialState)();
|
|
244
|
-
let authHeader = '';
|
|
245
|
-
if (apiKey) {
|
|
246
|
-
authHeader = `Bearer ${apiKey}`;
|
|
247
|
-
}
|
|
248
|
-
else if (trial?.trialId) {
|
|
249
|
-
authHeader = `Trial ${trial.trialId}`;
|
|
250
|
-
}
|
|
251
|
-
if (!authHeader)
|
|
252
|
-
return;
|
|
253
|
-
const controller = new AbortController();
|
|
254
|
-
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
255
|
-
const response = await fetch(`${apiUrl}/api/content`, {
|
|
256
|
-
method: 'GET',
|
|
257
|
-
headers: {
|
|
258
|
-
'Authorization': authHeader,
|
|
259
|
-
},
|
|
260
|
-
signal: controller.signal,
|
|
261
|
-
});
|
|
262
|
-
clearTimeout(timeout);
|
|
263
|
-
if (!response.ok)
|
|
264
|
-
return;
|
|
265
|
-
const content = await response.json();
|
|
266
|
-
// Update CLAUDE.md
|
|
267
|
-
if (content.router) {
|
|
268
|
-
(0, fs_1.writeFileSync)(claudeMdPath, content.router);
|
|
269
|
-
}
|
|
270
|
-
// Update pattern modules
|
|
271
|
-
if (content.modules && Object.keys(content.modules).length > 0) {
|
|
272
|
-
if (!(0, fs_1.existsSync)(claudeDir)) {
|
|
273
|
-
(0, fs_1.mkdirSync)(claudeDir, { recursive: true });
|
|
274
|
-
}
|
|
275
|
-
for (const [name, data] of Object.entries(content.modules)) {
|
|
276
|
-
(0, fs_1.writeFileSync)((0, path_1.join)(claudeDir, name), data);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
// Write version file
|
|
280
|
-
const moduleCount = Object.keys(content.modules || {}).length;
|
|
281
|
-
const versionInfo = {
|
|
282
|
-
version: content.version,
|
|
283
|
-
moduleCount,
|
|
284
|
-
updatedAt: new Date().toISOString(),
|
|
285
|
-
cliVersion: (0, config_js_2.getCliVersion)(),
|
|
286
|
-
};
|
|
287
|
-
(0, fs_1.writeFileSync)((0, path_1.join)(claudeDir, '.version.json'), JSON.stringify(versionInfo, null, 2));
|
|
288
|
-
// Show subtle notification
|
|
289
|
-
console.log(chalk_1.default.green(` ✓ Patterns auto-updated to v${content.version} (${moduleCount} modules)\n`));
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
// Silently fail - don't block the user
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
156
|
// Show welcome message when no command is provided
|
|
296
157
|
function showWelcome() {
|
|
297
158
|
console.log(chalk_1.default.blue(`
|
|
@@ -305,28 +166,29 @@ function showWelcome() {
|
|
|
305
166
|
`));
|
|
306
167
|
console.log(chalk_1.default.white(' Getting Started:\n'));
|
|
307
168
|
console.log(chalk_1.default.cyan(' codebakers go') + chalk_1.default.gray(' Start free trial instantly (no signup!)'));
|
|
169
|
+
console.log(chalk_1.default.cyan(' codebakers build') + chalk_1.default.gray(' Describe your project → Get working code'));
|
|
308
170
|
console.log(chalk_1.default.cyan(' codebakers scaffold') + chalk_1.default.gray(' Create a new project from scratch'));
|
|
309
|
-
console.log(chalk_1.default.cyan(' codebakers init') + chalk_1.default.gray('
|
|
171
|
+
console.log(chalk_1.default.cyan(' codebakers init') + chalk_1.default.gray(' Set up CodeBakers in existing project\n'));
|
|
310
172
|
console.log(chalk_1.default.white(' Development:\n'));
|
|
311
173
|
console.log(chalk_1.default.cyan(' codebakers generate') + chalk_1.default.gray(' Generate components, APIs, services'));
|
|
312
|
-
console.log(chalk_1.default.cyan(' codebakers upgrade') + chalk_1.default.gray('
|
|
313
|
-
console.log(chalk_1.default.cyan(' codebakers status') + chalk_1.default.gray(' Check
|
|
174
|
+
console.log(chalk_1.default.cyan(' codebakers upgrade') + chalk_1.default.gray(' Check for CLI updates'));
|
|
175
|
+
console.log(chalk_1.default.cyan(' codebakers status') + chalk_1.default.gray(' Check project status'));
|
|
314
176
|
console.log(chalk_1.default.cyan(' codebakers config') + chalk_1.default.gray(' View or modify configuration\n'));
|
|
315
177
|
console.log(chalk_1.default.white(' Examples:\n'));
|
|
178
|
+
console.log(chalk_1.default.gray(' $ ') + chalk_1.default.cyan('codebakers build "SaaS for invoicing"'));
|
|
179
|
+
console.log(chalk_1.default.gray(' AI generates full project with auth, payments, dashboard\n'));
|
|
316
180
|
console.log(chalk_1.default.gray(' $ ') + chalk_1.default.cyan('codebakers scaffold'));
|
|
317
181
|
console.log(chalk_1.default.gray(' Create a new Next.js + Supabase + Drizzle project\n'));
|
|
318
182
|
console.log(chalk_1.default.gray(' $ ') + chalk_1.default.cyan('codebakers generate component Button'));
|
|
319
183
|
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
184
|
console.log(chalk_1.default.white(' Quality:\n'));
|
|
323
185
|
console.log(chalk_1.default.cyan(' codebakers audit') + chalk_1.default.gray(' Run automated code quality checks'));
|
|
324
186
|
console.log(chalk_1.default.cyan(' codebakers heal') + chalk_1.default.gray(' Auto-detect and fix common issues'));
|
|
325
187
|
console.log(chalk_1.default.cyan(' codebakers doctor') + chalk_1.default.gray(' Check CodeBakers setup\n'));
|
|
326
188
|
console.log(chalk_1.default.white(' All Commands:\n'));
|
|
327
|
-
console.log(chalk_1.default.gray(' go, extend, billing,
|
|
328
|
-
console.log(chalk_1.default.gray(' audit, heal, doctor, config, login
|
|
329
|
-
console.log(chalk_1.default.gray('
|
|
189
|
+
console.log(chalk_1.default.gray(' go, extend, billing, build, build-status, setup, scaffold, init'));
|
|
190
|
+
console.log(chalk_1.default.gray(' generate, upgrade, status, audit, heal, doctor, config, login'));
|
|
191
|
+
console.log(chalk_1.default.gray(' serve, mcp-config, mcp-uninstall\n'));
|
|
330
192
|
console.log(chalk_1.default.gray(' Run ') + chalk_1.default.cyan('codebakers <command> --help') + chalk_1.default.gray(' for more info\n'));
|
|
331
193
|
}
|
|
332
194
|
const program = new commander_1.Command();
|
|
@@ -350,6 +212,21 @@ program
|
|
|
350
212
|
.alias('subscribe')
|
|
351
213
|
.description('Manage subscription or upgrade to paid plan')
|
|
352
214
|
.action(billing_js_1.billing);
|
|
215
|
+
// AI Build command - describe what you want, get working code
|
|
216
|
+
program
|
|
217
|
+
.command('build [description]')
|
|
218
|
+
.description('Build a project from description - AI generates actual files')
|
|
219
|
+
.option('-o, --output <dir>', 'Output directory (default: current directory)')
|
|
220
|
+
.option('-v, --verbose', 'Show detailed progress')
|
|
221
|
+
.action((description, options) => (0, build_js_1.build)({
|
|
222
|
+
description,
|
|
223
|
+
output: options.output,
|
|
224
|
+
verbose: options.verbose,
|
|
225
|
+
}));
|
|
226
|
+
program
|
|
227
|
+
.command('build-status')
|
|
228
|
+
.description('Check status of recent builds')
|
|
229
|
+
.action(build_js_1.buildStatus);
|
|
353
230
|
// Primary command - one-time setup (for paid users)
|
|
354
231
|
program
|
|
355
232
|
.command('setup')
|
|
@@ -472,13 +349,9 @@ program
|
|
|
472
349
|
// Add update check hook (runs before every command)
|
|
473
350
|
program.hook('preAction', async () => {
|
|
474
351
|
// Run CLI auto-update first (if enabled and conditions met)
|
|
475
|
-
// Then run pattern auto-update in parallel with update banner check
|
|
476
352
|
await autoUpdateCli();
|
|
477
|
-
//
|
|
478
|
-
await
|
|
479
|
-
checkForUpdatesInBackground(),
|
|
480
|
-
autoUpdatePatterns(),
|
|
481
|
-
]);
|
|
353
|
+
// Check for CLI updates in background
|
|
354
|
+
await checkForUpdatesInBackground();
|
|
482
355
|
});
|
|
483
356
|
// Show welcome if no command provided
|
|
484
357
|
if (process.argv.length <= 2) {
|
package/package.json
CHANGED
|
@@ -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
|
+
}
|