offgrid-ai 0.2.1 → 0.2.2
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/backends.mjs +0 -14
- package/src/cli.mjs +38 -16
- package/src/ui.mjs +0 -23
package/package.json
CHANGED
package/src/backends.mjs
CHANGED
|
@@ -57,20 +57,6 @@ export function backendFor(backendId) {
|
|
|
57
57
|
return backend;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
export function inferBackendId(modelOrProfile) {
|
|
61
|
-
const haystack = [
|
|
62
|
-
modelOrProfile?.path,
|
|
63
|
-
modelOrProfile?.modelPath,
|
|
64
|
-
modelOrProfile?.label,
|
|
65
|
-
modelOrProfile?.modelAlias,
|
|
66
|
-
modelOrProfile?.id,
|
|
67
|
-
modelOrProfile?.providerId,
|
|
68
|
-
modelOrProfile?.backend,
|
|
69
|
-
].filter(Boolean).join(" ").toLowerCase();
|
|
70
|
-
if (haystack.includes("mtp")) return "llama-cpp-mtp";
|
|
71
|
-
return "llama-cpp";
|
|
72
|
-
}
|
|
73
|
-
|
|
74
60
|
export async function backendBinaryFor(backendId) {
|
|
75
61
|
const backend = BACKENDS[backendId ?? "llama-cpp"];
|
|
76
62
|
if (backend.type === "managed-server") return null;
|
package/src/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
-
import { existsSync, rmSync } 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";
|
|
@@ -76,23 +76,11 @@ export async function mainFlow() {
|
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
//
|
|
79
|
+
// 6. Interactive: pick an action
|
|
80
80
|
startInteractive("offgrid-ai");
|
|
81
81
|
const prompt = createPrompt();
|
|
82
82
|
try {
|
|
83
|
-
//
|
|
84
|
-
const items = [];
|
|
85
|
-
|
|
86
|
-
// Existing profiles (quick run)
|
|
87
|
-
if (profiles.length > 0) {
|
|
88
|
-
for (const profile of profiles) {
|
|
89
|
-
const running = await isProfileRunning(profile);
|
|
90
|
-
const backend = backendFor(profile.backend);
|
|
91
|
-
items.push({ type: "profile", profile, running });
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// New GGUF models (auto-setup)
|
|
83
|
+
// Show what we found
|
|
96
84
|
const profiledPaths = new Set(profiles.map((p) => p.modelPath).filter(Boolean));
|
|
97
85
|
const newModels = ggufModels.filter((m) => !profiledPaths.has(m.path));
|
|
98
86
|
|
|
@@ -592,6 +580,7 @@ async function onboardFlow() {
|
|
|
592
580
|
{ value: "ollama", label: "Ollama", hint: "brew install ollama — models download on demand" },
|
|
593
581
|
{ value: "lmstudio", label: "LM Studio", hint: "brew install --cask lm-studio — visual model browser" },
|
|
594
582
|
{ value: "omlx", label: "oMLX", hint: "brew tap jundot/omlx && brew install omlx — Apple Silicon optimized" },
|
|
583
|
+
{ value: "all", label: "Install all three", hint: "Ollama + LM Studio + oMLX" },
|
|
595
584
|
{ value: "skip", label: "Skip for now", hint: "I'll set up models myself" },
|
|
596
585
|
], "ollama");
|
|
597
586
|
|
|
@@ -639,6 +628,39 @@ async function onboardFlow() {
|
|
|
639
628
|
console.log(pc.red(`Failed to install oMLX: ${err.message}`));
|
|
640
629
|
console.log(pc.dim("Install it manually: brew tap jundot/omlx && brew install omlx"));
|
|
641
630
|
}
|
|
631
|
+
} else if (backendChoice === "all") {
|
|
632
|
+
let installed = [];
|
|
633
|
+
// Ollama
|
|
634
|
+
console.log(pc.cyan("Installing Ollama via Homebrew..."));
|
|
635
|
+
try {
|
|
636
|
+
await promisify(execFile)("brew", ["install", "ollama"], { stdio: "inherit" });
|
|
637
|
+
installed.push("Ollama");
|
|
638
|
+
} catch {
|
|
639
|
+
console.log(pc.yellow("Ollama installation failed. Install manually from https://ollama.com"));
|
|
640
|
+
}
|
|
641
|
+
// LM Studio
|
|
642
|
+
console.log(pc.cyan("Installing LM Studio via Homebrew..."));
|
|
643
|
+
try {
|
|
644
|
+
await promisify(execFile)("brew", ["install", "--cask", "lm-studio"], { stdio: "inherit" });
|
|
645
|
+
installed.push("LM Studio");
|
|
646
|
+
} catch {
|
|
647
|
+
console.log(pc.yellow("LM Studio installation failed. Download from https://lmstudio.ai"));
|
|
648
|
+
}
|
|
649
|
+
// oMLX
|
|
650
|
+
console.log(pc.cyan("Installing oMLX via Homebrew..."));
|
|
651
|
+
try {
|
|
652
|
+
await promisify(execFile)("brew", ["tap", "jundot/omlx", "https://github.com/jundot/omlx"], { stdio: "inherit" });
|
|
653
|
+
await promisify(execFile)("brew", ["install", "omlx"], { stdio: "inherit" });
|
|
654
|
+
installed.push("oMLX");
|
|
655
|
+
} catch {
|
|
656
|
+
console.log(pc.yellow("oMLX installation failed. Install manually: brew tap jundot/omlx && brew install omlx"));
|
|
657
|
+
}
|
|
658
|
+
if (installed.length > 0) {
|
|
659
|
+
console.log(pc.green(`\n✓ Installed: ${installed.join(", ")}`));
|
|
660
|
+
console.log(pc.yellow("Next steps:"));
|
|
661
|
+
console.log(pc.bold(" ollama pull gemma3:4b"));
|
|
662
|
+
console.log(pc.dim("Or open LM Studio to browse models, or run: omlx start"));
|
|
663
|
+
}
|
|
642
664
|
} else {
|
|
643
665
|
console.log(pc.dim("Run offgrid-ai again when you've set up a model backend."));
|
|
644
666
|
}
|
|
@@ -657,7 +679,7 @@ async function uninstallCommand(argv) {
|
|
|
657
679
|
if (!process.stdin.isTTY) {
|
|
658
680
|
// Non-interactive: remove everything
|
|
659
681
|
await removeDataDir();
|
|
660
|
-
removeSelf();
|
|
682
|
+
await removeSelf();
|
|
661
683
|
return;
|
|
662
684
|
}
|
|
663
685
|
|
package/src/ui.mjs
CHANGED
|
@@ -4,20 +4,6 @@ import pc from "picocolors";
|
|
|
4
4
|
export { pc };
|
|
5
5
|
export { pc as colors };
|
|
6
6
|
|
|
7
|
-
export function printHelp() {
|
|
8
|
-
console.log(`${pc.bold("offgrid-ai")} — privacy-first local LLM runner\n`);
|
|
9
|
-
console.log("Usage:");
|
|
10
|
-
console.log(" offgrid-ai Pick a model and run");
|
|
11
|
-
console.log(" offgrid-ai models List profiles and models");
|
|
12
|
-
console.log(" offgrid-ai run [id] Run a profile (start server + launch Pi)");
|
|
13
|
-
console.log(" offgrid-ai stop [id] Stop a running server");
|
|
14
|
-
console.log(" offgrid-ai benchmark Run a benchmark prompt");
|
|
15
|
-
console.log("");
|
|
16
|
-
console.log(pc.bold("Run modes (--with):"));
|
|
17
|
-
console.log(" pi Launch Pi with the selected model (default)");
|
|
18
|
-
console.log(" server Start server only, no harness");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
7
|
export function formatBytes(bytes) {
|
|
22
8
|
if (!Number.isFinite(bytes)) return "unknown";
|
|
23
9
|
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
@@ -63,15 +49,6 @@ function handleCancel(value) {
|
|
|
63
49
|
return value;
|
|
64
50
|
}
|
|
65
51
|
|
|
66
|
-
export function relativeTime(date) {
|
|
67
|
-
const ms = Date.now() - date.getTime();
|
|
68
|
-
const abs = Math.abs(ms);
|
|
69
|
-
for (const [label, size] of [["day", 86400000], ["hour", 3600000], ["minute", 60000], ["second", 1000]]) {
|
|
70
|
-
if (abs >= size) { const v = Math.round(abs / size); return `${v} ${label}${v === 1 ? "" : "s"} ago`; }
|
|
71
|
-
}
|
|
72
|
-
return "just now";
|
|
73
|
-
}
|
|
74
|
-
|
|
75
52
|
export function renderRows(rows) {
|
|
76
53
|
const width = Math.max(...rows.map(([key]) => pc.strip(String(key)).length));
|
|
77
54
|
return rows.map(([key, value]) => {
|