dolphin-server-modules 2.8.0 → 2.9.0

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 (87) hide show
  1. package/dist/adapters/mongoose/index.js +1 -4
  2. package/dist/adapters/mongoose/index.js.map +1 -1
  3. package/dist/adapters/mongoose/index.test.js +2 -4
  4. package/dist/adapters/mongoose/index.test.js.map +1 -1
  5. package/dist/adapters/mongoose/integration.test.js +19 -54
  6. package/dist/adapters/mongoose/integration.test.js.map +1 -1
  7. package/dist/auth/auth.js +23 -29
  8. package/dist/auth/auth.js.map +1 -1
  9. package/dist/auth/auth.test.js +5 -10
  10. package/dist/auth/auth.test.js.map +1 -1
  11. package/dist/authController/authController.js +8 -48
  12. package/dist/authController/authController.js.map +1 -1
  13. package/dist/authController/authController.test.js +121 -123
  14. package/dist/authController/authController.test.js.map +1 -1
  15. package/dist/bin/cli.js +233 -625
  16. package/dist/bin/cli.js.map +1 -1
  17. package/dist/controller/controller.js +4 -11
  18. package/dist/controller/controller.js.map +1 -1
  19. package/dist/controller/controller.test.js +4 -6
  20. package/dist/controller/controller.test.js.map +1 -1
  21. package/dist/curd/crud.js +4 -11
  22. package/dist/curd/crud.js.map +1 -1
  23. package/dist/curd/crud.test.js +2 -4
  24. package/dist/curd/crud.test.js.map +1 -1
  25. package/dist/demo-server.js +21 -59
  26. package/dist/demo-server.js.map +1 -1
  27. package/dist/djson/djson.js +10 -22
  28. package/dist/djson/djson.js.map +1 -1
  29. package/dist/djson/djson.test.js +149 -184
  30. package/dist/djson/djson.test.js.map +1 -1
  31. package/dist/dolphin-bench.js +4 -9
  32. package/dist/dolphin-bench.js.map +1 -1
  33. package/dist/hard-performance-test.js +9 -14
  34. package/dist/hard-performance-test.js.map +1 -1
  35. package/dist/index.js +4 -20
  36. package/dist/index.js.map +1 -1
  37. package/dist/middleware/zod.js +9 -15
  38. package/dist/middleware/zod.js.map +1 -1
  39. package/dist/middleware/zod.test.js +12 -14
  40. package/dist/middleware/zod.test.js.map +1 -1
  41. package/dist/performance-test.js +9 -14
  42. package/dist/performance-test.js.map +1 -1
  43. package/dist/real-test-mongoose.js +17 -22
  44. package/dist/real-test-mongoose.js.map +1 -1
  45. package/dist/realtime/codec.js +3 -8
  46. package/dist/realtime/codec.js.map +1 -1
  47. package/dist/realtime/core.js +20 -64
  48. package/dist/realtime/core.js.map +1 -1
  49. package/dist/realtime/index.js +4 -20
  50. package/dist/realtime/index.js.map +1 -1
  51. package/dist/realtime/plugins.js +6 -12
  52. package/dist/realtime/plugins.js.map +1 -1
  53. package/dist/realtime/realtime.test.js +10 -45
  54. package/dist/realtime/realtime.test.js.map +1 -1
  55. package/dist/realtime/trie.js +1 -5
  56. package/dist/realtime/trie.js.map +1 -1
  57. package/dist/router/router.js +1 -4
  58. package/dist/router/router.js.map +1 -1
  59. package/dist/router/router.test.js +9 -11
  60. package/dist/router/router.test.js.map +1 -1
  61. package/dist/server/server.js +12 -18
  62. package/dist/server/server.js.map +1 -1
  63. package/dist/server/server.test.js +21 -26
  64. package/dist/server/server.test.js.map +1 -1
  65. package/dist/services/ai-service.d.ts +16 -0
  66. package/dist/services/ai-service.js +141 -0
  67. package/dist/services/ai-service.js.map +1 -0
  68. package/dist/signaling/index.js +4 -9
  69. package/dist/signaling/index.js.map +1 -1
  70. package/dist/signaling/signaling.test.js +13 -15
  71. package/dist/signaling/signaling.test.js.map +1 -1
  72. package/dist/swagger/swagger.js +3 -8
  73. package/dist/swagger/swagger.js.map +1 -1
  74. package/dist/swagger/swagger.test.js +10 -12
  75. package/dist/swagger/swagger.test.js.map +1 -1
  76. package/dist/templates/index.d.ts +9 -0
  77. package/dist/templates/index.js +112 -0
  78. package/dist/templates/index.js.map +1 -0
  79. package/dist/test-2fa-real.js +6 -11
  80. package/dist/test-2fa-real.js.map +1 -1
  81. package/dist/test-dolphin.js +6 -8
  82. package/dist/test-dolphin.js.map +1 -1
  83. package/dist/utils/ui.d.ts +10 -0
  84. package/dist/utils/ui.js +36 -0
  85. package/dist/utils/ui.js.map +1 -0
  86. package/package.json +2 -1
  87. package/scripts/client.js +3 -1
package/dist/bin/cli.js CHANGED
@@ -1,675 +1,283 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k;
5
- var desc = Object.getOwnPropertyDescriptor(m, k);
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k]; } };
8
- }
9
- Object.defineProperty(o, k2, desc);
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- o[k2] = m[k];
13
- }));
14
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
- Object.defineProperty(o, "default", { enumerable: true, value: v });
16
- }) : function(o, v) {
17
- o["default"] = v;
18
- });
19
- var __importStar = (this && this.__importStar) || (function () {
20
- var ownKeys = function(o) {
21
- ownKeys = Object.getOwnPropertyNames || function (o) {
22
- var ar = [];
23
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
- return ar;
25
- };
26
- return ownKeys(o);
27
- };
28
- return function (mod) {
29
- if (mod && mod.__esModule) return mod;
30
- var result = {};
31
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
- __setModuleDefault(result, mod);
33
- return result;
34
- };
35
- })();
36
- var __importDefault = (this && this.__importDefault) || function (mod) {
37
- return (mod && mod.__esModule) ? mod : { "default": mod };
38
- };
39
- Object.defineProperty(exports, "__esModule", { value: true });
40
- const fs_1 = __importDefault(require("fs"));
41
- const path_1 = __importDefault(require("path"));
42
- const https_1 = __importDefault(require("https"));
43
- const server_1 = require("../server/server");
44
- const core_1 = require("../realtime/core");
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { createServer } from 'http';
5
+ import { AIService } from '../services/ai-service.js';
6
+ import { CLIUI } from '../utils/ui.js';
7
+ import { TEMPLATES } from '../templates/index.js';
45
8
  const args = process.argv.slice(2);
