fleetbo-cockpit-cli 1.0.215 → 1.0.217

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 +81 -139
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -390,7 +390,6 @@ if (command === 'alex') {
390
390
  }
391
391
 
392
392
  if (actualMetalCode) {
393
- //const sourceText = fs.existsSync(localKtPath) ? "LOCAL" : "CACHE";
394
393
  process.stdout.write(` \x1b[32mFOUND METAL\x1b[0m\n`);
395
394
 
396
395
  // Extraction des schémas mémoires (uniquement dispo dans le cache cloud)
@@ -436,32 +435,24 @@ if (command === 'alex') {
436
435
 
437
436
  const promptWithTime = prompt + `\n\n[SYSTEM INFO: The exact current timestamp is ${exactTime}. Use it STRICTLY for your signature '// ⚡ Forged by Alex on...' at the bottom of your files.]`;
438
437
 
439
- const result = await axios.post(ALEX_ENGINE_URL, { prompt: promptWithTime, projectType: 'android', jsFramework: JS_FRAMEWORK }, {
440
- headers: { 'x-project-id': projectId }
441
- });
442
-
443
- let aiData = result.data;
444
-
445
- // 🛡️ ROBUST PARSER: Unwrap all nesting levels until we find a valid aiData object
438
+ // =================================================================
439
+ // 🚀 THE AUTOMATED 3-STEP WORKFLOW (Cross-Platform)
440
+ // =================================================================
441
+
446
442
  const deepUnwrap = (raw, depth = 0) => {
447
- if (depth > 5) return raw; // Safety: max 5 nesting levels
443
+ if (depth > 5) return raw;
448
444
  if (typeof raw === 'string') {
449
- // Try to extract a JSON object even if there's trailing garbage (truncation)
450
445
  const jsonMatch = raw.match(/\{[\s\S]*\}/);
451
446
  if (jsonMatch) {
452
- try {
453
- return deepUnwrap(JSON.parse(jsonMatch[0]), depth + 1);
454
- } catch (_) {}
447
+ try { return deepUnwrap(JSON.parse(jsonMatch[0]), depth + 1); } catch (_) {}
455
448
  }
456
449
  return raw;
457
450
  }
458
451
  if (typeof raw === 'object' && raw !== null) {
459
- // If the message field itself is a JSON string, unwrap it and merge moduleData
460
452
  if (typeof raw.message === 'string' && raw.message.trimStart().startsWith('{')) {
461
453
  try {
462
454
  const nested = JSON.parse(raw.message);
463
455
  if (nested && typeof nested === 'object' && nested.status) {
464
- // Merge: keep top-level moduleData if present, else take nested one
465
456
  if (!raw.moduleData && nested.moduleData) raw.moduleData = nested.moduleData;
466
457
  raw.message = nested.message || raw.message;
467
458
  if (!raw.status && nested.status) raw.status = nested.status;
@@ -473,90 +464,98 @@ if (command === 'alex') {
473
464
  return raw;
474
465
  };
475
466
 
476
- aiData = deepUnwrap(aiData);
467
+ const fetchStep = async (stepName, memory = {}) => {
468
+ const res = await axios.post(ALEX_ENGINE_URL, {
469
+ prompt: promptWithTime,
470
+ projectType: 'android',
471
+ jsFramework: JS_FRAMEWORK,
472
+ step: stepName,
473
+ memory: memory
474
+ }, { headers: { 'x-project-id': projectId } });
477
475
 
478
- // DISPLAY REASONING IN TERMINAL
479
- if (aiData.thinking_process) {
480
- // Show start of reasoning in gray for info
481
- console.log(` \x1b[90m🧠 Alex Analysis: ${aiData.thinking_process.substring(0, 150)}...\x1b[0m`);
482
- }
476
+ return deepUnwrap(res.data);
477
+ };
478
+
479
+ // Fonction pour animer les étapes silencieusement
480
+ const logStep = (icon, text) => {
481
+ // \x1b[2K efface la ligne courante, \r ramène au début
482
+ process.stdout.write(`\x1b[2K\r ${icon} ${text}`);
483
+ };
483
484
 
484
485
  process.stdout.write('\x1b[A\r' + ' '.repeat(50) + '\r');
485
486
 
487
+ // --- ÉTAPE 1 : NATIF ---
488
+ logStep('🧠', '\x1b[33m1/3 - Alex forge les fondations natives...\x1b[0m');
489
+ let aiData = await fetchStep('native_only');
490
+
486
491
  if (aiData.status === 'quota_exceeded') {
487
492
  console.log(`\n\x1b[31m⛔ ARCHITECT QUOTA REACHED:\x1b[0m ${aiData.message}`);
488
493
  return;
489
494
  }
490
-
491
- if (aiData.status === 'success' || aiData.status === 'message' || aiData.status === 'complex_refusal') {
492
- console.log('');
493
- let rawMsg = aiData.message || "I'm ready.";
494
495
 
495
- if (typeof rawMsg === 'object') {
496
- rawMsg = rawMsg.message || rawMsg.text || "Module generated.";
497
- }
496
+ let mergedModuleData = aiData.moduleData || {};
497
+ let aiDataMessage = aiData.message;
498
+
499
+ // 🛡️ RÉCTROCOMPATIBILITÉ BACKEND :
500
+ // Si le cloud renvoie déjà le Mock, c'est que ton index.js n'est pas encore mis à jour.
501
+ // On bypass la suite pour ne pas générer de doublons et consommer du token pour rien.
502
+ const isCloudUpdated = !mergedModuleData.mockCode;
503
+
504
+ if (isCloudUpdated && mergedModuleData.code) {
505
+ logStep('✅', '\x1b[32m1/3 - Code Natif généré.\x1b[0m\n');
498
506
 
499
- // 🟢 LE SYSTÈME DE WRAP ANTI-COUPURE (Ultra Robuste)
500
- // 1. On récupère la vraie largeur, ou 80 par défaut (standard des terminaux)
501
- const termWidth = process.stdout.columns || 80;
502
- // 2. On prend 85% de l'espace pour un confort de lecture optimal
503
- const maxWidth = Math.max(40, Math.floor(termWidth * 0.85));
507
+ // --- ÉTAPE 2 : MOCK ---
508
+ logStep('🎨', '\x1b[33m2/3 - Analyse du Natif et génération de l\'Interface (Mock)...\x1b[0m');
509
+ const aiData2 = await fetchStep('mock_only', { nativeCode: mergedModuleData.code });
504
510
 
505
- const wrapDynamic = (text, max) => {
506
- return text.split('\n').map(line => {
507
- // On ignore les lignes de code, les listes, ou les lignes déjà courtes
508
- if (/^[\s]*[-*•\d]/.test(line) || line.startsWith(" ") || line.length <= max) {
509
- return line;
510
- }
511
-
512
- const words = line.split(' ');
513
- let wrapped = [];
514
- let currentLine = '';
515
-
516
- for (let i = 0; i < words.length; i++) {
517
- const word = words[i];
518
-
519
- // Sécurité : Si un mot est IMMENSE (ex: un long lien HTTP), on le force sur sa propre ligne
520
- if (word.length > max) {
521
- if (currentLine) wrapped.push(currentLine);
522
- wrapped.push(word);
523
- currentLine = '';
524
- continue;
525
- }
526
-
527
- const spaceNeeded = currentLine.length > 0 ? 1 : 0;
528
-
529
- // Est-ce que le mot rentre sur la ligne courante ?
530
- if (currentLine.length + word.length + spaceNeeded > max) {
531
- wrapped.push(currentLine); // Non -> on sauvegarde la ligne
532
- currentLine = word; // On commence une nouvelle ligne avec ce mot
533
- } else {
534
- currentLine += (spaceNeeded ? ' ' : '') + word; // Oui -> on l'ajoute
535
- }
536
- }
537
-
538
- if (currentLine) wrapped.push(currentLine);
539
- return wrapped.join('\n');
540
- }).join('\n');
541
- };
511
+ if (aiData2.moduleData) {
512
+ mergedModuleData.mockCode = aiData2.moduleData.mockCode;
513
+ mergedModuleData.mockFileName = aiData2.moduleData.mockFileName;
514
+ }
515
+ logStep('✅', '\x1b[32m2/3 - Interface Mock générée.\x1b[0m\n');
542
516
 
543
- const formattedMsg = wrapDynamic(rawMsg, maxWidth);
517
+ // --- ÉTAPE 3 : JSON CONFIG ---
518
+ logStep('⚙️', '\x1b[33m3/3 - Finalisation de la plomberie JSON...\x1b[0m');
519
+ const aiData3 = await fetchStep('json_only', {
520
+ nativeCode: mergedModuleData.code,
521
+ mockCode: mergedModuleData.mockCode
522
+ });
544
523
 
545
- console.log('\x1b[32mAlex ❯\x1b[0m ' + formattedMsg);
524
+ if (aiData3.moduleData) {
525
+ mergedModuleData.config_offload = aiData3.moduleData.config_offload;
526
+ mergedModuleData.dataSchema = aiData3.moduleData.dataSchema;
527
+ mergedModuleData.uiSchema = aiData3.moduleData.uiSchema;
528
+ }
529
+ logStep('✅', '\x1b[32m3/3 - Configuration terminée.\x1b[0m\n');
530
+ } else {
531
+ // Le cloud n'est pas à jour, on efface le loader de l'étape 1 en silence
532
+ process.stdout.write(`\x1b[2K\r`);
533
+ }
534
+
535
+ // DISPLAY REASONING IN TERMINAL
536
+ if (aiData.thinking_process) {
537
+ console.log(` \x1b[90m🧠 Alex Analysis: ${aiData.thinking_process.substring(0, 150)}...\x1b[0m`);
538
+ }
539
+
540
+ // --- OUTPUT VERS XTERM ---
541
+ if (aiData.status === 'success' || aiData.status === 'message' || aiData.status === 'complex_refusal') {
542
+ let rawMsg = aiDataMessage || "I'm ready.";
543
+ if (typeof rawMsg === 'object') {
544
+ rawMsg = rawMsg.message || rawMsg.text || "Module generated.";
545
+ }
546
+ console.log(`[ALEX_START]${rawMsg}[ALEX_END]`);
546
547
  }
547
548
 
548
549
  // --- FILE CREATION LOGIC ---
549
- if (aiData.status === 'success' && aiData.moduleData) {
550
- let { fileName, code, mockFileName, mockCode, moduleName, instructions, config_offload, dataSchema, uiSchema } = aiData.moduleData;
550
+ if (aiData.status === 'success' && mergedModuleData && Object.keys(mergedModuleData).length > 0) {
551
+ let { fileName, code, mockFileName, mockCode, moduleName, config_offload, dataSchema, uiSchema } = mergedModuleData;
551
552
 
552
- // 🛡️ ANTI-DUMP SHIELD (Prevents terminal flooding)
553
553
  if (moduleName) {
554
- // Cleanup module name
555
554
  moduleName = moduleName.split('\n')[0].replace(/["'{}]/g, '').trim();
556
555
  if (moduleName.length > 40) moduleName = moduleName.substring(0, 40) + "...";
557
556
  }
558
557
 
559
- console.log(`\n \x1b[90mArchitecting: ${moduleName}\x1b[0m`);
558
+ console.log(`\n \x1b[90mArchitecting: ${moduleName || 'Unknown Module'}\x1b[0m`);
560
559
 
561
560
  const writeFile = (dir, name, content) => {
562
561
  const fullPath = path.join(process.cwd(), dir);
@@ -565,19 +564,6 @@ if (command === 'alex') {
565
564
  fs.writeFileSync(filePath, content);
566
565
  };
567
566
 
568
- // --- SUPPRIME CE BLOC ENTIER ---
569
- /*
570
- if (instructions && Array.isArray(instructions) && instructions.length > 0) {
571
- console.log('\n\x1b[33m--- GUIDE (MCI) ---\x1b[0m');
572
- instructions.forEach(line => {
573
- if (typeof line === 'string') {
574
- const formattedLine = line.replace(/ACTION|CAPTURE|PERSPECTIVE/g, '\x1b[1m$&\x1b[0m');
575
- console.log(` ${formattedLine}`);
576
- }
577
- });
578
- }
579
- */
580
-
581
567
  if (code && fileName) {
582
568
  const folder = fileName.endsWith('.kt') ? 'public/native/android/' : 'src/app/';
583
569
  writeFile(folder, fileName, code);
@@ -615,21 +601,14 @@ if (command === 'alex') {
615
601
  console.error(` ⚠️ OS sync failed: ${err.message}`);
616
602
  }
617
603
 
618
- // 💡 LES ASTUCES CONTEXTUELLES DYNAMIQUES
619
604
  console.log('');
620
-
621
- // Astuce 1 : Le Patch manuel
622
605
  console.log(`\x1b[90m If you manually edit the native code, sync it to the Cloud OS:\x1b[0m`);
623
606
  console.log(`\x1b[33m patch module\x1b[0m \x1b[36m${moduleName}\x1b[0m \x1b[33mandroid\x1b[0m \x1b[90m(or ios)\x1b[0m`);
624
-
625
- // Astuce 2 : L'éradication
626
607
  console.log(`\x1b[90m To eradicate this module later, open a new terminal and type:\x1b[0m`);
627
608
  console.log(`\x1b[31m npm run fleetbo rm\x1b[0m \x1b[36m${moduleName}\x1b[0m`);
628
609
 
629
-
630
- } else if (aiData.status === 'success' && !aiData.moduleData) {
631
- // SAFETY: If formatting is broken
632
- console.log(`\n\x1b[31m⚠️ Error: Alex replied, but source code could not be extracted. Try the command again.\x1b[0m\n`);
610
+ } else if (aiData.status === 'success' && (!mergedModuleData || Object.keys(mergedModuleData).length === 0)) {
611
+ console.log(`\n\x1b[31m⚠️ Error: Alex replied, but source code could not be extracted.\x1b[0m\n`);
633
612
  }
634
613
  } catch (error) {
635
614
  process.stdout.write('\r' + ' '.repeat(50) + '\r');
@@ -638,12 +617,7 @@ if (command === 'alex') {
638
617
  };
639
618
 
640
619
  // ============================================================
641
- // CLI PATCH startAlexSession (cli.js)
642
- //
643
- // Remplace le bloc ENTIER de startAlexSession, depuis :
644
- // const startAlexSession = async () => {
645
- // Jusqu'à (NON INCLUS) :
646
- // const rl = readline.createInterface({
620
+ // DÉMARRAGE DE LA SESSION INTERACTIVE ALEX
647
621
  // ============================================================
648
622
 
649
623
  const startAlexSession = async () => {
@@ -664,8 +638,6 @@ if (command === 'alex') {
664
638
 
665
639
  if (validation.data?.isRunning) {
666
640
  isReady = true;
667
- // ASTUCE : On confirme que le moteur est allumé
668
- // process.stdout.write('[ALEX_ONLINE]\n');
669
641
  dynamicUsername = validation.data.username || 'Pilot';
670
642
  hasAiKey = validation.data.hasAiKey || false;
671
643
  aiProvider = validation.data.aiProvider || null;
@@ -688,9 +660,6 @@ if (command === 'alex') {
688
660
 
689
661
  process.stdout.write(' '.repeat(60) + '\r');
690
662
 
691
- // ═══════════════════════════════════════════════
692
- // AI KEY CHECK — Block if no key configured
693
- // ═══════════════════════════════════════════════
694
663
  if (!hasAiKey) {
695
664
  console.log('');
696
665
  console.log('\x1b[1m\x1b[33m AI Configuration Required\x1b[0m');
@@ -710,9 +679,6 @@ if (command === 'alex') {
710
679
  process.exit(0);
711
680
  }
712
681
 
713
- // ═══════════════════════════════════════════════
714
- // ALEX WELCOME — Key is configured, show capabilities
715
- // ═══════════════════════════════════════════════
716
682
  const providerLabel = aiProvider === 'anthropic' ? 'Anthropic' : 'Google AI';
717
683
  const modelLabel = aiModel && aiModel !== 'auto' ? aiModel : 'auto';
718
684
 
@@ -726,12 +692,10 @@ if (command === 'alex') {
726
692
  console.log('\x1b[90m│ │\x1b[0m');
727
693
  console.log('\x1b[90m└─────────────────────────────────────────────────────────┘\x1b[0m');
728
694
 
729
- // AI ENGINE STATUS
730
695
  console.log('');
731
696
  console.log(`\x1b[32m✓\x1b[0m ${providerLabel} \x1b[90m· ${modelLabel}\x1b[0m`);
732
697
  console.log(`\x1b[32m✓\x1b[0m ${JS_FRAMEWORK === 'vue' ? '\x1b[32mVue.js\x1b[0m' : '\x1b[36mReact\x1b[0m'} \x1b[90m· JavaScript Framework\x1b[0m`);
733
698
 
734
- // FORGE CAPABILITIES
735
699
  console.log('');
736
700
  console.log('\x1b[36mCAPABILITIES\x1b[0m');
737
701
  console.log('\x1b[90m─────────────────────────────────────────────────────\x1b[0m');
@@ -741,7 +705,6 @@ if (command === 'alex') {
741
705
  console.log('\x1b[1mFleetbo View\x1b[0m\x1b[90m Full native tab (120 FPS)\x1b[0m');
742
706
  console.log('\x1b[90m─────────────────────────────────────────────────────\x1b[0m');
743
707
 
744
- // TRIGGER SYNTAX
745
708
  console.log('');
746
709
  console.log('\x1b[36mCODE GENERATION SYNTAX\x1b[0m \x1b[90m(strict format required)\x1b[0m');
747
710
  console.log('');
@@ -749,31 +712,17 @@ if (command === 'alex') {
749
712
  console.log('\x1b[90mVerbs: \x1b[33mforge\x1b[0m \x1b[90m·\x1b[0m \x1b[33mcreate\x1b[0m \x1b[90m·\x1b[0m \x1b[33mupdate\x1b[0m \x1b[90m·\x1b[0m \x1b[33mmodify\x1b[0m \x1b[90m·\x1b[0m \x1b[33mfix\x1b[0m \x1b[90m·\x1b[0m \x1b[33medit\x1b[0m');
750
713
  console.log('\x1b[90mExample: \x1b[0m\x1b[33mupdate\x1b[0m module \x1b[36mProfileManager\x1b[0m \x1b[90mwith a red button\x1b[0m');
751
714
  console.log('');
752
- console.log('\x1b[90mWithout this exact structure, Alex answers but does not generate code.\x1b[0m');
753
-
754
- // EXAMPLES
755
- console.log('');
756
- console.log('\x1b[36mEXAMPLES\x1b[0m');
757
- console.log('');
758
- console.log('\x1b[33m›\x1b[0m \x1b[90m"\x1b[33mforge\x1b[90m a camera module to scan receipts for my expense tracker"\x1b[0m');
759
- console.log('\x1b[33m›\x1b[0m \x1b[90m"\x1b[33mcreate\x1b[90m a form module to add products with photos to my catalog"\x1b[0m');
760
- console.log('\x1b[33m›\x1b[0m \x1b[90m"\x1b[33mupdate\x1b[90m the CloudCamera module to save in the collection orders"\x1b[0m');
761
715
 
762
-
763
- // READY
764
- console.log('');
765
716
  console.log('\x1b[32mAlex ❯\x1b[0m Describe your feature using the Compose panel...');
766
717
 
767
- // 1. ON SUPPRIME LE PROMPT VISUEL (Plus de "Pilot ❯")
768
718
  const rl = readline.createInterface({
769
719
  input: process.stdin,
770
720
  output: process.stdout
771
721
  });
772
722
 
773
- // LE BOUCLIER ANTI CTRL+C (SIGINT)
774
723
  rl.on('SIGINT', () => {
775
724
  console.log('\n\x1b[90m Alex session aborted (Ctrl+C).\x1b[0m');
776
- process.stdout.write('[ALEX_OFFLINE]\n'); // Prévient React !
725
+ process.stdout.write('[ALEX_OFFLINE]\n');
777
726
  rl.close();
778
727
  process.exit(0);
779
728
  });
@@ -790,22 +739,15 @@ if (command === 'alex') {
790
739
  }
791
740
 
792
741
  if (text !== "") {
793
- if (text.length > 4000) {
794
- console.log(`\n\x1b[31m⛔ [Alex Safety] Mission rejected: Excessive size (${text.length}/4000 characters).\x1b[0m`);
795
- return;
796
- }
797
-
798
742
  isProcessing = true;
799
743
 
800
- // 2. On exécute la requête sans faire de "rl.setPrompt()"
801
744
  await processAlexRequest(text);
802
745
 
803
746
  isProcessing = false;
804
- console.log(''); // Petit saut de ligne esthétique quand Alex a fini
747
+ console.log('');
805
748
  }
806
749
  };
807
750
 
808
- // 3. RÉCEPTEUR SILENCIEUX (Plus de "rl.prompt()" qui pollue l'écran)
809
751
  let pasteTimer = null;
810
752
 
811
753
  rl.on('line', async (line) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbo-cockpit-cli",
3
- "version": "1.0.215",
3
+ "version": "1.0.217",
4
4
  "description": "Fleetbo CLI - Build native mobile apps with React",
5
5
  "author": "Fleetbo",
6
6
  "license": "MIT",