offgrid-ai 0.8.1 → 0.8.3

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/install.sh CHANGED
@@ -131,10 +131,10 @@ if command -v npm &>/dev/null; then
131
131
  # Try the modern way first, then legacy
132
132
  NPM_PREFIX="$(npm prefix -g 2>/dev/null || true)"
133
133
  if [[ -n "$NPM_PREFIX" ]]; then
134
- NPM_BIN="${NPM_PREFIX}/bin"
134
+ NPM_BIN="${NPM_PREFIX%/}/bin"
135
135
  fi
136
136
  # Validate: the binary should exist there
137
- if [[ ! -d "$NPM_BIN" ]]; then
137
+ if [[ ! -x "$NPM_BIN/offgrid-ai" ]]; then
138
138
  NPM_BIN=""
139
139
  fi
140
140
  fi
@@ -158,12 +158,14 @@ if [[ -n "$NPM_BIN" && -x "$NPM_BIN/offgrid-ai" ]]; then
158
158
 
159
159
  # Add to shell config for future sessions (pick first existing or .zshrc)
160
160
  ADDED_TO_RC=false
161
- for RC_FILE in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile"; do
161
+ # Respect user's shell preference; default to .zshrc on macOS
162
+ [[ "$OSTYPE" == darwin* ]] && [[ -z "$DEFAULT_RC" ]] && DEFAULT_RC="$HOME/.zshrc"
163
+ RC_CANDIDATES=("${DEFAULT_RC:-}" "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile")
164
+ for RC_FILE in "${RC_CANDIDATES[@]}"; do
165
+ [[ -z "$RC_FILE" ]] && continue
162
166
  if [[ -f "$RC_FILE" || "$RC_FILE" == "$HOME/.zshrc" ]]; then
163
167
  if ! grep -qF "$NPM_BIN" "$RC_FILE" 2>/dev/null; then
164
- echo '' >> "$RC_FILE"
165
- echo '# Added by offgrid-ai installer' >> "$RC_FILE"
166
- echo "export PATH=\"$NPM_BIN:\$PATH\"" >> "$RC_FILE"
168
+ { echo ''; echo '# Added by offgrid-ai installer'; echo "export PATH=\"$NPM_BIN:\$PATH\""; } >> "$RC_FILE"
167
169
  ok "Added $NPM_BIN to $RC_FILE"
168
170
  ADDED_TO_RC=true
