letfixit 0.1.2 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "letfixit",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "LetFixIt — AI-based realtime performance analysis & suggestions for Flutter, Android & iOS, powered by the local Ultron n0-beta model.",
5
5
  "keywords": [
6
6
  "flutter",
@@ -59,7 +59,10 @@
59
59
  "setup:force": "node scripts/setup-model.js --force",
60
60
  "encrypt-model": "node scripts/encrypt-model.js",
61
61
  "fill-manifest": "node scripts/fill-manifest.js",
62
+ "verify": "node scripts/verify-chunk.js",
63
+ "clean": "node scripts/clean-model.js",
62
64
  "postinstall": "node scripts/setup-model.js",
65
+ "preuninstall": "node scripts/clean-model.js",
63
66
  "prepublishOnly": "npm run build"
64
67
  },
65
68
  "dependencies": {
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ // ── Cleanup ───────────────────────────────────────────────────────────────────
3
+ // Removes the downloaded model and any temp/partial files so nothing is left on
4
+ // disk. Runs as a `preuninstall` hook (best-effort — npm's uninstall hooks are
5
+ // unreliable across versions) and is also exposed as `letfixit clean` /
6
+ // `npm run clean` for a guaranteed manual wipe.
7
+ const fs = require('fs');
8
+ const mc = require('../server/model_config');
9
+
10
+ function clean() {
11
+ let removed = false;
12
+ try {
13
+ if (fs.existsSync(mc.MODEL_DIR)) {
14
+ const gb = mc.sizeGB();
15
+ fs.rmSync(mc.MODEL_DIR, { recursive: true, force: true }); // model + .tmp partials
16
+ console.log(` ✓ Removed ${mc.MODEL_NAME}${gb ? ` (${gb} GB)` : ''} — ${mc.MODEL_DIR}`);
17
+ removed = true;
18
+ }
19
+ } catch (e) {
20
+ // Never fail an uninstall on cleanup trouble.
21
+ console.log(` ⚠ cleanup note: ${e.message}`);
22
+ }
23
+ if (!removed) console.log(' ✓ No model files to remove.');
24
+ }
25
+
26
+ if (require.main === module) clean();
27
+ module.exports = { clean };
@@ -142,7 +142,7 @@ async function fetchChunk(ch, encPath, baseLabel) {
142
142
  async function resolveKey() {
143
143
  if (process.env.PARITYFIX_MODEL_KEY) return process.env.PARITYFIX_MODEL_KEY.trim();
144
144
  if (!process.stdin.isTTY) return null; // can't prompt (postinstall in CI, piped stdin)
145
- const k = await promptHidden(` 🔑 Enter the ${mc.MODEL_NAME} decryption key: `);
145
+ const k = await promptHidden(' 🔑 Enter acceptance key: ');
146
146
  return k || null;
147
147
  }
148
148
 
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ // ── Quick verify ──────────────────────────────────────────────────────────────
3
+ // Downloads ONLY the first chunk from the manifest and decrypts it, to confirm
4
+ // the acceptance key + hosted chunks + manifest all line up — without pulling the
5
+ // full ~2.6 GB model.
6
+ //
7
+ // PARITYFIX_MODEL_KEY=<key> npm run verify (or: node scripts/verify-chunk.js)
8
+ // node scripts/verify-chunk.js <key> (pass the key as an argument)
9
+ // If no key is given it prompts (hidden input).
10
+ require('./load-env')();
11
+ const fs = require('fs');
12
+ const os = require('os');
13
+ const path = require('path');
14
+ const readline = require('readline');
15
+ const mc = require('../server/model_config');
16
+ const { deriveKey } = require('./model_crypto');
17
+ const sm = require('./setup-model'); // fetchChunk, decryptChunk, sha256File
18
+
19
+ const C = { reset: '\x1b[0m', mag: '\x1b[95m', grn: '\x1b[92m', red: '\x1b[91m', dim: '\x1b[2m', cyan: '\x1b[96m' };
20
+ const c = (x, s) => `${x}${s}${C.reset}`;
21
+
22
+ function promptHidden(q) {
23
+ return new Promise((resolve) => {
24
+ process.stdout.write(c(C.mag, q));
25
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
26
+ rl._writeToOutput = () => {};
27
+ rl.question('', (a) => { rl.close(); process.stdout.write('\n'); resolve(a.trim()); });
28
+ });
29
+ }
30
+
31
+ async function run(keyArg) {
32
+ const manifest = mc.loadManifest();
33
+ if (!manifest || !manifest.chunks || !manifest.chunks.length) {
34
+ console.error(c(C.red, ' ❌ No manifest found (config/model.manifest.json).'));
35
+ process.exit(1);
36
+ }
37
+
38
+ let key = process.env.PARITYFIX_MODEL_KEY || keyArg;
39
+ if (!key && process.stdin.isTTY) key = await promptHidden(' 🔑 Enter acceptance key: ');
40
+ if (!key) {
41
+ console.error(c(C.red, ' ❌ No key. Set PARITYFIX_MODEL_KEY, pass it as an argument, or run in a terminal.'));
42
+ process.exit(1);
43
+ }
44
+
45
+ const ch = [...manifest.chunks].sort((a, b) => a.index - b.index)[0];
46
+ const tmp = path.join(os.tmpdir(), 'letfixit-verify');
47
+ fs.mkdirSync(tmp, { recursive: true });
48
+ const enc = path.join(tmp, 'chunk0.enc');
49
+ const dec = path.join(tmp, 'chunk0.dec');
50
+
51
+ console.log(c(C.dim, ` Verifying chunk 0 of ${manifest.chunks.length} — ${ch.url}\n`));
52
+ let ok = false;
53
+ try {
54
+ await sm.fetchChunk(ch, enc, c(C.cyan, 'chunk 0')); // downloads + verifies sha256
55
+ await sm.decryptChunk(enc, dec, deriveKey(key.trim(), manifest.salt), ch.iv, ch.authTag);
56
+ const size = fs.statSync(dec).size;
57
+ ok = size === (ch.bytes || size);
58
+ console.log('\n ' + c(C.grn, '✅ VERIFIED') + c(C.dim, ` — download + checksum + decrypt all passed (${(size / 1048576).toFixed(1)} MB plaintext).`));
59
+ console.log(' ' + c(C.dim, 'Your acceptance key and hosted chunks are correct; the full setup will work.'));
60
+ } catch (e) {
61
+ if (/authenticate|unable to authenticate|bad decrypt/i.test(e.message)) {
62
+ console.log('\n ' + c(C.red, '❌ Decryption failed — the acceptance key is incorrect for these chunks.'));
63
+ } else {
64
+ console.log('\n ' + c(C.red, `❌ Verify failed: ${e.message}`));
65
+ }
66
+ } finally {
67
+ fs.rmSync(tmp, { recursive: true, force: true });
68
+ }
69
+ process.exit(ok ? 0 : 1);
70
+ }
71
+
72
+ if (require.main === module) run(process.argv[2]); // node scripts/verify-chunk.js [key]
73
+ module.exports = { run };
package/server/index.js CHANGED
@@ -642,6 +642,8 @@ function printHelp() {
642
642
  ${col(C.cyan, 'letfixit')} Launch the analyzer (first run installs ${model.MODEL_NAME})
643
643
  ${col(C.cyan, 'letfixit setup')} Download & set up the model (prompts for the decryption key)
644
644
  ${col(C.cyan, 'letfixit setup --force')} Re-download / reinstall the model
645
+ ${col(C.cyan, 'letfixit verify')} Quick test: download + decrypt only chunk 0
646
+ ${col(C.cyan, 'letfixit clean')} Remove the downloaded model (free disk / no footprint)
645
647
  ${col(C.cyan, 'letfixit version')} Print the installed version (or --version / -v)
646
648
  ${col(C.cyan, 'letfixit --help')} Show this help
647
649
 
@@ -665,6 +667,12 @@ if (cmd === '--version' || cmd === '-v') {
665
667
  require('../scripts/setup-model').setup()
666
668
  .then(() => process.exit(0))
667
669
  .catch((e) => { console.error(' ❌ setup failed:', e.message); process.exit(1); });
670
+ } else if (cmd === 'verify') {
671
+ // Quick test: download + decrypt only the first chunk to validate the key/host.
672
+ require('../scripts/verify-chunk').run(process.argv[3]);
673
+ } else if (cmd === 'clean') {
674
+ // Remove the downloaded model + temp files (no footprint).
675
+ require('../scripts/clean-model').clean();
668
676
  } else {
669
677
  main(); // default: launch the analyzer
670
678
  }