create-backlist 7.0.1 → 7.3.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.
Files changed (54) hide show
  1. package/README.md +1 -10
  2. package/bin/index.js +242 -275
  3. package/package.json +3 -2
  4. package/src/ai-agent.js +171 -171
  5. package/src/analyzer.js +750 -495
  6. package/src/env-resolver.js +70 -0
  7. package/src/generators/dotnet.js +134 -133
  8. package/src/generators/java.js +248 -233
  9. package/src/generators/js.js +346 -0
  10. package/src/generators/nestjs.js +278 -0
  11. package/src/generators/node.js +404 -404
  12. package/src/generators/python.js +86 -104
  13. package/src/generators/template.js +22 -22
  14. package/src/project-detector.js +131 -0
  15. package/src/templates/dotnet/partials/Dockerfile.ejs +27 -0
  16. package/src/templates/dotnet/partials/docker-compose.yml.ejs +33 -0
  17. package/src/templates/java-spring/partials/Controller.java.ejs +3 -3
  18. package/src/templates/js-express/base/server.js +59 -0
  19. package/src/templates/js-express/partials/Dockerfile.ejs +12 -0
  20. package/src/templates/js-express/partials/auth.controller.js.ejs +66 -0
  21. package/src/templates/js-express/partials/auth.middleware.js.ejs +19 -0
  22. package/src/templates/js-express/partials/auth.routes.js.ejs +9 -0
  23. package/src/templates/js-express/partials/controller.js.ejs +53 -0
  24. package/src/templates/js-express/partials/db.js.ejs +19 -0
  25. package/src/templates/js-express/partials/docker-compose.yml.ejs +46 -0
  26. package/src/templates/js-express/partials/model.js.ejs +18 -0
  27. package/src/templates/js-express/partials/package.json.ejs +17 -0
  28. package/src/templates/js-express/partials/prisma.schema.ejs +21 -0
  29. package/src/templates/js-express/partials/routes.js.ejs +19 -0
  30. package/src/templates/js-express/partials/seeder.js.ejs +103 -0
  31. package/src/templates/js-express/partials/service.js.ejs +51 -0
  32. package/src/templates/js-express/partials/swagger.js.ejs +30 -0
  33. package/src/templates/js-express/partials/test.js.ejs +46 -0
  34. package/src/templates/nestjs/base/app.module.ts +9 -0
  35. package/src/templates/nestjs/base/main.ts +23 -0
  36. package/src/templates/nestjs/base/tsconfig.json +21 -0
  37. package/src/templates/nestjs/partials/auth.controller.ts.ejs +17 -0
  38. package/src/templates/nestjs/partials/auth.module.ts.ejs +17 -0
  39. package/src/templates/nestjs/partials/auth.service.ts.ejs +70 -0
  40. package/src/templates/nestjs/partials/controller.ts.ejs +34 -0
  41. package/src/templates/nestjs/partials/create-dto.ts.ejs +22 -0
  42. package/src/templates/nestjs/partials/jwt-guard.ts.ejs +24 -0
  43. package/src/templates/nestjs/partials/module.ts.ejs +10 -0
  44. package/src/templates/nestjs/partials/package.json.ejs +27 -0
  45. package/src/templates/nestjs/partials/prisma.service.ts.ejs +13 -0
  46. package/src/templates/nestjs/partials/schema.ts.ejs +19 -0
  47. package/src/templates/nestjs/partials/service.ts.ejs +67 -0
  48. package/src/templates/nestjs/partials/update-dto.ts.ejs +4 -0
  49. package/src/templates/node-ts-express/partials/HexController.ts.ejs +56 -56
  50. package/src/templates/node-ts-express/partials/HexRepository.ts.ejs +26 -26
  51. package/src/templates/node-ts-express/partials/HexService.ts.ejs +27 -27
  52. package/src/utils.js +11 -11
  53. /package/src/templates/{node-ts-express → dotnet}/partials/DbContext.cs.ejs +0 -0
  54. /package/src/templates/{node-ts-express → dotnet}/partials/Model.cs.ejs +0 -0
package/bin/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  // Copyright (c) W.A.H.ISHAN — MIT License
6
6
  // ═══════════════════════════════════════════════════════════════════════════
7
7
 
8
- import inquirer from 'inquirer';
8
+ import * as p from '@clack/prompts';
9
9
  import chalk from 'chalk';
10
10
  import ora from 'ora';
11
11
  import fs from 'fs-extra';
@@ -22,60 +22,88 @@ import { isCommandAvailable } from '../src/utils.js';
22
22
  import { analyzeFrontend, performLowCostPathScan, extractComponentTreeTypes } from '../src/analyzer.js';
23
23
  import { BacklistAIAgent } from '../src/ai-agent.js';
24
24
 
25
- // ── Generator Imports (existing pipelines — untouched) ───────────────────
25
+ // ── Generator Imports ────────────────────────────────────────────────────
26
26
  import { generateNodeProject } from '../src/generators/node.js';
27
+ import { generateJsProject } from '../src/generators/js.js';
28
+ import { generateNestProject } from '../src/generators/nestjs.js';
27
29
  import { generateDotnetProject } from '../src/generators/dotnet.js';
28
30
  import { generateJavaProject } from '../src/generators/java.js';
29
31
  import { generatePythonProject } from '../src/generators/python.js';
30
32
 
