fleetbo-cockpit-cli 1.0.72 → 1.0.74
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/cli.js +120 -841
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -31,894 +31,173 @@ process.env.DOTENV_SILENT = 'true';
|
|
|
31
31
|
const envPath = path.join(process.cwd(), '.env');
|
|
32
32
|
|
|
33
33
|
if (!fs.existsSync(envPath)) {
|
|
34
|
-
console.error('\x1b[31m
|
|
35
|
-
console.error('\x1b[90m%s\x1b[0m', 'Make sure you are in a Fleetbo project directory.\n');
|
|
34
|
+
console.error('\x1b[31m[Fleetbo] ❌ Error: .env file not found in current directory.\x1b[0m');
|
|
36
35
|
process.exit(1);
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
dotenv.config({ path: envPath
|
|
38
|
+
dotenv.config({ path: envPath });
|
|
40
39
|
|
|
41
|
-
const projectId = process.env.
|
|
42
|
-
const
|
|
43
|
-
const testerEmail = process.env.REACT_APP_TESTER_EMAIL;
|
|
40
|
+
const projectId = process.env.FLEETBO_PROJECT_ID;
|
|
41
|
+
const developerToken = process.env.FLEETBO_DEVELOPER_TOKEN;
|
|
44
42
|
|
|
45
|
-
if (!projectId) {
|
|
46
|
-
console.error('\
|
|
43
|
+
if (!projectId || !developerToken) {
|
|
44
|
+
console.error('\x1b[31m[Fleetbo] ❌ Error: FLEETBO_PROJECT_ID or FLEETBO_DEVELOPER_TOKEN missing in .env\x1b[0m');
|
|
47
45
|
process.exit(1);
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
// ============================================
|
|
51
|
-
//
|
|
49
|
+
// CORE LOGIC: ALEX ENGINE (Generation)
|
|
52
50
|
// ============================================
|
|
53
|
-
const wrapText = (text, maxWidth) => {
|
|
54
|
-
if (!text) return "";
|
|
55
|
-
const rawLines = text.split('\n');
|
|
56
|
-
let formattedLines = [];
|
|
57
|
-
rawLines.forEach(line => {
|
|
58
|
-
if (line.trim().length === 0) {
|
|
59
|
-
formattedLines.push("");
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const isSpecialFormat = /^[\s]*[-*•\d]/.test(line) || line.startsWith(" ");
|
|
63
|
-
if (isSpecialFormat) {
|
|
64
|
-
formattedLines.push(line);
|
|
65
|
-
} else {
|
|
66
|
-
const words = line.split(" ");
|
|
67
|
-
let currentLine = words[0];
|
|
68
|
-
for (let i = 1; i < words.length; i++) {
|
|
69
|
-
if (currentLine.length + 1 + words[i].length <= maxWidth) {
|
|
70
|
-
currentLine += " " + words[i];
|
|
71
|
-
} else {
|
|
72
|
-
formattedLines.push(currentLine);
|
|
73
|
-
currentLine = words[i];
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
formattedLines.push(currentLine);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
return formattedLines.join('\n ');
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const checkGitSecurity = () => {
|
|
83
|
-
const gitDir = path.join(process.cwd(), '.git');
|
|
84
|
-
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
85
|
-
if (fs.existsSync(gitDir)) {
|
|
86
|
-
if (!fs.existsSync(gitignorePath)) {
|
|
87
|
-
console.error('\n\x1b[31m🚨 SECURITY ALERT:\x1b[0m .git detected but no .gitignore found.');
|
|
88
|
-
process.exit(1);
|
|
89
|
-
}
|
|
90
|
-
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
|
91
|
-
if (!gitignoreContent.includes('.env')) {
|
|
92
|
-
console.error('\n\x1b[31m🚨 CRITICAL RISK:\x1b[0m .env is NOT ignored by Git.');
|
|
93
|
-
process.exit(1);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const injectRouteIntoAppJs = (moduleName, subPath = '') => {
|
|
99
|
-
const appJsPath = path.join(process.cwd(), 'src', 'App.js');
|
|
100
|
-
if (!fs.existsSync(appJsPath)) {
|
|
101
|
-
console.error(` \x1b[31m[Safety Stop]\x1b[0m App.js missing.`);
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
let content = fs.readFileSync(appJsPath, 'utf8');
|
|
106
|
-
const importAnchor = '// FLEETBO_MORE_IMPORTS';
|
|
107
|
-
const routeAnchor = '{/* FLEETBO_DYNAMIC ROUTES */}';
|
|
108
|
-
|
|
109
|
-
if (!content.includes(importAnchor) || !content.includes(routeAnchor)) {
|
|
110
|
-
console.log(` \x1b[33m[Skipped]\x1b[0m Anchors missing in App.js. Manual injection required.`);
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const cleanSubPath = subPath ? `${subPath}/` : '';
|
|
115
|
-
const importLine = `import ${moduleName} from './app/${cleanSubPath}${moduleName}';`;
|
|
116
|
-
const routeLine = `<Route path="/${cleanSubPath}${moduleName.toLowerCase()}" element={<${moduleName} />} />`;
|
|
117
|
-
|
|
118
|
-
let modified = false;
|
|
119
|
-
|
|
120
|
-
if (!content.includes(importLine)) {
|
|
121
|
-
content = content.replace(importAnchor, `${importLine}\n${importAnchor}`);
|
|
122
|
-
modified = true;
|
|
123
|
-
}
|
|
124
|
-
if (!content.includes(routeLine)) {
|
|
125
|
-
content = content.replace(routeAnchor, `${routeLine}\n ${routeAnchor}`);
|
|
126
|
-
modified = true;
|
|
127
|
-
}
|
|
128
|
-
if (modified) {
|
|
129
|
-
fs.writeFileSync(appJsPath, content);
|
|
130
|
-
console.log(` \x1b[32m[Routed]\x1b[0m ${moduleName} injected into App.js safely.`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return modified;
|
|
134
|
-
};
|
|
135
51
|
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
process.stdout.write(`\r \x1b[32m⚡ Propulsion Sync:\x1b[0m [${dots}${empty}] ${Math.round((i / width) * 100)}%`);
|
|
142
|
-
await new Promise(r => setTimeout(r, 45));
|
|
52
|
+
const runAlexEngine = async () => {
|
|
53
|
+
const prompt = args.slice(1).join(' ');
|
|
54
|
+
if (!prompt) {
|
|
55
|
+
console.log('\n\x1b[36mAlex ❯\x1b[0m I am ready. Describe your feature, and I will architect the solution.\n');
|
|
56
|
+
process.exit(0);
|
|
143
57
|
}
|
|
144
|
-
process.stdout.write('\n');
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
// Détecte TOUS les mots en PascalCase (ex: GuestCreator, CameraModule, Tab2)
|
|
148
|
-
const extractPotentialModules = (text) => {
|
|
149
|
-
const regex = /\b[A-Z][a-zA-Z0-9_]{2,}\b/g;
|
|
150
|
-
const matches = text.match(regex) || [];
|
|
151
|
-
return [...new Set(matches)]; // Dédoublonne les résultats
|
|
152
|
-
};
|
|
153
58
|
|
|
59
|
+
console.log(`\n\x1b[90m🧠 Alex is thinking...\x1b[0m`);
|
|
60
|
+
process.stdout.write(`\x1b[90m \x1b[0m`);
|
|
154
61
|
|
|
155
|
-
// Sert uniquement à définir le TON du contexte envoyé à Alex
|
|
156
|
-
const getContextIntent = (text) => {
|
|
157
|
-
const modifierKeywords = [
|
|
158
|
-
'modifier', 'corrige', 'ajoute', 'erreur', 'plante', 'problème', 'bug', 'change',
|
|
159
|
-
'update', 'fix', 'edit', 'error', 'fail', 'crash', 'issue', 'add'
|
|
160
|
-
];
|
|
161
|
-
const inspireKeywords = [
|
|
162
|
-
'inspire', 'base', 'comme', 'modèle', 'reference', 'reprends', 'copie',
|
|
163
|
-
'inspire', 'based on', 'model', 'reference', 'like', 'copy', 'similar'
|
|
164
|
-
];
|
|
165
|
-
|
|
166
|
-
const lower = text.toLowerCase();
|
|
167
|
-
if (modifierKeywords.some(k => lower.includes(k))) return "MODIFICATION";
|
|
168
|
-
if (inspireKeywords.some(k => lower.includes(k))) return "INSPIRATION";
|
|
169
|
-
return "REFERENCE";
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const getModuleCache = async ({ projectId, moduleName }) => {
|
|
173
62
|
try {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return { found: false };
|
|
182
|
-
}
|
|
183
|
-
};
|
|
63
|
+
// --- MEMORY SYSTEM (SMART CACHE SCANNER V3 - SOUVERAINETÉ DU MÉTAL) ---
|
|
64
|
+
// On cherche les noms de modules (PascalCase) dans le prompt
|
|
65
|
+
const moduleMatches = prompt.match(/\b[A-Z][a-z]+[A-Z][a-zA-Z0-9_]*\b/g) || [];
|
|
66
|
+
const uniqueModules = [...new Set(moduleMatches)];
|
|
67
|
+
|
|
68
|
+
let targetModuleContext = "";
|
|
69
|
+
let referenceContexts = "";
|
|
184
70
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
let content = fs.readFileSync(appJsPath, 'utf8');
|
|
190
|
-
|
|
191
|
-
// Pattern exact pour l'import et la route (gestion du sous-dossier mocks/)
|
|
192
|
-
const importLine = `import ${moduleName} from './app/mocks/${moduleName}';`;
|
|
193
|
-
const routeLine = `<Route path="/mocks/${moduleName.toLowerCase()}" element={<${moduleName} />} />`;
|
|
194
|
-
|
|
195
|
-
const originalContent = content;
|
|
196
|
-
|
|
197
|
-
// On retire les lignes si elles existent (avec le retour à la ligne)
|
|
198
|
-
content = content.replace(new RegExp(`${importLine.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\n?`, 'g'), '');
|
|
199
|
-
content = content.replace(new RegExp(`\\s*${routeLine.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\n?`, 'g'), '');
|
|
200
|
-
|
|
201
|
-
if (content !== originalContent) {
|
|
202
|
-
fs.writeFileSync(appJsPath, content);
|
|
203
|
-
console.log(` \x1b[32m[Unrouted]\x1b[0m ${moduleName} removed from App.js.`);
|
|
204
|
-
return true;
|
|
205
|
-
}
|
|
206
|
-
return false;
|
|
207
|
-
};
|
|
71
|
+
if (uniqueModules.length > 0) {
|
|
72
|
+
const cachePromises = uniqueModules.map(async (modName) => {
|
|
73
|
+
const isReferenceOnly = modName.endsWith('Ref');
|
|
74
|
+
const cleanModName = isReferenceOnly ? modName.replace('Ref', '') : modName;
|
|
208
75
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const processAlexRequest = async (prompt) => {
|
|
217
|
-
if (prompt.length > 1000) {
|
|
218
|
-
console.log('\n\x1b[31m⛔ [Alex Safety] Request too long (' + prompt.length + '/1000 chars).\x1b[0m');
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
76
|
+
try {
|
|
77
|
+
const cacheRes = await axios.post(CACHE_URL, {
|
|
78
|
+
projectId,
|
|
79
|
+
developerToken,
|
|
80
|
+
moduleName: cleanModName
|
|
81
|
+
});
|
|
221
82
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (cacheRes.found !== false && cacheRes.modules && cacheRes.modules.length > 0) {
|
|
229
|
-
console.log(`\n\x1b[36m⚡ FLEETBO OS MODULES (${cacheRes.modules.length}):\x1b[0m`);
|
|
230
|
-
cacheRes.modules.forEach(m => {
|
|
231
|
-
const metalColor = m.platform === 'android' ? '\x1b[32m' : '\x1b[34m';
|
|
232
|
-
console.log(` ${metalColor}■\x1b[0m \x1b[1m${m.moduleName}\x1b[0m \x1b[90m(${m.platform})\x1b[0m`);
|
|
233
|
-
});
|
|
234
|
-
} else {
|
|
235
|
-
console.log(`\n \x1b[90mAucun module trouvé dans l'infrastructure de ce projet.\x1b[0m`);
|
|
236
|
-
}
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
83
|
+
return { name: cleanModName, isRef: isReferenceOnly, found: cacheRes.data.found, module: cacheRes.data.module };
|
|
84
|
+
} catch (e) {
|
|
85
|
+
return { name: cleanModName, isRef: isReferenceOnly, found: false };
|
|
86
|
+
}
|
|
87
|
+
});
|
|
239
88
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
// --- SYSTÈME DE MÉMOIRE (SMART CACHE SCANNER) ---
|
|
244
|
-
let contextInjection = "";
|
|
245
|
-
const potentialModules = extractPotentialModules(prompt);
|
|
246
|
-
|
|
247
|
-
for (const modName of potentialModules) {
|
|
248
|
-
process.stdout.write(` \x1b[90m🔍 Checking os cache for ${modName}...\x1b[0m`);
|
|
249
|
-
const cache = await getModuleCache({ projectId, moduleName: modName });
|
|
250
|
-
|
|
89
|
+
const results = await Promise.all(cachePromises);
|
|
90
|
+
|
|
91
|
+
results.forEach(cache => {
|
|
251
92
|
if (cache.found) {
|
|
252
93
|
process.stdout.write(` \x1b[32mFOUND METAL\x1b[0m\n`);
|
|
253
94
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
contextInjection = `\n--- CONTEXTE SOUVERAIN : ${contextTitle} (${modName}) ---\nDOGME: Le Métal est la source de vérité absolue. Le Mock n'est qu'une illusion.\nTu DOIS analyser ce code Natif pour comprendre EXACTEMENT comment les données ont été structurées, nommées (clés JSON) et sauvegardées.\n\n[CODE NATIF EXISTANT]\n${cache.module.code}\n--- FIN DU CONTEXTE ---\n`;
|
|
95
|
+
// ✅ NOUVEAU BLOC : INJECTION DE LA DOUBLE MÉMOIRE
|
|
96
|
+
let memoryScript = "";
|
|
97
|
+
if (cache.module.dataSchema) memoryScript += `\n[SCRIPT MÉMOIRE DONNÉES]\n${cache.module.dataSchema}\n`;
|
|
98
|
+
if (cache.module.uiSchema) memoryScript += `\n[SCRIPT MÉMOIRE UI NATIF]\n${cache.module.uiSchema}\n`;
|
|
260
99
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
100
|
+
if (!memoryScript) memoryScript = `\n[SCRIPT MÉMOIRE DU MODULE ${cache.name}]\nAucun schéma enregistré pour ce module.\n`;
|
|
101
|
+
|
|
102
|
+
if (cache.isRef) {
|
|
103
|
+
// 🚨 CAS A : INSPIRATION (Lecture seule)
|
|
104
|
+
referenceContexts += `\n--- CONTEXTE : MODULE DE RÉFÉRENCE (${cache.name}) ---\nDOGME : Ne modifie pas ce module (Lecture seule). Tu dois l'utiliser comme modèle. Aligne-toi sur ses Scripts Mémoires (Données et/ou UI) et sur son Code Natif en fonction de ce que le Pilote te demande d'imiter.\n${memoryScript}\n[CODE NATIF DE RÉFÉRENCE]\n${cache.module.code}\n`;
|
|
105
|
+
} else {
|
|
106
|
+
// 🚨 CAS B : MODIFICATION (Souveraineté du Métal - Amnésie du Mock)
|
|
107
|
+
targetModuleContext += `\n--- CONTEXTE : MÉTAL EXISTANT À MODIFIER (${cache.name}) ---\nDOGME : Tu dois modifier ce code Natif. Ensuite, tu forgeras un Mock JSX entièrement neuf basé UNIQUEMENT sur ton nouveau code Natif.\n${memoryScript}\n[CODE NATIF EXISTANT]\n${cache.module.code}\n`;
|
|
108
|
+
}
|
|
265
109
|
}
|
|
266
|
-
}
|
|
267
|
-
// --- FIN MODIFICATION MÉMOIRE ---
|
|
268
|
-
|
|
269
|
-
const result = await axios.post(ALEX_ENGINE_URL, { prompt, projectType: 'android' }, {
|
|
270
|
-
headers: { 'x-project-id': projectId }
|
|
271
110
|
});
|
|
111
|
+
}
|
|
272
112
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (typeof aiData === 'string') {
|
|
276
|
-
try { aiData = JSON.parse(aiData); } catch (_) {}
|
|
277
|
-
}
|
|
113
|
+
const fullPrompt = `${referenceContexts}\n${targetModuleContext}\n--- FIN DU CONTEXTE ---\n\n[INSTRUCTION DU PILOTE]\n${prompt}`;
|
|
278
114
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (aiData.status === 'success' || aiData.status === 'message' || aiData.status === 'complex_refusal') {
|
|
287
|
-
console.log('');
|
|
288
|
-
let rawMsg = aiData.message || "I'm ready.";
|
|
289
|
-
|
|
290
|
-
if (typeof rawMsg === 'string' && rawMsg.trimStart().startsWith('{')) {
|
|
291
|
-
try {
|
|
292
|
-
const nested = JSON.parse(rawMsg);
|
|
293
|
-
if (nested && nested.message) {
|
|
294
|
-
rawMsg = nested.message;
|
|
295
|
-
if (!aiData.moduleData && nested.moduleData) {
|
|
296
|
-
aiData.moduleData = nested.moduleData;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
} catch (_) {}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (typeof rawMsg === 'object') {
|
|
303
|
-
rawMsg = rawMsg.message || rawMsg.text || "Module generated.";
|
|
304
|
-
}
|
|
305
|
-
try {
|
|
306
|
-
const testParse = JSON.parse(rawMsg);
|
|
307
|
-
if (testParse && typeof testParse === 'object' && testParse.status) {
|
|
308
|
-
rawMsg = testParse.message || "Module generated.";
|
|
309
|
-
}
|
|
310
|
-
} catch (_) { }
|
|
311
|
-
|
|
312
|
-
const formattedMsg = wrapText(rawMsg, 85);
|
|
313
|
-
console.log('\x1b[32mAlex ❯\x1b[0m ' + formattedMsg);
|
|
314
|
-
|
|
315
|
-
if (aiData.remainingConsultations !== undefined) {
|
|
316
|
-
const remaining = aiData.remainingConsultations;
|
|
317
|
-
const limit = aiData.consultationLimit || 7;
|
|
318
|
-
const tierLabel = aiData.tier === 'senior' ? 'SENIOR' : aiData.tier === 'expert' ? 'EXPERT' : 'JUNIOR';
|
|
319
|
-
const percent = Math.round((remaining / limit) * 100);
|
|
320
|
-
const energyColor = percent > 20 ? '\x1b[32m' : '\x1b[31m';
|
|
321
|
-
console.log(`\n\x1b[36m⚡ Architect Fuel:\x1b[0m ${energyColor}${percent}%\x1b[0m (${remaining}/${limit} instructions left) [${tierLabel}]\n`);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// --- C'EST ICI QUE LES FICHIERS SONT CRÉÉS ---
|
|
326
|
-
if (aiData.status === 'success' && aiData.moduleData) {
|
|
327
|
-
const { fileName, code, mockFileName, mockCode, moduleName, instructions, config_offload } = aiData.moduleData;
|
|
328
|
-
console.log(` \x1b[90m Architecting: ${moduleName}\x1b[0m`);
|
|
329
|
-
|
|
330
|
-
const writeFile = (dir, name, content) => {
|
|
331
|
-
const fullPath = path.join(process.cwd(), dir);
|
|
332
|
-
const filePath = path.join(fullPath, name);
|
|
333
|
-
if (!fs.existsSync(fullPath)) fs.mkdirSync(fullPath, { recursive: true });
|
|
334
|
-
fs.writeFileSync(filePath, content);
|
|
335
|
-
console.log(` \x1b[32m[Written]\x1b[0m ${dir}${name}`);
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
if (instructions && Array.isArray(instructions) && instructions.length > 0) {
|
|
339
|
-
console.log('\n\x1b[33m--- GUIDE (MCI) ---\x1b[0m');
|
|
340
|
-
instructions.forEach(line => {
|
|
341
|
-
if (typeof line === 'string') {
|
|
342
|
-
const formattedLine = line.replace(/ACTION|CAPTURE|PERSPECTIVE/g, '\x1b[1m$&\x1b[0m');
|
|
343
|
-
console.log(` ${formattedLine}`);
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (code && fileName) {
|
|
349
|
-
const folder = fileName.endsWith('.kt') ? 'public/native/android/' : 'src/app/';
|
|
350
|
-
writeFile(folder, fileName, code);
|
|
351
|
-
if (fileName.endsWith('.jsx')) injectRouteIntoAppJs(fileName.replace('.jsx', ''));
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (mockCode && mockFileName) {
|
|
355
|
-
const pageName = mockFileName.replace('.jsx', '');
|
|
356
|
-
writeFile('src/app/mocks/', mockFileName, mockCode);
|
|
357
|
-
const injected = injectRouteIntoAppJs(pageName, 'mocks');
|
|
358
|
-
if (injected) {
|
|
359
|
-
console.log(` \x1b[32m[Routed]\x1b[0m App.js -> /mocks/${pageName.toLowerCase()}`);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// --- SYNCHRONISATION DU KERNEL ---
|
|
364
|
-
const depsCount = config_offload?.dependencies?.length || 0;
|
|
365
|
-
process.stdout.write(` \x1b[33m[Cloud Inject]\x1b[0m Archiving ${moduleName} to OS (${depsCount} libs)...`);
|
|
366
|
-
|
|
367
|
-
try {
|
|
368
|
-
await axios.post(INJECT_DEPS_URL, {
|
|
369
|
-
projectId: projectId,
|
|
370
|
-
fileData: {
|
|
371
|
-
path: fileName,
|
|
372
|
-
moduleName: moduleName,
|
|
373
|
-
fileName: fileName,
|
|
374
|
-
code: code,
|
|
375
|
-
mockFileName: mockFileName,
|
|
376
|
-
mockCode: mockCode,
|
|
377
|
-
config_offload: config_offload || { dependencies: [], permissions: [] }
|
|
378
|
-
}
|
|
379
|
-
});
|
|
380
|
-
process.stdout.write(` \x1b[32mOK\x1b[0m\n`);
|
|
381
|
-
} catch (err) {
|
|
382
|
-
process.stdout.write(` \x1b[31mFAILED\x1b[0m\n`);
|
|
383
|
-
console.error(` ⚠️ OS sync failed: ${err.message}`);
|
|
384
|
-
}
|
|
385
|
-
} else if (aiData.status === 'success' && !aiData.moduleData) {
|
|
386
|
-
// SÉCURITÉ : Si Alex s'emmêle les pinceaux et renvoie un mauvais format JSON
|
|
387
|
-
console.log(`\n\x1b[31m⚠️ Erreur : Alex a répondu, mais le code source n'a pas pu être extrait. Réessayez la commande.\x1b[0m\n`);
|
|
388
|
-
}
|
|
389
|
-
} catch (error) {
|
|
390
|
-
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
391
|
-
console.error('\n\x1b[31m Alex Error:\x1b[0m ' + (error.response?.data?.message || error.message));
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
const startAlexSession = async () => {
|
|
396
|
-
process.stdout.write('\x1b[33m🛡️ Alex is checking runtime state...\x1b[0m\r');
|
|
397
|
-
let attempts = 0;
|
|
398
|
-
const maxAttempts = 5;
|
|
399
|
-
let isReady = false;
|
|
400
|
-
let dynamicUsername = 'Pilot';
|
|
401
|
-
|
|
402
|
-
while (attempts < maxAttempts && !isReady) {
|
|
403
|
-
try {
|
|
404
|
-
const validation = await axios.post(ALEX_ENGINE_URL, {
|
|
405
|
-
prompt: "ping", validateProject: true, checkNetwork: true, projectKey: keyApp
|
|
406
|
-
}, { headers: { 'x-project-id': projectId }, timeout: 5000 });
|
|
407
|
-
|
|
408
|
-
if (validation.data?.isRunning) {
|
|
409
|
-
isReady = true;
|
|
410
|
-
dynamicUsername = validation.data.username || 'Pilot';
|
|
411
|
-
break;
|
|
412
|
-
}
|
|
413
|
-
attempts++;
|
|
414
|
-
if (attempts < maxAttempts) await new Promise(r => setTimeout(r, 2000));
|
|
415
|
-
} catch (error) {
|
|
416
|
-
attempts++;
|
|
417
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
if (!isReady) {
|
|
422
|
-
console.error('\n\x1b[31m⚠️ ENGINE OFFLINE:\x1b[0m Start Fleetbo runtime first: "npm run fleetbo" ');
|
|
423
|
-
console.error(`\x1b[90m(Ensure you are running the runtime for project: ${keyApp})\x1b[0m`);
|
|
424
|
-
process.exit(1);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
process.stdout.write(' '.repeat(60) + '\r');
|
|
428
|
-
|
|
429
|
-
// 1. IDENTITÉ
|
|
430
|
-
console.log('\n\x1b[32m🤖 Alex is online.\x1b[0m');
|
|
431
|
-
console.log('\x1b[90m Your JS stays the brain. I forge the native muscle.\x1b[0m');
|
|
432
|
-
|
|
433
|
-
// 2. CE QUE JE FORGE
|
|
434
|
-
console.log('\n\x1b[36m⚡ WHAT I CAN FORGE:\x1b[0m');
|
|
435
|
-
console.log('');
|
|
436
|
-
console.log(' \x1b[1m📷 Hardware\x1b[0m\x1b[90m Camera, Scanner, GPS, Biometrics, Sensors\x1b[0m');
|
|
437
|
-
console.log(' \x1b[1m🎬 High-Perf\x1b[0m\x1b[90m Infinite Feeds, Video Players, Swipe Decks\x1b[0m');
|
|
438
|
-
console.log(' \x1b[1m🏗️ Sovereign\x1b[0m\x1b[90m Full screens: form + photo + save-to-cloud\x1b[0m');
|
|
439
|
-
|
|
440
|
-
// 3. LA COLLABORATION
|
|
441
|
-
console.log('\n\x1b[36m💡 TELL ME "WHAT + WHY":\x1b[0m');
|
|
442
|
-
console.log('\x1b[90m I will analyze your need and recommend the perfect module to forge.\x1b[0m');
|
|
443
|
-
console.log('');
|
|
444
|
-
console.log(' \x1b[33m"I need a camera [WHAT] to scan receipts for my expense tracker [WHY]"\x1b[0m');
|
|
445
|
-
console.log(' \x1b[33m"I need a form [WHAT] to add products with photos to my catalog [WHY]"\x1b[0m');
|
|
446
|
-
|
|
447
|
-
// 4. LA PRISE DE COMMANDE
|
|
448
|
-
console.log('\n\x1b[32mAlex ❯\x1b[0m I am ready. Describe your feature, and I will architect the solution.');
|
|
449
|
-
console.log('');
|
|
450
|
-
|
|
451
|
-
const rl = readline.createInterface({
|
|
452
|
-
input: process.stdin,
|
|
453
|
-
output: process.stdout,
|
|
454
|
-
prompt: `\x1b[34m${dynamicUsername} ❯ \x1b[0m`
|
|
115
|
+
// Requête à l'IA
|
|
116
|
+
const exactTime = new Date().toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
|
|
117
|
+
const res = await axios.post(ALEX_ENGINE_URL, {
|
|
118
|
+
prompt: fullPrompt,
|
|
119
|
+
projectId,
|
|
120
|
+
developerToken,
|
|
121
|
+
systemInfo: `[SYSTEM INFO: The exact current timestamp is ${exactTime}. Use it STRICTLY for your signature '// Forged by Alex on...' at the end of 'code' and 'mockCode'.]`
|
|
455
122
|
});
|
|
456
|
-
|
|
457
|
-
process.stdout.write('\n\x1b[F');
|
|
458
|
-
rl.prompt();
|
|
459
123
|
|
|
460
|
-
|
|
461
|
-
let isProcessing = false;
|
|
124
|
+
const aiData = res.data;
|
|
462
125
|
|
|
463
|
-
|
|
464
|
-
|
|
126
|
+
if (aiData.message) {
|
|
127
|
+
readline.clearLine(process.stdout, 0);
|
|
128
|
+
readline.cursorTo(process.stdout, 0);
|
|
129
|
+
console.log(`\x1b[36mAlex ❯\x1b[0m ${aiData.message}\n`);
|
|
130
|
+
}
|
|
465
131
|
|
|
466
|
-
|
|
132
|
+
// --- FILE CREATION LOGIC ---
|
|
133
|
+
if (aiData.status === 'success' && aiData.moduleData) {
|
|
134
|
+
let { fileName, code, mockFileName, mockCode, moduleName, instructions, config_offload, dataSchema, uiSchema } = aiData.moduleData;
|
|
467
135
|
|
|
468
|
-
if (
|
|
469
|
-
console.
|
|
470
|
-
|
|
471
|
-
return;
|
|
136
|
+
if (!fileName || !code) {
|
|
137
|
+
console.error('\x1b[31m[Fleetbo] ❌ Critical: Alex provided incomplete source code.\x1b[0m');
|
|
138
|
+
process.exit(1);
|
|
472
139
|
}
|
|
473
140
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
479
|
-
if (inputBuffer.trim() !== "") {
|
|
480
|
-
const finalPrompt = inputBuffer.trim();
|
|
481
|
-
inputBuffer = "";
|
|
482
|
-
|
|
483
|
-
if (finalPrompt.length > 1000) {
|
|
484
|
-
console.log(`\n\x1b[31m⛔ [Alex Safety] Mission rejetée : Taille excessive (${finalPrompt.length}/1000 caractères).\x1b[0m`);
|
|
485
|
-
rl.setPrompt(`\x1b[34m${dynamicUsername} ❯ \x1b[0m`);
|
|
486
|
-
rl.prompt();
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
141
|
+
// Correction automatique du format de fichier
|
|
142
|
+
if (!fileName.includes('.')) fileName += '.kt';
|
|
143
|
+
if (mockFileName && !mockFileName.includes('.')) mockFileName += '.jsx';
|
|
489
144
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
rl.setPrompt(`\x1b[34m${dynamicUsername} ❯ \x1b[0m`);
|
|
497
|
-
rl.prompt();
|
|
498
|
-
} else {
|
|
499
|
-
rl.setPrompt(`\x1b[34m${dynamicUsername} ❯ \x1b[0m`);
|
|
500
|
-
rl.prompt();
|
|
501
|
-
}
|
|
145
|
+
// Sauvegarde locale (optionnelle pour debug, mais prioritairement injection Cloud)
|
|
146
|
+
const buildPath = path.join(process.cwd(), 'fleetbo_build');
|
|
147
|
+
if (!fs.existsSync(buildPath)) fs.mkdirSync(buildPath);
|
|
148
|
+
fs.writeFileSync(path.join(buildPath, fileName), code);
|
|
149
|
+
if (mockCode && mockFileName) {
|
|
150
|
+
fs.writeFileSync(path.join(buildPath, mockFileName), mockCode);
|
|
502
151
|
}
|
|
503
|
-
});
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
if (!initialPrompt || initialPrompt === '?') startAlexSession();
|
|
507
|
-
else processAlexRequest(initialPrompt);
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// ============================================
|
|
512
|
-
// COMMAND: rm (MODULE ANNIHILATION)
|
|
513
|
-
// ============================================
|
|
514
|
-
else if (command === 'rm') {
|
|
515
|
-
const moduleName = args[1];
|
|
516
|
-
if (!moduleName) {
|
|
517
|
-
console.error('\n\x1b[31m❌ Error: Module name required.\x1b[0m');
|
|
518
|
-
console.log('\x1b[90mUsage: npm run fleetbo rm [ModuleName]\x1b[0m\n');
|
|
519
|
-
process.exit(1);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
console.log(`\n\x1b[33m🗑️ Annihilating module: ${moduleName}...\x1b[0m`);
|
|
523
|
-
|
|
524
|
-
// 1. Define physical paths
|
|
525
|
-
const ktPath = path.join(process.cwd(), 'public', 'native', 'android', `${moduleName}.kt`);
|
|
526
|
-
const jsxPath = path.join(process.cwd(), 'src', 'app', 'mocks', `${moduleName}.jsx`);
|
|
527
|
-
|
|
528
|
-
let actionsDone = 0;
|
|
529
|
-
|
|
530
|
-
// 2. Eradicate Metal Engine (Kotlin)
|
|
531
|
-
if (fs.existsSync(ktPath)) {
|
|
532
|
-
fs.unlinkSync(ktPath);
|
|
533
|
-
console.log(` \x1b[32m[Deleted]\x1b[0m Metal file (.kt) eradicated.`);
|
|
534
|
-
actionsDone++;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// 3. Eradicate Virtual Twin (Mock JSX)
|
|
538
|
-
if (fs.existsSync(jsxPath)) {
|
|
539
|
-
fs.unlinkSync(jsxPath);
|
|
540
|
-
console.log(` \x1b[32m[Deleted]\x1b[0m Virtual Twin (.jsx) eradicated.`);
|
|
541
|
-
actionsDone++;
|
|
542
|
-
}
|
|
543
152
|
|
|
544
|
-
|
|
545
|
-
const unrouted = removeRouteFromAppJs(moduleName);
|
|
546
|
-
if (unrouted) actionsDone++;
|
|
547
|
-
|
|
548
|
-
if (actionsDone === 0) {
|
|
549
|
-
console.log(`\n\x1b[31m⚠️ No trace of module "${moduleName}" found in the OS.\x1b[0m\n`);
|
|
550
|
-
} else {
|
|
551
|
-
console.log(`\n\x1b[32m Module ${moduleName} successfully eradicated from the OS.\x1b[0m\n`);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// ============================================
|
|
556
|
-
// COMMAND: android / ios (PROPULSION BUILD)
|
|
557
|
-
// ============================================
|
|
558
|
-
else if (command === 'android' || command === 'ios') {
|
|
559
|
-
|
|
560
|
-
// 🟢 DÉBUT DE LA PROTECTION (Fonction Async Immédiate)
|
|
561
|
-
// Cela garantit que le code fonctionne partout, même via 'require()'
|
|
562
|
-
(async () => {
|
|
563
|
-
|
|
564
|
-
// 🛑 INTERCEPTION IOS : BLOQUAGE NET (MAINTENANCE/BETA)
|
|
565
|
-
if (command === 'ios') {
|
|
566
|
-
console.log(`\n\x1b[36m⚡ FLEETBO IOS PROPULSION\x1b[0m`);
|
|
567
|
-
console.log(`\x1b[33m[0/3] Initializing Neural Uplink...\x1b[0m`);
|
|
568
|
-
|
|
569
|
-
// ✅ Ce 'await' est maintenant sécurisé
|
|
570
|
-
await new Promise(r => setTimeout(r, 800));
|
|
571
|
-
|
|
572
|
-
console.log(`\n\x1b[31m⛔ PROPULSION ABORTED: iOS Frequency Restricted.\x1b[0m`);
|
|
573
|
-
console.log(`\x1b[90m This module is currently reserved for Vanguard Pilots (Closed Beta).\x1b[0m`);
|
|
574
|
-
console.log(`\x1b[90m Please engage propulsion on Android frequency for now.\x1b[0m\n`);
|
|
575
|
-
|
|
576
|
-
process.exit(1);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
checkGitSecurity();
|
|
580
|
-
const platform = command;
|
|
581
|
-
const nativeDir = platform === 'android' ? 'public/native/android/' : 'public/native/ios/';
|
|
582
|
-
const extension = platform === 'android' ? '.kt' : '.swift';
|
|
583
|
-
const nativePath = path.join(process.cwd(), nativeDir);
|
|
584
|
-
|
|
585
|
-
// Vérification des modules natifs
|
|
586
|
-
let hasNativeFiles = false;
|
|
587
|
-
let nativeFileCount = 0;
|
|
588
|
-
if (fs.existsSync(nativePath)) {
|
|
589
|
-
const files = fs.readdirSync(nativePath);
|
|
590
|
-
const nativeFiles = files.filter(file => file.endsWith(extension));
|
|
591
|
-
hasNativeFiles = nativeFiles.length > 0;
|
|
592
|
-
nativeFileCount = nativeFiles.length;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
if (!hasNativeFiles) {
|
|
596
|
-
console.log(`\n\x1b[31m⚠️ ENGINE INCOMPLETE:\x1b[0m No native blueprints detected for \x1b[1m${platform.toUpperCase()}\x1b[0m.`);
|
|
597
|
-
console.log(`\x1b[90mAlex must architect at least one ${extension} module before deployment.\x1b[0m`);
|
|
598
|
-
console.log(`\x1b[90mRun: npm run fleetbo alex\x1b[0m\n`);
|
|
599
|
-
process.exit(1);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const targetUrl = platform === 'android' ? ANDROID_BUILD_URL : IOS_BUILD_URL;
|
|
603
|
-
|
|
604
|
-
console.log(`\n\x1b[36m⚡ FLEETBO ${platform.toUpperCase()} PROPULSION\x1b[0m`);
|
|
605
|
-
console.log(`\x1b[90m ${nativeFileCount} native module(s) detected\x1b[0m\n`);
|
|
606
|
-
|
|
607
|
-
try {
|
|
608
|
-
// ==========================================================
|
|
609
|
-
// PRE-FLIGHT CHECK QUOTAS & TIER
|
|
610
|
-
// ==========================================================
|
|
611
|
-
process.stdout.write(`\x1b[33m[0/3]\x1b[0m Checking Propulsion Access... `);
|
|
153
|
+
// INJECTION CLOUD (Indispensable pour le build et la mémoire d'Alex)
|
|
612
154
|
try {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
155
|
+
await axios.post(INJECT_DEPS_URL, {
|
|
156
|
+
projectId,
|
|
157
|
+
developerToken,
|
|
158
|
+
fileData: {
|
|
159
|
+
path: fileName,
|
|
160
|
+
moduleName: moduleName || fileName.split('.')[0],
|
|
161
|
+
fileName: fileName,
|
|
162
|
+
code: code,
|
|
163
|
+
mockFileName: mockFileName,
|
|
164
|
+
mockCode: mockCode,
|
|
165
|
+
config_offload: config_offload || { dependencies: [], permissions: [] },
|
|
166
|
+
dataSchema: dataSchema || null,
|
|
167
|
+
uiSchema: uiSchema || null
|
|
618
168
|
}
|
|
619
169
|
});
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
} catch (preflightError) {
|
|
624
|
-
process.stdout.write(`\x1b[31mDENIED\x1b[0m\n`);
|
|
625
|
-
|
|
626
|
-
const errData = preflightError.response?.data;
|
|
627
|
-
|
|
628
|
-
// 🛑 1. INTERCEPTION SPÉCIFIQUE JUNIOR
|
|
629
|
-
// C'est ici qu'on lit le code renvoyé par index.js
|
|
630
|
-
if (errData?.code === 'junior_restriction') {
|
|
631
|
-
console.log(`\n\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
632
|
-
console.log(`\x1b[31m⛔ ACCESS DENIED: JUNIOR PILOT DETECTED\x1b[0m`);
|
|
633
|
-
console.log(`\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
634
|
-
console.log(``);
|
|
635
|
-
console.log(` \x1b[33m This feature is locked for Junior Pilots.\x1b[0m`);
|
|
636
|
-
console.log(` \x1b[32m Upgrade to Senior on fleetbo.io to unlock Propulsion.\x1b[0m`);
|
|
637
|
-
console.log(``);
|
|
638
|
-
process.exit(1); // Arrêt immédiat et propre du script
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// 2. Gestion des autres erreurs (Quota Senior dépassé, Serveur HS, etc.)
|
|
642
|
-
if (errData && errData.error) {
|
|
643
|
-
throw new Error(errData.error);
|
|
644
|
-
}
|
|
645
|
-
throw preflightError;
|
|
170
|
+
console.log(`\x1b[32m[Fleetbo] ✅ ${fileName} has been forged and fused with the Core.\x1b[0m`);
|
|
171
|
+
} catch (injectErr) {
|
|
172
|
+
console.error(`\x1b[31m[Fleetbo] ❌ Forge failed: Core rejection.\x1b[0m`, injectErr.response?.data || injectErr.message);
|
|
646
173
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
console.log(`\x1b[33m[1/3]\x1b[0m Synthesizing Fleetbo Core Logic...`);
|
|
650
|
-
execSync('npm run build', { stdio: 'inherit' });
|
|
651
|
-
|
|
652
|
-
let buildDir = fs.existsSync(path.join(process.cwd(), 'dist')) ? 'dist' : 'build';
|
|
653
|
-
const buildPath = path.join(process.cwd(), buildDir);
|
|
654
|
-
|
|
655
|
-
if (!fs.existsSync(buildPath)) {
|
|
656
|
-
throw new Error(`Build directory not found: ${buildDir}`);
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// Étape 2: Créer le ZIP
|
|
660
|
-
console.log(`\n\x1b[33m[2/3]\x1b[0m Packaging bundle + native modules...`);
|
|
661
|
-
|
|
662
|
-
const zipBuffer = await new Promise((resolve, reject) => {
|
|
663
|
-
const chunks = [];
|
|
664
|
-
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
665
|
-
|
|
666
|
-
archive.on('data', chunk => chunks.push(chunk));
|
|
667
|
-
archive.on('end', () => resolve(Buffer.concat(chunks)));
|
|
668
|
-
archive.on('error', reject);
|
|
669
|
-
archive.on('warning', (err) => {
|
|
670
|
-
if (err.code !== 'ENOENT') reject(err);
|
|
671
|
-
});
|
|
672
|
-
|
|
673
|
-
archive.directory(buildPath, 'build');
|
|
674
|
-
if (fs.existsSync(nativePath)) {
|
|
675
|
-
archive.directory(nativePath, `build/native/${platform}`);
|
|
676
|
-
}
|
|
677
|
-
archive.finalize();
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
|
|
681
|
-
console.log(` \x1b[32m✓\x1b[0m Bundle ready: ${sizeMB} MB`);
|
|
682
|
-
|
|
683
|
-
// Étape 3: Upload
|
|
684
|
-
console.log(`\n\x1b[33m[3/3]\x1b[0m Uploading to Fleetbo OS...`);
|
|
685
|
-
await showEnergyTransfer();
|
|
686
|
-
|
|
687
|
-
let uploadResponse;
|
|
688
|
-
try {
|
|
689
|
-
uploadResponse = await axios.post(targetUrl, zipBuffer, {
|
|
690
|
-
headers: {
|
|
691
|
-
'Content-Type': 'application/zip',
|
|
692
|
-
'x-project-id': projectId
|
|
693
|
-
},
|
|
694
|
-
maxContentLength: Infinity,
|
|
695
|
-
maxBodyLength: Infinity,
|
|
696
|
-
timeout: 120000
|
|
697
|
-
});
|
|
698
|
-
} catch (axiosError) {
|
|
699
|
-
if (axiosError.response && axiosError.response.data && axiosError.response.data.error) {
|
|
700
|
-
throw new Error(axiosError.response.data.error);
|
|
701
|
-
} else {
|
|
702
|
-
throw new Error(`Connection to OS failed: ${axiosError.message}`);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
if (uploadResponse.data && uploadResponse.data.success) {
|
|
707
|
-
console.log(`\n\x1b[32m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
708
|
-
console.log(`\x1b[32m✓ ${platform.toUpperCase()} PROPULSION SUCCESSFUL\x1b[0m`);
|
|
709
|
-
console.log(`\x1b[32m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
710
|
-
console.log(`\x1b[90m Deployment ID: ${uploadResponse.data.deploymentId || 'N/A'}\x1b[0m`);
|
|
711
|
-
console.log(`\x1b[90m ${uploadResponse.data.message || 'Complete.'}\x1b[0m\n`);
|
|
712
|
-
} else {
|
|
713
|
-
throw new Error(uploadResponse.data?.error || 'Unknown logical error from Factory');
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
} catch (error) {
|
|
717
|
-
console.log(`\n\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
718
|
-
console.log(`\x1b[31m✗ PROPULSION FAILED\x1b[0m`);
|
|
719
|
-
console.log(`\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
720
|
-
|
|
721
|
-
console.error(`\x1b[31m Error:\x1b[0m ${error.message}`);
|
|
722
|
-
|
|
723
|
-
if (error.message.includes('Limit') || error.message.includes('Quota')) {
|
|
724
|
-
console.log(`\n\x1b[33m 💡 Tip:\x1b[0m Upgrade to Senior Pilot for more builds.`);
|
|
725
|
-
} else if (error.message.includes('No native module')) {
|
|
726
|
-
console.log(`\n\x1b[33m 💡 Tip:\x1b[0m Run "npm run fleetbo alex" to create native modules first.`);
|
|
727
|
-
} else if (error.message.includes('Trial Period Ended')) {
|
|
728
|
-
console.log(`\n\x1b[33m 💡 Tip:\x1b[0m Your free sprint is over. Upgrade to Senior Pilot on fleetbo.io.`);
|
|
729
|
-
}
|
|
730
|
-
console.log('');
|
|
731
|
-
process.exit(1);
|
|
174
|
+
} else if (aiData.status === 'success') {
|
|
175
|
+
console.error('\x1b[33m⚠️ Error: Alex replied, but source code could not be extracted. Try the command again.\x1b[0m');
|
|
732
176
|
}
|
|
733
177
|
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
// ============================================
|
|
739
|
-
else if (['page', 'g', 'generate'].includes(command)) {
|
|
740
|
-
const pageGeneratorPath = path.join(__dirname, 'page.js');
|
|
741
|
-
try {
|
|
742
|
-
require(pageGeneratorPath);
|
|
743
|
-
} catch (e) {
|
|
744
|
-
console.error('\x1b[31m Page Generator Error:\x1b[0m', e.message);
|
|
745
|
-
process.exit(1);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
readline.clearLine(process.stdout, 0);
|
|
180
|
+
readline.cursorTo(process.stdout, 0);
|
|
181
|
+
console.error(`\x1b[31m[Fleetbo] ❌ Alex Engine Error:\x1b[0m`, error.response?.data || error.message);
|
|
746
182
|
}
|
|
747
|
-
}
|
|
183
|
+
};
|
|
184
|
+
|
|
748
185
|
// ============================================
|
|
749
|
-
// COMMAND
|
|
186
|
+
// COMMAND ROUTER
|
|
750
187
|
// ============================================
|
|
751
|
-
else {
|
|
752
|
-
const NULL_DEV = process.platform === 'win32' ? '>nul 2>&1' : '2>/dev/null';
|
|
753
|
-
|
|
754
|
-
function killProcessOnPort(port) {
|
|
755
|
-
try {
|
|
756
|
-
if (process.platform !== 'win32') {
|
|
757
|
-
const pid = execSync(`lsof -ti:${port} ${NULL_DEV}`).toString().trim();
|
|
758
|
-
if (pid) execSync(`kill -9 ${pid.split('\n').join(' ')} ${NULL_DEV}`);
|
|
759
|
-
}
|
|
760
|
-
} catch (e) {}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
const killNetworkService = () => {
|
|
764
|
-
if (uplinkProcess) {
|
|
765
|
-
try {
|
|
766
|
-
uplinkProcess.kill('SIGINT');
|
|
767
|
-
console.log('[Fleetbo] Engine closed.');
|
|
768
|
-
} catch (e) {
|
|
769
|
-
console.error('[Fleetbo] Error closing tunnel:', e.message);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
};
|
|
773
|
-
|
|
774
|
-
let isExiting = false;
|
|
775
|
-
|
|
776
|
-
async function cleanupAndExit(code = 0) {
|
|
777
|
-
if (isExiting) return;
|
|
778
|
-
isExiting = true;
|
|
779
|
-
console.log('\n\x1b[33m[Fleetbo] 🛑 Stopping environment & Cleaning Uplink...\x1b[0m');
|
|
780
|
-
try {
|
|
781
|
-
await axios.post(UPDATE_NETWORK_URL, { keyApp, networkUrl: '', tester: testerEmail });
|
|
782
|
-
console.log('\x1b[32m[Fleetbo] ✓ Network status reset to offline.\x1b[0m');
|
|
783
|
-
} catch (e) {
|
|
784
|
-
console.error('[Fleetbo] Network cleanup warning:', e.message);
|
|
785
|
-
}
|
|
786
|
-
killNetworkService();
|
|
787
|
-
killProcessOnPort(PORT);
|
|
788
|
-
console.log('[Fleetbo] Bye.');
|
|
789
|
-
process.exit(code);
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
process.on('SIGINT', () => cleanupAndExit(0));
|
|
793
|
-
process.on('SIGTERM', () => cleanupAndExit(0));
|
|
794
|
-
|
|
795
|
-
async function syncFirebase(keyApp, networkUrl, testerEmail) {
|
|
796
|
-
try {
|
|
797
|
-
await axios.post(UPDATE_NETWORK_URL, { keyApp, networkUrl, tester: testerEmail });
|
|
798
|
-
console.log('\n\x1b[32mEngine started successfully\x1b[0m');
|
|
799
|
-
console.log(`\x1b[32m[Fleetbo]\x1b[0m -------------------------------------------------------------`);
|
|
800
|
-
console.log('\x1b[32m[Fleetbo] \x1b[1mGO GO GO ! FLEETBO COCKPIT IS READY\x1b[0m');
|
|
801
|
-
console.log('\x1b[32m[Fleetbo] You can now start coding and previewing in Studio. 🚀\x1b[0m');
|
|
802
|
-
console.log(`\x1b[32m[Fleetbo]\x1b[0m -------------------------------------------------------------`);
|
|
803
|
-
console.log(`\x1b[34mPilot Instruction ❯\x1b[0m Switch to your Fleetbo Cockpit tab to begin.\n`);
|
|
804
|
-
} catch (err) {
|
|
805
|
-
console.error(`[Fleetbo] Sync Error: ${err.message}`);
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
async function runDevEnvironment() {
|
|
810
|
-
console.log(`[Fleetbo] 🛡️ Initializing Dev Environment...`);
|
|
811
|
-
|
|
812
|
-
// Mise à jour silencieuse de browserslist
|
|
813
|
-
try {
|
|
814
|
-
const npxExec = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
815
|
-
execSync(`${npxExec} -y update-browserslist-db@latest`, { stdio: 'ignore' });
|
|
816
|
-
} catch (e) {}
|
|
817
|
-
|
|
818
|
-
killNetworkService();
|
|
819
|
-
killProcessOnPort(PORT);
|
|
820
|
-
|
|
821
|
-
if (!testerEmail) {
|
|
822
|
-
console.error('\x1b[31mError: REACT_APP_TESTER_EMAIL missing in .env\x1b[0m');
|
|
823
|
-
process.exit(1);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
827
|
-
const devServer = spawn(npmCmd, ['start'], {
|
|
828
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
829
|
-
shell: true,
|
|
830
|
-
env: {
|
|
831
|
-
...process.env,
|
|
832
|
-
NODE_OPTIONS: '--no-deprecation',
|
|
833
|
-
BROWSER: 'none',
|
|
834
|
-
PORT: PORT.toString(),
|
|
835
|
-
DANGEROUSLY_DISABLE_HOST_CHECK: 'true',
|
|
836
|
-
HOST: '0.0.0.0',
|
|
837
|
-
WDS_SOCKET_HOST: 'localhost',
|
|
838
|
-
WDS_SOCKET_PORT: PORT.toString()
|
|
839
|
-
}
|
|
840
|
-
});
|
|
841
188
|
|
|
842
|
-
|
|
843
|
-
|
|
189
|
+
switch (command) {
|
|
190
|
+
case 'alex':
|
|
191
|
+
runAlexEngine();
|
|
192
|
+
break;
|
|
844
193
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
if (!connectionStarted && (output.includes('Local:') || output.includes('Compiled successfully'))) {
|
|
851
|
-
connectionStarted = true;
|
|
852
|
-
|
|
853
|
-
console.log('\n[Fleetbo] ---------------------------------------------------');
|
|
854
|
-
console.log(`[Fleetbo] 🔗 Establishing Secure Uplink...`);
|
|
855
|
-
console.log(`[Fleetbo] ⏳ Please wait for the green message...`);
|
|
856
|
-
console.log('[Fleetbo] ---------------------------------------------------');
|
|
857
|
-
|
|
858
|
-
// ============================================
|
|
859
|
-
// UPLINK avec auto-retry (Fleetbo OS Resilience)
|
|
860
|
-
// ============================================
|
|
861
|
-
const MAX_UPLINK_RETRIES = 5;
|
|
862
|
-
const RETRY_DELAYS = [0, 10, 20, 30, 45];
|
|
863
|
-
let uplinkFound = false;
|
|
864
|
-
|
|
865
|
-
const startUplink = (attempt) => {
|
|
866
|
-
if (uplinkFound) return;
|
|
867
|
-
|
|
868
|
-
const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
869
|
-
|
|
870
|
-
if (attempt > 0) {
|
|
871
|
-
console.log(`\x1b[33m[Fleetbo] 🔄 Uplink reconnection ${attempt}/${MAX_UPLINK_RETRIES - 1}...\x1b[0m`);
|
|
872
|
-
}
|
|
194
|
+
default:
|
|
195
|
+
console.log(`
|
|
196
|
+
\x1b[36mFleetbo OS CLI\x1b[0m
|
|
197
|
+
Usage: npm run fleetbo [command]
|
|
873
198
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
'--url', `http://127.0.0.1:${PORT}`,
|
|
879
|
-
'--http-host-header', `127.0.0.1:${PORT}`
|
|
880
|
-
], { shell: true });
|
|
881
|
-
|
|
882
|
-
const handleUplinkOutput = (chunk) => {
|
|
883
|
-
const text = chunk.toString();
|
|
884
|
-
if (uplinkFound) return;
|
|
885
|
-
const match = text.match(/https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/);
|
|
886
|
-
if (match) {
|
|
887
|
-
uplinkFound = true;
|
|
888
|
-
syncFirebase(process.env.REACT_KEY_APP, match[0], process.env.REACT_APP_TESTER_EMAIL);
|
|
889
|
-
}
|
|
890
|
-
};
|
|
891
|
-
|
|
892
|
-
// Écoute sur les deux flux (stdout + stderr)
|
|
893
|
-
uplinkProcess.stdout.on('data', handleUplinkOutput);
|
|
894
|
-
uplinkProcess.stderr.on('data', handleUplinkOutput);
|
|
895
|
-
|
|
896
|
-
uplinkProcess.on('error', (err) => {
|
|
897
|
-
if (uplinkFound) return;
|
|
898
|
-
console.error(`\x1b[31m[Fleetbo] ⚠️ Uplink Connection failed to establish.\x1b[0m`);
|
|
899
|
-
});
|
|
900
|
-
|
|
901
|
-
uplinkProcess.on('close', (code) => {
|
|
902
|
-
if (uplinkFound) return;
|
|
903
|
-
|
|
904
|
-
const nextAttempt = attempt + 1;
|
|
905
|
-
if (nextAttempt < MAX_UPLINK_RETRIES) {
|
|
906
|
-
const delay = RETRY_DELAYS[nextAttempt] || 30;
|
|
907
|
-
console.log(`\x1b[33m[Fleetbo] ⚠️ Uplink interrupted. Fleetbo OS retrying in ${delay}s... (${nextAttempt}/${MAX_UPLINK_RETRIES - 1})\x1b[0m`);
|
|
908
|
-
setTimeout(() => startUplink(nextAttempt), delay * 1000);
|
|
909
|
-
} else {
|
|
910
|
-
console.error(`\x1b[31m[Fleetbo] ❌ Secure Uplink could not be established.\x1b[0m`);
|
|
911
|
-
console.error(`\x1b[90m[Fleetbo] Fleetbo OS network is temporarily unavailable.\x1b[0m`);
|
|
912
|
-
console.error(`\x1b[90m[Fleetbo] Your dev server is still running on http://localhost:${PORT}\x1b[0m`);
|
|
913
|
-
console.error(`\x1b[90m[Fleetbo] Restart with "npm run fleetbo" when the network is back.\x1b[0m`);
|
|
914
|
-
}
|
|
915
|
-
});
|
|
916
|
-
};
|
|
917
|
-
|
|
918
|
-
startUplink(0);
|
|
919
|
-
}
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
runDevEnvironment();
|
|
199
|
+
Commands:
|
|
200
|
+
alex [prompt] Talk to the Architect to forge or modify modules.
|
|
201
|
+
`);
|
|
202
|
+
break;
|
|
924
203
|
}
|