offgrid-ai 0.3.4 → 0.3.5

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": "offgrid-ai",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Privacy-first CLI for running local LLMs — discover, configure, run, benchmark",
5
5
  "author": "Eeshan Srivastava (https://eeshans.com)",
6
6
  "type": "module",
@@ -29,10 +29,12 @@
29
29
  "scripts": {
30
30
  "start": "node bin/offgrid-ai.mjs",
31
31
  "test": "node --test test/*.mjs",
32
+ "lint": "eslint src/*.mjs bin/*.mjs",
32
33
  "check:privacy": "node scripts/privacy-gate.mjs",
33
34
  "release:check": "bash scripts/release-check.sh",
34
35
  "release:check:fast": "bash scripts/release-check.sh --skip-install --skip-manual",
35
- "prepack": "npm run check:privacy"
36
+ "prepack": "npm run check:privacy",
37
+ "pretest": "npm run lint"
36
38
  },
37
39
  "dependencies": {
38
40
  "@clack/prompts": "^1.4.0",
@@ -47,5 +49,10 @@
47
49
  "llm",
48
50
  "ai"
49
51
  ],
50
- "license": "MIT"
51
- }
52
+ "license": "MIT",
53
+ "devDependencies": {
54
+ "@eslint/js": "^10.0.1",
55
+ "eslint": "^10.4.1",
56
+ "globals": "^17.6.0"
57
+ }
58
+ }
package/src/cli.mjs CHANGED
@@ -1,6 +1,5 @@
1
- import { homedir, totalmem } from "node:os";
1
+ import { totalmem } from "node:os";
2
2
  import { existsSync, statSync, rmSync } from "node:fs";
3
- import { join } from "node:path";
4
3
  import { ensureDirs, findLlamaServer, hasHomebrew, DATA_DIR } from "./config.mjs";
5
4
  import { scanGgufModels } from "./scan.mjs";
6
5
  import { createProfileFromModel, normalizeProfile } from "./profiles.mjs";
@@ -145,9 +144,8 @@ export async function mainFlow() {
145
144
  console.log(pc.bold("\nSaved profiles"));
146
145
  for (const profile of profiles) {
147
146
  const backend = backendFor(profile.backend);
148
- const running = await isProfileRunning(profile);
149
- const idx = items.length;
150
147
  const colorMap = { "llama-cpp": pc.yellow, "llama-cpp-mtp": pc.blue, "ollama": pc.magenta, "omlx": pc.cyan };
148
+ const running = await isProfileRunning(profile);
151
149
  const c = colorMap[profile.backend] ?? pc.magenta;
152
150
  console.log(` ${running ? pc.green("●") : pc.dim("○")} ${pc.bold(profile.label)} ${c(`[${backend.label}]`)} · ${pc.cyan(profile.modelAlias)}`);
153
151
  }
@@ -294,13 +292,22 @@ async function runProfile(profile, options = {}) {
294
292
  console.log(pc.dim("Use --reuse-existing to reuse this server."));
295
293
  } else if (!ready) {
296
294
  console.log(pc.dim(`Starting ${backend.label} for ${profile.label}...`));
297
- const state = await startServer(profile);
298
- const tail = state?.rawLogPath ? tailFriendly(state.rawLogPath, state.friendlyLogPath) : { stop() {} };
295
+ let state;
299
296
  try {
300
- await waitForReady(profile, state?.pid, state?.rawLogPath);
301
- console.log(pc.green(`[ready] ${profile.baseUrl}/models`));
302
- } finally {
303
- tail.stop();
297
+ state = await startServer(profile);
298
+ const tail = state?.rawLogPath ? tailFriendly(state.rawLogPath, state.friendlyLogPath) : { stop() {} };
299
+ try {
300
+ await waitForReady(profile, state?.pid, state?.rawLogPath);
301
+ console.log(pc.green(`[ready] ${profile.baseUrl}/models`));
302
+ } finally {
303
+ tail.stop();
304
+ }
305
+ } catch (err) {
306
+ // Clean up orphaned server process if startup failed
307
+ if (state?.pid) {
308
+ try { await stopProfile(profile); } catch { /* best effort */ }
309
+ }
310
+ throw err;
304
311
  }
305
312
  }
306
313
  }
