fleetbo-cockpit-cli 1.0.248 → 1.0.250
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 +24 -1087
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -1,1099 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
process.stdout.write("\x1b[1A\x1b[2K".repeat(4));
|
|
4
3
|
console.clear();
|
|
5
4
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const os = require('os');
|
|
12
|
-
const archiver = require('archiver');
|
|
13
|
-
const readline = require('readline');
|
|
5
|
+
const RED = '\x1b[31m';
|
|
6
|
+
const YELLOW = '\x1b[33m';
|
|
7
|
+
const CYAN = '\x1b[36m';
|
|
8
|
+
const BOLD = '\x1b[1m';
|
|
9
|
+
const RESET = '\x1b[0m';
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const INJECT_DEPS_URL = "https://savegeneratedfile-jqycakhlxa-uc.a.run.app";
|
|
20
|
-
const CACHE_URL = "https://getmodulecache-jqycakhlxa-uc.a.run.app";
|
|
21
|
-
const PORT = 8888;
|
|
11
|
+
console.log(`
|
|
12
|
+
${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}
|
|
13
|
+
${RED}${BOLD} DEPRECATED : FLEETBO CLI IS MOVING NATIVE${RESET}
|
|
14
|
+
${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}
|
|
22
15
|
|
|
23
|
-
|
|
24
|
-
const args = process.argv.slice(2);
|
|
25
|
-
const command = args[0];
|
|
16
|
+
${YELLOW}Attention Pilot!${RESET}
|
|
26
17
|
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
This NPM package (${CYAN}fleetbo-cockpit-cli${RESET}) is no longer maintained.
|
|
19
|
+
The Fleetbo Engine has been fully integrated into the ${BOLD}Fleetbo OS IDE${RESET}.
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
21
|
+
${BOLD}WHY THIS CHANGE?${RESET}
|
|
22
|
+
To provide a faster, more secure, and 100% offline development
|
|
23
|
+
experience, the CLI is now a core component of the desktop app.
|
|
35
24
|
|
|
36
|
-
|
|
25
|
+
${BOLD}WHAT SHOULD YOU DO?${RESET}
|
|
26
|
+
1. ${RED}Uninstall this package:${RESET} npm uninstall -g fleetbo-cockpit-cli
|
|
27
|
+
2. ${CYAN}Download Fleetbo OS:${RESET} Visit https://fleetbo.io
|
|
28
|
+
3. ${CYAN}Use the Internal Console:${RESET} Command everything from the IDE.
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const testerEmail = process.env.VITE_FLEETBO_TESTER_EMAIL;
|
|
30
|
+
${YELLOW}Thank you for being part of the early flight.
|
|
31
|
+
See you in the new cockpit.${RESET}
|
|
41
32
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
45
|
-
if (!fs.existsSync(pkgPath)) return 'react';
|
|
46
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
47
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
48
|
-
if (deps['vue'] || deps['@vue/core'] || deps['nuxt']) return 'vue';
|
|
49
|
-
return 'react';
|
|
50
|
-
} catch (e) {
|
|
51
|
-
return 'react';
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
const JS_FRAMEWORK = detectFramework();
|
|
33
|
+
${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}
|
|
34
|
+
`);
|
|
55
35
|
|
|
56
|
-
|
|
57
|
-
console.error('\x1b[31m[Error] Project ID missing in .env.\x1b[0m\n');
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const checkGitSecurity = () => {
|
|
62
|
-
const gitDir = path.join(process.cwd(), '.git');
|
|
63
|
-
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
64
|
-
if (fs.existsSync(gitDir)) {
|
|
65
|
-
if (!fs.existsSync(gitignorePath)) {
|
|
66
|
-
console.error('\x1b[31m[Security Alert]\x1b[0m .git detected but no .gitignore found.');
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
|
70
|
-
if (!gitignoreContent.includes('.env')) {
|
|
71
|
-
console.error('\x1b[31m[Critical Risk]\x1b[0m .env is NOT ignored by Git.');
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const injectRouteIntoAppJs = (moduleName, subPath = '') => {
|
|
78
|
-
const targetFile = JS_FRAMEWORK === 'vue'
|
|
79
|
-
? path.join(process.cwd(), 'src', 'router.js')
|
|
80
|
-
: path.join(process.cwd(), 'src', 'App.jsx');
|
|
81
|
-
|
|
82
|
-
if (!fs.existsSync(targetFile)) {
|
|
83
|
-
console.error(`\x1b[31m[Safety Stop]\x1b[0m ${path.basename(targetFile)} missing.`);
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let content = fs.readFileSync(targetFile, 'utf8');
|
|
88
|
-
const importAnchor = '// FLEETBO_MORE_IMPORTS';
|
|
89
|
-
const routeAnchor = JS_FRAMEWORK === 'vue'
|
|
90
|
-
? '/* FLEETBO_DYNAMIC ROUTES */'
|
|
91
|
-
: '{/* FLEETBO_DYNAMIC ROUTES */}';
|
|
92
|
-
|
|
93
|
-
if (!content.includes(importAnchor) || !content.includes(routeAnchor)) {
|
|
94
|
-
console.log(`\x1b[33m[Skipped]\x1b[0m Anchors missing in ${path.basename(targetFile)}. Manual injection required.`);
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const cleanSubPath = subPath ? `${subPath}/` : '';
|
|
99
|
-
const importLine = `import ${moduleName} from './app/${cleanSubPath}${moduleName}';`;
|
|
100
|
-
let routeLine;
|
|
101
|
-
if (JS_FRAMEWORK === 'vue') {
|
|
102
|
-
routeLine = `{ path: '/${cleanSubPath}${moduleName.toLowerCase()}', component: ${moduleName} },`;
|
|
103
|
-
} else {
|
|
104
|
-
routeLine = `<Route path="/${cleanSubPath}${moduleName.toLowerCase()}" element={<${moduleName} />} />`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
let modified = false;
|
|
108
|
-
|
|
109
|
-
if (!content.includes(importLine)) {
|
|
110
|
-
const importMatch = content.match(new RegExp(`(\\n[ \\t]*)${importAnchor.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')}`));
|
|
111
|
-
const importIndent = importMatch ? importMatch[1] : '\n';
|
|
112
|
-
content = content.replace(importAnchor, `${importLine}${importIndent}${importAnchor}`);
|
|
113
|
-
modified = true;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (!content.includes(routeLine)) {
|
|
117
|
-
const routeMatch = content.match(new RegExp(`(\\n[ \\t]*)${routeAnchor.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')}`));
|
|
118
|
-
const routeIndent = routeMatch ? routeMatch[1] : '\n';
|
|
119
|
-
content = content.replace(routeAnchor, `${routeLine}${routeIndent}${routeAnchor}`);
|
|
120
|
-
modified = true;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (modified) {
|
|
124
|
-
fs.writeFileSync(targetFile, content);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return modified;
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const showEnergyTransfer = async () => {
|
|
131
|
-
const width = 30;
|
|
132
|
-
for (let i = 0; i <= width; i++) {
|
|
133
|
-
const dots = "█".repeat(i);
|
|
134
|
-
const empty = "░".repeat(width - i);
|
|
135
|
-
process.stdout.write(`\r\x1b[32m[Propulsion Sync]\x1b[0m [${dots}${empty}] ${Math.round((i / width) * 100)}%`);
|
|
136
|
-
await new Promise(r => setTimeout(r, 45));
|
|
137
|
-
}
|
|
138
|
-
process.stdout.write('\n');
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const extractPotentialModules = (text) => {
|
|
142
|
-
const regex = /\b[A-Z][a-z]+[A-Z][a-zA-Z0-9_]*\b/g;
|
|
143
|
-
const matches = text.match(regex) || [];
|
|
144
|
-
return [...new Set(matches)];
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const getContextIntent = (text) => {
|
|
148
|
-
const modifierKeywords = [
|
|
149
|
-
'modifier', 'modifie', 'corrige', 'ajoute', 'change', 'met a jour', 'mets à jour',
|
|
150
|
-
'optimise', 'améliore', 'refactorise', 'répare', 'adapte', 'remplace',
|
|
151
|
-
'update', 'fix', 'edit', 'add', 'modify', 'upgrade', 'optimize',
|
|
152
|
-
'improve', 'refactor', 'repair', 'adapt', 'replace', 'tweak',
|
|
153
|
-
'modifica', 'edita', 'actualiza', 'añade', 'agrega', 'mejora',
|
|
154
|
-
'optimiza', 'refactoriza', 'repara', 'adapta', 'reemplaza'
|
|
155
|
-
];
|
|
156
|
-
|
|
157
|
-
const inspireKeywords = [
|
|
158
|
-
'inspire', 'base', 'comme', 'modèle', 'reference', 'reprends', 'copie', 'imite', 'basé sur',
|
|
159
|
-
'based on', 'model', 'like', 'copy', 'similar', 'imitate', 'replicate',
|
|
160
|
-
'inspira', 'basado', 'como', 'modelo', 'referencia', 'copia', 'imita', 'básate'
|
|
161
|
-
];
|
|
162
|
-
|
|
163
|
-
const lower = text.toLowerCase();
|
|
164
|
-
if (modifierKeywords.some(k => lower.includes(k))) return "MODIFICATION";
|
|
165
|
-
if (inspireKeywords.some(k => lower.includes(k))) return "INSPIRATION";
|
|
166
|
-
return "REFERENCE";
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const getModuleCache = async ({ projectId, moduleName }) => {
|
|
170
|
-
try {
|
|
171
|
-
const res = await axios.post(CACHE_URL, { projectId, moduleName });
|
|
172
|
-
if (res.data && res.data.found) {
|
|
173
|
-
return { found: true, module: res.data.module, modules: res.data.modules };
|
|
174
|
-
}
|
|
175
|
-
return { found: false };
|
|
176
|
-
} catch (e) {
|
|
177
|
-
return { found: false };
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const removeRouteFromAppJs = (moduleName) => {
|
|
182
|
-
const targetFile = JS_FRAMEWORK === 'vue'
|
|
183
|
-
? path.join(process.cwd(), 'src', 'router.js')
|
|
184
|
-
: path.join(process.cwd(), 'src', 'App.jsx');
|
|
185
|
-
if (!fs.existsSync(targetFile)) return false;
|
|
186
|
-
|
|
187
|
-
let content = fs.readFileSync(targetFile, 'utf8');
|
|
188
|
-
|
|
189
|
-
let routeLine;
|
|
190
|
-
if (JS_FRAMEWORK === 'vue') {
|
|
191
|
-
routeLine = `{ path: '/mocks/${moduleName.toLowerCase()}', component: ${moduleName} },`;
|
|
192
|
-
} else {
|
|
193
|
-
routeLine = `<Route path="/mocks/${moduleName.toLowerCase()}" element={<${moduleName} />} />`;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const originalContent = content;
|
|
197
|
-
|
|
198
|
-
const importRegex = new RegExp(
|
|
199
|
-
`import\\s+${moduleName}\\s+from\\s+['"]\\./app/mocks/${moduleName}(?:\\.jsx|\\.vue)?['"];?\\n?`,
|
|
200
|
-
'g'
|
|
201
|
-
);
|
|
202
|
-
content = content.replace(importRegex, '');
|
|
203
|
-
|
|
204
|
-
const routeRegex = new RegExp(`\\n?\\s*${routeLine.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'g');
|
|
205
|
-
content = content.replace(routeRegex, '');
|
|
206
|
-
|
|
207
|
-
if (content !== originalContent) {
|
|
208
|
-
fs.writeFileSync(targetFile, content);
|
|
209
|
-
console.log(`\x1b[32m[Unrouted]\x1b[0m ${moduleName} removed from ${path.basename(targetFile)}.`);
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
return false;
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
if (command === 'alex') {
|
|
216
|
-
checkGitSecurity();
|
|
217
|
-
const initialPrompt = args.slice(1).join(' ');
|
|
218
|
-
|
|
219
|
-
const processAlexRequest = async (prompt) => {
|
|
220
|
-
// 🟢 LIMITE RÉDUITE À 2000 CARACTÈRES (~500 TOKENS)
|
|
221
|
-
if (prompt.length > 2000) {
|
|
222
|
-
console.log('\x1b[31m[Alex Safety] Request too long (' + prompt.length + '/2000 chars).\x1b[0m');
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const patchMatch = prompt.match(/^patch\s+module\s+([a-zA-Z0-9_]+)\s+(android|ios)/i);
|
|
227
|
-
const partialPatchMatch = prompt.match(/^patch\s+module\s+([a-zA-Z0-9_]+)$/i);
|
|
228
|
-
|
|
229
|
-
if (partialPatchMatch && !patchMatch) {
|
|
230
|
-
console.log(`\x1b[31m[Rejected]\x1b[0m Ambiguous command. Platform is mandatory to avoid Metal corruption.`);
|
|
231
|
-
console.log(`Type: patch module ${partialPatchMatch[1]} android`);
|
|
232
|
-
console.log(`Or: patch module ${partialPatchMatch[1]} ios`);
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (patchMatch) {
|
|
237
|
-
const modName = patchMatch[1];
|
|
238
|
-
const targetPlatform = patchMatch[2].toLowerCase();
|
|
239
|
-
|
|
240
|
-
console.log(`\x1b[33m[Integrity Check] Verifying ${modName} (${targetPlatform.toUpperCase()})...\x1b[0m`);
|
|
241
|
-
|
|
242
|
-
const cacheRes = await getModuleCache({ projectId, moduleName: modName });
|
|
243
|
-
if (!cacheRes.found) {
|
|
244
|
-
console.log(`\x1b[31m[Rejected]\x1b[0m Module "${modName}" does not exist in the Cloud. The first draft must ALWAYS be forged by Alex.`);
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const nativeExt = targetPlatform === 'android' ? 'kt' : 'swift';
|
|
249
|
-
const nativeFileName = `${modName}.${nativeExt}`;
|
|
250
|
-
const nativePathAPI = `public/native/${targetPlatform}/${nativeFileName}`;
|
|
251
|
-
const nativePathFull = path.join(process.cwd(), 'public', 'native', targetPlatform, nativeFileName);
|
|
252
|
-
|
|
253
|
-
const mockExt = JS_FRAMEWORK === 'vue' ? 'vue' : 'jsx';
|
|
254
|
-
const mockPath = path.join(process.cwd(), 'src', 'app', 'mocks', `${modName}.${mockExt}`);
|
|
255
|
-
|
|
256
|
-
if (!fs.existsSync(nativePathFull)) {
|
|
257
|
-
console.log(`\x1b[31m[Fatal Error]\x1b[0m Target file not found on disk (${nativePathAPI}).`);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const localNativeCode = fs.readFileSync(nativePathFull, 'utf8');
|
|
262
|
-
const localMockCode = fs.existsSync(mockPath) ? fs.readFileSync(mockPath, 'utf8') : null;
|
|
263
|
-
|
|
264
|
-
process.stdout.write(`\x1b[33m[Patch Sync]\x1b[0m Pushing ${targetPlatform.toUpperCase()} modifications to Cloud OS... `);
|
|
265
|
-
try {
|
|
266
|
-
await axios.post(INJECT_DEPS_URL, {
|
|
267
|
-
projectId: projectId,
|
|
268
|
-
fileData: {
|
|
269
|
-
moduleName: modName,
|
|
270
|
-
path: nativePathAPI,
|
|
271
|
-
fileName: nativeFileName,
|
|
272
|
-
code: localNativeCode,
|
|
273
|
-
mockFileName: `${modName}.${mockExt}`,
|
|
274
|
-
mockCode: localMockCode,
|
|
275
|
-
isPatch: true
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
process.stdout.write(`\x1b[32m[OK]\x1b[0m\n`);
|
|
279
|
-
console.log(`\x1b[32m[Success]\x1b[0m Local version of ${nativeFileName} is now the official reference in the Cloud.`);
|
|
280
|
-
} catch (err) {
|
|
281
|
-
process.stdout.write(`\x1b[31m[FAILED]\x1b[0m\n`);
|
|
282
|
-
console.error(`\x1b[31m[Error] OS sync failed: ${err.message}\x1b[0m`);
|
|
283
|
-
}
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const isListingRequest = /^(liste|list|listar|lista|quels modules|montre les modules|affiche les modules|show modules|what modules|display modules|qu[eé] m[oó]dulos|muestra los m[oó]dulos|ver m[oó]dulos)/i.test(prompt);
|
|
288
|
-
if (isListingRequest) {
|
|
289
|
-
process.stdout.write(`\x1b[90m[Scan] Checking OS Cache...\x1b[0m\n`);
|
|
290
|
-
const cacheRes = await getModuleCache({ projectId, moduleName: null });
|
|
291
|
-
|
|
292
|
-
if (cacheRes.found !== false && cacheRes.modules && cacheRes.modules.length > 0) {
|
|
293
|
-
console.log(`\x1b[36m[Fleetbo OS Modules] (${cacheRes.modules.length}):\x1b[0m`);
|
|
294
|
-
cacheRes.modules.forEach(m => {
|
|
295
|
-
console.log(`[${m.platform}] \x1b[1m${m.moduleName}\x1b[0m`);
|
|
296
|
-
});
|
|
297
|
-
} else {
|
|
298
|
-
console.log(`\x1b[90mNo modules found in the infrastructure of this project.\x1b[0m`);
|
|
299
|
-
}
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
console.log('Alex is thinking...');
|
|
304
|
-
|
|
305
|
-
try {
|
|
306
|
-
let contextInjection = "";
|
|
307
|
-
const potentialModules = extractPotentialModules(prompt);
|
|
308
|
-
|
|
309
|
-
let targetModuleContext = "";
|
|
310
|
-
let referenceContexts = "";
|
|
311
|
-
|
|
312
|
-
for (let modName of potentialModules) {
|
|
313
|
-
let isReferenceOnly = false;
|
|
314
|
-
|
|
315
|
-
if (modName.endsWith('Ref')) {
|
|
316
|
-
isReferenceOnly = true;
|
|
317
|
-
modName = modName.replace('Ref', '');
|
|
318
|
-
} else if (modName.endsWith('Schema')) {
|
|
319
|
-
isReferenceOnly = true;
|
|
320
|
-
modName = modName.replace('Schema', '');
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
process.stdout.write(`\x1b[90mChecking Alex memory for ${modName} module...\x1b[0m `);
|
|
324
|
-
const cache = await getModuleCache({ projectId, moduleName: modName });
|
|
325
|
-
|
|
326
|
-
const localKtPath = path.join(process.cwd(), 'public', 'native', 'android', `${modName}.kt`);
|
|
327
|
-
let actualMetalCode = "";
|
|
328
|
-
|
|
329
|
-
if (fs.existsSync(localKtPath)) {
|
|
330
|
-
actualMetalCode = fs.readFileSync(localKtPath, 'utf8');
|
|
331
|
-
} else if (cache.found && cache.module.code) {
|
|
332
|
-
actualMetalCode = cache.module.code;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (actualMetalCode) {
|
|
336
|
-
process.stdout.write(`\x1b[32m[FOUND METAL]\x1b[0m\n`);
|
|
337
|
-
|
|
338
|
-
let memoryScript = "";
|
|
339
|
-
if (cache.found && cache.module.dataSchema) memoryScript += `\n[DATA MEMORY SCRIPT]\n${cache.module.dataSchema}\n`;
|
|
340
|
-
if (cache.found && cache.module.uiSchema) memoryScript += `\n[NATIVE UI MEMORY SCRIPT]\n${cache.module.uiSchema}\n`;
|
|
341
|
-
|
|
342
|
-
if (!memoryScript) memoryScript = `\n[MODULE MEMORY SCRIPT ${modName}]\nNo schema registered for this module.\n`;
|
|
343
|
-
|
|
344
|
-
const isTabModule = actualMetalCode.includes('action == "tab"');
|
|
345
|
-
const levelWarning = isTabModule
|
|
346
|
-
? "\n[CRITICAL SYSTEM ALERT]: This native code contains 'action == \"tab\"'. It is DEFINITIVELY a LEVEL 5 (Fleetbo View / Sovereign Tab). You are STRICTLY FORBIDDEN to propose Fleetbo.exec() for this module. You MUST use Fleetbo.openView('ModuleName', true) in a useEffect() to integrate it, otherwise it will destroy the application.\n"
|
|
347
|
-
: "";
|
|
348
|
-
|
|
349
|
-
if (isReferenceOnly) {
|
|
350
|
-
referenceContexts += `\n--- CONTEXT: REFERENCE MODULE (${modName}) ---\nDOGMA: Do not modify this module. Align with its Memory Scripts and Native Code.\n${memoryScript}\n[REFERENCE NATIVE CODE]\n${actualMetalCode}\n`;
|
|
351
|
-
} else if (!targetModuleContext) {
|
|
352
|
-
const intent = getContextIntent(prompt);
|
|
353
|
-
if (intent === "MODIFICATION") {
|
|
354
|
-
targetModuleContext = `\n--- CONTEXT: EXISTING NATIVE CODE TO MODIFY (${modName}) ---\nDOGMA: You must modify this Native code. Then, you will forge a new JSX Mock based SOLELY on your new Native code.${levelWarning}\n${memoryScript}\n[EXISTING NATIVE CODE]\n${actualMetalCode}\n--- END OF CONTEXT ---\n`;
|
|
355
|
-
} else {
|
|
356
|
-
targetModuleContext = `\n--- CONTEXT: READ ONLY (${modName}) ---\nFor information, here is the current state of the module. Do not modify it, use it to accurately answer the Pilot.${levelWarning}\n${memoryScript}\n[NATIVE CODE]\n${actualMetalCode}\n`;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
} else {
|
|
360
|
-
process.stdout.write(`\x1b[31m[NOT FOUND]\x1b[0m\n`);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
contextInjection = referenceContexts + targetModuleContext;
|
|
365
|
-
if (contextInjection) {
|
|
366
|
-
prompt = contextInjection + "\n\n[PILOT INSTRUCTION]\n" + prompt;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const now = new Date();
|
|
370
|
-
const exactTime = now.toISOString().split('T')[0] + ' at ' + now.toTimeString().split(' ')[0];
|
|
371
|
-
|
|
372
|
-
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.]`;
|
|
373
|
-
|
|
374
|
-
const deepUnwrap = (raw, depth = 0) => {
|
|
375
|
-
if (depth > 5) return raw;
|
|
376
|
-
if (typeof raw === 'string') {
|
|
377
|
-
const jsonMatch = raw.match(/\{[\s\S]*\}/);
|
|
378
|
-
if (jsonMatch) {
|
|
379
|
-
try { return deepUnwrap(JSON.parse(jsonMatch[0]), depth + 1); } catch (_) {}
|
|
380
|
-
}
|
|
381
|
-
return raw;
|
|
382
|
-
}
|
|
383
|
-
if (typeof raw === 'object' && raw !== null) {
|
|
384
|
-
if (typeof raw.message === 'string' && raw.message.trimStart().startsWith('{')) {
|
|
385
|
-
try {
|
|
386
|
-
const nested = JSON.parse(raw.message);
|
|
387
|
-
if (nested && typeof nested === 'object' && nested.status) {
|
|
388
|
-
if (!raw.moduleData && nested.moduleData) raw.moduleData = nested.moduleData;
|
|
389
|
-
raw.message = nested.message || raw.message;
|
|
390
|
-
if (!raw.status && nested.status) raw.status = nested.status;
|
|
391
|
-
}
|
|
392
|
-
} catch (_) {}
|
|
393
|
-
}
|
|
394
|
-
return raw;
|
|
395
|
-
}
|
|
396
|
-
return raw;
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
const fetchStep = async (stepName, memory = {}) => {
|
|
400
|
-
const res = await axios.post(ALEX_ENGINE_URL, {
|
|
401
|
-
prompt: promptWithTime,
|
|
402
|
-
projectType: 'android',
|
|
403
|
-
jsFramework: JS_FRAMEWORK,
|
|
404
|
-
step: stepName,
|
|
405
|
-
memory: memory
|
|
406
|
-
}, { headers: { 'x-project-id': projectId } });
|
|
407
|
-
|
|
408
|
-
return deepUnwrap(res.data);
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
const logStep = (prefix, text) => {
|
|
412
|
-
process.stdout.write(`\r\x1b[2K${prefix} ${text}`);
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
let totalOutputTokens = 0;
|
|
416
|
-
|
|
417
|
-
logStep('[1/3]', '\x1b[33mAlex is forging native foundations...\x1b[0m');
|
|
418
|
-
let aiData = await fetchStep('native_only');
|
|
419
|
-
|
|
420
|
-
if (aiData.status === 'quota_exceeded') {
|
|
421
|
-
console.log(`\n\x1b[31m[Architect Quota Reached]\x1b[0m ${aiData.message}`);
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
totalOutputTokens += aiData.outputTokensUsed || 0;
|
|
426
|
-
|
|
427
|
-
let mergedModuleData = aiData.moduleData || {};
|
|
428
|
-
let aiDataMessage = aiData.message;
|
|
429
|
-
|
|
430
|
-
const isCloudUpdated = !mergedModuleData.mockCode;
|
|
431
|
-
|
|
432
|
-
if (isCloudUpdated && mergedModuleData.code) {
|
|
433
|
-
logStep('[1/3]', `\x1b[32mNative code generated.\x1b[0m \x1b[90m(${aiData.outputTokensUsed || 0} tokens)\x1b[0m\n`);
|
|
434
|
-
|
|
435
|
-
logStep('[2/3]', '\x1b[33mAnalyzing and generating Mock file...\x1b[0m');
|
|
436
|
-
const aiData2 = await fetchStep('mock_only', { nativeCode: mergedModuleData.code });
|
|
437
|
-
|
|
438
|
-
if (aiData2.moduleData) {
|
|
439
|
-
mergedModuleData.mockCode = aiData2.moduleData.mockCode;
|
|
440
|
-
mergedModuleData.mockFileName = aiData2.moduleData.mockFileName;
|
|
441
|
-
}
|
|
442
|
-
totalOutputTokens += aiData2.outputTokensUsed || 0;
|
|
443
|
-
logStep('[2/3]', `\x1b[32mMock generated.\x1b[0m \x1b[90m(${aiData2.outputTokensUsed || 0} tokens)\x1b[0m\n`);
|
|
444
|
-
|
|
445
|
-
logStep('[3/3]', '\x1b[33mFinalizing JSON configuration...\x1b[0m');
|
|
446
|
-
|
|
447
|
-
// 🟢 MOCK DROP : ON N'ENVOIE QUE LE NATIF À L'ÉTAPE 3
|
|
448
|
-
const aiData3 = await fetchStep('json_only', {
|
|
449
|
-
nativeCode: mergedModuleData.code
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
if (aiData3.moduleData) {
|
|
453
|
-
mergedModuleData.config_offload = aiData3.moduleData.config_offload;
|
|
454
|
-
mergedModuleData.dataSchema = aiData3.moduleData.dataSchema;
|
|
455
|
-
mergedModuleData.uiSchema = aiData3.moduleData.uiSchema;
|
|
456
|
-
}
|
|
457
|
-
totalOutputTokens += aiData3.outputTokensUsed || 0;
|
|
458
|
-
logStep('[3/3]', `\x1b[32mConfiguration completed.\x1b[0m \x1b[90m(${aiData3.outputTokensUsed || 0} tokens)\x1b[0m\n\n`);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (aiData.thinking_process) {
|
|
462
|
-
console.log(`\x1b[90m[Analysis] ${aiData.thinking_process.substring(0, 150)}...\x1b[0m`);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
if (aiData.status === 'success' || aiData.status === 'message' || aiData.status === 'complex_refusal') {
|
|
466
|
-
let rawMsg = aiDataMessage || "I'm ready.";
|
|
467
|
-
if (typeof rawMsg === 'object') {
|
|
468
|
-
rawMsg = rawMsg.message || rawMsg.text || "Module generated.";
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (aiData.status === 'message') {
|
|
472
|
-
rawMsg = "\x1b[33m[Chat Mode]\x1b[0m " + rawMsg;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
process.stdout.write(`[ALEX_START]${rawMsg}[ALEX_END]`);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
if (aiData.status === 'success' && mergedModuleData && Object.keys(mergedModuleData).length > 0) {
|
|
479
|
-
let { fileName, code, mockFileName, mockCode, moduleName, config_offload, dataSchema, uiSchema } = mergedModuleData;
|
|
480
|
-
|
|
481
|
-
if (moduleName) {
|
|
482
|
-
moduleName = moduleName.split('\n')[0].replace(/["'{}]/g, '').trim();
|
|
483
|
-
if (moduleName.length > 40) moduleName = moduleName.substring(0, 40) + "...";
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
const writeFile = (dir, name, content) => {
|
|
487
|
-
const fullPath = path.join(process.cwd(), dir);
|
|
488
|
-
const filePath = path.join(fullPath, name);
|
|
489
|
-
if (!fs.existsSync(fullPath)) fs.mkdirSync(fullPath, { recursive: true });
|
|
490
|
-
fs.writeFileSync(filePath, content);
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
if (code && fileName) {
|
|
494
|
-
const folder = fileName.endsWith('.kt') ? 'public/native/android/' : 'src/app/';
|
|
495
|
-
writeFile(folder, fileName, code);
|
|
496
|
-
if (fileName.endsWith('.jsx')) injectRouteIntoAppJs(fileName.replace('.jsx', ''));
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
if (mockCode && mockFileName) {
|
|
500
|
-
const pageName = mockFileName.replace(/\.(jsx|vue)$/, '');
|
|
501
|
-
writeFile('src/app/mocks/', mockFileName, mockCode);
|
|
502
|
-
const injected = injectRouteIntoAppJs(pageName, 'mocks');
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
const depsCount = config_offload?.dependencies?.length || 0;
|
|
506
|
-
process.stdout.write(`\n\x1b[33m[OS Sync]\x1b[0m Archiving ${moduleName} to OS (${depsCount} libs)... `);
|
|
507
|
-
|
|
508
|
-
try {
|
|
509
|
-
await axios.post(INJECT_DEPS_URL, {
|
|
510
|
-
projectId: projectId,
|
|
511
|
-
fileData: {
|
|
512
|
-
path: fileName,
|
|
513
|
-
moduleName: moduleName,
|
|
514
|
-
fileName: fileName,
|
|
515
|
-
code: code,
|
|
516
|
-
mockFileName: mockFileName,
|
|
517
|
-
mockCode: mockCode,
|
|
518
|
-
config_offload: config_offload || { dependencies: [], permissions: [] },
|
|
519
|
-
dataSchema: dataSchema || null,
|
|
520
|
-
uiSchema: uiSchema || null
|
|
521
|
-
}
|
|
522
|
-
});
|
|
523
|
-
process.stdout.write(`\x1b[32m[OK]\x1b[0m\n`);
|
|
524
|
-
} catch (err) {
|
|
525
|
-
process.stdout.write(`\x1b[31m[FAILED]\x1b[0m\n`);
|
|
526
|
-
console.error(`\x1b[31m[Error] OS sync failed: ${err.message}\x1b[0m`);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
console.log('\x1b[90mIf you manually edit the native code, sync it to the Cloud OS:\x1b[0m');
|
|
530
|
-
console.log(`\x1b[33mpatch module\x1b[0m \x1b[36m${moduleName}\x1b[0m \x1b[33mandroid\x1b[0m \x1b[90m(or ios)\x1b[0m`);
|
|
531
|
-
console.log('\x1b[90mTo eradicate this module later, open a new terminal and type:\x1b[0m');
|
|
532
|
-
console.log(`\x1b[31mnpm run fleetbo rm\x1b[0m \x1b[36m${moduleName}\x1b[0m`);
|
|
533
|
-
|
|
534
|
-
if (totalOutputTokens > 0) {
|
|
535
|
-
console.log(`\x1b[90m[Telemetry] Output tokens generated: ${totalOutputTokens} (Limit: 8192/step)\x1b[0m`);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
} else if (aiData.status === 'success' && (!mergedModuleData || Object.keys(mergedModuleData).length === 0)) {
|
|
539
|
-
console.log('\x1b[31m[Error] Alex replied, but source code could not be extracted.\x1b[0m');
|
|
540
|
-
}
|
|
541
|
-
} catch (error) {
|
|
542
|
-
process.stdout.write('\x1b[2K\r');
|
|
543
|
-
console.error('\x1b[31m[Alex Error]\x1b[0m ' + (error.response?.data?.message || error.message));
|
|
544
|
-
}
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
const startAlexSession = async () => {
|
|
548
|
-
process.stdout.write('\x1b[33m[Alex] Checking runtime state...\x1b[0m\r');
|
|
549
|
-
let attempts = 0;
|
|
550
|
-
const maxAttempts = 5;
|
|
551
|
-
let isReady = false;
|
|
552
|
-
let dynamicUsername = 'Pilot';
|
|
553
|
-
let hasAiKey = false;
|
|
554
|
-
let aiProvider = null;
|
|
555
|
-
let aiModel = null;
|
|
556
|
-
|
|
557
|
-
while (attempts < maxAttempts && !isReady) {
|
|
558
|
-
try {
|
|
559
|
-
const validation = await axios.post(ALEX_ENGINE_URL, {
|
|
560
|
-
prompt: "ping", validateProject: true, checkNetwork: true, projectKey: keyApp
|
|
561
|
-
}, { headers: { 'x-project-id': projectId }, timeout: 5000 });
|
|
562
|
-
|
|
563
|
-
if (validation.data?.isRunning) {
|
|
564
|
-
isReady = true;
|
|
565
|
-
dynamicUsername = validation.data.username || 'Pilot';
|
|
566
|
-
hasAiKey = validation.data.hasAiKey || false;
|
|
567
|
-
aiProvider = validation.data.aiProvider || null;
|
|
568
|
-
aiModel = validation.data.aiModel || null;
|
|
569
|
-
break;
|
|
570
|
-
}
|
|
571
|
-
attempts++;
|
|
572
|
-
if (attempts < maxAttempts) await new Promise(r => setTimeout(r, 2000));
|
|
573
|
-
} catch (error) {
|
|
574
|
-
attempts++;
|
|
575
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
if (!isReady) {
|
|
580
|
-
console.error('\x1b[31m[ENGINE OFFLINE]\x1b[0m Start Fleetbo runtime first: "npm run fleetbo" ');
|
|
581
|
-
console.error(`\x1b[90m(Ensure you are running the runtime for project: ${keyApp})\x1b[0m`);
|
|
582
|
-
process.exit(1);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
process.stdout.write(' '.repeat(60) + '\r');
|
|
586
|
-
|
|
587
|
-
if (!hasAiKey) {
|
|
588
|
-
console.log('\x1b[1m\x1b[33mAI Configuration Required\x1b[0m\n');
|
|
589
|
-
console.log('Alex needs a powerful AI model to forge native code.');
|
|
590
|
-
console.log('Connect your own AI key to continue.\n');
|
|
591
|
-
console.log('\x1b[1mSupported providers:\x1b[0m');
|
|
592
|
-
console.log('\x1b[32m[+]\x1b[0m Google AI \x1b[90m(Gemini Pro)\x1b[0m');
|
|
593
|
-
console.log('\x1b[36mhttps://aistudio.google.com/app/apikey\x1b[0m\n');
|
|
594
|
-
console.log('\x1b[33m[+]\x1b[0m Anthropic \x1b[90m(Claude Sonnet / Opus)\x1b[0m');
|
|
595
|
-
console.log('\x1b[36mhttps://console.anthropic.com/settings/keys\x1b[0m\n');
|
|
596
|
-
console.log('\x1b[90mClick the \x1b[32m[+]\x1b[90m button in the Terminal top-right bar\x1b[0m\n');
|
|
597
|
-
process.exit(0);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
const providerLabel = aiProvider === 'anthropic' ? 'Anthropic' : 'Google AI';
|
|
601
|
-
const modelLabel = aiModel && aiModel !== 'auto' ? aiModel : 'auto';
|
|
602
|
-
|
|
603
|
-
process.stdout.write(`[ALEX_ONLINE:${dynamicUsername}]\n`);
|
|
604
|
-
|
|
605
|
-
console.log('\x1b[90m┌─────────────────────────────────────────────────────────┐\x1b[0m');
|
|
606
|
-
console.log('\x1b[90m│ │\x1b[0m');
|
|
607
|
-
console.log('\x1b[90m│\x1b[0m \x1b[1m\x1b[36m[ALEX]\x1b[0m \x1b[90m— System Architect · Fleetbo OS\x1b[0m \x1b[90m│\x1b[0m');
|
|
608
|
-
console.log('\x1b[90m│ │\x1b[0m');
|
|
609
|
-
console.log('\x1b[90m│\x1b[0m \x1b[90mYour JS stays the brain. I forge the metal.\x1b[0m \x1b[90m│\x1b[0m');
|
|
610
|
-
console.log('\x1b[90m│ │\x1b[0m');
|
|
611
|
-
console.log('\x1b[90m└─────────────────────────────────────────────────────────┘\x1b[0m');
|
|
612
|
-
|
|
613
|
-
console.log(`\n\x1b[32m[OK]\x1b[0m ${providerLabel} \x1b[90m· ${modelLabel}\x1b[0m`);
|
|
614
|
-
console.log(`\x1b[32m[OK]\x1b[0m ${JS_FRAMEWORK === 'vue' ? '\x1b[32mVue.js\x1b[0m' : '\x1b[36mReact\x1b[0m'} \x1b[90m· JavaScript Framework\x1b[0m\n`);
|
|
615
|
-
|
|
616
|
-
console.log('\x1b[36mCAPABILITIES\x1b[0m');
|
|
617
|
-
console.log('\x1b[90m─────────────────────────────────────────────────────\x1b[0m');
|
|
618
|
-
console.log('\x1b[1mHardware\x1b[0m\x1b[90m Camera · Scanner · GPS · Biometrics\x1b[0m');
|
|
619
|
-
console.log('\x1b[1mHigh-Perf\x1b[0m\x1b[90m Video Feed · Swipe Deck · Audio\x1b[0m');
|
|
620
|
-
console.log('\x1b[1mSovereign\x1b[0m\x1b[90m Form + Photo + Save-to-Cloud\x1b[0m');
|
|
621
|
-
console.log('\x1b[1mFleetbo View\x1b[0m\x1b[90m Full native tab (120 FPS)\x1b[0m');
|
|
622
|
-
console.log('\x1b[90m─────────────────────────────────────────────────────\x1b[0m\n');
|
|
623
|
-
|
|
624
|
-
console.log('\x1b[36mCODE GENERATION SYNTAX\x1b[0m \x1b[90m(strict format required)\x1b[0m');
|
|
625
|
-
console.log('\x1b[90m─────────────────────────────────────────────────────\x1b[0m');
|
|
626
|
-
console.log('\x1b[90mFormat: \x1b[33m<verb>\x1b[0m module \x1b[36m<ModuleName>\x1b[0m');
|
|
627
|
-
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');
|
|
628
|
-
console.log('\x1b[90mExample: \x1b[0m\x1b[33mupdate\x1b[0m module \x1b[36mProfileManager\x1b[0m \x1b[90mwith a red button\x1b[0m');
|
|
629
|
-
console.log('\x1b[90m─────────────────────────────────────────────────────\x1b[0m');
|
|
630
|
-
|
|
631
|
-
console.log('\n\x1b[32mAlex ❯\x1b[0m Describe your feature using the Compose panel...');
|
|
632
|
-
|
|
633
|
-
const rl = readline.createInterface({
|
|
634
|
-
input: process.stdin,
|
|
635
|
-
output: process.stdout
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
rl.on('SIGINT', () => {
|
|
639
|
-
console.log('\x1b[90mAlex session aborted (Ctrl+C).\x1b[0m');
|
|
640
|
-
process.stdout.write('[ALEX_OFFLINE]\n');
|
|
641
|
-
rl.close();
|
|
642
|
-
process.exit(0);
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
let standardBuffer = [];
|
|
646
|
-
let isProcessing = false;
|
|
647
|
-
|
|
648
|
-
const executePrompt = async (text) => {
|
|
649
|
-
if (['exit', 'quit'].includes(text.toLowerCase())) {
|
|
650
|
-
console.log('\x1b[90mAlex session closed.\x1b[0m');
|
|
651
|
-
process.stdout.write('[ALEX_OFFLINE]\n');
|
|
652
|
-
rl.close();
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
if (text !== "") {
|
|
657
|
-
isProcessing = true;
|
|
658
|
-
await processAlexRequest(text);
|
|
659
|
-
isProcessing = false;
|
|
660
|
-
|
|
661
|
-
}
|
|
662
|
-
};
|
|
663
|
-
|
|
664
|
-
let pasteTimer = null;
|
|
665
|
-
|
|
666
|
-
rl.on('line', async (line) => {
|
|
667
|
-
if (isProcessing) return;
|
|
668
|
-
|
|
669
|
-
standardBuffer.push(line);
|
|
670
|
-
|
|
671
|
-
if (pasteTimer) clearTimeout(pasteTimer);
|
|
672
|
-
|
|
673
|
-
pasteTimer = setTimeout(async () => {
|
|
674
|
-
pasteTimer = null;
|
|
675
|
-
|
|
676
|
-
const finalPrompt = standardBuffer.join('\n').trim();
|
|
677
|
-
standardBuffer = [];
|
|
678
|
-
|
|
679
|
-
if (finalPrompt !== "") {
|
|
680
|
-
await executePrompt(finalPrompt);
|
|
681
|
-
}
|
|
682
|
-
}, 30);
|
|
683
|
-
});
|
|
684
|
-
};
|
|
685
|
-
|
|
686
|
-
if (!initialPrompt || initialPrompt === '?') startAlexSession();
|
|
687
|
-
else processAlexRequest(initialPrompt);
|
|
688
|
-
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
else if (command === 'rm') {
|
|
692
|
-
const moduleName = args[1];
|
|
693
|
-
if (!moduleName) {
|
|
694
|
-
console.error('\x1b[31m[Error] Module name required.\x1b[0m');
|
|
695
|
-
console.log('\x1b[90mUsage: npm run fleetbo rm [ModuleName]\x1b[0m\n');
|
|
696
|
-
process.exit(1);
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
console.log(`\x1b[33m[Annihilation] Removing module: ${moduleName}...\x1b[0m`);
|
|
700
|
-
|
|
701
|
-
const ktPath = path.join(process.cwd(), 'public', 'native', 'android', `${moduleName}.kt`);
|
|
702
|
-
const jsxPath = path.join(process.cwd(), 'src', 'app', 'mocks', `${moduleName}.jsx`);
|
|
703
|
-
const vuePath = path.join(process.cwd(), 'src', 'app', 'mocks', `${moduleName}.vue`);
|
|
704
|
-
const mockPath = fs.existsSync(jsxPath) ? jsxPath : fs.existsSync(vuePath) ? vuePath : null;
|
|
705
|
-
const mockExt = mockPath ? path.extname(mockPath) : '';
|
|
706
|
-
|
|
707
|
-
let actionsDone = 0;
|
|
708
|
-
|
|
709
|
-
if (fs.existsSync(ktPath)) {
|
|
710
|
-
fs.unlinkSync(ktPath);
|
|
711
|
-
console.log(`\x1b[32m[Deleted]\x1b[0m Metal file (.kt) eradicated.`);
|
|
712
|
-
actionsDone++;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
if (mockPath) {
|
|
716
|
-
fs.unlinkSync(mockPath);
|
|
717
|
-
console.log(`\x1b[32m[Deleted]\x1b[0m Virtual Twin (${mockExt}) eradicated.`);
|
|
718
|
-
actionsDone++;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
const unrouted = removeRouteFromAppJs(moduleName);
|
|
722
|
-
if (unrouted) actionsDone++;
|
|
723
|
-
|
|
724
|
-
if (actionsDone === 0) {
|
|
725
|
-
console.log(`\x1b[31m[Warning] No trace of module "${moduleName}" found in the OS.\x1b[0m\n`);
|
|
726
|
-
} else {
|
|
727
|
-
console.log(`\x1b[32mModule ${moduleName} successfully eradicated from the OS.\x1b[0m\n`);
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
else if (command === 'android' || command === 'ios') {
|
|
732
|
-
|
|
733
|
-
(async () => {
|
|
734
|
-
|
|
735
|
-
if (command === 'ios') {
|
|
736
|
-
console.log(`\x1b[36m[FLEETBO IOS PROPULSION]\x1b[0m`);
|
|
737
|
-
console.log(`\x1b[33m[0/3] Initializing Neural Uplink...\x1b[0m`);
|
|
738
|
-
|
|
739
|
-
await new Promise(r => setTimeout(r, 800));
|
|
740
|
-
|
|
741
|
-
console.log(`\x1b[31m[PROPULSION ABORTED] iOS Frequency Restricted.\x1b[0m`);
|
|
742
|
-
console.log(`\x1b[90mThis module is currently reserved for Vanguard Pilots (Closed Beta).\x1b[0m`);
|
|
743
|
-
console.log(`\x1b[90mPlease engage propulsion on Android frequency for now.\x1b[0m\n`);
|
|
744
|
-
|
|
745
|
-
process.exit(1);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
checkGitSecurity();
|
|
749
|
-
const platform = command;
|
|
750
|
-
const nativeDir = platform === 'android' ? 'public/native/android/' : 'public/native/ios/';
|
|
751
|
-
const extension = platform === 'android' ? '.kt' : '.swift';
|
|
752
|
-
const nativePath = path.join(process.cwd(), nativeDir);
|
|
753
|
-
|
|
754
|
-
let hasNativeFiles = false;
|
|
755
|
-
let nativeFileCount = 0;
|
|
756
|
-
if (fs.existsSync(nativePath)) {
|
|
757
|
-
const files = fs.readdirSync(nativePath);
|
|
758
|
-
const nativeFiles = files.filter(file => file.endsWith(extension));
|
|
759
|
-
hasNativeFiles = nativeFiles.length > 0;
|
|
760
|
-
nativeFileCount = nativeFiles.length;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
if (!hasNativeFiles) {
|
|
764
|
-
console.log(`\x1b[31m[ENGINE INCOMPLETE]\x1b[0m No native blueprints detected for \x1b[1m${platform.toUpperCase()}\x1b[0m.`);
|
|
765
|
-
console.log(`\x1b[90mAlex must architect at least one ${extension} module before deployment.\x1b[0m`);
|
|
766
|
-
console.log(`\x1b[90mRun: npm run fleetbo alex\x1b[0m\n`);
|
|
767
|
-
process.exit(1);
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
const targetUrl = platform === 'android' ? ANDROID_BUILD_URL : IOS_BUILD_URL;
|
|
771
|
-
|
|
772
|
-
console.log(`\x1b[36m[FLEETBO ${platform.toUpperCase()} PROPULSION]\x1b[0m`);
|
|
773
|
-
console.log(`\x1b[90m${nativeFileCount} fleetbo module(s) detected\x1b[0m\n`);
|
|
774
|
-
|
|
775
|
-
try {
|
|
776
|
-
process.stdout.write(`\x1b[33m[0/3]\x1b[0m Checking Propulsion Access... `);
|
|
777
|
-
try {
|
|
778
|
-
await axios.post(targetUrl, {}, {
|
|
779
|
-
headers: {
|
|
780
|
-
'x-project-id': projectId,
|
|
781
|
-
'x-preflight': 'true'
|
|
782
|
-
}
|
|
783
|
-
});
|
|
784
|
-
process.stdout.write(`\x1b[32mOK (Senior Pilot)\x1b[0m\n\n`);
|
|
785
|
-
|
|
786
|
-
} catch (preflightError) {
|
|
787
|
-
process.stdout.write(`\x1b[31mDENIED\x1b[0m\n`);
|
|
788
|
-
|
|
789
|
-
const errData = preflightError.response?.data;
|
|
790
|
-
|
|
791
|
-
if (errData?.code === 'junior_restriction') {
|
|
792
|
-
console.log(`\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
793
|
-
console.log(`\x1b[31m[ACCESS DENIED] JUNIOR PILOT DETECTED\x1b[0m`);
|
|
794
|
-
console.log(`\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m\n`);
|
|
795
|
-
console.log(`\x1b[33mThis feature is locked for Junior Pilots.\x1b[0m`);
|
|
796
|
-
console.log(`\x1b[32mUpgrade to Senior on fleetbo.io to unlock Propulsion.\x1b[0m\n`);
|
|
797
|
-
process.exit(1);
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
if (errData && errData.error) {
|
|
801
|
-
throw new Error(errData.error);
|
|
802
|
-
}
|
|
803
|
-
throw preflightError;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
console.log(`\x1b[33m[1/3]\x1b[0m Synthesizing Fleetbo Core Logic...`);
|
|
807
|
-
execSync('npm run build', { stdio: 'inherit' });
|
|
808
|
-
|
|
809
|
-
let buildDir = fs.existsSync(path.join(process.cwd(), 'dist')) ? 'dist' : 'build';
|
|
810
|
-
const buildPath = path.join(process.cwd(), buildDir);
|
|
811
|
-
|
|
812
|
-
if (!fs.existsSync(buildPath)) {
|
|
813
|
-
throw new Error(`Build directory not found: ${buildDir}`);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
console.log(`\x1b[33m[2/3]\x1b[0m Packaging bundle + fleetbo modules...`);
|
|
817
|
-
|
|
818
|
-
const zipBuffer = await new Promise((resolve, reject) => {
|
|
819
|
-
const chunks = [];
|
|
820
|
-
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
821
|
-
|
|
822
|
-
archive.on('data', chunk => chunks.push(chunk));
|
|
823
|
-
archive.on('end', () => resolve(Buffer.concat(chunks)));
|
|
824
|
-
archive.on('error', reject);
|
|
825
|
-
archive.on('warning', (err) => {
|
|
826
|
-
if (err.code !== 'ENOENT') reject(err);
|
|
827
|
-
});
|
|
828
|
-
|
|
829
|
-
archive.directory(buildPath, 'build');
|
|
830
|
-
if (fs.existsSync(nativePath)) {
|
|
831
|
-
archive.directory(nativePath, `build/native/${platform}`);
|
|
832
|
-
}
|
|
833
|
-
archive.finalize();
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
|
|
837
|
-
console.log(`\x1b[32m[Ready]\x1b[0m Bundle ready: ${sizeMB} MB`);
|
|
838
|
-
|
|
839
|
-
console.log(`\x1b[33m[3/3]\x1b[0m Uploading to Fleetbo OS...`);
|
|
840
|
-
await showEnergyTransfer();
|
|
841
|
-
|
|
842
|
-
let uploadResponse;
|
|
843
|
-
try {
|
|
844
|
-
uploadResponse = await axios.post(targetUrl, zipBuffer, {
|
|
845
|
-
headers: {
|
|
846
|
-
'Content-Type': 'application/zip',
|
|
847
|
-
'x-project-id': projectId
|
|
848
|
-
},
|
|
849
|
-
maxContentLength: Infinity,
|
|
850
|
-
maxBodyLength: Infinity,
|
|
851
|
-
timeout: 120000
|
|
852
|
-
});
|
|
853
|
-
} catch (axiosError) {
|
|
854
|
-
if (axiosError.response && axiosError.response.data && axiosError.response.data.error) {
|
|
855
|
-
throw new Error(axiosError.response.data.error);
|
|
856
|
-
} else {
|
|
857
|
-
throw new Error(`Connection to OS failed: ${axiosError.message}`);
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
if (uploadResponse.data && uploadResponse.data.success) {
|
|
862
|
-
console.log(`\x1b[32m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
863
|
-
console.log(`\x1b[32m[SUCCESS] ${platform.toUpperCase()} PROPULSION SUCCESSFUL\x1b[0m`);
|
|
864
|
-
console.log(`\x1b[32m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
865
|
-
console.log(`\x1b[90mDeployment ID: ${uploadResponse.data.deploymentId || 'N/A'}\x1b[0m`);
|
|
866
|
-
console.log(`\x1b[90m${uploadResponse.data.message || 'Complete.'}\x1b[0m\n`);
|
|
867
|
-
} else {
|
|
868
|
-
throw new Error(uploadResponse.data?.error || 'Unknown logical error from Factory');
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
} catch (error) {
|
|
872
|
-
console.log(`\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
873
|
-
console.log(`\x1b[31m[Propulsion Failed]\x1b[0m`);
|
|
874
|
-
console.log(`\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
875
|
-
|
|
876
|
-
console.error(`\x1b[31m[Error]\x1b[0m ${error.message}`);
|
|
877
|
-
|
|
878
|
-
if (error.message.includes('Limit') || error.message.includes('Quota')) {
|
|
879
|
-
console.log(`\x1b[33m[Tip]\x1b[0m Upgrade to Senior Pilot for more builds.`);
|
|
880
|
-
} else if (error.message.includes('No native module')) {
|
|
881
|
-
console.log(`\x1b[33m[Tip]\x1b[0m Run "npm run fleetbo alex" to create native modules first.`);
|
|
882
|
-
} else if (error.message.includes('Trial Period Ended')) {
|
|
883
|
-
console.log(`\x1b[33m[Tip]\x1b[0m Your free sprint is over. Upgrade to Senior Pilot on fleetbo.io.`);
|
|
884
|
-
}
|
|
885
|
-
console.log('');
|
|
886
|
-
process.exit(1);
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
})();
|
|
890
|
-
}
|
|
891
|
-
else if (['page', 'g', 'generate'].includes(command)) {
|
|
892
|
-
const pageGeneratorPath = path.join(__dirname, 'page.js');
|
|
893
|
-
try {
|
|
894
|
-
require(pageGeneratorPath);
|
|
895
|
-
} catch (e) {
|
|
896
|
-
console.error('\x1b[31m[Page Generator Error]\x1b[0m', e.message);
|
|
897
|
-
process.exit(1);
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
else {
|
|
901
|
-
const NULL_DEV = process.platform === 'win32' ? '>nul 2>&1' : '2>/dev/null';
|
|
902
|
-
|
|
903
|
-
// 🟢 On crée une variable pour garder la trace du serveur Vite
|
|
904
|
-
let devServerProcess = null;
|
|
905
|
-
|
|
906
|
-
function killProcessOnPort(port) {
|
|
907
|
-
try {
|
|
908
|
-
if (process.platform !== 'win32') {
|
|
909
|
-
const pid = execSync(`lsof -ti:${port} ${NULL_DEV}`).toString().trim();
|
|
910
|
-
if (pid) execSync(`kill -9 ${pid.split('\n').join(' ')} ${NULL_DEV}`);
|
|
911
|
-
}
|
|
912
|
-
} catch (e) {}
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
// 🟢 On tue TOUS les processus enfants (Cloudflare ET Vite) proprement
|
|
916
|
-
const killAllServices = () => {
|
|
917
|
-
const killProc = (proc) => {
|
|
918
|
-
if (proc && proc.pid) {
|
|
919
|
-
try {
|
|
920
|
-
if (process.platform === 'win32') {
|
|
921
|
-
spawn('taskkill', ['/pid', String(proc.pid), '/T', '/F'], {
|
|
922
|
-
stdio: 'ignore', detached: true, shell: false
|
|
923
|
-
}).unref();
|
|
924
|
-
} else {
|
|
925
|
-
proc.kill('SIGINT');
|
|
926
|
-
}
|
|
927
|
-
} catch (e) {}
|
|
928
|
-
}
|
|
929
|
-
};
|
|
930
|
-
killProc(uplinkProcess);
|
|
931
|
-
killProc(devServerProcess);
|
|
932
|
-
};
|
|
933
|
-
|
|
934
|
-
let isExiting = false;
|
|
935
|
-
|
|
936
|
-
async function cleanupAndExit(code = 0) {
|
|
937
|
-
if (isExiting) return;
|
|
938
|
-
isExiting = true;
|
|
939
|
-
console.log('\n\x1b[33m[Fleetbo] Stopping environment...\x1b[0m');
|
|
940
|
-
|
|
941
|
-
// On tue instantanément les processus pour libérer le terminal
|
|
942
|
-
killAllServices();
|
|
943
|
-
killProcessOnPort(PORT);
|
|
944
|
-
|
|
945
|
-
try {
|
|
946
|
-
// 🟢 Timeout ultra-court (1s) pour ne jamais bloquer la sortie
|
|
947
|
-
await axios.post(UPDATE_NETWORK_URL, { keyApp, networkUrl: '', tester: testerEmail }, { timeout: 1000 });
|
|
948
|
-
console.log('\x1b[32m[Fleetbo] Network status reset to offline.\x1b[0m');
|
|
949
|
-
} catch (e) {
|
|
950
|
-
// On ignore silencieusement si ça échoue, le but principal est de quitter.
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
console.log('[Fleetbo] Bye.');
|
|
954
|
-
process.exit(code);
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
if (process.platform === 'win32') {
|
|
958
|
-
const rlWin = require('readline').createInterface({ input: process.stdin, output: process.stdout });
|
|
959
|
-
rlWin.on('SIGINT', () => cleanupAndExit(0));
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
process.on('SIGINT', () => cleanupAndExit(0));
|
|
963
|
-
process.on('SIGTERM', () => cleanupAndExit(0));
|
|
964
|
-
|
|
965
|
-
async function syncFirebase(keyApp, networkUrl, testerEmail) {
|
|
966
|
-
try {
|
|
967
|
-
await axios.post(UPDATE_NETWORK_URL, { keyApp, networkUrl, tester: testerEmail });
|
|
968
|
-
console.log('\x1b[32mEngine started successfully\x1b[0m\n');
|
|
969
|
-
console.log(`\x1b[32mFleetbo OS ❯\x1b[0m -------------------------------------------------------------`);
|
|
970
|
-
console.log(`\x1b[32mFleetbo OS ❯\x1b[0m \x1b[1mProject [${keyApp}] running in OS\x1b[0m`);
|
|
971
|
-
console.log('\x1b[32mFleetbo OS ❯\x1b[0m You can now start coding and previewing.');
|
|
972
|
-
console.log(`\x1b[32mFleetbo OS ❯\x1b[0m -------------------------------------------------------------\n`);
|
|
973
|
-
console.log(`\x1b[34mPilot Instruction ❯\x1b[0m Return to the Workspace. The Engine is ready for your orders.`);
|
|
974
|
-
} catch (err) {
|
|
975
|
-
console.error(`\x1b[31mFleetbo OS ❯\x1b[0m Sync Error: ${err.message}`);
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
async function runDevEnvironment() {
|
|
980
|
-
console.log(`[Fleetbo] Initializing Universal Dev Environment...\n`);
|
|
981
|
-
|
|
982
|
-
killAllServices();
|
|
983
|
-
killProcessOnPort(PORT);
|
|
984
|
-
|
|
985
|
-
if (!testerEmail) {
|
|
986
|
-
console.error('\x1b[31m[Error] VITE_FLEETBO_TESTER_EMAIL missing in .env\x1b[0m');
|
|
987
|
-
process.exit(1);
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
991
|
-
|
|
992
|
-
devServerProcess = spawn(npmCmd, ['run', 'dev', '--silent', '--', '--host', '127.0.0.1', '--port', String(PORT)], {
|
|
993
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
994
|
-
shell: true,
|
|
995
|
-
env: {
|
|
996
|
-
...process.env,
|
|
997
|
-
NODE_OPTIONS: '--no-deprecation'
|
|
998
|
-
}
|
|
999
|
-
});
|
|
1000
|
-
|
|
1001
|
-
devServerProcess.stderr.pipe(process.stderr);
|
|
1002
|
-
|
|
1003
|
-
let connectionStarted = false;
|
|
1004
|
-
let detectedPort = PORT;
|
|
1005
|
-
|
|
1006
|
-
let viteBuffer = "";
|
|
1007
|
-
|
|
1008
|
-
devServerProcess.stdout.on('data', (data) => {
|
|
1009
|
-
const chunkText = data.toString();
|
|
1010
|
-
viteBuffer += chunkText;
|
|
1011
|
-
|
|
1012
|
-
const lines = chunkText.split('\n');
|
|
1013
|
-
const forbiddenTerms = [
|
|
1014
|
-
'Attempting to bind to HOST', 'If this was unintentional', 'Learn more here:',
|
|
1015
|
-
'Starting the development server', 'You can now view', 'vite v', 'VITE v', 'Port', 'is in use',
|
|
1016
|
-
'ready in', 'Local:', 'Network:', 'press h + enter', '> vite', '> fleetbo', '> npx fleetbo-cockpit-cli@latest'
|
|
1017
|
-
];
|
|
1018
|
-
|
|
1019
|
-
const filteredOutput = lines.filter(line => {
|
|
1020
|
-
const cleanLine = line.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
|
1021
|
-
return !forbiddenTerms.some(term => cleanLine.includes(term));
|
|
1022
|
-
}).join('\n');
|
|
1023
|
-
|
|
1024
|
-
if (filteredOutput.trim() !== '') {
|
|
1025
|
-
process.stdout.write(filteredOutput + '\n');
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
if (!connectionStarted) {
|
|
1029
|
-
const portMatch = viteBuffer.match(/http:\/\/(?:localhost|127\.0\.0\.1):(\d+)/);
|
|
1030
|
-
|
|
1031
|
-
if (portMatch) {
|
|
1032
|
-
detectedPort = portMatch[1];
|
|
1033
|
-
connectionStarted = true;
|
|
1034
|
-
|
|
1035
|
-
console.log('\x1b[33mFleetbo OS ❯\x1b[0m ---------------------------------------------------');
|
|
1036
|
-
console.log(`\x1b[33mFleetbo OS ❯\x1b[0m Establishing Secure Uplink...`);
|
|
1037
|
-
console.log(`\x1b[33mFleetbo OS ❯\x1b[0m Please wait for the green message...`);
|
|
1038
|
-
console.log('\x1b[33mFleetbo OS ❯\x1b[0m ---------------------------------------------------\n');
|
|
1039
|
-
|
|
1040
|
-
const MAX_UPLINK_RETRIES = 5;
|
|
1041
|
-
const RETRY_DELAYS = [0, 10, 20, 30, 45];
|
|
1042
|
-
let uplinkFound = false;
|
|
1043
|
-
|
|
1044
|
-
const startUplink = (attempt) => {
|
|
1045
|
-
if (uplinkFound) return;
|
|
1046
|
-
|
|
1047
|
-
const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
1048
|
-
|
|
1049
|
-
if (attempt > 0) {
|
|
1050
|
-
console.log(`\x1b[33m[Fleetbo] Uplink reconnection ${attempt}/${MAX_UPLINK_RETRIES - 1}...\x1b[0m`);
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
uplinkProcess = spawn(npxCmd, ['-y', 'cloudflared', 'tunnel', '--url', `http://127.0.0.1:${detectedPort}`, '--http-host-header', `127.0.0.1:${detectedPort}`], { shell: true });
|
|
1054
|
-
|
|
1055
|
-
let uplinkBuffer = "";
|
|
1056
|
-
|
|
1057
|
-
const handleUplinkOutput = (chunk) => {
|
|
1058
|
-
if (uplinkFound) return;
|
|
1059
|
-
|
|
1060
|
-
uplinkBuffer += chunk.toString();
|
|
1061
|
-
|
|
1062
|
-
const match = uplinkBuffer.match(/https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/i);
|
|
1063
|
-
if (match) {
|
|
1064
|
-
uplinkFound = true;
|
|
1065
|
-
setTimeout(() => {
|
|
1066
|
-
syncFirebase(process.env.VITE_FLEETBO_KEY_APP, match[0], process.env.VITE_FLEETBO_TESTER_EMAIL);
|
|
1067
|
-
}, 1500);
|
|
1068
|
-
}
|
|
1069
|
-
};
|
|
1070
|
-
|
|
1071
|
-
uplinkProcess.stdout.on('data', handleUplinkOutput);
|
|
1072
|
-
uplinkProcess.stderr.on('data', handleUplinkOutput);
|
|
1073
|
-
|
|
1074
|
-
uplinkProcess.on('error', () => {
|
|
1075
|
-
if (uplinkFound) return;
|
|
1076
|
-
console.error(`\x1b[31m[Fleetbo] Uplink Connection failed to establish.\x1b[0m`);
|
|
1077
|
-
});
|
|
1078
|
-
|
|
1079
|
-
uplinkProcess.on('close', () => {
|
|
1080
|
-
if (uplinkFound) return;
|
|
1081
|
-
const nextAttempt = attempt + 1;
|
|
1082
|
-
if (nextAttempt < MAX_UPLINK_RETRIES) {
|
|
1083
|
-
const delay = RETRY_DELAYS[nextAttempt] || 30;
|
|
1084
|
-
console.log(`\x1b[33m[Fleetbo] Uplink interrupted. Fleetbo OS retrying in ${delay}s... (${nextAttempt}/${MAX_UPLINK_RETRIES - 1})\x1b[0m`);
|
|
1085
|
-
setTimeout(() => startUplink(nextAttempt), delay * 1000);
|
|
1086
|
-
} else {
|
|
1087
|
-
console.error(`\x1b[31m[Fleetbo] Secure Uplink could not be established.\x1b[0m`);
|
|
1088
|
-
}
|
|
1089
|
-
});
|
|
1090
|
-
};
|
|
1091
|
-
|
|
1092
|
-
startUplink(0);
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
});
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
runDevEnvironment();
|
|
1099
|
-
}
|
|
36
|
+
process.exit(0);
|