alvin-bot 4.22.3 → 4.23.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,52 @@
2
2
 
3
3
  All notable changes to Alvin Bot are documented here.
4
4
 
5
+ ## [4.23.1] — 2026-05-12
6
+
7
+ ### Fixed: install.sh now installs via npm (no more git clone collision)
8
+
9
+ The `install.sh` one-line installer was broken on two counts, both pre-existing and surfaced when end-to-end testing the 4.23.0 install path on a fresh machine:
10
+
11
+ 1. **Data-dir collision with `~/.alvin-bot/`.** The installer cloned the alvin-bot source into `~/.alvin-bot/`, but that path is the per-user **data directory** in 4.x (env, memory, logs, cron-jobs.json), created and owned by the bot itself. On any second run, `git pull` in the data dir failed (`fatal: not a git repository`).
12
+
13
+ 2. **`npm install --omit=dev` + `npm run build` doesn't work.** TypeScript needs `@types/better-sqlite3` (a `devDependency`), so building from source with prod-only deps fails with 6 `TS7016` errors.
14
+
15
+ Both bugs only mattered on the `install.sh` path; the canonical `npm install -g alvin-bot` was unaffected.
16
+
17
+ **Fix:** `install.sh` now does exactly what the README's Quick Start documents — bootstraps Node (the new code from 4.23.0) and then `npm install -g alvin-bot`. Dropped: `git clone`, `npm run build`, the symlink dance into `/usr/local/bin`, and the `INSTALL_DIR`/`REPO_URL`/`BIN_LINK` machinery. The canonical install lives at `/opt/homebrew/lib/node_modules/alvin-bot/` (Apple Silicon) or `/usr/local/lib/node_modules/alvin-bot/` (Intel/Linux); `~/.alvin-bot/` is reserved exclusively for the bot's own data files.
18
+
19
+ The new flow also:
20
+ - Retries with `sudo` if a non-sudo `npm install -g` fails with EACCES (common on Linux where node lives in `/usr/lib`).
21
+ - Skips the auto-launch of `alvin-bot setup` in non-interactive shells (i.e. raw `curl … | bash`) so we don't dump an interactive wizard into a pipe.
22
+ - Adds a `BASH_SOURCE` guard so the file can be `source`d for unit-testing helpers without firing `main`.
23
+
24
+ End-to-end verified on a fresh Apple Silicon Mac (macOS 26.4.1) with node and brew uninstalled: `curl … | bash` bootstraps Node via brew prompt, installs alvin-bot via npm, lands the binary in `/opt/homebrew/bin/alvin-bot` v4.23.0 — without ever touching `~/.alvin-bot/`.
25
+
26
+ ## [4.23.0] — 2026-05-12
27
+
28
+ ### Installer: bootstrap Node automatically + optional capability tools
29
+
30
+ The first-time install path no longer assumes Node (or Homebrew, or anything beyond a normal Mac/Linux shell) is already in place, and the setup wizard now offers to install a curated set of universally useful CLIs in one step.
31
+
32
+ - **`install.sh` — auto-bootstraps Node.** New `ensure_brew()` + `ensure_node()` helpers replace the old "fail with manual install instructions" behaviour. On macOS, if Homebrew is missing, the installer offers to install it via the official `curl|bash` (with `NONINTERACTIVE=1` so it doesn't pause for ENTER). On Debian/Ubuntu, offers Node 22 via the NodeSource repo (with sudo confirmation). Non-interactive shells fall back to clear manual-install messages. Skipping the prompts is always an option. `eval "$(brew shellenv)"` is invoked after a fresh brew install so the rest of the script sees brew on PATH.
33
+
34
+ - **`alvin-bot setup` — optional tools step.** After Telegram + AI provider config, the wizard now lists eight commonly useful tools and lets the user install just the ones they want with a comma-separated picklist (or `a` for all missing, `n` to skip). Already-installed tools are marked `✓` and skipped automatically. Install failures don't block bot setup — the user gets a working bot regardless.
35
+
36
+ - **New `alvin-bot tools` command.** Re-runnable later: `alvin-bot tools list` shows what's installed, `alvin-bot tools install` opens the same interactive menu the setup wizard uses.
37
+
38
+ Tools offered (curated, all generic — none require maintainer-specific creds or workflows):
39
+
40
+ | Tool | What it unlocks |
41
+ |---|---|
42
+ | Playwright + Chromium | Tier 1 stealth browser automation (web-research, social-fetch, browser-manager) |
43
+ | agent-browser CLI | Tier 1.5 token-efficient web automation (Vercel Labs) |
44
+ | ffmpeg | Audio/video processing for media-transcribe, video-generate, voice features |
45
+ | ImageMagick | Image conversion & manipulation for image-generate and visual skills |
46
+ | Pandoc | Markdown ↔ PDF/DOCX/HTML conversion for document-creation skill |
47
+ | ripgrep | Fast file/code search (`alvin-bot search`, code-aware skills) |
48
+ | jq | JSON parsing for helper scripts |
49
+ | himalaya | Multi-account IMAP/SMTP email CLI (configure after install) |
50
+
5
51
  ## [4.22.3] — 2026-05-12
6
52
 
7
53
  ### Fixed
package/bin/cli.js CHANGED
@@ -128,6 +128,237 @@ const PROVIDERS = [
128
128
  },
129
129
  ];