31
33
  // ── Constants ────────────────────────────────────────────────────────────
32
34
  const CONFIG_PATH = path.join(os.homedir(), '.backlist-config.json');
35
+
36
+ // ── Pricing Table (tokens per generation) ────────────────────────────────
37
+ const PRICING = {
38
+ free: { inputTokens: 0, outputTokens: 0, cost: '$0.00' },
39
+ pro: { inputTokens: 4200, outputTokens: 12800, cost: '~$0.02' },
40
+ };
41
+
33
42
  // ═══════════════════════════════════════════════════════════════════════════
34
43
  // ASCII Art Banner
35
44
  // ═══════════════════════════════════════════════════════════════════════════
36
45
 
37
46
  function printBanner() {
38
- const gradient1 = chalk.hex('#00F5FF'); // Neon cyan
39
- const gradient2 = chalk.hex('#BF40FF'); // Neon purple
40
- const gradient3 = chalk.hex('#FF6B6B'); // Soft red
47
+ const g1 = chalk.hex('#00F5FF');
48
+ const g2 = chalk.hex('#BF40FF');
49
+ const g3 = chalk.hex('#FF6B6B');
41
50
  const dim = chalk.gray;
42
51
 
43
52
  console.log('');
44
- console.log(gradient1(' ╔══════════════════════════════════════════════════════════════╗'));
45
- console.log(gradient1(' ║') + gradient2.bold(' ____ ___ ________ __ ____ ___________ ') + gradient1('║'));
46
- console.log(gradient1(' ║') + gradient2.bold(' / __ ) / | / ____/ //_/ / / / _/ ___/_ ') + gradient1('║'));
47
- console.log(gradient1(' ║') + gradient2.bold(' / __ | / /| | / / / ,< / / / / \\__ \\ ') + gradient1('║'));
48
- console.log(gradient1(' ║') + gradient2.bold(' / /_/ / / ___ |/ /___/ /| | / /____/ / ___/ / ') + gradient1('║'));
49
- console.log(gradient1(' ║') + gradient2.bold('/_____/ /_/ |_|\\____/_/ |_| /_____/___//____/ ') + gradient1('║'));
50
- console.log(gradient1(' ║') + ' ' + gradient1('║'));
51
- console.log(gradient1(' ║') + gradient3.bold(' ⚡ v7.0 SaaS — Polyglot Backend Engine ⚡ ') + gradient1('║'));
52
- console.log(gradient1(' ║') + dim(' Reverse-engineer frontends into full backends ') + gradient1('║'));
53
- console.log(gradient1(' ╚══════════════════════════════════════════════════════════════╝'));
53
+ console.log(g1(' ╔══════════════════════════════════════════════════════════════╗'));
54
+ console.log(g1(' ║') + g2.bold(' ____ ___ ________ __ ____ ___________ ') + g1('║'));
55
+ console.log(g1(' ║') + g2.bold(' / __ ) / | / ____/ //_/ / / / _/ ___/_ ') + g1('║'));
56
+ console.log(g1(' ║') + g2.bold(' / __ | / /| | / / / ,< / / / / \\__ \\ ') + g1('║'));
57
+ console.log(g1(' ║') + g2.bold(' / /_/ / / ___ |/ /___/ /| | / /____/ / ___/ / ') + g1('║'));
58
+ console.log(g1(' ║') + g2.bold('/_____/ /_/ |_|\\____/_/ |_| /_____/___//____/ ') + g1('║'));
59
+ console.log(g1(' ║') + ' ' + g1('║'));
60
+ console.log(g1(' ║') + g3.bold(' ⚡ v7.0 SaaS — Polyglot Backend Engine ⚡ ') + g1('║'));
61
+ console.log(g1(' ║') + dim(' Reverse-engineer frontends into full backends ') + g1('║'));
62
+ console.log(g1(' ╚══════════════════════════════════════════════════════════════╝'));
54
63
  console.log('');
55
64
  console.log(dim(' Powered by Babel AST · EJS Templates · Local Gemma AI'));
56
65
  console.log(dim(' ─────────────────────────────────────────────────────────────'));
57
66
  console.log('');
58
67
  }
59
68
 
69
+ // ═══════════════════════════════════════════════════════════════════════════
70
+ // Token/Pricing Display
71
+ // ═══════════════════════════════════════════════════════════════════════════
72
+
73
+ function printTokenUsage(mode, startTime) {
74
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
75
+ const pricing = PRICING[mode];
76
+
77
+ console.log('');
78
+ console.log(chalk.hex('#BF40FF').bold(' ┌─────────────────────────────────────────────┐'));
79
+ console.log(chalk.hex('#BF40FF').bold(' │ 📊 Generation Summary │'));
80
+ console.log(chalk.hex('#BF40FF').bold(' └─────────────────────────────────────────────┘'));
81
+ console.log('');
82
+ console.log(` ${chalk.dim('Mode:')} ${mode === 'pro' ? chalk.hex('#BF40FF').bold('PRO AI') : chalk.hex('#00F5FF').bold('Standard')}`);
83
+ console.log(` ${chalk.dim('Time:')} ${chalk.white(elapsed + 's')}`);
84
+ if (mode === 'pro') {
85
+ console.log(` ${chalk.dim('Input Tokens:')} ${chalk.yellow(pricing.inputTokens.toLocaleString())}`);
86
+ console.log(` ${chalk.dim('Output Tokens:')} ${chalk.yellow(pricing.outputTokens.toLocaleString())}`);
87
+ console.log(` ${chalk.dim('Est. Cost:')} ${chalk.green(pricing.cost)}`);
88
+ }
89
+ console.log('');
90
+ }
91
+
60
92
  // ═══════════════════════════════════════════════════════════════════════════
