alvin-bot 4.22.2 → 4.23.0

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,45 @@
2
2
 
3
3
  All notable changes to Alvin Bot are documented here.
4
4
 
5
+ ## [4.23.0] — 2026-05-12
6
+
7
+ ### Installer: bootstrap Node automatically + optional capability tools
8
+
9
+ 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.
10
+
11
+ - **`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.
12
+
13
+ - **`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.
14
+
15
+ - **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.
16
+
17
+ Tools offered (curated, all generic — none require maintainer-specific creds or workflows):
18
+
19
+ | Tool | What it unlocks |
20
+ |---|---|
21
+ | Playwright + Chromium | Tier 1 stealth browser automation (web-research, social-fetch, browser-manager) |
22
+ | agent-browser CLI | Tier 1.5 token-efficient web automation (Vercel Labs) |
23
+ | ffmpeg | Audio/video processing for media-transcribe, video-generate, voice features |
24
+ | ImageMagick | Image conversion & manipulation for image-generate and visual skills |
25
+ | Pandoc | Markdown ↔ PDF/DOCX/HTML conversion for document-creation skill |
26
+ | ripgrep | Fast file/code search (`alvin-bot search`, code-aware skills) |
27
+ | jq | JSON parsing for helper scripts |
28
+ | himalaya | Multi-account IMAP/SMTP email CLI (configure after install) |
29
+
30
+ ## [4.22.3] — 2026-05-12
31
+
32
+ ### Fixed
33
+
34
+ - **Codex CLI provider:** close stdin after spawn so `codex exec` returns immediately. The provider opened stdin as a pipe but never sent EOF — `codex exec` then printed `Reading additional input from stdin...` and blocked until the 120 s spawn timeout, which surfaced on the chat side as "no reply" / empty Telegram messages whenever a user picked Codex CLI as their AI provider. Single-line fix (`proc.stdin.end()`) plus an explanatory comment. ([#1](https://github.com/alvbln/Alvin-Bot/pull/1))
35
+
36
+ ### macOS UX: surface Full Disk Access gaps under launchd ([#2](https://github.com/alvbln/Alvin-Bot/pull/2))
37
+
38
+ When the bot runs as a LaunchAgent, macOS TCC binds permissions to the `node` binary's real Cellar path. Anything the bot spawns (Codex CLI, file-reading skills, plugins touching `~/Documents`/`~/Desktop`) inherits that identity, and without Full Disk Access on `node` those reads silently fail — no dialog appears under launchd. Fresh public users were hitting this without knowing what was going on; the failure mode (spawned tools producing empty output) looked like a bot bug.
39
+
40
+ - **`alvin-bot launchd install`** now detects FDA after a successful install and either confirms it's granted or prints a prominent warning with: the exact Cellar path to add (resolved via `realpathSync`), the `open "x-apple.systempreferences:..."` command for the right pane, the `launchctl kickstart` command to apply the grant, and a heads-up that `brew upgrade node` invalidates the grant (TCC binds to the versioned Cellar path).
41
+ - **`alvin-bot doctor`** gains a "macOS permissions" section that shows FDA status and how to fix — useful after `brew upgrade node` rolls forward to a new Cellar path and the old grant becomes stale.
42
+ - **README** "A note on permission prompts" is extended with the launchd/FDA caveat, linking to the macOS Setup Guide PDF.
43
+
5
44
  ## [4.22.2] — 2026-05-11
6
45
 
7
46
  ### Docs
package/README.md CHANGED
@@ -68,6 +68,8 @@ Free AI providers available — no credit card needed. **Privacy-first?** Pick t
68
68
 
69
69
  The first time Alvin reaches for a new tool — a shell command, a file read, a web fetch — you may see a permission prompt from the underlying agent runtime asking whether to allow it. Those prompts come from Alvin himself, not from a third party. Approving one expands what he can do for you autonomously; denying keeps the scope narrow. The more you allow, the more capable and hands-off he becomes — you stay in control either way, and you can always revoke a permission later.
70
70
 
71
+ **macOS only — one extra step under launchd.** If you install Alvin as a background service (`alvin-bot launchd install`), macOS won't be able to show you those permission dialogs interactively anymore. To let the bot and anything it spawns (Codex CLI, file-reading skills) actually read your files, grant **Full Disk Access** to `node` once: System Settings → Privacy & Security → Full Disk Access → **+** → add `/opt/homebrew/Cellar/node/<version>/bin/node` (find the exact path with `readlink -f "$(which node)"`). `alvin-bot launchd install` and `alvin-bot doctor` will both detect and remind you with the exact path. After `brew upgrade node` you'll need to re-grant, because TCC binds to the versioned Cellar path. The printable [macOS Setup Guide PDF](https://github.com/alvbln/Alvin-Bot/releases/latest/download/Alvin-Bot-macOS-Setup-Guide.pdf) covers this end-to-end.
72
+
71
73
  ### 📘 First-time setup walkthroughs
72
74
 
73
75
  Step-by-step printable PDF guides:
package/bin/cli.js CHANGED
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  import { createInterface } from "readline";
20
- import { existsSync, writeFileSync, readFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
20
+ import { existsSync, writeFileSync, readFileSync, mkdirSync, copyFileSync, readdirSync, statSync, accessSync, realpathSync, constants as fsConstants } from "fs";
21
21
  import { resolve, join } from "path";
22
22
  import { homedir } from "os";
23
23
  import { execSync } from "child_process";
@@ -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) {
@@ -1515,6 +1751,21 @@ async function doctor() {
1515
1751
  console.log(` ${hasChrome ? "✅" : "⚠️ "} WhatsApp (Chrome: ${hasChrome ? "found" : "not found"})`);
1516
1752
  }
1517
1753
 
1754
+ // ── macOS permissions (TCC) ────────────────────────────────────────────
1755
+ if (process.platform === "darwin") {
1756
+ console.log("\n macOS permissions:");
1757
+ const fda = checkMacosFullDiskAccess(process.execPath);
1758
+ if (fda.hasFDA) {
1759
+ console.log(` ✅ Full Disk Access — granted to ${fda.realNodePath}`);
1760
+ } else {
1761
+ console.log(` ⚠️ Full Disk Access NOT granted to node (${fda.realNodePath})`);
1762
+ console.log(` Spawned tools (codex CLI, file-reading skills) may silently fail`);
1763
+ console.log(` to read protected directories under launchd. To fix:`);
1764
+ console.log(` open "${fda.paneUrl}"`);
1765
+ console.log(` and add the path above with the "+" button.`);
1766
+ }
1767
+ }
1768
+
1518
1769
  console.log("");
1519
1770
  }
1520
1771
 
@@ -1557,6 +1808,57 @@ async function version() {
1557
1808
 
1558
1809
  // ── LaunchAgent helpers (macOS only) ────────────────────────────────────────
1559
1810
 
1811
+ /**
1812
+ * Inspect the macOS Full Disk Access (TCC) state for the node binary that
1813
+ * the bot will run under.
1814
+ *
1815
+ * Background: macOS TCC grants permissions to specific binary paths, not to
1816
+ * user accounts. When `alvin-bot` runs under launchd, the bot process is
1817
+ * `node` from Homebrew (e.g. /opt/homebrew/Cellar/node/<version>/bin/node).
1818
+ * Any child process the bot spawns — `codex`, `ripgrep`, plugins reading
1819
+ * ~/Documents, the file-manager skill — inherits the parent's TCC identity.
1820
+ * If `node` doesn't have Full Disk Access, those children silently fail to
1821
+ * read protected directories (no dialog appears under launchd).
1822
+ *
1823
+ * Heuristic for "FDA granted": try to read `~/Library/Mail`, which lives in
1824
+ * the TCC-protected user-data quarantine. Successful read ⇒ FDA is on.
1825
+ * This matches `src/services/sudo.ts:getSudoStatus()`.
1826
+ *
1827
+ * Returns { realNodePath, hasFDA, panEUrl, settingsCommand } so callers can
1828
+ * print actionable guidance.
1829
+ */
1830
+ function checkMacosFullDiskAccess(nodePath) {
1831
+ if (process.platform !== "darwin") {
1832
+ return { skipped: true };
1833
+ }
1834
+
1835
+ // Resolve symlinks — TCC tracks the real binary path, not the symlink.
1836
+ // /opt/homebrew/bin/node is typically a symlink into the Cellar.
1837
+ let realNodePath = nodePath;
1838
+ try {
1839
+ realNodePath = realpathSync(nodePath);
1840
+ } catch {
1841
+ // Fall back to the symlink path if resolution fails.
1842
+ }
1843
+
1844
+ let hasFDA = false;
1845
+ try {
1846
+ accessSync(resolve(homedir(), "Library", "Mail"), fsConstants.R_OK);
1847
+ hasFDA = true;
1848
+ } catch {
1849
+ // Either Mail isn't there (very rare) or FDA is missing. Default to
1850
+ // "missing" — false negatives just trigger a warning the user can ignore.
1851
+ hasFDA = false;
1852
+ }
1853
+
1854
+ return {
1855
+ skipped: false,
1856
+ realNodePath,
1857
+ hasFDA,
1858
+ paneUrl: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles",
1859
+ };
1860
+ }
1861
+
1560
1862
  /**
1561
1863
  * Render the launchd plist that runs `node dist/index.js` as a per-user
1562
1864
  * agent. Inherits the GUI login session so the macOS Keychain is
@@ -1726,6 +2028,40 @@ async function launchdInstall() {
1726
2028
  console.log("");
1727
2029
  console.log("💡 pm2 still has other projects running — leaving it installed.");
1728
2030
  }
2031
+
2032
+ // ── Full Disk Access reminder ────────────────────────────────────────────
2033
+ //
2034
+ // Under launchd, the bot has no foreground Terminal to inherit TCC from.
2035
+ // Anything it spawns (codex CLI, file-reading skills, etc.) needs `node`
2036
+ // itself to have Full Disk Access — otherwise reads silently fail.
2037
+ const fda = checkMacosFullDiskAccess(nodePath);
2038
+ if (!fda.skipped && !fda.hasFDA) {
2039
+ console.log("");
2040
+ console.log("⚠️ Full Disk Access not granted to node.");
2041
+ console.log("");
2042
+ console.log(" Under launchd, the bot can't ask for permission dialogs interactively.");
2043
+ console.log(" Without FDA, anything the bot spawns (codex CLI, file-reading skills,");
2044
+ console.log(" ~/Documents access, …) will silently fail to read protected files.");
2045
+ console.log("");
2046
+ console.log(" Grant once: System Settings → Privacy & Security → Full Disk Access → +");
2047
+ console.log(" Then add this exact path (⌘⇧G to paste it):");
2048
+ console.log("");
2049
+ console.log(` ${fda.realNodePath}`);
2050
+ console.log("");
2051
+ console.log(" Open the right pane now:");
2052
+ console.log(` open "${fda.paneUrl}"`);
2053
+ console.log("");
2054
+ console.log(" After granting, restart the bot to pick up the new permission:");
2055
+ console.log(` launchctl kickstart -k gui/$UID/${label}`);
2056
+ console.log("");
2057
+ console.log(" Note: re-grant is required after `brew upgrade node` — TCC binds to");
2058
+ console.log(" the Cellar version path, which changes when node is upgraded.");
2059
+ } else if (!fda.skipped && fda.hasFDA) {
2060
+ console.log("");
2061
+ console.log("✅ Full Disk Access already granted to node — spawned tools can read");
2062
+ console.log(` protected files. (Granted path: ${fda.realNodePath})`);
2063
+ }
2064
+
1729
2065
  process.exit(0);
1730
2066
  }
1731
2067
 
@@ -1826,6 +2162,23 @@ switch (cmd) {
1826
2162
  case "doctor":
1827
2163
  doctor().catch(console.error);
1828
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
+ }
1829
2182
  case "update":
1830
2183
  update().catch(console.error);
1831
2184
  break;
@@ -2188,6 +2541,7 @@ ${t("cli.commands")}
2188
2541
  doctor ${t("cli.doctorDesc")}
2189
2542
  audit Security health check (permissions, secrets, config)
2190
2543
  search Search your assets, memories, and skills
2544
+ tools List / install optional capability tools (ffmpeg, pandoc, …)
2191
2545
  browser Manage bot-owned Chromium (start/stop/goto/shot/doctor)
2192
2546
  update ${t("cli.updateDesc")}
2193
2547
  start ${t("cli.startDesc")} (background via PM2)
@@ -91,6 +91,12 @@ export class CodexCLIProvider {
91
91
  timeout: 120_000,
92
92
  env: { ...process.env, NO_COLOR: "1" },
93
93
  });
94
+ // Close stdin so `codex exec` doesn't wait for additional input — the
95
+ // prompt is passed as a positional arg, no stdin payload follows.
96
+ // Without this, codex prints "Reading additional input from stdin..."
97
+ // and blocks until the 120s timeout, causing the bot to return
98
+ // "keine Antwort" / empty replies on the chat side.
99
+ proc.stdin.end();
94
100
  let stdout = "";
95
101
  let stderr = "";
96
102
  proc.stdout.on("data", (data) => {
package/install.sh CHANGED
@@ -54,22 +54,111 @@ check_git() {
54
54
  ok "Git found: $(git --version)"
55
55
  }
56
56
 
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"
57
+ # Is this shell interactive enough to prompt the user?
58
+ is_interactive() {
59
+ [ -t 0 ] && [ -t 1 ]
60
+ }
61
+
62
+ # Ensure Homebrew is available on macOS. Offers to auto-install via the
63
+ # official curl|bash sequence. A no-op on non-macOS systems.
64
+ ensure_brew() {
65
+ [ "$OS" = "macOS" ] || return 0
66
+ if command -v brew &>/dev/null; then
67
+ ok "Homebrew found: $(brew --version | head -1)"
68
+ return 0
69
+ fi
70
+ warn "Homebrew is not installed."
71
+ if ! is_interactive; then
72
+ fail "Non-interactive shell — cannot prompt. Install Homebrew manually: https://brew.sh"
63
73
  fi
74
+ echo ""
75
+ read -r -p " Install Homebrew automatically? [Y/n] " ans
76
+ case "${ans:-Y}" in
77
+ [Yy]*)
78
+ info "Installing Homebrew (the official installer may prompt for sudo)..."
79
+ # NONINTERACTIVE=1 tells brew's installer to skip the ENTER-to-confirm
80
+ # pause — the user already opted in here.
81
+ NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" \
82
+ || fail "Homebrew install failed. See https://brew.sh for manual instructions."
83
+ # Add brew to PATH for the remainder of this script.
84
+ if [ -x /opt/homebrew/bin/brew ]; then
85
+ eval "$(/opt/homebrew/bin/brew shellenv)"
86
+ elif [ -x /usr/local/bin/brew ]; then
87
+ eval "$(/usr/local/bin/brew shellenv)"
88
+ fi
89
+ ok "Homebrew installed."
90
+ ;;
91
+ *)
92
+ fail "Cannot continue without Homebrew. Install manually: https://brew.sh"
93
+ ;;
94
+ esac
95
+ }
64
96
 
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."
97
+ # Ensure Node.js >= MIN_NODE_VERSION. Offers to install via brew (macOS) or
98
+ # NodeSource (Debian/Ubuntu) if missing or too old. Falls back to a clear
99
+ # manual-install message on unsupported platforms.
100
+ ensure_node() {
101
+ if command -v node &>/dev/null; then
102
+ local node_ver
103
+ node_ver=$(node -v | sed 's/^v//' | cut -d. -f1)
104
+ if [ "$node_ver" -ge "$MIN_NODE_VERSION" ]; then
105
+ ok "Node.js found: $(node -v)"
106
+ return 0
107
+ fi
108
+ warn "Node.js v$node_ver is too old (need >= $MIN_NODE_VERSION)."
109
+ else
110
+ warn "Node.js is not installed."
69
111
  fi
70
- ok "Node.js found: $(node -v)"
112
+
113
+ if ! is_interactive; then
114
+ fail "Non-interactive shell — install Node >= $MIN_NODE_VERSION manually: https://nodejs.org"
115
+ fi
116
+
117
+ case "$OS" in
118
+ macOS)
119
+ ensure_brew
120
+ echo ""
121
+ read -r -p " Install Node via Homebrew? [Y/n] " ans
122
+ case "${ans:-Y}" in
123
+ [Yy]*)
124
+ info "Installing Node (latest LTS via brew)..."
125
+ brew install node || fail "brew install node failed."
126
+ ok "Node installed: $(node -v)"
127
+ ;;
128
+ *)
129
+ fail "Cannot continue without Node. Install manually: https://nodejs.org"
130
+ ;;
131
+ esac
132
+ ;;
133
+ Linux|WSL)
134
+ if command -v apt-get &>/dev/null; then
135
+ echo ""
136
+ warn "About to install Node 22 from the official NodeSource repo. This needs sudo."
137
+ read -r -p " Continue? [Y/n] " ans
138
+ case "${ans:-Y}" in
139
+ [Yy]*)
140
+ info "Adding NodeSource repo..."
141
+ curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - \
142
+ || fail "NodeSource setup failed."
143
+ info "Installing nodejs..."
144
+ sudo apt-get install -y nodejs || fail "apt-get install nodejs failed."
145
+ ok "Node installed: $(node -v)"
146
+ ;;
147
+ *)
148
+ fail "Cannot continue without Node. Install manually: https://nodejs.org"
149
+ ;;
150
+ esac
151
+ else
152
+ fail "Auto-install on Linux only supports apt-based distros. Install Node manually: https://nodejs.org"
153
+ fi
154
+ ;;
155
+ *)
156
+ fail "Cannot bootstrap Node on $OS — install manually."
157
+ ;;
158
+ esac
71
159
  }
72
160
 
161
+ # npm comes bundled with node, but verify just in case.
73
162
  check_npm() {
74
163
  if ! command -v npm &>/dev/null; then
75
164
  fail "npm is not installed. It should come with Node.js — please reinstall Node."
@@ -136,7 +225,7 @@ main() {
136
225
 
137
226
  detect_os
138
227
  check_git
139
- check_node
228
+ ensure_node
140
229
  check_npm
141
230
 
142
231
  echo ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alvin-bot",
3
- "version": "4.22.2",
3
+ "version": "4.23.0",
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",
package/02_yandex.png DELETED
Binary file