169
171
  # Only add to one rc file to avoid duplicates
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "offgrid-ai",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
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",
@@ -1,12 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import { existsSync, readFileSync, appendFileSync } from "node:fs";
3
- import { join } from "node:path";
3
+ import { mkdirSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
4
5
 
5
6
  if (process.env.CI || process.env.OFFGRID_SKIP_POSTINSTALL) process.exit(0);
6
- if (process.env.npm_config_global !== "true") process.exit(0);
7
+
8
+ // npm v9+ sets npm_config_global="true" for global installs. Some contexts (Hermes,
9
+ // older npm, or wrapped installs) may not set it, so also check the prefix.
10
+ const isGlobalInstall = process.env.npm_config_global === "true" || isLikelyGlobalPrefix(process.env.npm_config_prefix, process.env.HOME);
11
+ if (!isGlobalInstall) process.exit(0);
7
12
 
8
13
  const prefix = process.env.npm_config_prefix;
9
- if (!prefix) process.exit(0);
14
+ if (!prefix) {
15
+ console.log("offgrid-ai postinstall: npm global prefix not detected; skipping PATH update.");
16
+ process.exit(0);
17
+ }
10
18
 
11
19
  if (isHermesPrefix(prefix, process.env.HOME)) {
12
20
  console.log("offgrid-ai installed with a Hermes-managed npm prefix.");
@@ -19,35 +27,80 @@ const marker = "# Added by offgrid-ai installer";
19
27
  const pathLine = `export PATH="${npmBin}:$PATH"`;
20
28
  const currentPath = process.env.PATH ?? "";
21
29
 
22
- if (currentPath.split(":").includes(npmBin)) process.exit(0);
30
+ if (currentPath.split(":").includes(npmBin)) {
31
+ console.log(`offgrid-ai is already on PATH (${npmBin})`);
32
+ process.exit(0);
33
+ }
23
34
 
24
35
  const home = process.env.HOME;
25
- if (!home) process.exit(0);
36
+ if (!home) {
37
+ console.log("offgrid-ai postinstall: HOME not set; cannot update shell PATH.");
38
+ process.exit(0);
39
+ }
26
40
 
41
+ // Detect target shell config file. Prefer the file matching the user's shell.
27
42
  const shell = process.env.SHELL ?? "";
28
- const rcCandidates = shell.endsWith("zsh")
29
- ? [join(home, ".zshrc"), join(home, ".zprofile"), join(home, ".profile")]
30
- : [join(home, ".bashrc"), join(home, ".bash_profile"), join(home, ".profile"), join(home, ".zshrc")];
43
+ const isZsh = /\bzsh$/u.test(shell);
44
+ const isBash = /\bbash$/u.test(shell);
45
+
46
+ let rcCandidates;
47
+ if (isZsh) {
48
+ rcCandidates = [".zshrc", ".zprofile", ".profile"];
49
+ } else if (isBash) {
50
+ rcCandidates = [".bashrc", ".bash_profile", ".profile"];
51
+ } else {
52
+ // Fallback: try common files, prefer existing ones
53
+ rcCandidates = [".bashrc", ".bash_profile", ".zshrc", ".zprofile", ".profile"];
54
+ }
55
+
56
+ const rcPaths = rcCandidates.map((name) => join(home, name));
57
+ let rcFile = rcPaths.find((file) => existsSync(file));
58
+ if (!rcFile) {
59
+ // No existing rc file: create the one matching the user's shell.
60
+ const defaultName = isZsh ? ".zshrc" : isBash ? ".bashrc" : ".bashrc";
61
+ rcFile = join(home, defaultName);
62
+ }
31
63
 
32
- const rcFile = rcCandidates.find((file) => existsSync(file)) ?? rcCandidates[0];
33
64
  let content = "";
34
65
  try {
35
66
  content = existsSync(rcFile) ? readFileSync(rcFile, "utf8") : "";
36
- } catch {
67
+ } catch (err) {
68
+ console.log(`offgrid-ai postinstall: could not read ${rcFile}: ${err.message}`);
37
69
  process.exit(0);
38
70
  }
39
71
 
40
72
  if (!content.includes(npmBin)) {
41
- appendFileSync(rcFile, `${content.endsWith("\n") || content.length === 0 ? "" : "\n"}\n${marker}\n${pathLine}\n`, "utf8");
42
- console.log(`offgrid-ai added ${npmBin} to ${rcFile}`);
43
- console.log(`Open a new terminal, or run: source ${rcFile}`);
73
+ try {
74
+ mkdirSync(dirname(rcFile), { recursive: true });
75
+ appendFileSync(rcFile, `${content.endsWith("\n") || content.length === 0 ? "" : "\n"}\n${marker}\n${pathLine}\n`, "utf8");
76
+ console.log(`offgrid-ai added ${npmBin} to ${rcFile}`);
77
+ console.log(`Open a new terminal, or run: source ${rcFile}`);
78
+ } catch (err) {
79
+ console.log(`offgrid-ai postinstall: could not write to ${rcFile}: ${err.message}`);
80
+ }
44
81
  } else {
45
82
  console.log(`offgrid-ai is installed in ${npmBin}`);
46
83
  console.log(`Open a new terminal if the command is not found yet.`);
47
84
  }
48
85
 
86
+ if (process.getuid?.() === 0) {
87
+ console.log("Warning: offgrid-ai was installed as root. PATH was added to root's shell config, not the regular user's.");
88
+ console.log("If you installed with sudo, run the installer as your normal user instead, or manually add the line above to your user's shell config.");
89
+ }
90
+
49
91
  function isHermesPrefix(prefix, home) {
50
92
  const normalized = prefix.replace(/\\/gu, "/");
51
93
  if (normalized.includes("/.hermes/")) return true;
52
94
  return Boolean(home && normalized === `${home.replace(/\\/gu, "/")}/.hermes/node`);
53
95
  }
96
+
97
+ function isLikelyGlobalPrefix(prefix, home) {
98
+ if (!prefix || !home) return false;
99
+ const normalized = prefix.replace(/\\/gu, "/");
100
+ const homeNormalized = home.replace(/\\/gu, "/");
101
+ // Common global prefixes: /usr/local, /opt/homebrew, nvm paths, ~/.npm-global, ~/.local
102
+ return normalized.startsWith("/usr/") ||
103
+ normalized.startsWith("/opt/") ||
104
+ normalized.startsWith(homeNormalized) ||
105
+ /\/versions\/node\/v?\d/u.test(normalized);
106
+ }
package/src/updates.mjs CHANGED
@@ -20,10 +20,7 @@ export async function checkForUpdate({ now = Date.now(), fetchImpl = globalThis.
20
20
  if (cached.currentVersion === currentVersion) {
21
21
  return updateResult(currentVersion, cached.latestVersion);
22
22
  }
23
- // Cache is from a different version — if latest isn't newer than current, no update
24
- if (cached.latestVersion && !isNewerVersion(cached.latestVersion, currentVersion)) {
25
- return null;
26
- }
23
+ // Cache is from a different installed version — invalidate and refetch
27
24
  }
28
25
 
29
26
  try {