130
130
 
131
+ // ── Optional capability tools ───────────────────────────────────────────────
132
+ //
133
+ // Curated, universally useful CLIs that significantly expand what skills can
134
+ // do. All optional, all opt-in. The list is intentionally short and generic —
135
+ // no maintainer-specific credentials or workflows are required to make any of
136
+ // these useful to a public user; auth/config is per-user and out of scope here.
137
+ //
138
+ // Each entry has:
139
+ // id machine-readable key
140
+ // name shown to the user
141
+ // desc one-liner explaining what skills/features it unlocks
142
+ // check() returns boolean — is it already installed?
143
+ // install either a shell-command string (single platform) or an object
144
+ // { macos, linux } with platform-specific shell commands, or a
145
+ // function that does whatever it takes
146
+ // skipOnLinuxIf optional: skip the Linux install if this command is missing
147
+ // (e.g. himalaya on Linux needs cargo)
148
+ const OPTIONAL_TOOLS = [
149
+ {
150
+ id: "playwright",
151
+ name: "Playwright + Chromium",
152
+ desc: "Browser automation (Tier 1 stealth) — used by web-research, social-fetch, browser-manager",
153
+ check: () => hasCommand("playwright") || hasCommand("npx") && tryNpx("playwright --version"),
154
+ install: () => {
155
+ execSync("npm install -g playwright", { stdio: "inherit" });
156
+ execSync("npx playwright install chromium", { stdio: "inherit" });
157
+ },
158
+ },
159
+ {
160
+ id: "agent-browser",
161
+ name: "agent-browser CLI (Vercel Labs)",
162
+ desc: "Tier 1.5 token-efficient web automation — ~90% cheaper than raw Playwright for AI-driven tasks",
163
+ check: () => hasCommand("agent-browser"),
164
+ install: () => {
165
+ execSync("npm install -g agent-browser", { stdio: "inherit" });
166
+ // Best-effort: ignore failure if the install subcommand isn't there
167
+ try { execSync("agent-browser install", { stdio: "inherit" }); } catch {}
168
+ },
169
+ },
170
+ {
171
+ id: "ffmpeg",
172
+ name: "ffmpeg",
173
+ desc: "Audio/video processing — required by media-transcribe, video-generate, voice features",
174
+ check: () => hasCommand("ffmpeg"),
175
+ install: { macos: "brew install ffmpeg", linux: "sudo apt-get install -y ffmpeg" },
176
+ },
177
+ {
178
+ id: "imagemagick",
179
+ name: "ImageMagick",
180
+ desc: "Image conversion & manipulation — used by image-generate and visual skills",
181
+ check: () => hasCommand("magick") || hasCommand("convert"),
182
+ install: { macos: "brew install imagemagick", linux: "sudo apt-get install -y imagemagick" },
183
+ },
184
+ {
185
+ id: "pandoc",
186
+ name: "Pandoc",
187
+ desc: "Document conversion (Markdown ↔ PDF/DOCX/HTML) — used by document-creation skill",
188
+ check: () => hasCommand("pandoc"),
189
+ install: { macos: "brew install pandoc", linux: "sudo apt-get install -y pandoc" },
190
+ },
191
+ {
192
+ id: "ripgrep",
193
+ name: "ripgrep (rg)",
194
+ desc: "Fast file & code search — used by `alvin-bot search` and code-aware skills",
195
+ check: () => hasCommand("rg"),
196
+ install: { macos: "brew install ripgrep", linux: "sudo apt-get install -y ripgrep" },
197
+ },
198
+ {
199
+ id: "jq",
200
+ name: "jq",
201
+ desc: "JSON parsing for shell scripts — used by many helper scripts and integrations",
202
+ check: () => hasCommand("jq"),
203
+ install: { macos: "brew install jq", linux: "sudo apt-get install -y jq" },
204
+ },
205
+ {
206
+ id: "himalaya",
207
+ name: "himalaya (email CLI)",
208
+ desc: "Multi-account IMAP/SMTP email CLI — bring your own email accounts, configure after install",
209
+ check: () => hasCommand("himalaya"),
210
+ install: { macos: "brew install himalaya", linux: "cargo install himalaya" },
211
+ skipOnLinuxIf: "cargo",
212
+ },
213
+ ];
214
+
215
+ function hasCommand(name) {
216
+ try {
217
+ execSync(`command -v ${name}`, { stdio: "pipe" });
218
+ return true;
219
+ } catch {
220
+ return false;
221
+ }
222
+ }
223
+
224
+ function tryNpx(args) {
225
+ try {
226
+ execSync(`npx --no-install ${args}`, { stdio: "pipe", timeout: 5000 });
227
+ return true;
228
+ } catch {
229
+ return false;
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Make sure `brew` is reachable in the current process. The setup wizard
235
+ * may be running in a fresh shell right after `install.sh` installed brew
236
+ * for the first time — in that case `/opt/homebrew/bin` may not be in
237
+ * PATH yet. Side-loads it if we find the binary.
238
+ */
239
+ function ensureBrewOnPath() {
240
+ if (hasCommand("brew")) return true;
241
+ for (const candidate of ["/opt/homebrew/bin", "/usr/local/bin"]) {
242
+ if (existsSync(join(candidate, "brew"))) {
243
+ process.env.PATH = `${candidate}:${process.env.PATH || ""}`;
244
+ return true;
245
+ }
246
+ }
247
+ return false;
248
+ }
249
+
250
+ /**
251
+ * Print the status of optional tools without prompting. Used by
252
+ * `alvin-bot tools list` for inspection and scripting.
253
+ */
254
+ function listOptionalTools() {
255
+ const platform = process.platform === "darwin" ? "macos"
256
+ : process.platform === "linux" ? "linux"
257
+ : null;
258
+ console.log(`\n📦 Optional capability tools — ${platform || process.platform}\n`);
259
+ const states = OPTIONAL_TOOLS.map(t => ({ ...t, installed: t.check() }));
260
+ states.forEach((t, i) => {
261
+ const mark = t.installed ? "✓" : "·";
262
+ console.log(` [${String(i + 1).padStart(2)}] ${mark} ${t.name}`);
263
+ console.log(` ${t.desc}`);
264
+ });
265
+ const missing = states.filter(t => !t.installed).length;
266
+ console.log(`\n ${states.length - missing} installed, ${missing} missing.`);
267
+ if (missing > 0) {
268
+ console.log(` Install missing with: alvin-bot tools install\n`);
269
+ } else {
270
+ console.log("");
271
+ }
272
+ }
273
+
274
+ async function runOptionalToolsStep() {
275
+ // Skip on platforms we don't have install commands for.
276
+ const platform = process.platform === "darwin" ? "macos"
277
+ : process.platform === "linux" ? "linux"
278
+ : null;
279
+ if (!platform) {
280
+ return;
281
+ }
282
+
283
+ // On macOS, anything that uses brew needs brew on PATH.
284
+ if (platform === "macos") {
285
+ if (!ensureBrewOnPath()) {
286
+ console.log("\n ℹ️ Skipping optional tools step — Homebrew not found.");
287
+ console.log(" Install brew from https://brew.sh and re-run 'alvin-bot setup' to revisit.");
288
+ return;
289
+ }
290
+ }
291
+
292
+ console.log(`\n━━━ Optional capability tools ━━━\n`);
293
+ console.log("These CLIs significantly expand what skills can do. All optional — skip");
294
+ console.log("now and run 'alvin-bot setup' later to revisit. Already-installed tools");
295
+ console.log("are skipped automatically.\n");
296
+
297
+ const states = OPTIONAL_TOOLS.map(t => ({ ...t, installed: t.check() }));
298
+ states.forEach((t, i) => {
299
+ const mark = t.installed ? "✓" : " ";
300
+ console.log(` [${String(i + 1).padStart(2)}] ${mark} ${t.name}`);
301
+ console.log(` ${t.desc}`);
302
+ });
303
+ console.log(`\n ✓ = already installed (will be skipped)\n`);
304
+
305
+ const ans = (await ask(" Install which? Comma-separated numbers, [a]ll missing, [n]one [n]: ")).trim().toLowerCase();
306
+
307
+ let selected = [];
308
+ if (ans === "" || ans === "n" || ans === "none") {
309
+ console.log(" Skipped — no tools installed this round.\n");
310
+ return;
311
+ } else if (ans === "a" || ans === "all") {
312
+ selected = states.filter(t => !t.installed);
313
+ } else {
314
+ const indices = ans.split(",").map(s => parseInt(s.trim(), 10)).filter(n => Number.isFinite(n));
315
+ selected = indices.map(n => states[n - 1]).filter(t => t && !t.installed);
316
+ }
317
+
318
+ if (selected.length === 0) {
319
+ console.log(" Nothing to install — every pick was either invalid or already present.\n");
320
+ return;
321
+ }
322
+
323
+ const installed = [];
324
+ const failed = [];
325
+ for (const tool of selected) {
326
+ // Linux-only fallbacks: skip if a hard prereq is missing.
327
+ if (platform === "linux" && tool.skipOnLinuxIf && !hasCommand(tool.skipOnLinuxIf)) {
328
+ console.log(`\n ⚠️ Skipping ${tool.name} — needs '${tool.skipOnLinuxIf}' which isn't installed.`);
329
+ failed.push({ tool, reason: `missing prerequisite: ${tool.skipOnLinuxIf}` });
330
+ continue;
331
+ }
332
+ console.log(`\n📦 Installing ${tool.name}...`);
333
+ try {
334
+ if (typeof tool.install === "function") {
335
+ tool.install();
336
+ } else if (typeof tool.install === "object" && tool.install[platform]) {
337
+ execSync(tool.install[platform], { stdio: "inherit" });
338
+ } else {
339
+ console.log(` ⚠️ No install command for ${platform}. Skipping.`);
340
+ failed.push({ tool, reason: `no install method for ${platform}` });
341
+ continue;
342
+ }
343
+ installed.push(tool);
344
+ console.log(` ✅ ${tool.name} installed.`);
345
+ } catch (err) {
346
+ const msg = err && err.message ? err.message : String(err);
347
+ console.log(` ❌ ${tool.name} install failed: ${msg.split("\n")[0]}`);
348
+ console.log(` Bot setup will continue — retry this tool later with its native install command.`);
349
+ failed.push({ tool, reason: "install command failed" });
350
+ }
351
+ }
352
+
353
+ console.log("");
354
+ if (installed.length) {
355
+ console.log(` ✅ Installed: ${installed.map(t => t.name).join(", ")}`);
356
+ }
357
+ if (failed.length) {
358
+ console.log(` ⚠️ Skipped/failed: ${failed.map(f => f.tool.name).join(", ")}`);
359
+ }
360
+ }
361
+
131
362
  // ── Offline mode: Ollama + Gemma 4 E4B ─────────────────────────────────────
132
363
 
133
364
  /**
@@ -1197,6 +1428,11 @@ async function setup() {
1197
1428
  console.log(" ✅ CLAUDE.md initialized from example");
1198
1429
  }
1199
1430
 
1431
+ // ── Optional capability tools (curated list of useful CLIs) ──────────────
1432
+ // Offered after the core setup (Telegram + AI provider) is complete so the
1433
+ // user always gets a working bot first — tool installs are pure upside.
1434
+ await runOptionalToolsStep();
1435
+
1200
1436
  // ── Build (only for local/dev installs — global npm installs already have dist/)
1201
1437
  const isGlobalInstall = !existsSync(resolve(process.cwd(), "tsconfig.json"));
1202
1438
  if (!isGlobalInstall) {
@@ -1926,6 +2162,23 @@ switch (cmd) {
1926
2162
  case "doctor":
1927
2163
  doctor().catch(console.error);
1928
2164
  break;
2165
+ case "tools": {
2166
+ const sub = process.argv[3] || "list";
2167
+ if (sub === "list" || sub === "ls" || sub === "status") {
2168
+ listOptionalTools();
2169
+ } else if (sub === "install") {
2170
+ runOptionalToolsStep().then(() => closeRL()).catch(err => {
2171
+ console.error(err);
2172
+ closeRL();
2173
+ });
2174
+ } else {
2175
+ console.log("Usage: alvin-bot tools <list|install>");
2176
+ console.log("");
2177
+ console.log(" list — Show installed / missing optional tools.");
2178
+ console.log(" install — Interactive menu to install missing tools.");
2179
+ }
2180
+ break;
2181
+ }
1929
2182
  case "update":
1930
2183
  update().catch(console.error);
1931
2184
  break;
@@ -2288,6 +2541,7 @@ ${t("cli.commands")}
2288
2541
  doctor ${t("cli.doctorDesc")}
2289
2542
  audit Security health check (permissions, secrets, config)
2290
2543
  search Search your assets, memories, and skills
2544
+ tools List / install optional capability tools (ffmpeg, pandoc, …)
2291
2545
  browser Manage bot-owned Chromium (start/stop/goto/shot/doctor)
2292
2546
  update ${t("cli.updateDesc")}
2293
2547
  start ${t("cli.startDesc")} (background via PM2)
package/install.sh CHANGED
@@ -1,7 +1,18 @@
1
1
  #!/usr/bin/env bash
2
2
  # ─────────────────────────────────────────────────────────────
3
3
  # Alvin Bot — One-Line Installer
4
- # Usage: curl -fsSL https://install.alvin-bot.dev | bash
4
+ # Usage: curl -fsSL https://raw.githubusercontent.com/alvbln/Alvin-Bot/main/install.sh | bash
5
+ #
6
+ # This script bootstraps anything missing (Homebrew on macOS, Node.js)
7
+ # and then installs alvin-bot from npm — the same path documented in the
8
+ # README's Quick Start. It does NOT clone the git repo. The canonical
9
+ # install lives at:
10
+ # /opt/homebrew/lib/node_modules/alvin-bot/ (Apple Silicon)
11
+ # /usr/local/lib/node_modules/alvin-bot/ (Intel macOS / Linux)
12
+ #
13
+ # `~/.alvin-bot/` is the per-user data directory (env, memory, logs,
14
+ # cron-jobs.json) and is created/managed by the bot itself — never
15
+ # touched by this installer.
5
16
  # ─────────────────────────────────────────────────────────────
6
17
  set -euo pipefail
7
18
 
@@ -13,9 +24,6 @@ BLUE='\033[0;34m'
13
24
  BOLD='\033[1m'
14
25
  NC='\033[0m' # No Color
15
26
 
16
- INSTALL_DIR="$HOME/.alvin-bot"
17
- REPO_URL="https://github.com/alvbln/alvin-bot.git"
18
- BIN_LINK="/usr/local/bin/alvin-bot"
19
27
  MIN_NODE_VERSION=18
20
28
 
21
29
  # ─── Helpers ────────────────────────────────────────────────
@@ -25,6 +33,11 @@ ok() { echo -e "${GREEN}✔${NC} $*"; }
25
33
  warn() { echo -e "${YELLOW}⚠${NC} $*"; }
26
34
  fail() { echo -e "${RED}✘${NC} $*"; exit 1; }
27
35
 
36
+ # Is this shell interactive enough to prompt the user?
37
+ is_interactive() {
38
+ [ -t 0 ] && [ -t 1 ]
39
+ }
40
+
28
41
  # ─── OS Detection ──────────────────────────────────────────
29
42
 
30
43
  detect_os() {
@@ -42,34 +55,107 @@ detect_os() {
42
55
  ok "Detected OS: ${BOLD}$OS${NC}"
43
56
  }
44
57
 
45
- # ─── Dependency Checks ─────────────────────────────────────
58
+ # ─── Prereqs ────────────────────────────────────────────────
46
59
 
47
- check_git() {
48
- if ! command -v git &>/dev/null; then
49
- fail "Git is not installed. Please install git first:
50
- macOS: xcode-select --install
51
- Linux: sudo apt install git (or yum/pacman equivalent)
52
- WSL: sudo apt install git"
60
+ # Ensure Homebrew is available on macOS. Offers to auto-install via the
61
+ # official curl|bash sequence. A no-op on non-macOS systems.
62
+ ensure_brew() {
63
+ [ "$OS" = "macOS" ] || return 0
64
+ if command -v brew &>/dev/null; then
65
+ ok "Homebrew found: $(brew --version | head -1)"
66
+ return 0
53
67
  fi
54
- ok "Git found: $(git --version)"
68
+ warn "Homebrew is not installed."
69
+ if ! is_interactive; then
70
+ fail "Non-interactive shell — cannot prompt. Install Homebrew manually: https://brew.sh"
71
+ fi
72
+ echo ""
73
+ read -r -p " Install Homebrew automatically? [Y/n] " ans
74
+ case "${ans:-Y}" in
75
+ [Yy]*)
76
+ info "Installing Homebrew (the official installer may prompt for sudo)..."
77
+ # NONINTERACTIVE=1 tells brew's installer to skip the ENTER-to-confirm
78
+ # pause — the user already opted in here.
79
+ NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" \
80
+ || fail "Homebrew install failed. See https://brew.sh for manual instructions."
81
+ # Add brew to PATH for the remainder of this script.
82
+ if [ -x /opt/homebrew/bin/brew ]; then
83
+ eval "$(/opt/homebrew/bin/brew shellenv)"
84
+ elif [ -x /usr/local/bin/brew ]; then
85
+ eval "$(/usr/local/bin/brew shellenv)"
86
+ fi
87
+ ok "Homebrew installed."
88
+ ;;
89
+ *)
90
+ fail "Cannot continue without Homebrew. Install manually: https://brew.sh"
91
+ ;;
92
+ esac
55
93
  }
56
94
 
57
- check_node() {
58
- if ! command -v node &>/dev/null; then
59
- fail "Node.js is not installed (>= $MIN_NODE_VERSION required).
60
- Install via: https://nodejs.org or
61
- macOS: brew install node@22
62
- Linux: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt install -y nodejs"
95
+ # Ensure Node.js >= MIN_NODE_VERSION. Offers to install via brew (macOS) or
96
+ # NodeSource (Debian/Ubuntu) if missing or too old.
97
+ ensure_node() {
98
+ if command -v node &>/dev/null; then
99
+ local node_ver
100
+ node_ver=$(node -v | sed 's/^v//' | cut -d. -f1)
101
+ if [ "$node_ver" -ge "$MIN_NODE_VERSION" ]; then
102
+ ok "Node.js found: $(node -v)"
103
+ return 0
104
+ fi
105
+ warn "Node.js v$node_ver is too old (need >= $MIN_NODE_VERSION)."
106
+ else
107
+ warn "Node.js is not installed."
63
108
  fi
64
109
 
65
- local node_ver
66
- node_ver=$(node -v | sed 's/^v//' | cut -d. -f1)
67
- if [ "$node_ver" -lt "$MIN_NODE_VERSION" ]; then
68
- fail "Node.js >= $MIN_NODE_VERSION required, but found v$(node -v). Please upgrade."
110
+ if ! is_interactive; then
111
+ fail "Non-interactive shell install Node >= $MIN_NODE_VERSION manually: https://nodejs.org"
69
112
  fi
70
- ok "Node.js found: $(node -v)"
113
+
114
+ case "$OS" in
115
+ macOS)
116
+ ensure_brew
117
+ echo ""
118
+ read -r -p " Install Node via Homebrew? [Y/n] " ans
119
+ case "${ans:-Y}" in
120
+ [Yy]*)
121
+ info "Installing Node (latest LTS via brew)..."
122
+ brew install node || fail "brew install node failed."
123
+ ok "Node installed: $(node -v)"
124
+ ;;
125
+ *)
126
+ fail "Cannot continue without Node. Install manually: https://nodejs.org"
127
+ ;;
128
+ esac
129
+ ;;
130
+ Linux|WSL)
131
+ if command -v apt-get &>/dev/null; then
132
+ echo ""
133
+ warn "About to install Node 22 from the official NodeSource repo. This needs sudo."
134
+ read -r -p " Continue? [Y/n] " ans
135
+ case "${ans:-Y}" in
136
+ [Yy]*)
137
+ info "Adding NodeSource repo..."
138
+ curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - \
139
+ || fail "NodeSource setup failed."
140
+ info "Installing nodejs..."
141
+ sudo apt-get install -y nodejs || fail "apt-get install nodejs failed."
142
+ ok "Node installed: $(node -v)"
143
+ ;;
144
+ *)
145
+ fail "Cannot continue without Node. Install manually: https://nodejs.org"
146
+ ;;
147
+ esac
148
+ else
149
+ fail "Auto-install on Linux only supports apt-based distros. Install Node manually: https://nodejs.org"
150
+ fi
151
+ ;;
152
+ *)
153
+ fail "Cannot bootstrap Node on $OS — install manually."
154
+ ;;
155
+ esac
71
156
  }
72
157
 
158
+ # npm comes bundled with node, but verify just in case.
73
159
  check_npm() {
74
160
  if ! command -v npm &>/dev/null; then
75
161
  fail "npm is not installed. It should come with Node.js — please reinstall Node."
@@ -80,50 +166,28 @@ check_npm() {
80
166
  # ─── Installation ──────────────────────────────────────────
81
167
 
82
168
  install_bot() {
83
- if [ -d "$INSTALL_DIR" ]; then
84
- warn "Existing installation found at $INSTALL_DIR"
85
- info "Updating..."
86
- cd "$INSTALL_DIR"
87
- git pull --ff-only || fail "Git pull failed. Resolve conflicts manually in $INSTALL_DIR"
169
+ info "Installing alvin-bot from the npm registry..."
170
+ # Try a regular global install first. If that fails with EACCES because
171
+ # the user's npm prefix isn't writable (common on Linux where node was
172
+ # installed via apt to /usr/lib), retry with sudo if available.
173
+ if npm install -g alvin-bot 2>&1; then
174
+ :
175
+ elif command -v sudo &>/dev/null; then
176
+ warn "npm install failed without sudo — retrying with sudo (you may be prompted)..."
177
+ sudo npm install -g alvin-bot || fail "npm install -g alvin-bot failed even with sudo."
88
178
  else
89
- info "Cloning alvin-bot to $INSTALL_DIR..."
90
- git clone --depth 1 "$REPO_URL" "$INSTALL_DIR" || fail "Git clone failed. Check your network."
91
- cd "$INSTALL_DIR"
179
+ fail "npm install -g alvin-bot failed. Re-run with sudo, or fix your npm prefix permissions."
92
180
  fi
93
181
 
94
- info "Installing production dependencies..."
95
- npm install --omit=dev || fail "npm install failed."
96
-
97
- info "Building TypeScript..."
98
- npm run build || fail "Build failed."
99
-
100
- ok "Installation complete!"
101
- }
102
-
103
- create_symlink() {
104
- local bin_dir
105
- bin_dir=$(dirname "$BIN_LINK")
106
-
107
- # Make CLI executable
108
- chmod +x "$INSTALL_DIR/bin/cli.js"
109
-
110
- # Try /usr/local/bin first, fall back to ~/.local/bin
111
- if [ -w "$bin_dir" ] || [ -w "$BIN_LINK" ] 2>/dev/null; then
112
- ln -sf "$INSTALL_DIR/bin/cli.js" "$BIN_LINK"
113
- ok "Symlinked: alvin-bot → $BIN_LINK"
114
- elif command -v sudo &>/dev/null; then
115
- info "Creating symlink (requires sudo)..."
116
- sudo ln -sf "$INSTALL_DIR/bin/cli.js" "$BIN_LINK"
117
- ok "Symlinked: alvin-bot → $BIN_LINK"
118
- else
119
- # Fallback: ~/.local/bin
120
- local fallback="$HOME/.local/bin"
121
- mkdir -p "$fallback"
122
- ln -sf "$INSTALL_DIR/bin/cli.js" "$fallback/alvin-bot"
123
- warn "Symlinked to $fallback/alvin-bot (add to PATH if not already)"
124
- warn " export PATH=\"$fallback:\$PATH\""
125
- BIN_LINK="$fallback/alvin-bot"
182
+ # Verify the binary is reachable. On Apple Silicon with Homebrew, the
183
+ # global bin is /opt/homebrew/bin we already PATH-loaded brew above
184
+ # via ensure_brew, so this should just work.
185
+ if ! command -v alvin-bot &>/dev/null; then
186
+ warn "alvin-bot installed, but not on PATH yet. Run 'npm prefix -g' to find the bin dir,"
187
+ warn "and add it to your shell PATH. Then re-open this terminal."
188
+ return 0
126
189
  fi
190
+ ok "alvin-bot installed: $(alvin-bot version 2>/dev/null | head -1)"
127
191
  }
128
192
 
129
193
  # ─── Main ──────────────────────────────────────────────────
@@ -135,13 +199,11 @@ main() {
135
199
  echo ""
136
200
 
137
201
  detect_os
138
- check_git
139
- check_node
202
+ ensure_node
140
203
  check_npm
141
204
 
142
205
  echo ""
143
206
  install_bot
144
- create_symlink
145
207
 
146
208
  echo ""
147
209
  echo -e "─────────────────────────────────────"
@@ -152,14 +214,22 @@ main() {
152
214
  echo -e " ${BOLD}alvin-bot start${NC} — Start the bot"
153
215
  echo -e " ${BOLD}alvin-bot --help${NC} — Show all commands"
154
216
  echo ""
155
- echo -e " Installed to: ${BLUE}$INSTALL_DIR${NC}"
156
- echo -e " Command: ${BLUE}$BIN_LINK${NC}"
157
- echo ""
158
217
 
159
- # Run interactive setup
160
- info "Starting setup wizard..."
161
- echo ""
162
- alvin-bot setup || warn "Setup skipped. Run 'alvin-bot setup' later to configure."
218
+ # Run interactive setup right away when we have a TTY. In piped/non-
219
+ # interactive mode (`curl ... | bash`) skip — the user can run setup
220
+ # themselves whenever they're ready.
221
+ if is_interactive; then
222
+ info "Starting setup wizard..."
223
+ echo ""
224
+ alvin-bot setup || warn "Setup skipped. Run 'alvin-bot setup' later to configure."
225
+ else
226
+ info "Non-interactive install detected — skipping the setup wizard."
227
+ info "Open a fresh terminal and run: alvin-bot setup"
228
+ fi
163
229
  }
164
230
 
165
- main "$@"
231
+ # Allow this file to be sourced (e.g. for testing helpers) without
232
+ # triggering main(). When executed directly, BASH_SOURCE[0] equals $0.
233
+ if [[ "${BASH_SOURCE[0]:-$0}" == "${0}" ]]; then
234
+ main "$@"
235
+ fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alvin-bot",
3
- "version": "4.22.3",
3
+ "version": "4.23.1",
4
4
  "description": "Alvin Bot — Your personal AI agent on Telegram, WhatsApp, Discord, Signal, and Web.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
Binary file