offgrid-ai 0.2.5 → 0.2.6
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 +110 -31
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -23,6 +23,7 @@ export async function run(argv) {
|
|
|
23
23
|
if (command === "status") return statusCommand();
|
|
24
24
|
if (command === "stop") return stopCommand(argv.slice(1));
|
|
25
25
|
if (command === "uninstall" || command === "--uninstall") return uninstallCommand(argv.slice(1));
|
|
26
|
+
if (command === "--verbose") return mainFlow(); // verbose flag handled inside onboardFlow
|
|
26
27
|
|
|
27
28
|
throw new Error(`Unknown command: ${command}. Run offgrid-ai help`);
|
|
28
29
|
}
|
|
@@ -491,17 +492,29 @@ async function runningProfiles() {
|
|
|
491
492
|
async function onboardFlow() {
|
|
492
493
|
startInteractive("offgrid-ai setup");
|
|
493
494
|
const prompt = createPrompt();
|
|
495
|
+
const verbose = process.argv.includes("--verbose");
|
|
494
496
|
|
|
495
497
|
const { spawn } = await import("node:child_process");
|
|
496
498
|
|
|
497
|
-
/** Run a command
|
|
499
|
+
/** Run a command. Verbose: stream output. Quiet: show only label + result. */
|
|
498
500
|
const run = (cmd, args, label) => new Promise((resolve, reject) => {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
501
|
+
if (verbose) {
|
|
502
|
+
const child = spawn(cmd, args, { stdio: "inherit" });
|
|
503
|
+
child.on("close", (code) => {
|
|
504
|
+
if (code === 0) resolve();
|
|
505
|
+
else reject(new Error(`${label || cmd} exited with code ${code}`));
|
|
506
|
+
});
|
|
507
|
+
child.on("error", (err) => reject(err));
|
|
508
|
+
} else {
|
|
509
|
+
const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
510
|
+
let stderr = "";
|
|
511
|
+
child.stderr.on("data", (d) => { stderr += d; });
|
|
512
|
+
child.on("close", (code) => {
|
|
513
|
+
if (code === 0) resolve();
|
|
514
|
+
else reject(new Error(stderr.split("\n").filter(l => l.trim()).slice(-3).join("\n") || `${label || cmd} exited with code ${code}`));
|
|
515
|
+
});
|
|
516
|
+
child.on("error", (err) => reject(err));
|
|
517
|
+
}
|
|
505
518
|
});
|
|
506
519
|
try {
|
|
507
520
|
console.log(pc.bold("Welcome to offgrid-ai!"));
|
|
@@ -626,15 +639,27 @@ async function onboardFlow() {
|
|
|
626
639
|
await run("brew", ["install", "--cask", "lm-studio"], "LM Studio");
|
|
627
640
|
const lmsReady = ensureLmsOnPath();
|
|
628
641
|
console.log(pc.green("✓ LM Studio installed"));
|
|
629
|
-
console.log(pc.yellow("\nDownload your first model:"));
|
|
630
642
|
if (lmsReady) {
|
|
631
|
-
|
|
643
|
+
const download = await prompt.yesNo("Download a model now? (qwen3.5-9b)", true);
|
|
644
|
+
if (download) {
|
|
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
|
+
}
|
|
632
656
|
} else {
|
|
633
|
-
console.log(pc.
|
|
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"));
|
|
634
659
|
}
|
|
635
660
|
console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
|
|
636
|
-
} catch
|
|
637
|
-
console.log(pc.red(
|
|
661
|
+
} catch {
|
|
662
|
+
console.log(pc.red("✗ LM Studio installation failed."));
|
|
638
663
|
console.log(pc.dim("Download it manually from https://lmstudio.ai"));
|
|
639
664
|
}
|
|
640
665
|
} else if (backendChoice === "ollama") {
|
|
@@ -648,11 +673,22 @@ async function onboardFlow() {
|
|
|
648
673
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
649
674
|
} catch { /* may already be running */ }
|
|
650
675
|
console.log(pc.green("Ollama is running."));
|
|
651
|
-
|
|
652
|
-
|
|
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
|
+
}
|
|
653
689
|
console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
|
|
654
|
-
} catch
|
|
655
|
-
console.log(pc.red(
|
|
690
|
+
} catch {
|
|
691
|
+
console.log(pc.red("✗ Ollama installation failed."));
|
|
656
692
|
console.log(pc.dim("Install it manually from https://ollama.com"));
|
|
657
693
|
}
|
|
658
694
|
} else if (backendChoice === "omlx") {
|
|
@@ -672,18 +708,21 @@ async function onboardFlow() {
|
|
|
672
708
|
let installed = [];
|
|
673
709
|
// LM Studio
|
|
674
710
|
console.log(pc.cyan("Installing LM Studio via Homebrew..."));
|
|
711
|
+
let lmsReady = false;
|
|
675
712
|
try {
|
|
676
713
|
await run("brew", ["install", "--cask", "lm-studio"], "LM Studio");
|
|
677
|
-
|
|
678
|
-
installed.push(
|
|
714
|
+
lmsReady = ensureLmsOnPath();
|
|
715
|
+
installed.push("LM Studio");
|
|
679
716
|
} catch {
|
|
680
717
|
console.log(pc.yellow("✗ LM Studio installation failed. Download from https://lmstudio.ai"));
|
|
681
718
|
}
|
|
682
719
|
// Ollama
|
|
683
720
|
console.log(pc.cyan("Installing Ollama via Homebrew..."));
|
|
721
|
+
let ollamaInstalled = false;
|
|
684
722
|
try {
|
|
685
723
|
await run("brew", ["install", "ollama"], "Ollama");
|
|
686
724
|
installed.push("Ollama");
|
|
725
|
+
ollamaInstalled = true;
|
|
687
726
|
} catch {
|
|
688
727
|
console.log(pc.yellow("✗ Ollama installation failed. Install manually from https://ollama.com"));
|
|
689
728
|
}
|
|
@@ -696,19 +735,45 @@ async function onboardFlow() {
|
|
|
696
735
|
} catch {
|
|
697
736
|
console.log(pc.yellow("✗ oMLX installation failed. Install manually: brew tap jundot/omlx && brew install omlx"));
|
|
698
737
|
}
|
|
738
|
+
// Auto-download model with best available backend
|
|
699
739
|
if (installed.length > 0) {
|
|
700
740
|
console.log(pc.green(`\n✓ Installed: ${installed.join(", ")}`));
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
741
|
+
if (lmsReady) {
|
|
742
|
+
const download = await prompt.yesNo("Download a model now? (qwen3.5-9b)", true);
|
|
743
|
+
if (download) {
|
|
744
|
+
console.log(pc.cyan("Downloading qwen/qwen3.5-9b via lms..."));
|
|
745
|
+
try {
|
|
746
|
+
await run("lms", ["get", "qwen/qwen3.5-9b", "-y"], "lms get qwen/qwen3.5-9b");
|
|
747
|
+
console.log(pc.green("✓ Model downloaded."));
|
|
748
|
+
} catch {
|
|
749
|
+
console.log(pc.yellow("Model download failed. Run manually: lms get qwen/qwen3.5-9b"));
|
|
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
|
+
}
|
|
712
777
|
}
|
|
713
778
|
console.log(pc.dim("Then run offgrid-ai again to pick and run a model."));
|
|
714
779
|
}
|
|
@@ -794,9 +859,20 @@ async function removeDataDir() {
|
|
|
794
859
|
async function removeSelf() {
|
|
795
860
|
console.log(pc.cyan("\nUninstalling offgrid-ai..."));
|
|
796
861
|
const { spawn: spawnUninstall } = await import("node:child_process");
|
|
862
|
+
const verbose = process.argv.includes("--verbose");
|
|
797
863
|
const runCmd = (cmd, args, label) => new Promise((resolve, reject) => {
|
|
798
|
-
const
|
|
799
|
-
child
|
|
864
|
+
const stdio = verbose ? "inherit" : ["ignore", "pipe", "pipe"];
|
|
865
|
+
const child = spawnUninstall(cmd, args, { stdio });
|
|
866
|
+
if (!verbose) {
|
|
867
|
+
let stderr = "";
|
|
868
|
+
child.stderr?.on("data", (d) => { stderr += d; });
|
|
869
|
+
child.on("close", (code) => {
|
|
870
|
+
if (code === 0) resolve();
|
|
871
|
+
else reject(new Error(stderr.split("\n").filter(l => l.trim()).slice(-3).join("\n") || `${label || cmd} exited with code ${code}`));
|
|
872
|
+
});
|
|
873
|
+
} else {
|
|
874
|
+
child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`${label || cmd} exited with code ${code}`)));
|
|
875
|
+
}
|
|
800
876
|
child.on("error", (err) => reject(err));
|
|
801
877
|
});
|
|
802
878
|
try {
|
|
@@ -847,6 +923,9 @@ Usage:
|
|
|
847
923
|
offgrid-ai help Show this help
|
|
848
924
|
offgrid-ai version Show version
|
|
849
925
|
|
|
926
|
+
Flags:
|
|
927
|
+
--verbose Show install output (brew, lms, ollama, etc.)
|
|
928
|
+
|
|
850
929
|
First run? offgrid-ai walks you through installing everything you need.
|
|
851
930
|
After that, just run it — it finds your models, auto-configures, and launches Pi.`);
|
|
852
931
|
}
|