46
- const command = args[0];
47
- const TEMPLATES = {
48
- app: `import { createDolphinServer } from 'dolphin-server-modules/server';
49
- const app = createDolphinServer();
50
-
51
- app.get('/', (ctx) => ctx.json({ message: 'Dolphin Server is running!' }));
52
-
53
- app.listen(3000, () => console.log('🐬 Dolphin swimming on port 3000'));`,
54
- mongoose: `import mongoose from 'mongoose';
55
- import { createMongooseAdapter } from 'dolphin-server-modules/adapters/mongoose';
56
-
57
- export async function connectDB(models = {}) {
58
- const uri = process.env.MONGO_URI || 'mongodb://localhost:27017/dolphin_db';
59
- await mongoose.connect(uri);
60
- console.log('✅ MongoDB Connected');
61
-
62
- return createMongooseAdapter({
63
- models: { ...models }
64
- });
65
- }`,
66
- sequelize: `import { Sequelize } from 'sequelize';
67
- // Note: This is a skeleton for Dolphin Sequelize Adapter
68
- export async function connectDB() {
69
- const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASS, {
70
- host: process.env.DB_HOST,
71
- dialect: 'mysql' // or 'postgres', 'sqlite'
9
+ const command = args[0] || 'help';
10
+ // Simple .env loader
11
+ const envPath = path.join(process.cwd(), '.env');
12
+ if (fs.existsSync(envPath)) {
13
+ const envContent = fs.readFileSync(envPath, 'utf8');
14
+ envContent.split('\n').forEach(line => {
15
+ const [key, ...valueParts] = line.split('=');
16
+ if (key && valueParts.length > 0) {
17
+ process.env[key.trim()] = valueParts.join('=').trim();
18
+ }
72
19
  });
73
-
74
- try {
75
- await sequelize.authenticate();
76
- console.log('✅ SQL Database Connected');
77
- } catch (error) {
78
- console.error('❌ Unable to connect to the database:', error);
79
- }
80
-
81
- return sequelize;
82
- }`,
83
- auth: `import { createDolphinAuthController } from 'dolphin-server-modules/auth-controller';
84
- import { createDolphinRouter } from 'dolphin-server-modules/router';
85
-
86
- export function setupAuth(dbAdapter, config) {
87
- const router = createDolphinRouter();
88
- const auth = createDolphinAuthController(dbAdapter, config);
89
-
90
- router.post('/register', auth.register);
91
- router.post('/login', auth.login);
92
- router.post('/refresh', auth.refresh);
93
- router.get('/me', auth.requireAuth, (ctx) => ctx.json(ctx.req.user));
94
-
95
- return router;
96
- }`,
97
- crud: (name) => `import { createCRUD } from 'dolphin-server-modules/crud';
98
-
99
- export function setup${name}CRUD(dbAdapter) {
100
- const service = createCRUD(dbAdapter, { enforceOwnership: false });
101
- const COLLECTION = '${name}';
102
-
103
- return {
104
- getAll: async (ctx) => ctx.json(await service.read(COLLECTION, ctx.query)),
105
- getOne: async (ctx) => ctx.json(await service.readOne(COLLECTION, ctx.params.id)),
106
- create: async (ctx) => ctx.json(await service.create(COLLECTION, ctx.body)),
107
- update: async (ctx) => ctx.json(await service.updateOne(COLLECTION, ctx.params.id, ctx.body)),
108
- delete: async (ctx) => ctx.json(await service.deleteOne(COLLECTION, ctx.params.id))
109
- };
110
- }`,
111
- authModel: `import mongoose from 'mongoose';
112
-
113
- const UserSchema = new mongoose.Schema({
114
- email: { type: String, required: true, unique: true },
115
- password: { type: String, required: true },
116
- name: { type: String },
117
- role: { type: String, default: 'user' },
118
- is2FAEnabled: { type: Boolean, default: false },
119
- twoFASecret: { type: String },
120
- recoveryCodes: [{ type: String }],
121
- createdAt: { type: Date, default: Date.now }
122
- });
123
-
124
- const RefreshTokenSchema = new mongoose.Schema({
125
- userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
126
- token: { type: String, required: true },
127
- expiresAt: { type: Date, required: true },
128
- createdAt: { type: Date, default: Date.now }
129
- });
130
-
131
- export const User = mongoose.model('User', UserSchema);
132
- export const RefreshToken = mongoose.model('RefreshToken', RefreshTokenSchema);`
20
+ }
21
+ const aiConfig = {
22
+ apiKey: (process.env.DOLPHIN_AI_KEY || process.env.GEMINI_API_KEY || '').trim(),
23
+ baseUrl: process.env.DOLPHIN_AI_BASE_URL,
24
+ model: process.env.DOLPHIN_AI_MODEL
133
25
  };
