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 +46 -0
- package/bin/cli.js +254 -0
- package/install.sh +144 -74
- package/package.json +1 -1
- package/alvin-bot-4.22.3.tgz +0 -0
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://
|
|
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
|
-
# ───
|
|
58
|
+
# ─── Prereqs ────────────────────────────────────────────────
|
|
46
59
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
package/alvin-bot-4.22.3.tgz
DELETED
|
Binary file
|