offgrid-ai 0.2.8 → 0.3.0

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.2.8",
3
+ "version": "0.3.0",
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",
package/src/cli.mjs CHANGED
@@ -60,14 +60,40 @@ export async function mainFlow() {
60
60
  // Fall through — they can still use managed backends
61
61
  }
62
62
 
63
- // 4. No models found at all (but backends exist)
63
+ // 4. No models found at all (but backends may exist)
64
64
  if (!hasAnyModels && profiles.length === 0) {
65
65
  if (!process.stdin.isTTY) {
66
- throw new Error("No models found. Download one in LM Studio or start Ollama, then run offgrid-ai.");
66
+ throw new Error("No models found. Download a model, then run offgrid-ai.");
67
67
  }
68
68
  console.log(pc.yellow("No models found."));
69
- console.log(pc.dim("Download a model in LM Studio (https://lmstudio.ai), start Ollama, or install oMLX."));
70
- console.log(pc.dim("Then run offgrid-ai again."));
69
+ console.log(pc.dim("You need to download a model to use offgrid-ai.\n"));
70
+ // Detect which backends are installed
71
+ const ollamaInstalled = await hasOllamaInstalled();
72
+ const omlxInstalled = await hasOmlxInstalled();
73
+ const lmStudioInstalled = existsSync("/Applications/LM Studio.app");
74
+ const hasBackends = llamaBinary || ollamaInstalled || omlxInstalled || lmStudioInstalled;
75
+ if (hasBackends) {
76
+ console.log(pc.bold("Backend status:"));
77
+ console.log(` ${lmStudioInstalled ? pc.green("✓") : pc.red("✗")} LM Studio ${lmStudioInstalled ? "— installed" : "— not installed"}`);
78
+ console.log(` ${ollamaInstalled ? pc.green("✓") : pc.red("✗")} Ollama ${ollamaInstalled ? "— installed" : "— not installed"}`);
79
+ console.log(` ${omlxInstalled ? pc.green("✓") : pc.red("✗")} oMLX ${omlxInstalled ? "— installed" : "— not installed"}`);
80
+ console.log(` ${llamaBinary ? pc.green("✓") : pc.red("✗")} llama-server ${llamaBinary ? "— installed" : "— not installed"}`);
81
+ console.log();
82
+ const model = recommendedModel();
83
+ console.log(pc.bold("Next step — download a model:"));
84
+ if (lmStudioInstalled) {
85
+ console.log(" Open LM Studio → browse models → download");
86
+ console.log(pc.dim(` Recommended: ${model.label}`));
87
+ }
88
+ if (ollamaInstalled) {
89
+ console.log(pc.bold(` ollama pull ${model.ollama}`));
90
+ }
91
+ if (omlxInstalled) {
92
+ console.log(pc.bold(" omlx start"));
93
+ }
94
+ } else {
95
+ console.log(pc.dim("Run offgrid-ai to install a backend and download a model."));
96
+ }
71
97
  return;
72
98
  }
73
99
 
@@ -593,7 +619,31 @@ async function onboardFlow() {
593
619
  }
594
620
  console.log(pc.green(`✓ llama-server: ${llamaBinary}`));
595
621
 
596
- // 3. Model backends — at least one is mandatory
622
+ // 3. Pi coding agent
623
+ const piInstalled = await hasPi();
624
+ if (!piInstalled) {
625
+ const install = await prompt.yesNo("Pi coding agent is required to chat with models. Install via npm?", true);
626
+ if (!install) {
627
+ console.log(pc.red("offgrid-ai needs Pi to run models."));
628
+ console.log(pc.dim("Install it manually: npm install -g --ignore-scripts @earendil-works/pi-coding-agent"));
629
+ return;
630
+ }
631
+ console.log(pc.cyan("Installing Pi..."));
632
+ try {
633
+ await run("npm", ["install", "-g", "--ignore-scripts", "@earendil-works/pi-coding-agent"], "Pi");
634
+ } catch {
635
+ console.log(pc.red("✗ Failed to install Pi."));
636
+ console.log(pc.dim("Install it manually: npm install -g --ignore-scripts @earendil-works/pi-coding-agent"));
637
+ return;
638
+ }
639
+ if (!(await hasPi())) {
640
+ console.log(pc.yellow("Pi was installed but not found on PATH. Restart your terminal and run offgrid-ai again."));
641
+ return;
642
+ }
643
+ }
644
+ console.log(pc.green("✓ Pi found"));
645
+
646
+ // 4. Model backends — at least one is mandatory
597
647
  const ggufModels = await scanGgufModels();
