offgrid-ai 0.2.6 → 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.
- package/package.json +1 -1
- package/src/cli.mjs +39 -110
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { homedir } from "node:os";
|
|
2
|
-
import { existsSync, statSync, rmSync
|
|
1
|
+
import { homedir, totalmem } from "node:os";
|
|
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";
|
|
@@ -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();
|
|
@@ -606,57 +622,16 @@ async function onboardFlow() {
|
|
|
606
622
|
{ value: "skip", label: "Skip for now", hint: "I'll set up models myself" },
|
|
607
623
|
], "lmstudio");
|
|
608
624
|
|
|
609
|
-
const
|
|
610
|
-
const lmsBin = join(homedir(), ".lmstudio", "bin");
|
|
611
|
-
|
|
612
|
-
const ensureLmsOnPath = () => {
|
|
613
|
-
const lmsDest = join(lmsBin, "lms");
|
|
614
|
-
// Bootstrap lms from app bundle if not yet in ~/.lmstudio/bin
|
|
615
|
-
if (!existsSync(lmsDest) && existsSync(lmsAppBundle)) {
|
|
616
|
-
mkdirSync(lmsBin, { recursive: true });
|
|
617
|
-
copyFileSync(lmsAppBundle, lmsDest);
|
|
618
|
-
}
|
|
619
|
-
if (!existsSync(lmsDest)) {
|
|
620
|
-
console.log(pc.yellow(" Note: lms CLI will be available after opening LM Studio once."));
|
|
621
|
-
return false;
|
|
622
|
-
}
|
|
623
|
-
if (process.env.PATH.split(":").includes(lmsBin)) return true;
|
|
624
|
-
process.env.PATH = `${lmsBin}:${process.env.PATH}`;
|
|
625
|
-
const profileFiles = [join(homedir(), ".zshrc"), join(homedir(), ".bash_profile")];
|
|
626
|
-
const line = `export PATH="$PATH:$HOME/.lmstudio/bin"`;
|
|
627
|
-
for (const f of profileFiles) {
|
|
628
|
-
if (!existsSync(f)) continue;
|
|
629
|
-
const content = readFileSync(f, "utf8");
|
|
630
|
-
if (content.includes(".lmstudio/bin")) continue;
|
|
631
|
-
appendFileSync(f, `\n${line}\n`);
|
|
632
|
-
}
|
|
633
|
-
return true;
|
|
634
|
-
};
|
|
625
|
+
const model = recommendedModel();
|
|
635
626
|
|
|
636
627
|
if (backendChoice === "lmstudio") {
|
|
637
628
|
console.log(pc.cyan("Installing LM Studio via Homebrew..."));
|
|
638
629
|
try {
|
|
639
630
|
await run("brew", ["install", "--cask", "lm-studio"], "LM Studio");
|
|
640
|
-
const lmsReady = ensureLmsOnPath();
|
|
641
631
|
console.log(pc.green("✓ LM Studio installed"));
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
console.log(pc.cyan("Downloading qwen/qwen3.5-9b via lms..."));
|
|
646
|
-
try {
|
|
647
|
-
await run("lms", ["get", "qwen/qwen3.5-9b", "-y"], "lms get qwen/qwen3.5-9b");
|
|
648
|
-
console.log(pc.green("✓ Model downloaded."));
|
|
649
|
-
} catch {
|
|
650
|
-
console.log(pc.yellow("Model download failed. Run manually: lms get qwen/qwen3.5-9b"));
|
|
651
|
-
}
|
|
652
|
-
} else {
|
|
653
|
-
console.log(pc.yellow("Download a model later:"));
|
|
654
|
-
console.log(pc.bold(" lms get qwen/qwen3.5-9b"));
|
|
655
|
-
}
|
|
656
|
-
} else {
|
|
657
|
-
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"));
|
|
659
|
-
}
|
|
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}`));
|
|
660
635
|
console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
|
|
661
636
|
} catch {
|
|
662
637
|
console.log(pc.red("✗ LM Studio installation failed."));
|
|
@@ -667,25 +642,9 @@ async function onboardFlow() {
|
|
|
667
642
|
try {
|
|
668
643
|
await run("brew", ["install", "ollama"], "Ollama");
|
|
669
644
|
console.log(pc.green("✓ Ollama installed"));
|
|
670
|
-
console.log(pc.
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
674
|
-
} catch { /* may already be running */ }
|
|
675
|
-
console.log(pc.green("Ollama is running."));
|
|
676
|
-
const download = await prompt.yesNo("Pull a model now? (gemma3:4b)", true);
|
|
677
|
-
if (download) {
|
|
678
|
-
console.log(pc.cyan("Pulling gemma3:4b via Ollama..."));
|
|
679
|
-
try {
|
|
680
|
-
await run("ollama", ["pull", "gemma3:4b"], "ollama pull gemma3:4b");
|
|
681
|
-
console.log(pc.green("✓ Model pulled."));
|
|
682
|
-
} catch {
|
|
683
|
-
console.log(pc.yellow("Model pull failed. Run manually: ollama pull gemma3:4b"));
|
|
684
|
-
}
|
|
685
|
-
} else {
|
|
686
|
-
console.log(pc.yellow("Pull a model later:"));
|
|
687
|
-
console.log(pc.bold(" ollama pull gemma3:4b"));
|
|
688
|
-
}
|
|
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}`));
|
|
689
648
|
console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
|
|
690
649
|
} catch {
|
|
691
650
|
console.log(pc.red("✗ Ollama installation failed."));
|
|
@@ -697,32 +656,29 @@ async function onboardFlow() {
|
|
|
697
656
|
await run("brew", ["tap", "jundot/omlx", "https://github.com/jundot/omlx"], "oMLX tap");
|
|
698
657
|
await run("brew", ["install", "omlx"], "oMLX");
|
|
699
658
|
console.log(pc.green("✓ oMLX installed"));
|
|
700
|
-
console.log(pc.yellow("\nStart oMLX
|
|
659
|
+
console.log(pc.yellow("\nStart oMLX and download a model:"));
|
|
701
660
|
console.log(pc.bold(" omlx start"));
|
|
661
|
+
console.log(pc.dim(`Recommended for your machine: ${model.label}`));
|
|
702
662
|
console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
|
|
703
|
-
} catch
|
|
704
|
-
console.log(pc.red(
|
|
663
|
+
} catch {
|
|
664
|
+
console.log(pc.red("✗ oMLX installation failed."));
|
|
705
665
|
console.log(pc.dim("Install manually: brew tap jundot/omlx && brew install omlx"));
|
|
706
666
|
}
|
|
707
667
|
} else if (backendChoice === "all") {
|
|
708
668
|
let installed = [];
|
|
709
669
|
// LM Studio
|
|
710
670
|
console.log(pc.cyan("Installing LM Studio via Homebrew..."));
|
|
711
|
-
let lmsReady = false;
|
|
712
671
|
try {
|
|
713
672
|
await run("brew", ["install", "--cask", "lm-studio"], "LM Studio");
|
|
714
|
-
lmsReady = ensureLmsOnPath();
|
|
715
673
|
installed.push("LM Studio");
|
|
716
674
|
} catch {
|
|
717
675
|
console.log(pc.yellow("✗ LM Studio installation failed. Download from https://lmstudio.ai"));
|
|
718
676
|
}
|
|
719
677
|
// Ollama
|
|
720
678
|
console.log(pc.cyan("Installing Ollama via Homebrew..."));
|
|
721
|
-
let ollamaInstalled = false;
|
|
722
679
|
try {
|
|
723
680
|
await run("brew", ["install", "ollama"], "Ollama");
|
|
724
681
|
installed.push("Ollama");
|
|
725
|
-
ollamaInstalled = true;
|
|
726
682
|
} catch {
|
|
727
683
|
console.log(pc.yellow("✗ Ollama installation failed. Install manually from https://ollama.com"));
|
|
728
684
|
}
|
|
@@ -735,45 +691,18 @@ async function onboardFlow() {
|
|
|
735
691
|
} catch {
|
|
736
692
|
console.log(pc.yellow("✗ oMLX installation failed. Install manually: brew tap jundot/omlx && brew install omlx"));
|
|
737
693
|
}
|
|
738
|
-
// Auto-download model with best available backend
|
|
739
694
|
if (installed.length > 0) {
|
|
740
695
|
console.log(pc.green(`\n✓ Installed: ${installed.join(", ")}`));
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
}
|
|
752
|
-
} else if (ollamaInstalled) {
|
|
753
|
-
// Start Ollama and offer to pull
|
|
754
|
-
console.log(pc.cyan("Starting Ollama..."));
|
|
755
|
-
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);
|
|
757
|
-
if (download) {
|
|
758
|
-
console.log(pc.cyan("Pulling gemma3:4b via Ollama..."));
|
|
759
|
-
try {
|
|
760
|
-
await run("ollama", ["pull", "gemma3:4b"], "ollama pull gemma3:4b");
|
|
761
|
-
console.log(pc.green("✓ Model pulled."));
|
|
762
|
-
} catch {
|
|
763
|
-
console.log(pc.yellow("Model pull failed. Run manually: ollama pull gemma3:4b"));
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
} else {
|
|
767
|
-
console.log(pc.yellow("Next steps — download your first model:"));
|
|
768
|
-
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"));
|
|
770
|
-
}
|
|
771
|
-
if (installed.includes("Ollama")) {
|
|
772
|
-
console.log(pc.bold(" ollama pull gemma3:4b"));
|
|
773
|
-
}
|
|
774
|
-
if (installed.includes("oMLX")) {
|
|
775
|
-
console.log(pc.bold(" omlx start"));
|
|
776
|
-
}
|
|
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"));
|
|
777
706
|
}
|
|
778
707
|
console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
|
|
779
708
|
}
|