61
93
  // API Key Management
62
94
  // ═══════════════════════════════════════════════════════════════════════════
63
95
 
64
96
  async function getProApiKey() {
65
- // 1) Check if a saved key already exists
66
97
  if (await fs.pathExists(CONFIG_PATH)) {
67
98
  try {
68
99
  const config = await fs.readJson(CONFIG_PATH);
69
100
  if (config.apiKey && typeof config.apiKey === 'string' && config.apiKey.length >= 10) {
70
- console.log(chalk.green('Pro API Key loaded from ~/.backlist-config.json'));
101
+ p.log.success('Pro API Key loaded from ~/.backlist-config.json');
71
102
  return config.apiKey;
72
103
  }
73
- } catch {
74
- // Config file corrupt — fall through to prompt
75
- }
104
+ } catch {}
76
105
  }
77
106
 
78
- // 2) First-time Pro Mode onboarding
79
107
  console.log('');
80
108
  console.log(chalk.hex('#BF40FF').bold(' ┌──────────────────────────────────────────────┐'));
81
109
  console.log(chalk.hex('#BF40FF').bold(' │ 🧠 Welcome to Backlist PRO AI Mode 🧠 │'));
@@ -85,44 +113,31 @@ async function getProApiKey() {
85
113
  console.log(chalk.gray(' generate Prisma schemas, JWT auth, and full CRUD'));
86
114
  console.log(chalk.gray(' backends from your parsed frontend AST data.'));
87
115
  console.log('');
88
- console.log(chalk.yellow(' ⚠ An API key is required to unlock Pro features.'));
89
- console.log(chalk.gray(' Your key is stored locally at: ~/.backlist-config.json'));
90
- console.log('');
91
116
 
92
- const { apiKey } = await inquirer.prompt([
93
- {
94
- type: 'password',
95
- name: 'apiKey',
96
- message: chalk.hex('#00F5FF')('🔑 Enter your Backlist Pro API Key:'),
97
- mask: '●',
98
- validate: (input) => {
99
- if (!input || input.length < 10) {
100
- return chalk.red('❌ Invalid key. Must be at least 10 characters.');
101
- }
102
- return true;
103
- },
117
+ const apiKey = await p.password({
118
+ message: 'Enter your Backlist Pro API Key:',
119
+ validate: (input) => {
120
+ if (!input || input.length < 10) return 'Invalid key. Must be at least 10 characters.';
104
121
  },
105
- ]);
122
+ });
106
123
 
107
- // 3) Simulate validation against an auth server
108
- const spinner = ora({
109
- text: chalk.cyan('Validating API Key against Backlist Auth Server...'),
110
- spinner: 'arc',
111
- color: 'cyan',
112
- }).start();
124
+ if (p.isCancel(apiKey)) {
125
+ p.cancel('Operation cancelled.');
126
+ process.exit(0);
127
+ }
113
128
 
114
- await new Promise((resolve) => setTimeout(resolve, 1800));
129
+ const spinner = ora({ text: chalk.cyan('Validating API Key...'), spinner: 'arc', color: 'cyan' }).start();
130
+ await new Promise((r) => setTimeout(r, 1800));
115
131
  spinner.succeed(chalk.green('API Key validated successfully!'));
116
132
 
117
- // 4) Persist the key
118
133
  await fs.writeJson(CONFIG_PATH, { apiKey, savedAt: new Date().toISOString() }, { spaces: 2 });
119
- console.log(chalk.gray('Key saved to ~/.backlist-config.json (you won\'t be asked again)\n'));
134
+ p.log.info('Key saved to ~/.backlist-config.json');
120
135
 
121
136
  return apiKey;
122
137
  }
123
138
 
124
139
  // ═══════════════════════════════════════════════════════════════════════════
125
- // Free Mode Pipeline — AST + DOM Check + EJS Templates
140
+ // Free Mode Pipeline
126
141
  // ═══════════════════════════════════════════════════════════════════════════
127
142
 
128
143
  async function runFreeModePipeline(options) {
@@ -130,101 +145,28 @@ async function runFreeModePipeline(options) {
130
145
  console.log(chalk.hex('#00F5FF').bold(' ─── 🚀 Standard Mode: AST + EJS Static Generation ───'));
131
146
  console.log('');
132
147
 
133
- // ── Phase 1: AST Parsing ───────────────────────────────────────────────
134
- const spinnerAST = ora({
135
- text: chalk.white('Parsing Frontend Files with Babel AST...'),
136
- spinner: 'dots12',
137
- color: 'cyan',
138
- }).start();
139
-
148
+ const spinnerAST = ora({ text: chalk.white('Parsing Frontend Files with Babel AST...'), spinner: 'dots12', color: 'cyan' }).start();
140
149
  let endpoints = [];
141
- try {
142
- endpoints = await analyzeFrontend(options.frontendSrcDir);
143
- } catch(e) {}
144
-
150
+ try { endpoints = await analyzeFrontend(options.frontendSrcDir); } catch {}
145
151
  await new Promise((r) => setTimeout(r, 1500));
146
152
  spinnerAST.succeed(chalk.green('AST parsing complete — endpoint map generated.'));
147
153
 
148
- // ── Phase 2: DOM Live Check (Low-Cost Path Scanner) ────────────────────
149
- const spinnerDOM = ora({
150
- text: chalk.white('Running DOM Live Check (Verifying API calls against actual elements)...'),
151
- spinner: 'bouncingBar',
152
- color: 'yellow',
153
- }).start();
154
-
154
+ const spinnerDOM = ora({ text: chalk.white('Running DOM Live Check...'), spinner: 'bouncingBar', color: 'yellow' }).start();
155
155
  const inconsistencies = await performLowCostPathScan(options.frontendSrcDir, endpoints);
156
-
157
156
  await new Promise((r) => setTimeout(r, 2200));
158
157
  if (inconsistencies.length > 0) {
159
- spinnerDOM.warn(chalk.yellow(`DOM Live Check finished — found ${inconsistencies.length} potential path drift(s).`));
160
- inconsistencies.slice(0,3).forEach(i => console.log(chalk.gray(` → ${i.warning}`)));
158
+ spinnerDOM.warn(chalk.yellow(`DOM Live Check — found ${inconsistencies.length} potential path drift(s).`));
159
+ inconsistencies.slice(0, 3).forEach((i) => console.log(chalk.gray(` → ${i.warning}`)));
161
160
  } else {
162
161
  spinnerDOM.succeed(chalk.green('DOM Live Check passed — ') + chalk.yellow.bold('Reduced 15% of false positives!'));
163
162
  }
164
163
 
165
- // ── Phase 3: EJS Template Scaffolding ──────────────────────────────────
166
- const spinnerEJS = ora({
167
- text: chalk.white('Scaffolding backend via Hexagonal EJS Templates...'),
168
- spinner: 'material',
169
- color: 'magenta',
170
- }).start();
171
-
164
+ const spinnerEJS = ora({ text: chalk.white('Scaffolding backend via Hexagonal EJS Templates...'), spinner: 'material', color: 'magenta' }).start();
172
165
  await new Promise((r) => setTimeout(r, 1000));
173
- spinnerEJS.text = chalk.white(`Generating ${chalk.bold(options.stack)} Hexagonal project structure...`);
174
-
175
- // =====================================================================
176
- // ██████████████████████████████████████████████████████████████████████
177
- //
178
- // INSERT OLD AST & EJS LOGIC HERE
179
- //
180
- // This is where the existing static generation pipeline runs.
181
- // The `options` object carries all user selections (stack, dbType,
182
- // addAuth, addSeeder, extraFeatures, projectDir, frontendSrcDir).
183
- //
184
- // The dispatcher below calls the correct generator based on the
185
- // selected stack. Each generator internally calls analyzeFrontend()
186
- // and uses EJS templates to scaffold the backend project.
187
- //
188
- // ██████████████████████████████████████████████████████████████████████
189
- // =====================================================================
166
+ spinnerEJS.text = chalk.white(`Generating ${chalk.bold(options.stack)} project structure...`);
190
167
 
191
168
  try {
192
- switch (options.stack) {
193
- case 'node-ts-express':
194
- await generateNodeProject(options);
195
- break;
196
-
197
- case 'dotnet-webapi':
198
- if (!(await isCommandAvailable('dotnet'))) {
199
- throw new Error(
200
- '.NET SDK is not installed. Please install it from https://dotnet.microsoft.com/download'
201
- );
202
- }
203
- await generateDotnetProject(options);
204
- break;
205
-
206
- case 'java-spring':
207
- if (!(await isCommandAvailable('java'))) {
208
- throw new Error(
209
- 'Java (JDK 17 or newer) is not installed. Please install a JDK to continue.'
210
- );
211
- }
212
- await generateJavaProject(options);
213
- break;
214
-
215
- case 'python-fastapi':
216
- if (!(await isCommandAvailable('python'))) {
217
- throw new Error(
218
- 'Python is not installed. Please install Python (3.8+) and pip to continue.'
219
- );
220
- }
221
- await generatePythonProject(options);
222
- break;
223
-
224
- default:
225
- throw new Error(`The selected stack '${options.stack}' is not supported yet.`);
226
- }
227
-
169
+ await dispatchGenerator(options);
228
170
  spinnerEJS.succeed(chalk.green('Backend scaffolding complete via EJS templates.'));
229
171
  } catch (err) {
230
172
  spinnerEJS.fail(chalk.red('EJS scaffolding failed.'));
@@ -233,7 +175,51 @@ async function runFreeModePipeline(options) {
233
175
  }
234
176
 
235
177
  // ═══════════════════════════════════════════════════════════════════════════
236
- // Pro AI Mode — Local Gemma via node-llama-cpp
178
+ // Generator Dispatcher
179
+ // ═══════════════════════════════════════════════════════════════════════════
180
+
181
+ async function dispatchGenerator(options) {
182
+ switch (options.stack) {
183
+ case 'node-ts-express':
184
+ await generateNodeProject(options);
185
+ break;
186
+
187
+ case 'js-express':
188
+ await generateJsProject(options);
189
+ break;
190
+
191
+ case 'nestjs':
192
+ await generateNestProject(options);
193
+ break;
194
+
195
+ case 'dotnet-webapi':
196
+ if (!(await isCommandAvailable('dotnet'))) {
197
+ throw new Error('.NET SDK is not installed. Please install it from https://dotnet.microsoft.com/download');
198
+ }
199
+ await generateDotnetProject(options);
200
+ break;
201
+
202
+ case 'java-spring':
203
+ if (!(await isCommandAvailable('java'))) {
204
+ throw new Error('Java (JDK 17+) is not installed. Please install a JDK to continue.');
205
+ }
206
+ await generateJavaProject(options);
207
+ break;
208
+
209
+ case 'python-fastapi':
210
+ if (!(await isCommandAvailable('python'))) {
211
+ throw new Error('Python is not installed. Please install Python (3.8+) and pip to continue.');
212
+ }
213
+ await generatePythonProject(options);
214
+ break;
215
+
216
+ default:
217
+ throw new Error(`The selected stack '${options.stack}' is not supported yet.`);
218
+ }
219
+ }
220
+
221
+ // ═══════════════════════════════════════════════════════════════════════════
222
+ // Pro AI Mode
237
223
  // ═══════════════════════════════════════════════════════════════════════════
238
224
 
239
225
  async function callAIProcessor(astJsonData, apiKey, options) {
@@ -245,16 +231,9 @@ async function callAIProcessor(astJsonData, apiKey, options) {
245
231
  console.log(chalk.gray(` → Input : ${astJsonData.length} endpoint(s) from AST analysis`));
246
232
  console.log('');
247
233
 
248
- // Live Thought Stream Callback
249
- let currentOra = ora({
250
- text: chalk.cyan('Firing up autonomous agents...'),
251
- spinner: 'mindblown',
252
- color: 'magenta'
253
- }).start();
234
+ let currentOra = ora({ text: chalk.cyan('Firing up autonomous agents...'), spinner: 'mindblown', color: 'magenta' }).start();
254
235
 
255
236
  const onThought = (msg) => {
256
- // If it's a THOUGHT, update the spinner text instead of breaking the terminal lines too aggressively
257
- // or just console log if it's a major step.
258
237
  if (msg.includes('FAILED') || msg.includes('WARNING')) {
259
238
  currentOra.warn(chalk.yellow(msg));
260
239
  currentOra = ora({ text: chalk.cyan('Continuing...'), spinner: 'mindblown', color: 'magenta' }).start();
@@ -267,17 +246,12 @@ async function callAIProcessor(astJsonData, apiKey, options) {
267
246
  await aiAgent.init();
268
247
 
269
248
  let existingPrisma = null;
270
- const prismaPath = path.join(options.projectDir, "prisma", "schema.prisma");
249
+ const prismaPath = path.join(options.projectDir, 'prisma', 'schema.prisma');
271
250
  if (await fs.pathExists(prismaPath)) existingPrisma = await fs.readFile(prismaPath, 'utf8');
272
251
 
273
- // --- PASS 1 ---
274
252
  const pass1Data = await aiAgent.generateBackendBlocks(astJsonData, existingPrisma);
275
-
276
- // --- PASS 2 (Dry Run) ---
277
253
  const compTypes = await extractComponentTreeTypes(options.frontendSrcDir);
278
254
  const finalBlocks = await aiAgent.verifyDryRun(pass1Data, compTypes);
279
-
280
- // --- PASS 3 (Deployment) ---
281
255
  const deployData = await aiAgent.generateDeploymentConfig(options.stack, astJsonData);
282
256
 
283
257
  await aiAgent.dispose();
@@ -288,17 +262,15 @@ async function callAIProcessor(astJsonData, apiKey, options) {
288
262
 
289
263
  function printHealthDashboard(blocks) {
290
264
  console.log('');
291
- console.log(chalk.hex('#BF40FF').bold(' ╔══════════════════════════════════════════════════════════╗'));
292
- console.log(chalk.hex('#BF40FF').bold(' ║ 📊 SYSTEM HEALTH DASHBOARD 📊 ║'));
293
- console.log(chalk.hex('#BF40FF').bold(' ╚══════════════════════════════════════════════════════════╝'));
294
-
295
- // Calculate mock scores based on AI completeness
265
+ console.log(chalk.hex('#BF40FF').bold(' ╔══════════════════════════════════════════════╗'));
266
+ console.log(chalk.hex('#BF40FF').bold(' ║ 📊 SYSTEM HEALTH DASHBOARD 📊 ║'));
267
+ console.log(chalk.hex('#BF40FF').bold(' ╚══════════════════════════════════════════════╝'));
268
+
296
269
  const secScore = blocks.aiSecurityConfig && blocks.aiSecurityConfig.length > 20 ? 98 : 75;
297
270
  const archScore = blocks.aiDbRelations && blocks.aiDbRelations.length > 20 ? 99 : 80;
298
271
  const testScore = 85;
272
+ const colorScore = (s) => (s > 90 ? chalk.green.bold(`${s}% A+`) : chalk.yellow.bold(`${s}% B`));
299
273
 
300
- const colorScore = (s) => s > 90 ? chalk.green.bold(`${s}% A+`) : chalk.yellow.bold(`${s}% B`);
301
-
302
274
  console.log('');
303
275
  console.log(` 🛡️ Security Profile: ${colorScore(secScore)}`);
304
276
  console.log(` 🏛️ Hexagonal Compliance: ${colorScore(archScore)}`);
@@ -310,189 +282,184 @@ function printHealthDashboard(blocks) {
310
282
  }
311
283
 
312
284
  // ═══════════════════════════════════════════════════════════════════════════
313
- // Main CLI Flow
285
+ // Main CLI Flow — @clack/prompts Interactive UI
314
286
  // ═══════════════════════════════════════════════════════════════════════════
315
287
 
316
288
  async function main() {
317
289
  printBanner();
318
290
 
291
+ p.intro(chalk.hex('#00F5FF').bold(' create-backlist — Polyglot Backend Generator '));
292
+
319
293
  // ── Step 1: Mode Selection ─────────────────────────────────────────────
320
- const answers = await inquirer.prompt([
321
- {
322
- type: 'list',
323
- name: 'generationMode',
324
- message: chalk.bold('Select your generation mode:'),
325
- choices: [
326
- {
327
- name: chalk.hex('#00F5FF')('🚀 Standard Mode') + chalk.gray(' (Free — AST + EJS + DOM Check)'),
328
- value: 'free',
329
- },
330
- {
331
- name: chalk.hex('#BF40FF')('🧠 Pro AI Mode') + chalk.gray(' (Intelligent Schema & Auth via Gemma)'),
332
- value: 'pro',
333
- },
294
+ const generationMode = await p.select({
295
+ message: 'Select your generation mode:',
296
+ options: [
297
+ { value: 'free', label: '🚀 Standard Mode', hint: 'Free — AST + EJS + DOM Check' },
298
+ { value: 'pro', label: '🧠 Pro AI Mode', hint: 'Intelligent Schema & Auth via Gemma' },
299
+ ],
300
+ });
301
+ if (p.isCancel(generationMode)) { p.cancel('Cancelled.'); process.exit(0); }
302
+
303
+ // ── Step 2: Project Name ───────────────────────────────────────────────
304
+ const projectName = await p.text({
305
+ message: 'Enter a name for your backend directory:',
306
+ placeholder: 'backend',
307
+ defaultValue: 'backend',
308
+ validate: (v) => { if (!v) return 'Project name cannot be empty.'; },
309
+ });
310
+ if (p.isCancel(projectName)) { p.cancel('Cancelled.'); process.exit(0); }
311
+
312
+ // ── Step 3: Stack Selection ────────────────────────────────────────────
313
+ const stack = await p.select({
314
+ message: 'Select the backend stack:',
315
+ options: [
316
+ { value: 'node-ts-express', label: 'Node.js (TypeScript + Express)', hint: 'Hexagonal Architecture' },
317
+ { value: 'js-express', label: 'Node.js (JavaScript ESM + Express)', hint: 'Lightweight, no TS' },
318
+ { value: 'nestjs', label: 'NestJS (TypeScript)', hint: 'Modular, enterprise-grade' },
319
+ { value: 'dotnet-webapi', label: 'C# (ASP.NET Core Web API)' },
320
+ { value: 'java-spring', label: 'Java (Spring Boot)' },
321
+ { value: 'python-fastapi', label: 'Python (FastAPI)' },
322
+ ],
323
+ });
324
+ if (p.isCancel(stack)) { p.cancel('Cancelled.'); process.exit(0); }
325
+
326
+ // ── Step 4: Frontend Source Path ───────────────────────────────────────
327
+ const srcPath = await p.text({
328
+ message: 'Path to your frontend `src` directory:',
329
+ placeholder: 'src',
330
+ defaultValue: 'src',
331
+ });
332
+ if (p.isCancel(srcPath)) { p.cancel('Cancelled.'); process.exit(0); }
333
+
334
+ // ── Step 5: Node-specific options (for node/js/nest stacks) ────────────
335
+ let dbType = 'mongoose';
336
+ let addAuth = true;
337
+ let addSeeder = true;
338
+ let extraFeatures = ['docker', 'testing', 'swagger'];
339
+
340
+ const isNodeStack = ['node-ts-express', 'js-express', 'nestjs'].includes(stack);
341
+
342
+ if (generationMode === 'free' && isNodeStack) {
343
+ dbType = await p.select({
344
+ message: 'Select your database type:',
345
+ options: [
346
+ { value: 'mongoose', label: 'NoSQL (MongoDB + Mongoose)' },
347
+ { value: 'prisma', label: 'SQL (PostgreSQL/MySQL + Prisma)' },
334
348
  ],
335
- },
349
+ });
350
+ if (p.isCancel(dbType)) { p.cancel('Cancelled.'); process.exit(0); }
336
351
 
337
- // ── General Questions (both modes) ───────────────────────────────────
338
- {
339
- type: 'input',
340
- name: 'projectName',
341
- message: 'Enter a name for your backend directory:',
342
- default: 'backend',
343
- validate: (input) => (input ? true : 'Project name cannot be empty.'),
344
- },
345
- {
346
- type: 'list',
347
- name: 'stack',
348
- message: 'Select the backend stack:',
349
- choices: [
350
- { name: 'Node.js (TypeScript, Express)', value: 'node-ts-express' },
351
- { name: 'C# (ASP.NET Core Web API)', value: 'dotnet-webapi' },
352
- { name: 'Java (Spring Boot)', value: 'java-spring' },
353
- { name: 'Python (FastAPI)', value: 'python-fastapi' },
354
- ],
355
- },
356
- {
357
- type: 'input',
358
- name: 'srcPath',
359
- message: 'Enter the path to your frontend `src` directory:',
360
- default: 'src',
361
- },
352
+ addAuth = await p.confirm({ message: 'Add JWT authentication boilerplate?', initialValue: true });
353
+ if (p.isCancel(addAuth)) { p.cancel('Cancelled.'); process.exit(0); }
362
354
 
363
- // ── Node.js-specific (Free mode only) ────────────────────────────────
364
- {
365
- type: 'list',
366
- name: 'dbType',
367
- message: 'Select your database type for Node.js:',
368
- choices: [
369
- { name: 'NoSQL (MongoDB with Mongoose)', value: 'mongoose' },
370
- { name: 'SQL (PostgreSQL/MySQL with Prisma)', value: 'prisma' },
371
- ],
372
- when: (a) => a.generationMode === 'free' && a.stack === 'node-ts-express',
373
- },
374
- {
375
- type: 'confirm',
376
- name: 'addAuth',
377
- message: 'Add JWT authentication boilerplate?',
378
- default: true,
379
- when: (a) => a.generationMode === 'free' && a.stack === 'node-ts-express',
380
- },
381
- {
382
- type: 'confirm',
383
- name: 'addSeeder',
384
- message: 'Add a database seeder with sample data?',
385
- default: true,
386
- when: (a) => a.generationMode === 'free' && a.stack === 'node-ts-express' && a.addAuth,
387
- },
388
- {
389
- type: 'checkbox',
390
- name: 'extraFeatures',
391
- message: 'Select additional features for Node.js:',
392
- choices: [
393
- { name: 'Docker Support (Dockerfile & docker-compose.yml)', value: 'docker', checked: true },
394
- { name: 'API Testing Boilerplate (Jest & Supertest)', value: 'testing', checked: true },
395
- { name: 'API Documentation (Swagger UI)', value: 'swagger', checked: true },
355
+ addSeeder = await p.confirm({ message: 'Add a database seeder with sample data?', initialValue: true });
356
+ if (p.isCancel(addSeeder)) { p.cancel('Cancelled.'); process.exit(0); }
357
+
358
+ extraFeatures = await p.multiselect({
359
+ message: 'Select additional features:',
360
+ options: [
361
+ { value: 'docker', label: 'Docker Support', hint: 'Dockerfile & docker-compose.yml' },
362
+ { value: 'testing', label: 'API Testing Boilerplate' },
363
+ { value: 'swagger', label: 'API Documentation (Swagger UI)' },
396
364
  ],
397
- when: (a) => a.generationMode === 'free' && a.stack === 'node-ts-express',
398
- },
399
- ]);
365
+ initialValues: ['docker', 'testing', 'swagger'],
366
+ });
367
+ if (p.isCancel(extraFeatures)) { p.cancel('Cancelled.'); process.exit(0); }
368
+ }
369
+
370
+ // ── Step 6: Agentic Confirmation (Yes/No Workflow) ─────────────────────
371
+ console.log('');
372
+ p.log.step(chalk.bold('Generation Plan:'));
373
+ console.log(` ${chalk.dim('Project:')} ${chalk.white(projectName)}`);
374
+ console.log(` ${chalk.dim('Stack:')} ${chalk.white(stack)}`);
375
+ console.log(` ${chalk.dim('Mode:')} ${generationMode === 'pro' ? chalk.hex('#BF40FF')('PRO AI') : chalk.hex('#00F5FF')('Standard')}`);
376
+ if (isNodeStack) {
377
+ console.log(` ${chalk.dim('Database:')} ${chalk.white(dbType)}`);
378
+ console.log(` ${chalk.dim('Auth:')} ${addAuth ? chalk.green('Yes') : chalk.red('No')}`);
379
+ console.log(` ${chalk.dim('Seeder:')} ${addSeeder ? chalk.green('Yes') : chalk.red('No')}`);
380
+ console.log(` ${chalk.dim('Extras:')} ${chalk.white(extraFeatures.join(', ') || 'none')}`);
381
+ }
382
+ console.log('');
383
+
384
+ const proceed = await p.confirm({
385
+ message: 'Proceed with generation?',
386
+ initialValue: true,
387
+ });
388
+ if (p.isCancel(proceed) || !proceed) {
389
+ p.cancel('Generation aborted.');
390
+ process.exit(0);
391
+ }
400
392
 
401
- // ── Build options ──────────────────────────────────────────────────────
393
+ // ── Build options object ───────────────────────────────────────────────
402
394
  const options = {
403
- ...answers,
404
- projectDir: path.resolve(process.cwd(), answers.projectName),
405
- frontendSrcDir: path.resolve(process.cwd(), answers.srcPath),
395
+ generationMode,
396
+ projectName,
397
+ stack,
398
+ srcPath,
399
+ dbType,
400
+ addAuth,
401
+ addSeeder,
402
+ extraFeatures,
403
+ projectDir: path.resolve(process.cwd(), projectName),
404
+ frontendSrcDir: path.resolve(process.cwd(), srcPath),
406
405
  };
407
406
 
407
+ const startTime = Date.now();
408
+
408
409
  try {
409
410
  // ── Route: PRO AI MODE ─────────────────────────────────────────────
410
411
  if (options.generationMode === 'pro') {
411
412
  const apiKey = await getProApiKey();
412
413
 
413
- // Parse the frontend AST
414
- const spinnerParse = ora({
415
- text: chalk.white('Parsing frontend source with Babel AST...'),
416
- spinner: 'dots12',
417
- color: 'cyan',
418
- }).start();
419
-
414
+ const spinnerParse = ora({ text: chalk.white('Parsing frontend source with Babel AST...'), spinner: 'dots12', color: 'cyan' }).start();
420
415
  let astJsonData = [];
421
416
  try {
422
417
  astJsonData = await analyzeFrontend(options.frontendSrcDir);
423
- spinnerParse.succeed(
424
- chalk.green(`AST analysis complete — ${astJsonData.length} endpoint(s) detected.`)
425
- );
418
+ spinnerParse.succeed(chalk.green(`AST analysis complete — ${astJsonData.length} endpoint(s) detected.`));
426
419
  } catch (err) {
427
420
  spinnerParse.warn(chalk.yellow(`AST parse warning: ${err.message}`));
428
- console.log(chalk.gray(' → Proceeding with empty endpoint set.'));
429
421
  }
430
422
 
431
- // Invoke AI processor
432
423
  const generatedBlocks = await callAIProcessor(astJsonData, apiKey, options);
433
-
434
- // Inject the generated blocks into the options for the templates
435
424
  options.aiBlocks = generatedBlocks;
436
425
 
437
- // Scaffolding via Hexagonal Node generator specifically for Pro Mode
438
426
  const spinnerGen = ora({ text: chalk.white('Writing Intelligent Hexagonal Output...'), spinner: 'material', color: 'magenta' }).start();
439
-
440
427
  try {
441
- switch (options.stack) {
442
- case 'node-ts-express':
443
- await generateNodeProject(options);
444
- break;
445
- // Note: Add Python/Java logic here mapping aiBlocks once hexagonalized completely
446
- default:
447
- throw new Error(`Pro Tier currently optimizes Node-TS Hexagonal structures. Using standard generation for ${options.stack}.`);
448
- }
428
+ await dispatchGenerator(options);
449
429
  spinnerGen.succeed(chalk.green('Hexagonal Auto-Write successful.'));
450
430
  } catch (err) {
451
431
  spinnerGen.fail(chalk.red('Write process failed.'));
452
432
  throw err;
453
433
  }
454
434
 
455
- // Write autonomous deployment workflows
456
435
  if (generatedBlocks.deployment) {
457
436
  await fs.ensureDir(path.join(options.projectDir, '.github', 'workflows'));
458
437
  await fs.writeFile(path.join(options.projectDir, 'docker-compose.yml'), generatedBlocks.deployment.dockerCompose);
459
438
  await fs.writeFile(path.join(options.projectDir, '.github', 'workflows', 'deploy.yml'), generatedBlocks.deployment.githubWorkflow);
460
439
  }
461
440
 
462
- // Print Health Dashboard
463
441
  printHealthDashboard(generatedBlocks);
442
+ printTokenUsage('pro', startTime);
443
+
444
+ p.outro(chalk.hex('#BF40FF').bold('PRO generation complete! cd ' + projectName));
464
445
  return;
465
446
  }
466
447
 
467
448
  // ── Route: FREE STANDARD MODE ──────────────────────────────────────
468
449
  await runFreeModePipeline(options);
450
+ printTokenUsage('free', startTime);
469
451
 
470
- // ── Success output ─────────────────────────────────────────────────
471
- console.log('');
472
- console.log(chalk.hex('#00F5FF').bold(' ╔══════════════════════════════════════════════╗'));
473
- console.log(chalk.hex('#00F5FF').bold(' ║ ✅ Backend Generation Complete! ✅ ║'));
474
- console.log(chalk.hex('#00F5FF').bold(' ╚══════════════════════════════════════════════╝'));
475
- console.log('');
476
- console.log(chalk.white(' Next Steps:'));
477
- console.log(chalk.cyan(` cd ${options.projectName}`));
478
- console.log(chalk.cyan(' (Check the generated README.md for instructions)'));
479
- console.log('');
452
+ p.outro(chalk.hex('#00F5FF').bold('Backend generated! cd ' + projectName));
480
453
  } catch (error) {
481
454
  console.log('');
482
- console.error(chalk.red.bold(' ❌ An error occurred during generation:'));
483
- console.error(chalk.red(` ${error.message || error}`));
455
+ p.log.error(chalk.red.bold(`Generation failed: ${error.message || error}`));
484
456
 
485
457
  if (error.stack) {
486
458
  console.log(chalk.gray(`\n Stack trace:\n${error.stack}`));
487
459
  }
488
460
 
489
- // Cleanup partial output
490
461
  if (options.projectDir && (await fs.pathExists(options.projectDir))) {
491
- const spinnerClean = ora({
492
- text: chalk.yellow('Cleaning up failed installation...'),
493
- spinner: 'line',
494
- color: 'yellow',
495
- }).start();
462
+ const spinnerClean = ora({ text: chalk.yellow('Cleaning up...'), spinner: 'line', color: 'yellow' }).start();
496
463
  await fs.remove(options.projectDir);
497
464
  spinnerClean.succeed(chalk.yellow('Cleanup complete.'));
498
465
  }