134
- function loadEnv() {
135
- const envPath = path_1.default.join(process.cwd(), '.env');
136
- if (fs_1.default.existsSync(envPath)) {
137
- const content = fs_1.default.readFileSync(envPath, 'utf8');
138
- content.split(/\r?\n/).forEach(line => {
139
- const trimmedLine = line.trim();
140
- if (!trimmedLine || trimmedLine.startsWith('#'))
141
- return;
142
- const [key, ...valueParts] = trimmedLine.split('=');
143
- if (key && valueParts.length > 0) {
144
- const value = valueParts.join('=').trim().replace(/^["']|["']$/g, '');
145
- process.env[key.trim()] = value;
146
- }
147
- });
148
- }
26
+ if (!aiConfig.apiKey && ['generate', 'generate-full', 'chat'].includes(command)) {
27
+ CLIUI.error('API Key not found! Please set DOLPHIN_AI_KEY or GEMINI_API_KEY in your .env file.');
28
+ process.exit(1);
149
29
  }
30
+ const ai = new AIService(aiConfig);
150
31
  async function run() {
151
- loadEnv();
152
32
  switch (command) {
33
+ case 'serve':
34
+ const port = parseInt(args.find(arg => arg.startsWith('--port='))?.split('=')[1] || '3000');
35
+ CLIUI.heading(`Dolphin Dev Server starting on port ${port}`);
36
+ const server = createServer((req, res) => {
37
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
38
+ res.end('Dolphin Server is swimming!\n');
39
+ });
40
+ server.listen(port, () => {
41
+ CLIUI.success(`Server is live at http://localhost:${port}`);
42
+ });
43
+ break;
153
44
  case 'generate':
154
- const requestedModel = args.find(arg => arg.startsWith('--model='))?.split('=')[1] || 'gemini-flash-latest';
155
- const prompt = args.filter(arg => !arg.startsWith('--')).slice(1).join(' ');
156
- const apiKey = (process.env.GEMINI_API_KEY || '').trim();
157
- if (!apiKey) {
158
- console.log('❌ Error: GEMINI_API_KEY environment variable is not set.');
159
- console.log('💡 Get a free key at: https://aistudio.google.com/app/apikey');
160
- break;
45
+ const prompt = args.slice(1).join(' ');
46
+ if (!prompt)
47
+ return CLIUI.error('Usage: dolphin generate "your prompt"');
48
+ CLIUI.startSpinner('AI is generating code');
49
+ try {
50
+ const response = await ai.request(prompt, "Return ONLY raw JavaScript code. No markdown.");
51
+ const cleanCode = response.replace(/```javascript|```js|```/g, '').trim();
52
+ fs.writeFileSync(path.join(process.cwd(), 'ai-generated.js'), cleanCode);
53
+ CLIUI.stopSpinner(true, 'File generated: ai-generated.js');
161
54
  }
162
- if (!prompt) {
163
- console.log('❌ Please provide a prompt. Example: dolphin generate "a library management system"');
164
- break;
55
+ catch (e) {
56
+ CLIUI.stopSpinner(false, e.message);
165
57
  }
166
- const fallbackModels = ['gemini-flash-latest', 'gemini-2.0-flash', 'gemini-2.5-flash', 'gemini-pro-latest'];
167
- const modelsToTry = [requestedModel, ...fallbackModels.filter(m => m !== requestedModel)];
168
- const versions = ['v1beta', 'v1'];
169
- const tryGenerate = (modelIndex, versionIndex) => {
170
- if (modelIndex >= modelsToTry.length) {
171
- console.log('❌ All AI models failed. Please ensure Generative Language API is enabled in Google AI Studio for your key.');
172
- return;
173
- }
174
- if (versionIndex >= versions.length) {
175
- return tryGenerate(modelIndex + 1, 0);
176
- }
177
- const currentModel = modelsToTry[modelIndex];
178
- const currentVersion = versions[versionIndex];
179
- console.log(`🤖 AI (${currentModel} via ${currentVersion}) is swimming with Dolphin...`);
180
- const aiData = JSON.stringify({
181
- contents: [{ parts: [{ text: `Generate a production-ready Node.js file using dolphin-server-modules.
182
- Rules:
183
- 1. Use ESM 'import' instead of 'require'.
184
- 2. Use 'const app = createDolphinServer();' from 'dolphin-server-modules/server'.
185
- 3. Use unified context '(ctx) => { ... }' instead of '(req, res)'.
186
- 4. Return objects directly for JSON response.
187
- 5. No markdown backticks, no explanations.
188
- Context: ${prompt}. Return ONLY raw JS code.` }] }]
189
- });
190
- const options = {
191
- hostname: 'generativelanguage.googleapis.com',
192
- path: `/${currentVersion}/models/${currentModel}:generateContent?key=${encodeURIComponent(apiKey)}`,
193
- method: 'POST',
194
- headers: { 'Content-Type': 'application/json' }
195
- };
196
- const req = https_1.default.request(options, (res) => {
197
- let body = '';
198
- res.on('data', (d) => body += d);
199
- res.on('end', () => {
200
- try {
201
- const json = JSON.parse(body);
202
- if (json.error) {
203
- console.log(`⚠️ ${currentModel} (${currentVersion}) unavailable. Error: ${json.error.status}. Trying next...`);
204
- tryGenerate(modelIndex, versionIndex + 1);
205
- return;
206
- }
207
- const rawCode = json.candidates?.[0]?.content?.parts?.[0]?.text || '';
208
- if (rawCode) {
209
- const codeMatch = rawCode.match(/(\/\*[\s\S]*?\*\/|\/\/.*|[\s\S])*?(import|const|let|var|app|function)[\s\S]*/);
210
- const code = (codeMatch ? codeMatch[0] : rawCode).replace(/```javascript|```js|```/g, '').trim();
211
- fs_1.default.writeFileSync(path_1.default.join(process.cwd(), 'ai-generated-app.js'), code);
212
- console.log(`✅ Success! Code generated using ${currentModel} in ai-generated-app.js`);
213
- }
214
- else {
215
- tryGenerate(modelIndex, versionIndex + 1);
216
- }
217
- }
218
- catch (e) {
219
- tryGenerate(modelIndex, versionIndex + 1);
220
- }
221
- });
222
- });
223
- req.on('error', () => tryGenerate(modelIndex, versionIndex + 1));
224
- req.write(aiData);
225
- req.end();
226
- };
227
- tryGenerate(0, 0);
228
58
  break;
229
59
  case 'generate-full':
