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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.mjs +110 -31
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "offgrid-ai",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
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
@@ -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, stream output to terminal, throw on failure. */
499
+ /** Run a command. Verbose: stream output. Quiet: show only label + result. */
498
500
  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));
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
- console.log(pc.bold(" lms get qwen/qwen3.5-9b"));
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.bold(" Open LM Studio once, then: lms get qwen/qwen3.5-9b"));
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 (err) {
637
- console.log(pc.red(`✗ LM Studio installation failed.`));
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
- console.log(pc.yellow("\nPull your first model:"));
652
- console.log(pc.bold(" ollama pull gemma3:4b"));
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 (err) {
655
- console.log(pc.red(`✗ Ollama installation failed.`));
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
- const lmsReady = ensureLmsOnPath();
678
- installed.push(lmsReady ? "LM Studio" : "LM Studio (open app once for lms CLI)");
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
- console.log(pc.yellow("Next steps — download your first model:"));
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"));
706
- }
707
- if (installed.includes("Ollama")) {
708
- console.log(pc.bold(" ollama pull gemma3:4b"));
709
- }
710
- if (installed.includes("oMLX")) {
711
- console.log(pc.bold(" omlx start"));
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 child = spawnUninstall(cmd, args, { stdio: "inherit" });
799
- child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`${label || cmd} exited with code ${code}`)));
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
  }