fleetbo-cockpit-cli 1.0.44 → 1.0.46

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 (2) hide show
  1. package/cli.js +101 -92
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -144,28 +144,49 @@ const showEnergyTransfer = async () => {
144
144
  process.stdout.write('\n');
145
145
  };
146
146
 
147
- const promptContainsModification = (text) => {
148
- // J'ai ajouté : 'bug', 'crash', 'erreur', 'error', 'fix', 'fail', 'plante'
149
- const keywords = [
150
- 'modifier', 'update', 'change', 'corrige', 'fix',
151
- 'ajoute', 'edit', 'refactor', 'bug', 'crash', 'set',
152
- 'erreur', 'error', 'fail', 'plante', 'problème'
153
- ];
154
- const lower = text.toLowerCase();
155
- return keywords.some(k => lower.includes(k));
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
156
152
  };
157
153
 
158
- const extractModuleName = (text) => {
159
- const matchExplicit = text.match(/(?:module|mod)\s+([A-Z][a-zA-Z0-9_]*)/i);
160
- if (matchExplicit) return matchExplicit[1];
161
-
162
-
163
- const words = text.split(' ');
164
- for (let i = 1; i < words.length; i++) {
165
- const w = words[i].replace(/[^a-zA-Z0-9]/g, '');
166
- if (/^[A-Z][a-z0-9]+/.test(w) && w.length > 3) return w;
154
+ // 📋 INTERCEPTION : LISTER LES MODULES
155
+ const isListingRequest = /^(liste|list|quels modules|montre les modules)/i.test(finalPrompt);
156
+ if (isListingRequest) {
157
+ process.stdout.write(` \x1b[90m🔍 Scanning Kernel Cache...\x1b[0m\n`);
158
+ const cacheRes = await getModuleCache({ projectId, moduleName: null });
159
+
160
+ if (cacheRes.found !== false && cacheRes.modules) { // Modification de la condition pour matcher l'API
161
+ console.log(`\n\x1b[36m⚡ FLEETBO KERNEL MODULES (${cacheRes.modules.length}):\x1b[0m`);
162
+ cacheRes.modules.forEach(m => {
163
+ const metalColor = m.platform === 'android' ? '\x1b[32m' : '\x1b[34m'; // Vert Android, Bleu iOS
164
+ console.log(` ${metalColor}■\x1b[0m \x1b[1m${m.moduleName}\x1b[0m \x1b[90m(${m.platform})\x1b[0m`);
165
+ });
166
+ } else {
167
+ console.log(`\n \x1b[90mAucun module trouvé dans l'infrastructure de ce projet.\x1b[0m`);
167
168
  }
168
- return null;
169
+ console.log('');
170
+ rl.setPrompt(`\x1b[34m${dynamicUsername} ❯ \x1b[0m`);
171
+ rl.prompt();
172
+ return; // On arrête là, pas besoin d'appeler l'IA pour lister !
173
+ }
174
+
175
+ // Sert uniquement à définir le TON du contexte envoyé à Alex
176
+ const getContextIntent = (text) => {
177
+ const modifierKeywords = [
178
+ 'modifier', 'corrige', 'ajoute', 'erreur', 'plante', 'problème', 'bug', 'change',
179
+ 'update', 'fix', 'edit', 'error', 'fail', 'crash', 'issue', 'add'
180
+ ];
181
+ const inspireKeywords = [
182
+ 'inspire', 'base', 'comme', 'modèle', 'reference', 'reprends', 'copie',
183
+ 'inspire', 'based on', 'model', 'reference', 'like', 'copy', 'similar'
184
+ ];
185
+
186
+ const lower = text.toLowerCase();
187
+ if (modifierKeywords.some(k => lower.includes(k))) return "MODIFICATION";
188
+ if (inspireKeywords.some(k => lower.includes(k))) return "INSPIRATION";
189
+ return "REFERENCE";
169
190
  };
170
191
 
171
192
  const getModuleCache = async ({ projectId, moduleName }) => {
@@ -218,47 +239,46 @@ if (command === 'alex') {
218
239
  console.log('\x1b[90mAlex prefers concise instructions. Please summarize.\x1b[0m');
219
240
  return;
220
241
  }
221
- //process.stdout.write('\x1b[33m🧠 Alex is thinking...\x1b[0m');
222
242
  console.log('\x1b[33m🧠 Alex is thinking...\x1b[0m');
223
243
 
224
244
  try {
225
- // --- DÉBUT MODIFICATION : SYSTÈME DE MÉMOIRE (CACHE) ---
245
+ // --- SYSTÈME DE MÉMOIRE (SMART CACHE SCANNER) ---
226
246
  let contextInjection = "";
227
-
228
- // 1. On détecte si c'est une update
229
- if (promptContainsModification(prompt)) {
230
- const moduleName = extractModuleName(prompt);
247
+ const potentialModules = extractPotentialModules(prompt);
248
+ let referenceFound = false;
249
+
250
+ for (const modName of potentialModules) {
251
+ process.stdout.write(` \x1b[90m🔍 Checking kernel cache for ${modName}...\x1b[0m`);
252
+ const cache = await getModuleCache({ projectId, moduleName: modName });
231
253
 
232
- if (moduleName) {
233
- process.stdout.write(` \x1b[90m🔍 Searching cache for ${moduleName}...\x1b[0m`);
234
- const cache = await getModuleCache({ projectId, moduleName });
254
+ if (cache.found) {
255
+ process.stdout.write(` \x1b[32mFOUND METAL\x1b[0m\n`);
235
256
 
236
- if (cache.found) {
237
- process.stdout.write(` \x1b[32mFOUND\x1b[0m\n`);
238
- // On prépare le contexte pour Alex
239
- contextInjection = `
240
-
241
- --- CONTEXTE : CODE EXISTANT (${moduleName}) ---
242
- L'utilisateur veut modifier ce module existant.
243
- Tu DOIS te baser sur ce code et appliquer les changements demandés.
244
-
245
- [EXISTING KOTLIN]
246
- ${cache.module.code}
247
-
248
- [EXISTING MOCK]
249
- ${cache.module.mockCode}
250
-
251
- --- FIN DU CONTEXTE ---
252
- `;
253
-
254
- // On injecte le contexte AVANT la demande pour qu'il soit traité comme une base de travail
255
- prompt = contextInjection + "\n\n[INSTRUCTION PILOTE]\n" + prompt;
256
- } else {
257
- process.stdout.write(` \x1b[31mNOT FOUND (Creating new)\x1b[0m\n`);
258
- }
257
+ const intent = getContextIntent(prompt);
258
+ const contextTitle = intent === "MODIFICATION"
259
+ ? "MÉTAL EXISTANT MODIFIER)"
260
+ : "MÉTAL DE RÉFÉRENCE (POUR PARITÉ DES DONNÉES)";
261
+
262
+ contextInjection = `
263
+
264
+ --- CONTEXTE SOUVERAIN : ${contextTitle} (${modName}) ---
265
+ DOGME: Le Métal est la source de vérité absolue. Le Mock n'est qu'une illusion.
266
+ Tu DOIS analyser ce code Natif pour comprendre EXACTEMENT comment les données ont été structurées, nommées (clés JSON) et sauvegardées.
267
+
268
+ [CODE NATIF EXISTANT]
269
+ ${cache.module.code}
270
+
271
+ --- FIN DU CONTEXTE ---
272
+ `;
273
+
274
+ prompt = contextInjection + "\n\n[INSTRUCTION DU PILOTE]\n" + prompt;
275
+ referenceFound = true;
276
+ break;
277
+ } else {
278
+ process.stdout.write(` \x1b[31mNOT FOUND\x1b[0m\n`);
259
279
  }
260
280
  }
261
- // --- FIN MODIFICATION ---
281
+ // --- FIN MODIFICATION MÉMOIRE ---
262
282
 
263
283
  const result = await axios.post(ALEX_ENGINE_URL, { prompt, projectType: 'android' }, {
264
284
  headers: { 'x-project-id': projectId }
@@ -270,7 +290,6 @@ if (command === 'alex') {
270
290
  try { aiData = JSON.parse(aiData); } catch (_) {}
271
291
  }
272
292
 
273
- //process.stdout.write('\r' + ' '.repeat(50) + '\r');
274
293
  process.stdout.write('\x1b[A\r' + ' '.repeat(50) + '\r');
275
294
 
276
295
  if (aiData.status === 'quota_exceeded') {
@@ -282,11 +301,7 @@ if (command === 'alex') {
282
301
  console.log('');
283
302
  let rawMsg = aiData.message || "I'm ready.";
284
303
 
285
- // 🛡️ FIX DÉFINITIF: Gemini imbrique parfois le JSON complet dans "message"
286
- // Le JSON imbriqué peut être tronqué (4000 tokens), donc JSON.parse échoue.
287
- // On extrait le vrai message par découpe de string.
288
304
  if (typeof rawMsg === 'string' && rawMsg.trimStart().startsWith('{')) {
289
- // Tentative 1: Parse complet (JSON non tronqué)
290
305
  try {
291
306
  const nested = JSON.parse(rawMsg);
292
307
  if (nested && nested.message) {
@@ -296,7 +311,6 @@ if (command === 'alex') {
296
311
  }
297
312
  }
298
313
  } catch (_) {
299
- // Tentative 2: Extraction par regex (JSON tronqué)
300
314
  const msgMatch = rawMsg.match(/"message"\s*:\s*"((?:[^"\\]|\\.)*)"/);
301
315
  if (msgMatch && msgMatch[1]) {
302
316
  rawMsg = msgMatch[1].replace(/\\n/g, '\n').replace(/\\"/g, '"');
@@ -306,18 +320,15 @@ if (command === 'alex') {
306
320
  }
307
321
  }
308
322
 
309
- // 🛡️ SAFETY: Si le message est un JSON stringifié ou contient du code brut,
310
- // on extrait uniquement la partie lisible pour le terminal.
311
323
  if (typeof rawMsg === 'object') {
312
324
  rawMsg = rawMsg.message || rawMsg.text || "Module generated.";
313
325
  }
314
- // Détection d'un JSON brut accidentellement injecté dans le message
315
326
  try {
316
327
  const testParse = JSON.parse(rawMsg);
317
328
  if (testParse && typeof testParse === 'object' && testParse.status) {
318
329
  rawMsg = testParse.message || "Module generated.";
319
330
  }
320
- } catch (_) { /* Not JSON, continue normally */ }
331
+ } catch (_) { }
321
332
 
322
333
  const formattedMsg = wrapText(rawMsg, 85);
323
334
  console.log('\x1b[32mAlex ❯\x1b[0m ' + formattedMsg);
@@ -371,22 +382,29 @@ if (command === 'alex') {
371
382
  }
372
383
  }
373
384
 
374
- if (config_offload && (config_offload.dependencies?.length > 0 || config_offload.permissions?.length > 0)) {
375
- process.stdout.write(` \x1b[33m[Cloud Inject]\x1b[0m Syncing ${config_offload.dependencies?.length || 0} libs to OS...`);
376
- try {
377
- await axios.post(INJECT_DEPS_URL, {
378
- projectId: projectId,
379
- fileData: {
380
- path: fileName,
381
- config_offload: config_offload
382
- }
383
- });
384
- process.stdout.write(` \x1b[32mOK\x1b[0m\n`);
385
- } catch (err) {
386
- process.stdout.write(` \x1b[31mFAILED\x1b[0m\n`);
387
- console.error(` ⚠️ Config sync failed: ${err.message}`);
388
- }
385
+ // --- CORRECTION : SYNCHRONISATION DU KERNEL (Cache + Dépendances INCONDITIONNELLE) ---
386
+ const depsCount = config_offload?.dependencies?.length || 0;
387
+ process.stdout.write(` \x1b[33m[Cloud Inject]\x1b[0m Archiving ${moduleName} to Kernel (${depsCount} libs)...`);
388
+
389
+ try {
390
+ await axios.post(INJECT_DEPS_URL, {
391
+ projectId: projectId,
392
+ fileData: {
393
+ path: fileName,
394
+ moduleName: moduleName, // INDISPENSABLE POUR LE CACHE
395
+ fileName: fileName,
396
+ code: code, // LE MÉTAL
397
+ mockFileName: mockFileName,
398
+ mockCode: mockCode, // LE MOCK
399
+ config_offload: config_offload || { dependencies: [], permissions: [] } // Sécurité
400
+ }
401
+ });
402
+ process.stdout.write(` \x1b[32mOK\x1b[0m\n`);
403
+ } catch (err) {
404
+ process.stdout.write(` \x1b[31mFAILED\x1b[0m\n`);
405
+ console.error(` ⚠️ Kernel sync failed: ${err.message}`);
389
406
  }
407
+ // --- FIN CORRECTION ---
390
408
  }
391
409
  } catch (error) {
392
410
  process.stdout.write('\r' + ' '.repeat(50) + '\r');
@@ -439,7 +457,7 @@ if (command === 'alex') {
439
457
  console.log(' \x1b[1m🎬 High-Perf\x1b[0m\x1b[90m Infinite Feeds, Video Players, Swipe Decks\x1b[0m');
440
458
  console.log(' \x1b[1m🏗️ Sovereign\x1b[0m\x1b[90m Full screens: form + photo + save-to-cloud\x1b[0m');
441
459
 
442
- // 3. LA COLLABORATION (Mise à jour V2)
460
+ // 3. LA COLLABORATION
443
461
  console.log('\n\x1b[36m💡 TELL ME "WHAT + WHY":\x1b[0m');
444
462
  console.log('\x1b[90m I will analyze your need and recommend the perfect module to forge.\x1b[0m');
445
463
  console.log('');
@@ -459,37 +477,29 @@ if (command === 'alex') {
459
477
  process.stdout.write('\n\x1b[F');
460
478
  rl.prompt();
461
479
 
462
- // --- NATURAL MULTI-LINE BUFFER (ZERO GLITCH V2) ---
463
480
  let inputBuffer = "";
464
481
  let isProcessing = false;
465
482
 
466
483
  rl.on('line', async (line) => {
467
- if (isProcessing) return; // Verrou pendant la forge
484
+ if (isProcessing) return;
468
485
 
469
486
  const trimmedLine = line.trim();
470
487
 
471
- // 1. COMMANDES DE SORTIE
472
488
  if (['exit', 'quit'].includes(trimmedLine.toLowerCase())) {
473
489
  console.log('\n\x1b[90m Alex session closed.\x1b[0m');
474
490
  rl.close();
475
491
  return;
476
492
  }
477
493
 
478
- // 2. ACCUMULATION (Si la ligne n'est pas vide)
479
494
  if (trimmedLine !== "") {
480
495
  inputBuffer += (inputBuffer ? "\n" : "") + line;
481
-
482
- // 🛡️ LE SECRET EST LÀ : On vide le préfixe pour les lignes suivantes.
483
- // Quand tu colles, NodeJS arrêtera de répéter "jojo ❯" partout !
484
496
  rl.setPrompt("");
485
497
  }
486
- // 3. VALIDATION (Touche Entrée sur une ligne vide)
487
498
  else {
488
499
  if (inputBuffer.trim() !== "") {
489
500
  const finalPrompt = inputBuffer.trim();
490
501
  inputBuffer = "";
491
502
 
492
- // 🛑 COUPE-CIRCUIT STRICT
493
503
  if (finalPrompt.length > 1000) {
494
504
  console.log(`\n\x1b[31m⛔ [Alex Safety] Mission rejetée : Taille excessive (${finalPrompt.length}/1000 caractères).\x1b[0m`);
495
505
  console.log(`\x1b[90m Veuillez être plus concis et vous concentrer sur l'essentiel.\x1b[0m\n`);
@@ -498,18 +508,17 @@ if (command === 'alex') {
498
508
  return;
499
509
  }
500
510
 
501
- // ✅ ON LANCE LA FORGE
502
511
  isProcessing = true;
503
- rl.setPrompt(""); // On masque le prompt pendant qu'Alex réfléchit
512
+ rl.setPrompt("");
504
513
  await processAlexRequest(finalPrompt);
505
514
  isProcessing = false;
506
515
 
507
516
  console.log('');
508
517
  rl.setPrompt(`\x1b[34m${dynamicUsername} ❯ \x1b[0m`);
509
- rl.prompt(); // On remet le prompt "jojo ❯" pour la mission suivante
518
+ rl.prompt();
510
519
  } else {
511
520
  rl.setPrompt(`\x1b[34m${dynamicUsername} ❯ \x1b[0m`);
512
- rl.prompt(); // Si appui sur Entrée dans le vide
521
+ rl.prompt();
513
522
  }
514
523
  }
515
524
  });
@@ -518,7 +527,7 @@ if (command === 'alex') {
518
527
  if (!initialPrompt || initialPrompt === '?') startAlexSession();
519
528
  else processAlexRequest(initialPrompt);
520
529
 
521
- }
530
+ }
522
531
 
523
532
  // ============================================
524
533
  // COMMAND: rm (MODULE ANNIHILATION)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbo-cockpit-cli",
3
- "version": "1.0.44",
3
+ "version": "1.0.46",
4
4
  "description": "Fleetbo CLI - Build native mobile apps with React",
5
5
  "author": "Fleetbo",
6
6
  "license": "MIT",