598
648
  const managedModels = await scanManagedModels();
599
649
  const totalManaged = managedModels.reduce((sum, m) => sum + m.models.length, 0);
@@ -629,9 +679,8 @@ async function onboardFlow() {
629
679
  try {
630
680
  await run("brew", ["install", "--cask", "lm-studio"], "LM Studio");
631
681
  console.log(pc.green("✓ LM Studio installed"));
632
- console.log(pc.yellow("\nOpen LM Studio, set up the app, and download a model."));
682
+ console.log(pc.yellow("\nOpen LM Studio and download a model to get started."));
633
683
  console.log(pc.dim(`Recommended for your machine: ${model.label}`));
634
- console.log(pc.bold(` lms get ${model.lms}`));
635
684
  console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
636
685
  } catch {
637
686
  console.log(pc.red("✗ LM Studio installation failed."));
@@ -693,18 +742,7 @@ async function onboardFlow() {
693
742
  }
694
743
  if (installed.length > 0) {
695
744
  console.log(pc.green(`\n✓ Installed: ${installed.join(", ")}`));
696
- console.log(pc.yellow("\nDownload a model to get started:"));
697
745
  console.log(pc.dim(`Recommended for your machine (${(totalmem() / (1024 ** 3)).toFixed(0)}GB RAM): ${model.label}`));
698
- if (installed.includes("LM Studio")) {
699
- console.log(pc.bold(` LM Studio → lms get ${model.lms}`));
700
- }
701
- if (installed.includes("Ollama")) {
702
- console.log(pc.bold(` Ollama → ollama pull ${model.ollama}`));
703
- }
704
- if (installed.includes("oMLX")) {
705
- console.log(pc.bold(" oMLX → omlx start"));
706
- }
707
- console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
708
746
  }
709
747
  } else {
710
748
  console.log(pc.dim("Run offgrid-ai again when you've set up a model backend."));
@@ -814,6 +852,26 @@ async function removeSelf() {
814
852
  }
815
853
  }
816
854
 
855
+ // ── Backend install detection (for status display) ────────────────────────
856
+
857
+ async function hasOllamaInstalled() {
858
+ try {
859
+ const { promisify } = await import("node:util");
860
+ const { execFile } = await import("node:child_process");
861
+ await promisify(execFile)("which", ["ollama"]);
862
+ return true;
863
+ } catch { return false; }
864
+ }
865
+
866
+ async function hasOmlxInstalled() {
867
+ try {
868
+ const { promisify } = await import("node:util");
869
+ const { execFile } = await import("node:child_process");
870
+ await promisify(execFile)("which", ["omlx"]);
871
+ return true;
872
+ } catch { return false; }
873
+ }
874
+
817
875
  // ── Helpers ─────────────────────────────────────────────────────────────────
818
876
 
819
877
  async function scanManagedModels() {
package/src/profiles.mjs CHANGED
@@ -3,6 +3,7 @@ import { mkdir, readdir, rm, unlink, writeFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
4
  import { PROFILE_DIR, RUN_DIR, LOG_DIR } from "./config.mjs";
5
5
  import { backendFor } from "./backends.mjs";
6
+ import { computeFlags } from "./autodetect.mjs";
6
7
  import { readJson, writeJson } from "./json.mjs";
7
8
 
8
9
  // ── Path helpers ───────────────────────────────────────────────────────────