@@ -420,7 +427,7 @@ async function removeProfileInteractive(id) {
420
427
 
421
428
  // ── Benchmark (stub) ────────────────────────────────────────────────────────
422
429
 
423
- async function benchmarkFlow(prompt, profiles) {
430
+ async function benchmarkFlow() {
424
431
  console.log(pc.yellow("Benchmark support coming soon."));
425
432
  console.log(pc.dim("This will require the local-llm-visual-benchmark repo."));
426
433
  console.log(pc.dim("For now, start a model with offgrid-ai and run benchmarks manually."));
@@ -598,7 +605,7 @@ async function onboardFlow() {
598
605
  break;
599
606
  }
600
607
  }
601
- } catch (err) {
608
+ } catch {
602
609
  console.log(pc.red(`✗ Homebrew installation failed.`));
603
610
  console.log(pc.dim("Install it manually from https://brew.sh, then run offgrid-ai again."));
604
611
  return;
@@ -775,8 +782,11 @@ async function onboardFlow() {
775
782
  // ── Uninstall ───────────────────────────────────────────────────────────────
776
783
 
777
784
  async function uninstallCommand(argv) {
778
- if (!process.stdin.isTTY) {
779
- // Non-interactive: remove everything
785
+ const { options } = parseOptions(argv);
786
+ const force = options.force || options.f;
787
+
788
+ if (!process.stdin.isTTY || force) {
789
+ // Non-interactive / forced: remove everything
780
790
  await removeDataDir();
781
791
  await removeSelf();
782
792
  return;
package/src/estimate.mjs CHANGED
@@ -1,6 +1,5 @@
1
1
  import { existsSync, statSync } from "node:fs";
2
2
  import { readGgufMetadata } from "./gguf.mjs";
3
- import pc from "picocolors";
4
3
 
5
4
  export function estimateMemory(modelPath, mmprojPath, draftModelPath, flags) {
6
5
  const modelBytes = statSync(modelPath).size;
package/src/process.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { execFile, spawn } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
- import { existsSync, openSync } from "node:fs";
3
+ import { openSync } from "node:fs";
4
4
  import { readFile, writeFile } from "node:fs/promises";
5
5
  import { join } from "node:path";
6
6
  import { LOG_DIR } from "./config.mjs";
package/src/profiles.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { existsSync, statSync } from "node:fs";
1
+ import { existsSync } from "node:fs";
2
2
  import { mkdir, readdir, rm, unlink, writeFile, readFile } from "node:fs/promises";
3
- import { dirname, join } from "node:path";
3
+ import { join } from "node:path";
4
4
  import { PROFILE_DIR, RUN_DIR, LOG_DIR } from "./config.mjs";
5
5
  import { backendFor } from "./backends.mjs";
6
6
  import { computeFlags } from "./autodetect.mjs";
@@ -113,7 +113,7 @@ export async function deleteProfile(id, options = {}) {
113
113
 
114
114
  // ── Normalize / auto-detect ────────────────────────────────────────────────
115
115
 
116
- export function normalizeProfile(profile, modelPath, mmprojPath) {
116
+ export function normalizeProfile(profile) {
117
117
  const backend = backendFor(profile.backend);
118
118
  const flags = {
119
119
  host: "127.0.0.1",
@@ -152,7 +152,7 @@ export async function createProfileFromModel(model, backendId = "llama-cpp") {
152
152
  preset: null, // no presets — auto-detected
153
153
  flags,
154
154
  commandArgv: argv,
155
- }, model.path, model.mmprojPath);
155
+ });
156
156
  }
157
157
 
158
158
  // ── State files (for running servers) ──────────────────────────────────────