offgrid-ai 0.2.7 → 0.2.8

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 +22 -112
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "offgrid-ai",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
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,5 +1,5 @@
1
1
  import { homedir, totalmem } from "node:os";
2
- import { existsSync, statSync, rmSync, readFileSync, appendFileSync, mkdirSync, copyFileSync } from "node:fs";
2
+ import { existsSync, statSync, rmSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { ensureDirs, findLlamaServer, hasHomebrew, DATA_DIR } from "./config.mjs";
5
5
  import { scanGgufModels } from "./scan.mjs";
@@ -622,88 +622,29 @@ async function onboardFlow() {
622
622
  { value: "skip", label: "Skip for now", hint: "I'll set up models myself" },
623
623
  ], "lmstudio");
624
624
 
625
- const lmsAppBundle = "/Applications/LM Studio.app/Contents/Resources/app/.webpack/lms";
626
- const lmsBin = join(homedir(), ".lmstudio", "bin");
627
-
628
- const ensureLmsOnPath = () => {
629
- const lmsDest = join(lmsBin, "lms");
630
- // Bootstrap lms from app bundle if not yet in ~/.lmstudio/bin
631
- if (!existsSync(lmsDest) && existsSync(lmsAppBundle)) {
632
- mkdirSync(lmsBin, { recursive: true });
633
- copyFileSync(lmsAppBundle, lmsDest);
634
- }
635
- if (!existsSync(lmsDest)) {
636
- console.log(pc.yellow(" Note: lms CLI will be available after opening LM Studio once."));
637
- return false;
638
- }
639
- if (process.env.PATH.split(":").includes(lmsBin)) return true;
640
- process.env.PATH = `${lmsBin}:${process.env.PATH}`;
641
- const profileFiles = [join(homedir(), ".zshrc"), join(homedir(), ".bash_profile")];
642
- const line = `export PATH="$PATH:$HOME/.lmstudio/bin"`;
643
- for (const f of profileFiles) {
644
- if (!existsSync(f)) continue;
645
- const content = readFileSync(f, "utf8");
646
- if (content.includes(".lmstudio/bin")) continue;
647
- appendFileSync(f, `\n${line}\n`);
648
- }
649
- return true;
650
- };
625
+ const model = recommendedModel();
651
626
 
