offgrid-ai 0.2.4 → 0.2.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 +1 -1
- package/src/cli.mjs +60 -40
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -491,6 +491,18 @@ async function runningProfiles() {
|
|
|
491
491
|
async function onboardFlow() {
|
|
492
492
|
startInteractive("offgrid-ai setup");
|
|
493
493
|
const prompt = createPrompt();
|
|
494
|
+
|
|
495
|
+
const { spawn } = await import("node:child_process");
|
|
496
|
+
|
|
497
|
+
/** Run a command, stream output to terminal, throw on failure. */
|
|
498
|
+
const run = (cmd, args, label) => new Promise((resolve, reject) => {
|
|
499
|
+
const child = spawn(cmd, args, { stdio: "inherit" });
|
|
500
|
+
child.on("close", (code) => {
|
|
501
|
+
if (code === 0) resolve();
|
|
502
|
+
else reject(new Error(`${label || cmd} exited with code ${code}`));
|
|
503
|
+
});
|
|
504
|
+
child.on("error", (err) => reject(err));
|
|
505
|
+
});
|
|
494
506
|
try {
|
|
495
507
|
console.log(pc.bold("Welcome to offgrid-ai!"));
|
|
496
508
|
console.log(pc.dim("Let's make sure you have everything you need to run local models.\n"));
|
|
@@ -505,10 +517,8 @@ async function onboardFlow() {
|
|
|
505
517
|
return;
|
|
506
518
|
}
|
|
507
519
|
console.log(pc.cyan("Installing Homebrew..."));
|
|
508
|
-
const { execFile } = await import("node:child_process");
|
|
509
|
-
const { promisify } = await import("node:util");
|
|
510
520
|
try {
|
|
511
|
-
await
|
|
521
|
+
await run("/bin/bash", ["-c", "NONINTERACTIVE=1 /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""], "Homebrew");
|
|
512
522
|
// Add brew to PATH for this session
|
|
513
523
|
const brewPaths = ["/opt/homebrew/bin", "/usr/local/bin"];
|
|
514
524
|
for (const p of brewPaths) {
|
|
@@ -518,7 +528,7 @@ async function onboardFlow() {
|
|
|
518
528
|
}
|
|
519
529
|
}
|
|
520
530
|
} catch (err) {
|
|
521
|
-
console.log(pc.red(
|
|
531
|
+
console.log(pc.red(`✗ Homebrew installation failed.`));
|
|
522
532
|
console.log(pc.dim("Install it manually from https://brew.sh, then run offgrid-ai again."));
|
|
523
533
|
return;
|
|
524
534
|
}
|
|
@@ -539,13 +549,12 @@ async function onboardFlow() {
|
|
|
539
549
|
return;
|
|
540
550
|
}
|
|
541
551
|
console.log(pc.cyan("Installing llama.cpp..."));
|
|
542
|
-
const { execFile } = await import("node:child_process");
|
|
543
|
-
const { promisify } = await import("node:util");
|
|
544
552
|
try {
|
|
545
|
-
await
|
|
553
|
+
await run("brew", ["install", "llama.cpp"], "llama.cpp");
|
|
546
554
|
llamaBinary = await findLlamaServer();
|
|
547
|
-
} catch
|
|
548
|
-
console.log(pc.red(
|
|
555
|
+
} catch {
|
|
556
|
+
console.log(pc.red("✗ Failed to install llama.cpp."));
|
|
557
|
+
console.log(pc.dim("Install it manually: brew install llama.cpp"));
|
|
549
558
|
return;
|
|
550
559
|
}
|
|
551
560
|
if (!llamaBinary) {
|
|
@@ -584,9 +593,6 @@ async function onboardFlow() {
|
|
|
584
593
|
{ value: "skip", label: "Skip for now", hint: "I'll set up models myself" },
|
|
585
594
|
], "lmstudio");
|
|
586
595
|
|
|
587
|
-
const { execFile } = await import("node:child_process");
|
|
588
|
-
const { promisify } = await import("node:util");
|
|
589
|
-
|
|
590
596
|
const lmsAppBundle = "/Applications/LM Studio.app/Contents/Resources/app/.webpack/lms";
|
|
591
597
|
const lmsBin = join(homedir(), ".lmstudio", "bin");
|
|
592
598
|
|
|
@@ -597,8 +603,11 @@ async function onboardFlow() {
|
|
|
597
603
|
mkdirSync(lmsBin, { recursive: true });
|
|
598
604
|
copyFileSync(lmsAppBundle, lmsDest);
|
|
599
605
|
}
|
|
600
|
-
if (!existsSync(lmsDest))
|
|
601
|
-
|
|
606
|
+
if (!existsSync(lmsDest)) {
|
|
607
|
+
console.log(pc.yellow(" Note: lms CLI will be available after opening LM Studio once."));
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
if (process.env.PATH.split(":").includes(lmsBin)) return true;
|
|
602
611
|
process.env.PATH = `${lmsBin}:${process.env.PATH}`;
|
|
603
612
|
const profileFiles = [join(homedir(), ".zshrc"), join(homedir(), ".bash_profile")];
|
|
604
613
|
const line = `export PATH="$PATH:$HOME/.lmstudio/bin"`;
|
|
@@ -608,29 +617,34 @@ async function onboardFlow() {
|
|
|
608
617
|
if (content.includes(".lmstudio/bin")) continue;
|
|
609
618
|
appendFileSync(f, `\n${line}\n`);
|
|
610
619
|
}
|
|
620
|
+
return true;
|
|
611
621
|
};
|
|
612
622
|
|
|
613
623
|
if (backendChoice === "lmstudio") {
|
|
614
624
|
console.log(pc.cyan("Installing LM Studio via Homebrew..."));
|
|
615
625
|
try {
|
|
616
|
-
await
|
|
617
|
-
ensureLmsOnPath();
|
|
626
|
+
await run("brew", ["install", "--cask", "lm-studio"], "LM Studio");
|
|
627
|
+
const lmsReady = ensureLmsOnPath();
|
|
618
628
|
console.log(pc.green("✓ LM Studio installed"));
|
|
619
629
|
console.log(pc.yellow("\nDownload your first model:"));
|
|
620
|
-
|
|
630
|
+
if (lmsReady) {
|
|
631
|
+
console.log(pc.bold(" lms get qwen/qwen3.5-9b"));
|
|
632
|
+
} else {
|
|
633
|
+
console.log(pc.bold(" Open LM Studio once, then: lms get qwen/qwen3.5-9b"));
|
|
634
|
+
}
|
|
621
635
|
console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
|
|
622
636
|
} catch (err) {
|
|
623
|
-
console.log(pc.red(
|
|
637
|
+
console.log(pc.red(`✗ LM Studio installation failed.`));
|
|
624
638
|
console.log(pc.dim("Download it manually from https://lmstudio.ai"));
|
|
625
639
|
}
|
|
626
640
|
} else if (backendChoice === "ollama") {
|
|
627
641
|
console.log(pc.cyan("Installing Ollama via Homebrew..."));
|
|
628
642
|
try {
|
|
629
|
-
await
|
|
643
|
+
await run("brew", ["install", "ollama"], "Ollama");
|
|
630
644
|
console.log(pc.green("✓ Ollama installed"));
|
|
631
645
|
console.log(pc.cyan("\nStarting Ollama..."));
|
|
632
646
|
try {
|
|
633
|
-
await
|
|
647
|
+
await run("ollama", ["serve"], "Ollama serve");
|
|
634
648
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
635
649
|
} catch { /* may already be running */ }
|
|
636
650
|
console.log(pc.green("Ollama is running."));
|
|
@@ -638,55 +652,57 @@ async function onboardFlow() {
|
|
|
638
652
|
console.log(pc.bold(" ollama pull gemma3:4b"));
|
|
639
653
|
console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
|
|
640
654
|
} catch (err) {
|
|
641
|
-
console.log(pc.red(
|
|
655
|
+
console.log(pc.red(`✗ Ollama installation failed.`));
|
|
642
656
|
console.log(pc.dim("Install it manually from https://ollama.com"));
|
|
643
657
|
}
|
|
644
658
|
} else if (backendChoice === "omlx") {
|
|
645
659
|
console.log(pc.cyan("Installing oMLX via Homebrew..."));
|
|
646
660
|
try {
|
|
647
|
-
await
|
|
648
|
-
await
|
|
661
|
+
await run("brew", ["tap", "jundot/omlx", "https://github.com/jundot/omlx"], "oMLX tap");
|
|
662
|
+
await run("brew", ["install", "omlx"], "oMLX");
|
|
649
663
|
console.log(pc.green("✓ oMLX installed"));
|
|
650
664
|
console.log(pc.yellow("\nStart oMLX server:"));
|
|
651
665
|
console.log(pc.bold(" omlx start"));
|
|
652
666
|
console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
|
|
653
667
|
} catch (err) {
|
|
654
|
-
console.log(pc.red(
|
|
655
|
-
console.log(pc.dim("Install
|
|
668
|
+
console.log(pc.red(`✗ oMLX installation failed.`));
|
|
669
|
+
console.log(pc.dim("Install manually: brew tap jundot/omlx && brew install omlx"));
|
|
656
670
|
}
|
|
657
671
|
} else if (backendChoice === "all") {
|
|
658
672
|
let installed = [];
|
|
659
673
|
// LM Studio
|
|
660
674
|
console.log(pc.cyan("Installing LM Studio via Homebrew..."));
|
|
661
675
|
try {
|
|
662
|
-
await
|
|
663
|
-
ensureLmsOnPath();
|
|
664
|
-
installed.push("LM Studio");
|
|
676
|
+
await run("brew", ["install", "--cask", "lm-studio"], "LM Studio");
|
|
677
|
+
const lmsReady = ensureLmsOnPath();
|
|
678
|
+
installed.push(lmsReady ? "LM Studio" : "LM Studio (open app once for lms CLI)");
|
|
665
679
|
} catch {
|
|
666
|
-
console.log(pc.yellow("LM Studio installation failed. Download from https://lmstudio.ai"));
|
|
680
|
+
console.log(pc.yellow("✗ LM Studio installation failed. Download from https://lmstudio.ai"));
|
|
667
681
|
}
|
|
668
682
|
// Ollama
|
|
669
683
|
console.log(pc.cyan("Installing Ollama via Homebrew..."));
|
|
670
684
|
try {
|
|
671
|
-
await
|
|
685
|
+
await run("brew", ["install", "ollama"], "Ollama");
|
|
672
686
|
installed.push("Ollama");
|
|
673
687
|
} catch {
|
|
674
|
-
console.log(pc.yellow("Ollama installation failed. Install manually from https://ollama.com"));
|
|
688
|
+
console.log(pc.yellow("✗ Ollama installation failed. Install manually from https://ollama.com"));
|
|
675
689
|
}
|
|
676
690
|
// oMLX
|
|
677
691
|
console.log(pc.cyan("Installing oMLX via Homebrew..."));
|
|
678
692
|
try {
|
|
679
|
-
await
|
|
680
|
-
await
|
|
693
|
+
await run("brew", ["tap", "jundot/omlx", "https://github.com/jundot/omlx"], "oMLX tap");
|
|
694
|
+
await run("brew", ["install", "omlx"], "oMLX");
|
|
681
695
|
installed.push("oMLX");
|
|
682
696
|
} catch {
|
|
683
|
-
console.log(pc.yellow("oMLX installation failed. Install manually: brew tap jundot/omlx && brew install omlx"));
|
|
697
|
+
console.log(pc.yellow("✗ oMLX installation failed. Install manually: brew tap jundot/omlx && brew install omlx"));
|
|
684
698
|
}
|
|
685
699
|
if (installed.length > 0) {
|
|
686
700
|
console.log(pc.green(`\n✓ Installed: ${installed.join(", ")}`));
|
|
687
701
|
console.log(pc.yellow("Next steps — download your first model:"));
|
|
688
|
-
|
|
689
|
-
|
|
702
|
+
const hasLms = installed.some(i => i.includes("LM Studio"));
|
|
703
|
+
if (hasLms) {
|
|
704
|
+
const lmsReady = installed.some(i => i === "LM Studio");
|
|
705
|
+
console.log(pc.bold(lmsReady ? " lms get qwen/qwen3.5-9b" : " Open LM Studio once, then: lms get qwen/qwen3.5-9b"));
|
|
690
706
|
}
|
|
691
707
|
if (installed.includes("Ollama")) {
|
|
692
708
|
console.log(pc.bold(" ollama pull gemma3:4b"));
|
|
@@ -777,14 +793,18 @@ async function removeDataDir() {
|
|
|
777
793
|
|
|
778
794
|
async function removeSelf() {
|
|
779
795
|
console.log(pc.cyan("\nUninstalling offgrid-ai..."));
|
|
780
|
-
const {
|
|
781
|
-
const
|
|
796
|
+
const { spawn: spawnUninstall } = await import("node:child_process");
|
|
797
|
+
const runCmd = (cmd, args, label) => new Promise((resolve, reject) => {
|
|
798
|
+
const child = spawnUninstall(cmd, args, { stdio: "inherit" });
|
|
799
|
+
child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`${label || cmd} exited with code ${code}`)));
|
|
800
|
+
child.on("error", (err) => reject(err));
|
|
801
|
+
});
|
|
782
802
|
try {
|
|
783
|
-
await
|
|
803
|
+
await runCmd("npm", ["uninstall", "-g", "offgrid-ai"], "npm uninstall");
|
|
784
804
|
console.log(pc.green("\n✓ offgrid-ai has been uninstalled."));
|
|
785
805
|
console.log(pc.dim("Reinstall anytime with: npm install -g offgrid-ai"));
|
|
786
806
|
} catch {
|
|
787
|
-
console.log(pc.red("\
|
|
807
|
+
console.log(pc.red("\n✗ Could not auto-uninstall. Run this manually:"));
|
|
788
808
|
console.log(pc.bold(" npm uninstall -g offgrid-ai"));
|
|
789
809
|
}
|
|
790
810
|
}
|