letfixit 0.1.1 → 0.1.2

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/package.json +12 -2
  2. package/server/index.js +75 -24
package/package.json CHANGED
@@ -1,8 +1,18 @@
1
1
  {
2
2
  "name": "letfixit",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "LetFixIt — AI-based realtime performance analysis & suggestions for Flutter, Android & iOS, powered by the local Ultron n0-beta model.",
5
- "keywords": ["flutter", "android", "ios", "performance", "profiler", "devtools", "observability", "llm", "local-ai"],
5
+ "keywords": [
6
+ "flutter",
7
+ "android",
8
+ "ios",
9
+ "performance",
10
+ "profiler",
11
+ "devtools",
12
+ "observability",
13
+ "llm",
14
+ "local-ai"
15
+ ],
6
16
  "license": "MIT",
7
17
  "author": "JahidDev24",
8
18
  "main": "server/index.js",
package/server/index.js CHANGED
@@ -29,7 +29,7 @@ const ENABLE_LOCAL_AI = process.env.ENABLE_LOCAL_AI !== 'false';
29
29
  const MODEL_PATH = model.MODEL_PATH;
30
30
 
31
31
  // ── Terminal styling + branded startup ─────────────────────────────────────────
32
- const C = { reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', mag: '\x1b[95m', grn: '\x1b[92m', yel: '\x1b[93m', cyan: '\x1b[96m' };
32
+ const C = { reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', mag: '\x1b[95m', grn: '\x1b[92m', yel: '\x1b[93m', red: '\x1b[91m', cyan: '\x1b[96m' };
33
33
  const col = (x, s) => `${x}${s}${C.reset}`;
34
34
  const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
35
35
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -42,10 +42,11 @@ function bannerBox(lines) {
42
42
  }
43
43
 
44
44
  function printWelcome() {
45
+ const v = require('../package.json').version;
45
46
  console.log('');
46
47
  bannerBox([
47
48
  '',
48
- col(C.bold + C.mag, '✦ P A R I T Y F I X') + col(C.dim, ' v0.1.0'),
49
+ col(C.bold + C.mag, '✦ P A R I T Y F I X') + col(C.dim, ` v${v}`),
49
50
  col(C.reset, 'AI-based realtime performance analysis & suggestions'),
50
51
  col(C.dim, 'Flutter · Android · iOS powered by Ultron n0-beta'),
51
52
  '',
@@ -53,32 +54,40 @@ function printWelcome() {
53
54
  console.log('');
54
55
  }
55
56
 
56
- // "Ultron n0-beta initializing…" starts the local model and waits briefly for
57
- // it to answer, without blocking the whole boot on weight loading.
57
+ // Boot the local model. On first run (model not installed) this RUNS the setup
58
+ // download + key prompt + decrypt + merge then starts the model. Returns
59
+ // true if AI is available (local model ready, or a cloud provider configured).
58
60
  async function initUltron() {
59
61
  if (!ENABLE_LOCAL_AI) {
60
62
  console.log(' ' + col(C.dim, 'Ultron n0-beta is disabled (ENABLE_LOCAL_AI=false) — using cloud fallback if configured.\n'));
61
- return;
63
+ return aiStatus().anyAvailable;
62
64
  }
65
+
66
+ // First-time setup: install the model (prompts for the decryption key).
63
67
  if (!model.isReady()) {
64
- console.log(' ' + col(C.yel, '⚠ Ultron n0-beta not installed.') + ' Run ' + col(C.cyan, 'npm run setup') + ' to download it.');
65
- const s = aiStatus();
66
- console.log(' ' + col(C.dim, s.anyAvailable ? 'A cloud fallback key is set — analysis will use it for now.\n' : 'No AI provider yet — findings will show without suggestions until setup.\n'));
67
- return;
68
+ console.log(' ' + col(C.mag, ' First-time setup') + col(C.dim, ` installing ${model.MODEL_NAME}. This runs once.\n`));
69
+ try {
70
+ await require('../scripts/setup-model').setup();
71
+ } catch (e) {
72
+ const msg = String(e.message || e);
73
+ if (msg.startsWith('WRONG_KEY:')) console.log('\n ' + col(C.red, '❌ ' + msg.replace('WRONG_KEY: ', '')));
74
+ else console.log('\n ' + col(C.red, '❌ Setup failed: ' + msg));
75
+ }
76
+ console.log('');
68
77
  }
69
78
 
70
- process.stdout.write(' ' + col(C.mag, '✦ Ultron n0-beta') + col(C.dim, ' initializing'));
71
- startLlamafile();
72
- // Poll health for a few seconds; don't hang the boot if weights are still loading.
73
- let ok = false;
74
- for (let i = 0; i < 8 && !ok; i++) { process.stdout.write(col(C.dim, '.')); ok = await isLlamaHealthy(); if (!ok) await sleep(500); }
75
- process.stdout.write('\n');
76
- if (ok) {
77
- console.log(' ' + col(C.grn, ' ready') + col(C.dim, `${model.MODEL_NAME} running locally on :${LOCAL_AI_PORT} (${model.sizeGB()} GB, private)`));
78
- } else {
79
- console.log(' ' + col(C.yel, '… still loading') + col(C.dim, ' — weights load in the background (first start can take ~20s).'));
79
+ if (model.isReady()) {
80
+ process.stdout.write(' ' + col(C.mag, '✦ Ultron n0-beta') + col(C.dim, ' initializing'));
81
+ startLlamafile();
82
+ let ok = false;
83
+ for (let i = 0; i < 8 && !ok; i++) { process.stdout.write(col(C.dim, '.')); ok = await isLlamaHealthy(); if (!ok) await sleep(500); }
84
+ process.stdout.write('\n');
85
+ if (ok) console.log(' ' + col(C.grn, '✓ ready') + col(C.dim, ` — ${model.MODEL_NAME} running locally on :${LOCAL_AI_PORT} (${model.sizeGB()} GB, private)`));
86
+ else console.log(' ' + col(C.yel, ' still loading') + col(C.dim, 'weights load in the background (first start can take ~20s).'));
87
+ console.log(' ' + col(C.dim, model.DISCLAIMER.split(' — ')[0] + ' — treat suggestions as hints.\n'));
80
88
  }
81
- console.log(' ' + col(C.dim, model.DISCLAIMER.split(' — ')[0] + ' — treat suggestions as hints.\n'));
89
+
90
+ return aiStatus().anyAvailable; // local ready OR cloud key present
82
91
  }
83
92
 
84
93
  // ── Interactive startup ───────────────────────────────────────────────────────
@@ -569,9 +578,16 @@ server.on('error', (err) => {
569
578
  // ── Startup ───────────────────────────────────────────────────────────────────
570
579
  async function main() {
571
580
  printWelcome();
572
- // "Ultron n0-beta initializing…" warms the local model up front (idempotent)
573
- // so it's ready by the time analysis needs it, and owns the AI startup output.
574
- await initUltron();
581
+ // First-run: install + start the model. Returns true only if AI is available.
582
+ const aiReady = await initUltron();
583
+
584
+ // AI is required to analyze — don't show the menu without it.
585
+ if (!aiReady) {
586
+ console.log(' ' + col(C.red, '⚠ AI is required to run analysis, and it isn\'t set up yet.'));
587
+ console.log(' ' + col(C.dim, `Re-run ${col(C.cyan, 'letfixit')} and paste your ${model.MODEL_NAME} key when prompted,`));
588
+ console.log(' ' + col(C.dim, `or add a cloud key (GROQ/GEMINI/CLAUDE) to .env, then start again.\n`));
589
+ process.exit(0);
590
+ }
575
591
 
576
592
  const setup = await interactiveSetup();
577
593
 
@@ -616,4 +632,39 @@ function shutdown() {
616
632
  process.on('SIGINT', shutdown);
617
633
  process.on('SIGTERM', shutdown);
618
634
 
619
- main();
635
+ // ── CLI dispatch ────────────────────────────────────────────────────────────
636
+ function printHelp() {
637
+ const v = require('../package.json').version;
638
+ console.log(`
639
+ ${col(C.bold + C.mag, 'LetFixIt')} v${v} ${col(C.dim, '— AI-based realtime performance analysis for Flutter, Android & iOS')}
640
+
641
+ ${col(C.bold, 'Usage')}
642
+ ${col(C.cyan, 'letfixit')} Launch the analyzer (first run installs ${model.MODEL_NAME})
643
+ ${col(C.cyan, 'letfixit setup')} Download & set up the model (prompts for the decryption key)
644
+ ${col(C.cyan, 'letfixit setup --force')} Re-download / reinstall the model
645
+ ${col(C.cyan, 'letfixit version')} Print the installed version (or --version / -v)
646
+ ${col(C.cyan, 'letfixit --help')} Show this help
647
+
648
+ ${col(C.bold, 'Environment')}
649
+ ${col(C.dim, 'PARITYFIX_MODEL_KEY')} decryption key for non-interactive / CI installs
650
+ ${col(C.dim, 'DASHBOARD_PORT')} dashboard port (default 8081)
651
+ ${col(C.dim, 'LOCAL_AI_PORT')} local model port (default 8080)
652
+ ${col(C.dim, 'ENABLE_LOCAL_AI')} set to "false" to use cloud fallback only
653
+ `);
654
+ }
655
+
656
+ const cmd = process.argv[2];
657
+ if (cmd === '--version' || cmd === '-v') {
658
+ console.log(require('../package.json').version); // bare number (scripts)
659
+ } else if (cmd === 'version') {
660
+ console.log(`letfixit v${require('../package.json').version}`); // friendly line
661
+ } else if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
662
+ printHelp();
663
+ } else if (cmd === 'setup') {
664
+ // Explicit setup: run the installer directly and exit (like `npm run setup`).
665
+ require('../scripts/setup-model').setup()
666
+ .then(() => process.exit(0))
667
+ .catch((e) => { console.error(' ❌ setup failed:', e.message); process.exit(1); });
668
+ } else {
669
+ main(); // default: launch the analyzer
670
+ }