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.
- package/cli.js +81 -139
- 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
|
-
|
|
440
|
-
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
496
|
-
|
|
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
|
-
//
|
|
500
|
-
|
|
501
|
-
const
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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' &&
|
|
550
|
-
let { fileName, code, mockFileName, mockCode, moduleName,
|
|
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
|
-
|
|
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
|
-
//
|
|
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');
|
|
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('');
|
|
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) => {
|