230
- const fullRequestedModel = args.find(arg => arg.startsWith('--model='))?.split('=')[1] || 'gemini-1.5-flash';
231
- const fullPrompt = args.filter(arg => !arg.startsWith('--')).slice(1).join(' ');
232
- const fullApiKey = (process.env.GEMINI_API_KEY || '').trim();
233
- if (!fullApiKey) {
234
- console.log('❌ Error: GEMINI_API_KEY environment variable is not set.');
235
- break;
60
+ const fullPrompt = args.slice(1).join(' ');
61
+ if (!fullPrompt)
62
+ return CLIUI.error('Usage: dolphin generate-full "project description"');
63
+ CLIUI.startSpinner('Architecting full project structure');
64
+ try {
65
+ const systemPrompt = `You are a Dolphin Framework expert. Generate a production-ready backend project.
66
+ Dolphin Patterns:
67
+ - controllers/ (Logic)
68
+ - models/ (Schemas: can be Mongoose or Sequelize)
69
+ - config/db.js (DB connection logic)
70
+ - .env (Environment variables)
71
+ Dolphin supports both Mongoose (MongoDB) and Sequelize (MySQL/PostgreSQL).
72
+ Choose the appropriate adapter based on the user's request.
73
+ Return ONLY a JSON object where keys are file paths and values are code content. No markdown.`;
74
+ const response = await ai.request(fullPrompt, systemPrompt);
75
+ const files = JSON.parse(response.replace(/```json|```/g, '').trim());
76
+ Object.entries(files).forEach(([fPath, content]) => {
77
+ const fullPath = path.join(process.cwd(), fPath);
78
+ // Don't overwrite existing .env files to protect API keys
79
+ if (fPath === '.env' && fs.existsSync(fullPath)) {
80
+ console.log(` ⚠️ Skipped: .env (file already exists)`);
81
+ return;
82
+ }
83
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
84
+ fs.writeFileSync(fullPath, content);
85
+ console.log(` 📄 Created: ${fPath}`);
86
+ });
87
+ CLIUI.stopSpinner(true, 'Project architected successfully!');
236
88
  }
237
- if (!fullPrompt) {
238
- console.log('❌ Please provide a prompt. Example: dolphin generate-full "a laundry management system"');
239
- break;
89
+ catch (e) {
90
+ CLIUI.stopSpinner(false, e.message);
240
91
  }
241
- console.log(`🤖 AI is architecting a full production-grade system...`);
242
- const tryGenerateFull = (modelIndex, versionIndex) => {
243
- const modelsToTry = ['gemini-1.5-flash-latest', 'gemini-flash-latest', 'gemini-1.5-pro-latest', 'gemini-pro-latest'];
244
- const versions = ['v1beta', 'v1'];
245
- if (modelIndex >= modelsToTry.length) {
246
- console.log('❌ All AI models failed for full architecture. Please check your prompt or API limits.');
247
- return;
248
- }
249
- if (versionIndex >= versions.length) {
250
- return tryGenerateFull(modelIndex + 1, 0);
251
- }
252
- const currentModel = modelsToTry[modelIndex];
253
- const currentVersion = versions[versionIndex];
254
- console.log(`🤖 AI (${currentModel} via ${currentVersion}) is architecting...`);
255
- const aiData = JSON.stringify({
256
- contents: [{ parts: [{ text: `Generate a full production-ready modular Node.js project structure using dolphin-server-modules.
257
- Rules:
258
- 1. Return ONLY a valid JSON object.
259
- 2. Keys are file paths (e.g., "routes/user.js", "models/User.js", "app.js").
260
- 3. Values are the file contents as strings.
261
- 4. Use ESM 'import'.
262
- 5. Use 'const app = createDolphinServer();' from 'dolphin-server-modules/server' to initialize the app.
263
- 6. Use 'createMongooseAdapter' from 'dolphin-server-modules/adapters/mongoose' for DB connectivity.
264
- 7. Use 'createDolphinAuthController' from 'dolphin-server-modules/auth-controller' for all authentication logic.
265
- 8. Use 'createCRUD' from 'dolphin-server-modules/crud' to generate standard API services.
266
- 9. Create REAL Mongoose schemas in the models/ folder and export them to be used by the adapter.
267
- 10. Use Dolphin unified context '(ctx) => { ... }' for all route handlers.
268
- 11. Return objects directly for JSON response (e.g., return { success: true, data }).
269
- 12. Include folders: models, controllers, routes, middleware, config, services.
270
- 13. Ensure 'app.js' connects everything: DB -> Adapter -> Auth/CRUD -> Router -> Server.
271
- Context: ${fullPrompt}.
272
- Return ONLY the JSON. No markdown backticks, no text before or after.` }] }]
273
- });
274
- const options = {
275
- hostname: 'generativelanguage.googleapis.com',
276
- path: `/${currentVersion}/models/${currentModel}:generateContent?key=${encodeURIComponent(fullApiKey)}`,
277
- method: 'POST',
278
- headers: { 'Content-Type': 'application/json' }
279
- };
280
- const req = https_1.default.request(options, (res) => {
281
- let body = '';
282
- res.on('data', (d) => body += d);
283
- res.on('end', () => {
92
+ break;
93
+ case 'chat':
94
+ CLIUI.heading('🐬 Dolphin Autonomous Agent Mode');
95
+ const readline = await import('readline');
96
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
97
+ const agentSystemPrompt = `You are the Dolphin Autonomous Agent.
98
+ You have FULL ACCESS to the filesystem and shell.
99
+ To perform actions, return a JSON block with "tool" and "params".
100
+ Available Tools:
101
+ 1. {"tool": "read", "path": "file_path"} - Read a file
102
+ 2. {"tool": "write", "path": "path", "content": "..."} - Create/Edit a file
103
+ 3. {"tool": "list", "path": "dir"} - List files
104
+ 4. {"tool": "shell", "cmd": "..."} - Run a terminal command
105
+ 5. {"tool": "done", "msg": "..."} - Final answer
106
+
107
+ Rules:
108
+ - Always check the folder structure before writing.
109
+ - If you see an error, fix it using the "write" tool.
110
+ - You can call multiple tools one after another.
111
+ - Use ESM (import/export).`;
112
+ const chatLoop = () => {
113
+ rl.question('\n💬 You: ', async (input) => {
114
+ if (input.toLowerCase() === 'exit')
115
+ return rl.close();
116
+ let currentPrompt = input;
117
+ let isRunning = true;
118
+ while (isRunning) {
119
+ CLIUI.startSpinner('Agent is thinking');
284
120
  try {
285
- const json = JSON.parse(body);
286
- if (json.error) {
287
- console.log(`⚠️ ${currentModel} (${currentVersion}) error: ${json.error.message}. Trying next...`);
288
- return tryGenerateFull(modelIndex, versionIndex + 1);
121
+ const response = await ai.request(currentPrompt, agentSystemPrompt);
122
+ CLIUI.stopSpinner();
123
+ // Try to parse tool call(s)
124
+ let toolCalls = [];
125
+ try {
126
+ const jsonMatch = response.match(/\[[\s\S]*\]|\{[\s\S]*\}/);
127
+ if (jsonMatch) {
128
+ const parsed = JSON.parse(jsonMatch[0]);
129
+ toolCalls = Array.isArray(parsed) ? parsed : [parsed];
130
+ }
289
131
  }
290
- const rawJson = json.candidates?.[0]?.content?.parts?.[0]?.text || '';
291
- const cleanJson = rawJson.replace(/```json|```/g, '').trim();
292
- const jsonMatch = cleanJson.match(/\{[\s\S]*\}/);
293
- if (jsonMatch) {
294
- try {
295
- const files = JSON.parse(jsonMatch[0]);
296
- Object.keys(files).forEach(filePath => {
297
- const fullPath = path_1.default.join(process.cwd(), filePath);
298
- const dirPath = path_1.default.dirname(fullPath);
299
- if (!fs_1.default.existsSync(dirPath)) {
300
- fs_1.default.mkdirSync(dirPath, { recursive: true });
301
- }
302
- fs_1.default.writeFileSync(fullPath, files[filePath]);
303
- console.log(`📄 Generated: ${filePath}`);
304
- });
305
- console.log('✅ Full project architected successfully! 🐬');
132
+ catch (e) { }
133
+ if (toolCalls.length > 0) {
134
+ let results = '';
135
+ for (const toolCall of toolCalls) {
136
+ if (!toolCall.tool)
137
+ continue;
138
+ switch (toolCall.tool) {
139
+ case 'read':
140
+ const rPath = path.join(process.cwd(), toolCall.path);
141
+ if (fs.existsSync(rPath)) {
142
+ const content = fs.readFileSync(rPath, 'utf8');
143
+ console.log(`📖 Read ${toolCall.path}`);
144
+ results += `Content of ${toolCall.path}:\n${content}\n\n`;
145
+ }
146
+ else {
147
+ results += `Error: ${toolCall.path} not found.\n`;
148
+ }
149
+ break;
150
+ case 'write':
151
+ const wPath = path.join(process.cwd(), toolCall.path);
152
+ fs.mkdirSync(path.dirname(wPath), { recursive: true });
153
+ fs.writeFileSync(wPath, toolCall.content);
154
+ console.log(`📝 Wrote to ${toolCall.path}`);
155
+ results += `Successfully wrote ${toolCall.path}.\n`;
156
+ break;
157
+ case 'list':
158
+ const lPath = path.join(process.cwd(), toolCall.path || '.');
159
+ if (fs.existsSync(lPath)) {
160
+ const files = fs.readdirSync(lPath);
161
+ console.log(`📁 Files in ${toolCall.path || '.'}: ${files.join(', ')}`);
162
+ results += `Files in ${toolCall.path || '.'}:\n${files.join('\n')}\n`;
163
+ }
164
+ else {
165
+ results += `Error: Directory ${toolCall.path} not found.\n`;
166
+ }
167
+ break;
168
+ case 'shell':
169
+ console.log(`💻 Running: ${toolCall.cmd}`);
170
+ const { execSync } = await import('child_process');
171
+ try {
172
+ const output = execSync(toolCall.cmd).toString();
173
+ results += `Shell Output of "${toolCall.cmd}":\n${output}\n`;
174
+ }
175
+ catch (err) {
176
+ results += `Shell Error of "${toolCall.cmd}":\n${err.message}\n`;
177
+ }
178
+ break;
179
+ case 'done':
180
+ console.log(`✔ ${toolCall.msg || 'Task complete'}`);
181
+ isRunning = false;
182
+ break;
183
+ }
306
184
  }
307
- catch (e) {
308
- console.log('❌ Error parsing project JSON. Trying next model...');
309
- tryGenerateFull(modelIndex, versionIndex + 1);
185
+ if (results) {
186
+ currentPrompt = `Results of actions:\n${results}\n\nWhat next?`;
187
+ continue; // Let the agent think again with results
310
188
  }
311
189
  }
312
190
  else {
313
- console.log('❌ AI failed to return valid JSON. Trying next model...');
314
- tryGenerateFull(modelIndex, versionIndex + 1);
315
- }
316
- }
317
- catch (e) {
318
- console.log('❌ Unexpected error. Trying next model...');
319
- tryGenerateFull(modelIndex, versionIndex + 1);
320
- }
321
- });
322
- });
323
- req.on('error', () => tryGenerateFull(modelIndex, versionIndex + 1));
324
- req.write(aiData);
325
- req.end();
326
- };
327
- tryGenerateFull(0, 0);
328
- break;
329
- case 'modify':
330
- const fileToModify = args[1];
331
- const modifyPrompt = args.slice(2).join(' ');
332
- const modifyApiKey = (process.env.GEMINI_API_KEY || '').trim();
333
- if (!modifyApiKey || !fileToModify || !modifyPrompt) {
334
- console.log('❌ Usage: dolphin modify <filename> "your instructions"');
335
- break;
336
- }
337
- const filePath = path_1.default.join(process.cwd(), fileToModify);
338
- if (!fs_1.default.existsSync(filePath)) {
339
- console.log(`❌ File not found: ${fileToModify}`);
340
- break;
341
- }
342
- const currentCode = fs_1.default.readFileSync(filePath, 'utf8');
343
- console.log(`🛠️ Modifying ${fileToModify}...`);
344
- const tryModify = (modelIndex, versionIndex) => {
345
- const models = ['gemini-1.5-flash-latest', 'gemini-flash-latest', 'gemini-1.5-pro-latest'];
346
- const versions = ['v1beta', 'v1'];
347
- if (modelIndex >= models.length) {
348
- console.log('❌ Modification failed after trying all models.');
349
- return;
350
- }
351
- if (versionIndex >= versions.length) {
352
- return tryModify(modelIndex + 1, 0);
353
- }
354
- const model = models[modelIndex];
355
- const version = versions[versionIndex];
356
- const aiData = JSON.stringify({
357
- contents: [{ parts: [{ text: `You are an expert Dolphin Framework developer.
358
- Task: Modify the following code based on the instructions.
359
- Instructions: ${modifyPrompt}
360
- Current Code:
361
- ${currentCode}
362
-
363
- Rules:
364
- 1. Return ONLY the updated code.
365
- 2. No markdown, no explanations.
366
- 3. Maintain Dolphin Framework standards (ctx usage, createDolphinServer, etc.).` }] }]
367
- });
368
- const options = {
369
- hostname: 'generativelanguage.googleapis.com',
370
- path: `/${version}/models/${model}:generateContent?key=${encodeURIComponent(modifyApiKey)}`,
371
- method: 'POST',
372
- headers: { 'Content-Type': 'application/json' }
373
- };
374
- const req = https_1.default.request(options, (res) => {
375
- let body = '';
376
- res.on('data', (d) => body += d);
377
- res.on('end', () => {
378
- try {
379
- const json = JSON.parse(body);
380
- if (json.error) {
381
- return tryModify(modelIndex, versionIndex + 1);
382
- }
383
- const rawCode = json.candidates?.[0]?.content?.parts?.[0]?.text || '';
384
- if (rawCode) {
385
- const newCode = rawCode.replace(/```javascript|```js|```/g, '').trim();
386
- fs_1.default.writeFileSync(filePath, newCode);
387
- console.log(`✅ ${fileToModify} updated successfully!`);
388
- }
389
- else {
390
- tryModify(modelIndex, versionIndex + 1);
191
+ console.log(`🤖 AI: ${response}`);
192
+ isRunning = false;
391
193
  }
392
194
  }
393
195
  catch (e) {
394
- tryModify(modelIndex, versionIndex + 1);
196
+ CLIUI.stopSpinner(false, e.message);
197
+ isRunning = false;
395
198
  }
396
- });
199
+ }
200
+ chatLoop();
397
201
  });
398
- req.on('error', () => tryModify(modelIndex, versionIndex + 1));
399
- req.write(aiData);
400
- req.end();
401
202
  };
402
- tryModify(0, 0);
403
- break;
404
- case 'chat':
405
- console.log('🐬 Welcome to Dolphin AI Agent! I can write files and run commands.');
406
- console.log('💡 Try: "Create a new model for Blog" or "Show me current files"');
407
- console.log('💬 Type "exit" to quit.\n');
408
- const agentApiKey = (process.env.GEMINI_API_KEY || '').trim();
409
- if (!agentApiKey) {
410
- console.log('❌ GEMINI_API_KEY not set.');
411
- break;
412
- }
413
- Promise.resolve().then(() => __importStar(require('readline'))).then(readline => {
414
- const rl = readline.createInterface({
415
- input: process.stdin,
416
- output: process.stdout
417
- });
418
- const ask = () => {
419
- rl.question('💬 You: ', (input) => {
420
- if (input.toLowerCase() === 'exit')
421
- return rl.close();
422
- const systemPrompt = `You are the Dolphin Framework AI Agent.
423
- You have access to the user's filesystem and shell.
424
-
425
- To read a file:
426
- FILE_READ: <path>
427
-
428
- To create or modify a file:
429
- FILE_WRITE: <path>
430
- <content>
431
- END_FILE
432
-
433
- To run a command:
434
- SHELL_RUN: <command>
435
-
436
- Current working directory: ${process.cwd()}
437
- Always use Dolphin Framework standards. If you need to modify a file, READ it first.`;
438
- const aiData = JSON.stringify({
439
- contents: [{ parts: [{ text: systemPrompt + "\nUser: " + input }] }]
440
- });
441
- const askAI = (modelIndex, versionIndex) => {
442
- const models = ['gemini-flash-latest', 'gemini-1.5-flash', 'gemini-pro-latest'];
443
- const versions = ['v1beta', 'v1'];
444
- if (modelIndex >= models.length) {
445
- console.log('🤖 Dolphin: Sorry, I am having trouble connecting right now.');
446
- return ask();
447
- }
448
- if (versionIndex >= versions.length)
449
- return askAI(modelIndex + 1, 0);
450
- const currentModel = models[modelIndex];
451
- const currentVersion = versions[versionIndex];
452
- const options = {
453
- hostname: 'generativelanguage.googleapis.com',
454
- path: `/${currentVersion}/models/${currentModel}:generateContent?key=${encodeURIComponent(agentApiKey)}`,
455
- method: 'POST',
456
- headers: { 'Content-Type': 'application/json' }
457
- };
458
- const req = https_1.default.request(options, (res) => {
459
- let body = '';
460
- res.on('data', (d) => body += d);
461
- res.on('end', async () => {
462
- try {
463
- const json = JSON.parse(body);
464
- const reply = json.candidates?.[0]?.content?.parts?.[0]?.text || '';
465
- if (!reply)
466
- return askAI(modelIndex, versionIndex + 1);
467
- // Handle FILE_WRITE
468
- if (reply.includes('FILE_WRITE:')) {
469
- const match = reply.match(/FILE_WRITE: (.*?)\n([\s\S]*?)END_FILE/);
470
- if (match) {
471
- const fPath = match[1].trim();
472
- const content = match[2].trim().replace(/```javascript|```js|```/g, '');
473
- const fullPath = path_1.default.join(process.cwd(), fPath);
474
- const dir = path_1.default.dirname(fullPath);
475
- if (!fs_1.default.existsSync(dir))
476
- fs_1.default.mkdirSync(dir, { recursive: true });
477
- fs_1.default.writeFileSync(fullPath, content);
478
- console.log(`🤖 Dolphin Agent: Created/Updated file [${fPath}]`);
479
- }
480
- }
481
- // Handle SHELL_RUN
482
- if (reply.includes('SHELL_RUN:')) {
483
- const cmdMatch = reply.match(/SHELL_RUN: (.*)/);
484
- if (cmdMatch) {
485
- const cmd = cmdMatch[1].trim();
486
- console.log(`🤖 Dolphin Agent: Executing [${cmd}]...`);
487
- try {
488
- const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
489
- execSync(cmd, { stdio: 'inherit' });
490
- }
491
- catch (e) {
492
- console.log(`❌ Command failed: ${cmd}`);
493
- }
494
- }
495
- }
496
- // Handle FILE_READ
497
- if (reply.includes('FILE_READ:')) {
498
- const readMatch = reply.match(/FILE_READ: (.*)/);
499
- if (readMatch) {
500
- const rPath = readMatch[1].trim();
501
- const fullRPath = path_1.default.join(process.cwd(), rPath);
502
- if (fs_1.default.existsSync(fullRPath)) {
503
- const content = fs_1.default.readFileSync(fullRPath, 'utf8');
504
- console.log(`🤖 Dolphin Agent: Read file [${rPath}]`);
505
- // We send the content back as a follow-up user message
506
- input = `Content of ${rPath}:\n${content}\nNow proceed with my previous request.`;
507
- return askAI(0, 0);
508
- }
509
- }
510
- }
511
- console.log(`🤖 Dolphin: ${reply.split('FILE_WRITE:')[0].split('SHELL_RUN:')[0].split('FILE_READ:')[0].trim()}\n`);
512
- ask();
513
- }
514
- catch (e) {
515
- askAI(modelIndex, versionIndex + 1);
516
- }
517
- });
518
- });
519
- req.on('error', () => askAI(modelIndex, versionIndex + 1));
520
- req.write(aiData);
521
- req.end();
522
- };
523
- askAI(0, 0);
524
- });
525
- };
526
- ask();
527
- });
528
- break;
529
- case 'clean':
530
- const filesToClean = ['ai-generated-app.js', 'ai-test-output.js'];
531
- filesToClean.forEach(file => {
532
- const filePath = path_1.default.join(process.cwd(), file);
533
- if (fs_1.default.existsSync(filePath)) {
534
- fs_1.default.unlinkSync(filePath);
535
- console.log(`🗑️ Deleted: ${file}`);
536
- }
537
- });
538
- console.log('✅ Cleanup complete.');
539
- break;
540
- case 'serve':
541
- let portStr = args.find(arg => arg.startsWith('--port='))?.split('=')[1];
542
- if (!portStr) {
543
- const portIdx = args.indexOf('--port');
544
- if (portIdx !== -1 && args[portIdx + 1])
545
- portStr = args[portIdx + 1];
546
- }
547
- const port = parseInt(portStr || '3000');
548
- const rt = new core_1.RealtimeCore({ debug: true });
549
- const server = (0, server_1.createDolphinServer)({ realtime: rt });
550
- server.get('/', (ctx) => ctx.html('<h1>Dolphin CLI Static Server</h1>'));
551
- server.listen(port, () => console.log(`✅ Dolphin Server running at http://localhost:${port}`));
203
+ chatLoop();
552
204
  break;
553
205
  case 'init':
554
- console.log('🏗️ Initializing Dolphin Project...');
555
- fs_1.default.writeFileSync(path_1.default.join(process.cwd(), 'app.js'), TEMPLATES.app);
556
- if (!fs_1.default.existsSync(path_1.default.join(process.cwd(), 'package.json'))) {
557
- fs_1.default.writeFileSync(path_1.default.join(process.cwd(), 'package.json'), JSON.stringify({
558
- name: path_1.default.basename(process.cwd()),
559
- version: '1.0.0',
560
- main: 'app.js',
561
- type: 'module',
562
- dependencies: {
563
- "dolphin-server-modules": "^2.2.2",
564
- "mongoose": "^8.0.0",
565
- "zod": "^3.22.0"
566
- }
567
- }, null, 2));
568
- console.log('✅ Created package.json with core dependencies.');
569
- }
570
- else {
571
- console.log('⚠️ package.json already exists. Skipping...');
572
- }
573
- if (!fs_1.default.existsSync(path_1.default.join(process.cwd(), '.gitignore'))) {
574
- fs_1.default.writeFileSync(path_1.default.join(process.cwd(), '.gitignore'), '.env\nnode_modules\ndist\n.DS_Store');
575
- console.log('✅ Created .gitignore');
576
- }
206
+ case 'init-prod':
207
+ CLIUI.heading('Scaffolding Production Project');
208
+ const dirs = ['models', 'controllers', 'routes', 'middleware', 'services', 'config'];
209
+ dirs.forEach(dir => {
210
+ const p = path.join(process.cwd(), dir);
211
+ if (!fs.existsSync(p))
212
+ fs.mkdirSync(p, { recursive: true });
213
+ });
214
+ fs.writeFileSync(path.join(process.cwd(), 'app.js'), TEMPLATES.app);
215
+ CLIUI.success('Folders and app.js created.');
577
216
  break;
578
217
  case 'add':
579
- const type = args[1]; // adapter, auth, crud
580
- const name = args[2] || 'Default';
581
- if (type === 'adapter') {
582
- if (name === 'mongoose') {
583
- fs_1.default.writeFileSync(path_1.default.join(process.cwd(), 'db.js'), TEMPLATES.mongoose);
584
- console.log('✅ Added Mongoose Adapter template to db.js');
585
- }
586
- else if (name === 'sequelize') {
587
- fs_1.default.writeFileSync(path_1.default.join(process.cwd(), 'db-sql.js'), TEMPLATES.sequelize);
588
- console.log('✅ Added Sequelize (SQL) Adapter template to db-sql.js');
218
+ const subCommand = args[1];
219
+ if (subCommand === 'adapter') {
220
+ const type = args[2];
221
+ if (type === 'mongoose') {
222
+ const configDir = path.join(process.cwd(), 'config');
223
+ if (!fs.existsSync(configDir))
224
+ fs.mkdirSync(configDir, { recursive: true });
225
+ fs.writeFileSync(path.join(configDir, 'db.js'), TEMPLATES.mongoose);
226
+ CLIUI.success('Mongoose adapter added to config/db.js');
589
227
  }
590
228
  else {
591
- console.log(' Unknown adapter. Use "mongoose" or "sequelize".');
229
+ CLIUI.error('Unsupported adapter. Try: dolphin add adapter mongoose');
592
230
  }
593
231
  }
594
- else if (type === 'auth') {
595
- fs_1.default.writeFileSync(path_1.default.join(process.cwd(), 'auth-router.js'), TEMPLATES.auth);
596
- console.log('✅ Added Auth Controller template to auth-router.js');
232
+ else if (subCommand === 'auth') {
233
+ const controllerDir = path.join(process.cwd(), 'controllers');
234
+ const modelDir = path.join(process.cwd(), 'models');
235
+ if (!fs.existsSync(controllerDir))
236
+ fs.mkdirSync(controllerDir, { recursive: true });
237
+ if (!fs.existsSync(modelDir))
238
+ fs.mkdirSync(modelDir, { recursive: true });
239
+ fs.writeFileSync(path.join(controllerDir, 'auth.js'), TEMPLATES.auth);
240
+ fs.writeFileSync(path.join(modelDir, 'User.js'), TEMPLATES.authModel);
241
+ CLIUI.success('Auth controller and User model added.');
597
242
  }
598
- else if (type === 'crud') {
599
- const crudTemplate = typeof TEMPLATES.crud === 'function' ? TEMPLATES.crud(name) : '';
600
- fs_1.default.writeFileSync(path_1.default.join(process.cwd(), `${name.toLowerCase()}-crud.js`), crudTemplate);
601
- console.log(`✅ Added CRUD Controller template for "${name}" to ${name.toLowerCase()}-crud.js`);
243
+ else if (subCommand === 'crud') {
244
+ const name = args[2];
245
+ if (!name)
246
+ return CLIUI.error('Usage: dolphin add crud <ModelName>');
247
+ const controllerDir = path.join(process.cwd(), 'controllers');
248
+ const modelDir = path.join(process.cwd(), 'models');
249
+ if (!fs.existsSync(controllerDir))
250
+ fs.mkdirSync(controllerDir, { recursive: true });
251
+ if (!fs.existsSync(modelDir))
252
+ fs.mkdirSync(modelDir, { recursive: true });
253
+ fs.writeFileSync(path.join(controllerDir, `${name.toLowerCase()}.js`), TEMPLATES.crud(name));
254
+ fs.writeFileSync(path.join(modelDir, `${name}.js`), TEMPLATES.crudModel(name));
255
+ CLIUI.success(`CRUD Controller and Model for ${name} generated successfully!`);
256
+ console.log(` 📄 Created: controllers/${name.toLowerCase()}.js`);
257
+ console.log(` 📄 Created: models/${name}.js`);
602
258
  }
603
259
  else {
604
- console.log(' Unknown type. Use "adapter", "auth", or "crud".');
605
- }
606
- break;
607
- case 'mongo-auth-model':
608
- const modelsDir = path_1.default.join(process.cwd(), 'models');
609
- if (!fs_1.default.existsSync(modelsDir)) {
610
- fs_1.default.mkdirSync(modelsDir);
611
- }
612
- fs_1.default.writeFileSync(path_1.default.join(modelsDir, 'auth-models.js'), TEMPLATES.authModel);
613
- console.log('✅ Generated fixed Auth models (User, RefreshToken) in models/auth-models.js');
614
- break;
615
- case 'init-prod':
616
- console.log('🚀 Scaffolding Production-Ready Dolphin Project...');
617
- const dirs = ['models', 'controllers', 'routes', 'middleware', 'services', 'config'];
618
- dirs.forEach(dir => {
619
- const dirPath = path_1.default.join(process.cwd(), dir);
620
- if (!fs_1.default.existsSync(dirPath)) {
621
- fs_1.default.mkdirSync(dirPath);
622
- console.log(`📁 Created: /${dir}`);
623
- }
624
- });
625
- // Create basic files in the structure
626
- fs_1.default.writeFileSync(path_1.default.join(process.cwd(), 'app.js'), TEMPLATES.app);
627
- fs_1.default.writeFileSync(path_1.default.join(process.cwd(), 'config', 'db.js'), TEMPLATES.mongoose);
628
- if (!fs_1.default.existsSync(path_1.default.join(process.cwd(), 'package.json'))) {
629
- fs_1.default.writeFileSync(path_1.default.join(process.cwd(), 'package.json'), JSON.stringify({
630
- name: path_1.default.basename(process.cwd()),
631
- version: '1.0.0',
632
- main: 'app.js',
633
- type: 'module',
634
- dependencies: {
635
- "dolphin-server-modules": "^2.2.4",
636
- "mongoose": "^8.0.0",
637
- "zod": "^3.22.0"
638
- }
639
- }, null, 2));
640
- }
641
- if (!fs_1.default.existsSync(path_1.default.join(process.cwd(), '.gitignore'))) {
642
- fs_1.default.writeFileSync(path_1.default.join(process.cwd(), '.gitignore'), '.env\nnode_modules\ndist\n.DS_Store');
643
- console.log('✅ Created .gitignore');
260
+ CLIUI.error('Usage: dolphin add <adapter|auth|crud>');
644
261
  }
645
- console.log('✅ Production scaffolding complete. Start swimming! 🐬');
646
262
  break;
647
263
  case 'help':
648
264
  default:
265
+ CLIUI.heading('Dolphin Framework CLI');
649
266
  console.log(`
650
- 🐬 Dolphin Framework CLI
651
267
  Commands:
652
- serve Start a basic development server
653
- init Bootstrap a basic Dolphin project
654
- init-prod Scaffold a production-grade folder structure
655
- generate <prompt> AI-powered single-file API generation
656
- generate-full <prompt> AI-powered full project architecture (folders + files)
657
- modify <file> <prompt> AI-powered file modification/update
658
- chat Interactive AI chat for coding help
659
- clean Remove AI generated files
660
- add adapter <type> Add a database adapter (mongoose, sequelize)
661
- add auth Add a pre-configured Auth controller
662
- add crud <Name> Add a pre-configured CRUD controller
663
- mongo-auth-model Generate fixed Mongoose models for Auth (User, RefreshToken)
664
- generate "prompt" --model=gemini-1.5-pro (Custom AI model support)
665
-
666
- Options:
667
- --port=3000 Specify port for serve command
268
+ serve Start development server
269
+ generate <prompt> Quick AI code generation
270
+ generate-full <p> Full project architecture
271
+ chat Interactive AI coding assistant
272
+ init-prod Scaffold production folder structure
273
+ add adapter <t> Add Mongoose/Sequelize adapter
274
+ add auth Add pre-built Auth controller
275
+ add crud <Name> Add CRUD controller for a model
668
276
  `);
669
277
  break;
670
278
  }
671
279
  }
672
280
  run().catch(err => {
673
- console.error('❌ CLI Error:', err);
281
+ CLIUI.error(err.message);
674
282
  });
675
283
  //# sourceMappingURL=cli.js.map