offgrid-ai 0.2.6 → 0.2.7

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 +1 -1
  2. package/src/cli.mjs +42 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "offgrid-ai",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
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
@@ -1,4 +1,4 @@
1
- import { homedir } from "node:os";
1
+ import { homedir, totalmem } from "node:os";
2
2
  import { existsSync, statSync, rmSync, readFileSync, appendFileSync, mkdirSync, copyFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { ensureDirs, findLlamaServer, hasHomebrew, DATA_DIR } from "./config.mjs";
@@ -489,6 +489,22 @@ async function runningProfiles() {
489
489
 
490
490
  // ── Onboarding ──────────────────────────────────────────────────────────────
491
491
 
492
+ // ── Model recommendations by RAM ───────────────────────────────────────
493
+ // Tier → { lms: [...], ollama: string, label }
494
+ // lms entries are tried in order (first staff-pick match wins, or @quant forces it)
495
+ const MODEL_TIERS = [
496
+ { maxGB: 8, lms: "google/gemma-4-e2b", ollama: "gemma4:e2b", label: "Gemma 4 E2B (2B effective)" },
497
+ { maxGB: 16, lms: "google/gemma-4-e4b", ollama: "gemma4:e4b", label: "Gemma 4 E4B (4B effective)" },
498
+ { maxGB: 32, lms: "qwen/qwen3.5-9b", ollama: "qwen3.5:9b-q4_K_M", label: "Qwen 3.5 9B" },
499
+ { maxGB: Infinity, lms: "qwen/qwen3.6-35b-a3b", ollama: "qwen3.6:35b-a3b", label: "Qwen 3.6 35B-A3B" },
500
+ ];
501
+
502
+ function recommendedModel() {
503
+ const gb = totalmem() / (1024 ** 3);
504
+ const tier = MODEL_TIERS.find(t => gb <= t.maxGB) || MODEL_TIERS[MODEL_TIERS.length - 1];
505
+ return tier;
506
+ }
507
+
492
508
  async function onboardFlow() {
493
509
  startInteractive("offgrid-ai setup");
494
510
  const prompt = createPrompt();
@@ -634,28 +650,29 @@ async function onboardFlow() {
634
650
  };
635
651
 
636
652
  if (backendChoice === "lmstudio") {
653
+ const model = recommendedModel();
637
654
  console.log(pc.cyan("Installing LM Studio via Homebrew..."));
638
655
  try {
639
656
  await run("brew", ["install", "--cask", "lm-studio"], "LM Studio");
640
657
  const lmsReady = ensureLmsOnPath();
641
658
  console.log(pc.green("✓ LM Studio installed"));
642
659
  if (lmsReady) {
643
- const download = await prompt.yesNo("Download a model now? (qwen3.5-9b)", true);
660
+ const download = await prompt.yesNo(`Download a model now? (${model.label})`, true);
644
661
  if (download) {
645
- console.log(pc.cyan("Downloading qwen/qwen3.5-9b via lms..."));
662
+ console.log(pc.cyan(`Downloading ${model.lms} via lms...`));
646
663
  try {
647
- await run("lms", ["get", "qwen/qwen3.5-9b", "-y"], "lms get qwen/qwen3.5-9b");
664
+ await run("lms", ["get", model.lms, "-y"], `lms get ${model.lms}`);
648
665
  console.log(pc.green("✓ Model downloaded."));
649
666
  } catch {
650
- console.log(pc.yellow("Model download failed. Run manually: lms get qwen/qwen3.5-9b"));
667
+ console.log(pc.yellow(`Model download failed. Run manually: lms get ${model.lms}`));
651
668
  }
652
669
  } else {
653
670
  console.log(pc.yellow("Download a model later:"));
654
- console.log(pc.bold(" lms get qwen/qwen3.5-9b"));
671
+ console.log(pc.bold(` lms get ${model.lms}`));
655
672
  }
656
673
  } else {
657
674
  console.log(pc.yellow("\nOpen LM Studio once to finish setup, then download a model:"));
658
- console.log(pc.bold(" lms get qwen/qwen3.5-9b"));
675
+ console.log(pc.bold(` lms get ${model.lms}`));
659
676
  }
660
677
  console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
661
678
  } catch {
@@ -663,6 +680,7 @@ async function onboardFlow() {
663
680
  console.log(pc.dim("Download it manually from https://lmstudio.ai"));
664
681
  }
665
682
  } else if (backendChoice === "ollama") {
683
+ const model = recommendedModel();
666
684
  console.log(pc.cyan("Installing Ollama via Homebrew..."));
667
685
  try {
668
686
  await run("brew", ["install", "ollama"], "Ollama");
@@ -673,18 +691,18 @@ async function onboardFlow() {
673
691
  await new Promise((resolve) => setTimeout(resolve, 2000));
674
692
  } catch { /* may already be running */ }
675
693
  console.log(pc.green("Ollama is running."));
676
- const download = await prompt.yesNo("Pull a model now? (gemma3:4b)", true);
694
+ const download = await prompt.yesNo(`Pull a model now? (${model.label})`, true);
677
695
  if (download) {
678
- console.log(pc.cyan("Pulling gemma3:4b via Ollama..."));
696
+ console.log(pc.cyan(`Pulling ${model.ollama} via Ollama...`));
679
697
  try {
680
- await run("ollama", ["pull", "gemma3:4b"], "ollama pull gemma3:4b");
698
+ await run("ollama", ["pull", model.ollama], `ollama pull ${model.ollama}`);
681
699
  console.log(pc.green("✓ Model pulled."));
682
700
  } catch {
683
- console.log(pc.yellow("Model pull failed. Run manually: ollama pull gemma3:4b"));
701
+ console.log(pc.yellow(`Model pull failed. Run manually: ollama pull ${model.ollama}`));
684
702
  }
685
703
  } else {
686
704
  console.log(pc.yellow("Pull a model later:"));
687
- console.log(pc.bold(" ollama pull gemma3:4b"));
705
+ console.log(pc.bold(` ollama pull ${model.ollama}`));
688
706
  }
689
707
  console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
690
708
  } catch {
@@ -705,6 +723,7 @@ async function onboardFlow() {
705
723
  console.log(pc.dim("Install manually: brew tap jundot/omlx && brew install omlx"));
706
724
  }
707
725
  } else if (backendChoice === "all") {
726
+ const model = recommendedModel();
708
727
  let installed = [];
709
728
  // LM Studio
710
729
  console.log(pc.cyan("Installing LM Studio via Homebrew..."));
@@ -739,37 +758,37 @@ async function onboardFlow() {
739
758
  if (installed.length > 0) {
740
759
  console.log(pc.green(`\n✓ Installed: ${installed.join(", ")}`));
741
760
  if (lmsReady) {
742
- const download = await prompt.yesNo("Download a model now? (qwen3.5-9b)", true);
761
+ const download = await prompt.yesNo(`Download a model now? (${model.label})`, true);
743
762
  if (download) {
744
- console.log(pc.cyan("Downloading qwen/qwen3.5-9b via lms..."));
763
+ console.log(pc.cyan(`Downloading ${model.lms} via lms...`));
745
764
  try {
746
- await run("lms", ["get", "qwen/qwen3.5-9b", "-y"], "lms get qwen/qwen3.5-9b");
765
+ await run("lms", ["get", model.lms, "-y"], `lms get ${model.lms}`);
747
766
  console.log(pc.green("✓ Model downloaded."));
748
767
  } catch {
749
- console.log(pc.yellow("Model download failed. Run manually: lms get qwen/qwen3.5-9b"));
768
+ console.log(pc.yellow(`Model download failed. Run manually: lms get ${model.lms}`));
750
769
  }
751
770
  }
752
771
  } else if (ollamaInstalled) {
753
772
  // Start Ollama and offer to pull
754
773
  console.log(pc.cyan("Starting Ollama..."));
755
774
  try { await run("ollama", ["serve"], "Ollama serve"); await new Promise(r => setTimeout(r, 2000)); } catch { /* may already be running */ }
756
- const download = await prompt.yesNo("Pull a model now? (gemma3:4b)", true);
775
+ const download = await prompt.yesNo(`Pull a model now? (${model.label})`, true);
757
776
  if (download) {
758
- console.log(pc.cyan("Pulling gemma3:4b via Ollama..."));
777
+ console.log(pc.cyan(`Pulling ${model.ollama} via Ollama...`));
759
778
  try {
760
- await run("ollama", ["pull", "gemma3:4b"], "ollama pull gemma3:4b");
779
+ await run("ollama", ["pull", model.ollama], `ollama pull ${model.ollama}`);
761
780
  console.log(pc.green("✓ Model pulled."));
762
781
  } catch {
763
- console.log(pc.yellow("Model pull failed. Run manually: ollama pull gemma3:4b"));
782
+ console.log(pc.yellow(`Model pull failed. Run manually: ollama pull ${model.ollama}`));
764
783
  }
765
784
  }
766
785
  } else {
767
- console.log(pc.yellow("Next steps download your first model:"));
786
+ console.log(pc.yellow(`Recommended model for your machine (${model.label}):`));
768
787
  if (installed.some(i => i.includes("LM Studio"))) {
769
- console.log(pc.bold(lmsReady ? " lms get qwen/qwen3.5-9b" : " Open LM Studio once, then: lms get qwen/qwen3.5-9b"));
788
+ console.log(pc.bold(lmsReady ? ` lms get ${model.lms}` : ` Open LM Studio once, then: lms get ${model.lms}`));
770
789
  }
771
790
  if (installed.includes("Ollama")) {
772
- console.log(pc.bold(" ollama pull gemma3:4b"));
791
+ console.log(pc.bold(` ollama pull ${model.ollama}`));
773
792
  }
774
793
  if (installed.includes("oMLX")) {
775
794
  console.log(pc.bold(" omlx start"));