652
627
  if (backendChoice === "lmstudio") {
653
- const model = recommendedModel();
654
628
  console.log(pc.cyan("Installing LM Studio via Homebrew..."));
655
629
  try {
656
630
  await run("brew", ["install", "--cask", "lm-studio"], "LM Studio");
657
- const lmsReady = ensureLmsOnPath();
658
631
  console.log(pc.green("✓ LM Studio installed"));
659
- if (lmsReady) {
660
- const download = await prompt.yesNo(`Download a model now? (${model.label})`, true);
661
- if (download) {
662
- console.log(pc.cyan(`Downloading ${model.lms} via lms...`));
663
- try {
664
- await run("lms", ["get", model.lms, "-y"], `lms get ${model.lms}`);
665
- console.log(pc.green("✓ Model downloaded."));
666
- } catch {
667
- console.log(pc.yellow(`Model download failed. Run manually: lms get ${model.lms}`));
668
- }
669
- } else {
670
- console.log(pc.yellow("Download a model later:"));
671
- console.log(pc.bold(` lms get ${model.lms}`));
672
- }
673
- } else {
674
- console.log(pc.yellow("\nOpen LM Studio once to finish setup, then download a model:"));
675
- console.log(pc.bold(` lms get ${model.lms}`));
676
- }
632
+ console.log(pc.yellow("\nOpen LM Studio, set up the app, and download a model."));
633
+ console.log(pc.dim(`Recommended for your machine: ${model.label}`));
634
+ console.log(pc.bold(` lms get ${model.lms}`));
677
635
  console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
678
636
  } catch {
679
637
  console.log(pc.red("✗ LM Studio installation failed."));
680
638
  console.log(pc.dim("Download it manually from https://lmstudio.ai"));
681
639
  }
682
640
  } else if (backendChoice === "ollama") {
683
- const model = recommendedModel();
684
641
  console.log(pc.cyan("Installing Ollama via Homebrew..."));
685
642
  try {
686
643
  await run("brew", ["install", "ollama"], "Ollama");
687
644
  console.log(pc.green("✓ Ollama installed"));
688
- console.log(pc.cyan("\nStarting Ollama..."));
689
- try {
690
- await run("ollama", ["serve"], "Ollama serve");
691
- await new Promise((resolve) => setTimeout(resolve, 2000));
692
- } catch { /* may already be running */ }
693
- console.log(pc.green("Ollama is running."));
694
- const download = await prompt.yesNo(`Pull a model now? (${model.label})`, true);
695
- if (download) {
696
- console.log(pc.cyan(`Pulling ${model.ollama} via Ollama...`));
697
- try {
698
- await run("ollama", ["pull", model.ollama], `ollama pull ${model.ollama}`);
699
- console.log(pc.green("✓ Model pulled."));
700
- } catch {
701
- console.log(pc.yellow(`Model pull failed. Run manually: ollama pull ${model.ollama}`));
702
- }
703
- } else {
704
- console.log(pc.yellow("Pull a model later:"));
705
- console.log(pc.bold(` ollama pull ${model.ollama}`));
706
- }
645
+ console.log(pc.yellow("\nStart Ollama and pull a model:"));
646
+ console.log(pc.bold(` ollama serve \u0026 ollama pull ${model.ollama}`));
647
+ console.log(pc.dim(`Recommended for your machine: ${model.label}`));
707
648
  console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
708
649
  } catch {
709
650
  console.log(pc.red("✗ Ollama installation failed."));
@@ -715,33 +656,29 @@ async function onboardFlow() {
715
656
  await run("brew", ["tap", "jundot/omlx", "https://github.com/jundot/omlx"], "oMLX tap");
716
657
  await run("brew", ["install", "omlx"], "oMLX");
717
658
  console.log(pc.green("✓ oMLX installed"));
718
- console.log(pc.yellow("\nStart oMLX server:"));
659
+ console.log(pc.yellow("\nStart oMLX and download a model:"));
719
660
  console.log(pc.bold(" omlx start"));
661
+ console.log(pc.dim(`Recommended for your machine: ${model.label}`));
720
662
  console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
721
- } catch (err) {
722
- console.log(pc.red(`✗ oMLX installation failed.`));
663
+ } catch {
664
+ console.log(pc.red("✗ oMLX installation failed."));
723
665
  console.log(pc.dim("Install manually: brew tap jundot/omlx && brew install omlx"));
724
666
  }
725
667
  } else if (backendChoice === "all") {
726
- const model = recommendedModel();
727
668
  let installed = [];
728
669
  // LM Studio
729
670
  console.log(pc.cyan("Installing LM Studio via Homebrew..."));
730
- let lmsReady = false;
731
671
  try {
732
672
  await run("brew", ["install", "--cask", "lm-studio"], "LM Studio");
733
- lmsReady = ensureLmsOnPath();
734
673
  installed.push("LM Studio");
735
674
  } catch {
736
675
  console.log(pc.yellow("✗ LM Studio installation failed. Download from https://lmstudio.ai"));
737
676
  }
738
677
  // Ollama
739
678
  console.log(pc.cyan("Installing Ollama via Homebrew..."));
740
- let ollamaInstalled = false;
741
679
  try {
742
680
  await run("brew", ["install", "ollama"], "Ollama");
743
681
  installed.push("Ollama");
744
- ollamaInstalled = true;
745
682
  } catch {
746
683
  console.log(pc.yellow("✗ Ollama installation failed. Install manually from https://ollama.com"));
747
684
  }
@@ -754,45 +691,18 @@ async function onboardFlow() {
754
691
  } catch {
755
692
  console.log(pc.yellow("✗ oMLX installation failed. Install manually: brew tap jundot/omlx && brew install omlx"));
756
693
  }
757
- // Auto-download model with best available backend
758
694
  if (installed.length > 0) {
759
695
  console.log(pc.green(`\n✓ Installed: ${installed.join(", ")}`));
760
- if (lmsReady) {
761
- const download = await prompt.yesNo(`Download a model now? (${model.label})`, true);
762
- if (download) {
763
- console.log(pc.cyan(`Downloading ${model.lms} via lms...`));
764
- try {
765
- await run("lms", ["get", model.lms, "-y"], `lms get ${model.lms}`);
766
- console.log(pc.green("✓ Model downloaded."));
767
- } catch {
768
- console.log(pc.yellow(`Model download failed. Run manually: lms get ${model.lms}`));
769
- }
770
- }
771
- } else if (ollamaInstalled) {
772
- // Start Ollama and offer to pull
773
- console.log(pc.cyan("Starting Ollama..."));
774
- try { await run("ollama", ["serve"], "Ollama serve"); await new Promise(r => setTimeout(r, 2000)); } catch { /* may already be running */ }
775
- const download = await prompt.yesNo(`Pull a model now? (${model.label})`, true);
776
- if (download) {
777
- console.log(pc.cyan(`Pulling ${model.ollama} via Ollama...`));
778
- try {
779
- await run("ollama", ["pull", model.ollama], `ollama pull ${model.ollama}`);
780
- console.log(pc.green("✓ Model pulled."));
781
- } catch {
782
- console.log(pc.yellow(`Model pull failed. Run manually: ollama pull ${model.ollama}`));
783
- }
784
- }
785
- } else {
786
- console.log(pc.yellow(`Recommended model for your machine (${model.label}):`));
787
- if (installed.some(i => i.includes("LM Studio"))) {
788
- console.log(pc.bold(lmsReady ? ` lms get ${model.lms}` : ` Open LM Studio once, then: lms get ${model.lms}`));
789
- }
790
- if (installed.includes("Ollama")) {
791
- console.log(pc.bold(` ollama pull ${model.ollama}`));
792
- }
793
- if (installed.includes("oMLX")) {
794
- console.log(pc.bold(" omlx start"));
795
- }
696
+ console.log(pc.yellow("\nDownload a model to get started:"));
697
+ 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"));
796
706
  }
797
707
  console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
798
708
  }