alvin-bot 4.4.4 → 4.4.6
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 +57 -0
- package/bin/cli.js +188 -12
- package/dist/services/security-audit.js +11 -3
- package/package.json +1 -1
- package/BACKLOG.md +0 -223
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,63 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Alvin Bot are documented here.
|
|
4
4
|
|
|
5
|
+
## [4.4.6] — 2026-04-09
|
|
6
|
+
|
|
7
|
+
### 🐛 Bug Fixes
|
|
8
|
+
|
|
9
|
+
**`alvin-bot audit` now reads `.env` from `DATA_DIR`** — Before this release, `audit` was a subprocess that never loaded the bot's config: it only inspected `process.env`, which for an ad-hoc CLI invocation is the shell environment, not the bot's actual runtime state. Result: `ALLOWED_USERS` and `WEB_PASSWORD` were always reported as "not set" even when the bot was correctly configured and running. `audit` now calls `dotenv.config({ path: ENV_FILE })` at the start of `runAudit()` so its output matches `alvin-bot doctor` and the actual engine.
|
|
10
|
+
|
|
11
|
+
**`alvin-bot doctor` no longer hangs indefinitely on missing `.env`** — The CLI's `readline` interface was created eagerly at module load, which made `stdin` readable for the entire process lifetime. Commands like `doctor`, `audit`, `version` that have no interactive prompts would therefore never terminate — even though the `doctor()` function correctly early-returned when `.env` was missing, `node` refused to exit because the event loop still saw stdin as a live resource. Readline is now lazy-created only when `ask()` is actually called. Measured improvement: **doctor with missing .env terminates in 82 ms** (previously: 20+ second hang, often requiring Ctrl+C).
|
|
12
|
+
|
|
13
|
+
**`validateProviderKey("claude-sdk", …)` no longer false-negatives on Agent SDK auth** — The CLI's Claude check ran `claude auth status` and hard-failed on `loggedIn: false`. But the Claude Agent SDK has multiple auth paths that the CLI doesn't see: `ANTHROPIC_API_KEY` env var, Claude Code IDE sessions, and native-binary session cookies. Real-world example: a bot that was actively answering Telegram messages correctly was reported as "❌ Claude CLI not authenticated" by `doctor`. The validation is now:
|
|
14
|
+
- `ANTHROPIC_API_KEY` set → `ok: true` (immediate pass, CLI irrelevant)
|
|
15
|
+
- `claude` binary present + `auth status: loggedIn: true` → `ok: true`
|
|
16
|
+
- `claude` binary present + `auth status: loggedIn: false` → `ok: true` with a **warning** (the Agent SDK may still work via session / env var; user is advised to run `claude auth login` only if the bot fails to respond)
|
|
17
|
+
- `claude` binary missing → `ok: false` (hard error with install hint)
|
|
18
|
+
|
|
19
|
+
`doctor` now renders the warning as ⚠️ instead of ❌, making the output match actual behavior.
|
|
20
|
+
|
|
21
|
+
### ✨ New Feature
|
|
22
|
+
|
|
23
|
+
**`alvin-bot setup --non-interactive` for CI, Docker, and scripted installs** — The interactive setup wizard was the only way to write `~/.alvin-bot/.env`, which blocked automated provisioning. Now supports flag-driven, non-interactive setup:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
alvin-bot setup --non-interactive \
|
|
27
|
+
--bot-token=123456789:AAE... \
|
|
28
|
+
--allowed-users=12345,67890 \
|
|
29
|
+
--primary-provider=claude-sdk \
|
|
30
|
+
--fallback-providers=ollama \
|
|
31
|
+
--groq-key=gsk_... \
|
|
32
|
+
--google-key=AIza... \
|
|
33
|
+
--openai-key=sk-... \
|
|
34
|
+
--nvidia-key=nvapi-... \
|
|
35
|
+
--anthropic-key=sk-ant-... \
|
|
36
|
+
--openrouter-key=sk-or-... \
|
|
37
|
+
--web-password=... \
|
|
38
|
+
--platform=telegram \
|
|
39
|
+
--skip-validation # optional, skips the live Telegram getMe call
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- Refuses to overwrite an existing `.env` (exits 1 with a clear message).
|
|
43
|
+
- Writes with mode `0600`.
|
|
44
|
+
- Validates `--bot-token` format and `--allowed-users` numeric format before writing.
|
|
45
|
+
- Optionally pings Telegram `getMe` unless `--skip-validation` is passed.
|
|
46
|
+
|
|
47
|
+
`-y` and `--yes` work as aliases for `--non-interactive`.
|
|
48
|
+
|
|
49
|
+
## [4.4.5] — 2026-04-09
|
|
50
|
+
|
|
51
|
+
### 🔐 Security / Information Disclosure
|
|
52
|
+
|
|
53
|
+
**`BACKLOG.md` removed from published tarball** — The project's internal roadmap was listed in `.gitignore` but not in `.npmignore`, so every `npm install -g alvin-bot` shipped an 8.7 KB file containing the full list of open P0/P1 issues, including known-but-unpatched security weaknesses (WebSocket auth gap, tool-executor sandbox gaps, Web UI HTTP-only, etc.). A published backlog of known vulnerabilities is effectively an attack roadmap for anyone inspecting the package.
|
|
54
|
+
|
|
55
|
+
`BACKLOG.md` is now listed in `.npmignore` alongside `CLAUDE.md`, `SOUL.md`, and `TOOLS.md`. Verified with `npm pack --dry-run`: the file no longer appears in the tarball.
|
|
56
|
+
|
|
57
|
+
Users on `4.4.4` or earlier should update:
|
|
58
|
+
```bash
|
|
59
|
+
npm update -g alvin-bot
|
|
60
|
+
```
|
|
61
|
+
|
|
5
62
|
## [4.4.4] — 2026-04-09
|
|
6
63
|
|
|
7
64
|
### 🔐 Security / Data Layout
|
package/bin/cli.js
CHANGED
|
@@ -29,8 +29,20 @@ const DATA_DIR = process.env.ALVIN_DATA_DIR || join(homedir(), ".alvin-bot");
|
|
|
29
29
|
// Init i18n early
|
|
30
30
|
initI18n();
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
// Lazy-create the readline interface. If we create it eagerly, stdin becomes
|
|
33
|
+
// "active" and Node refuses to exit even when a command like `doctor` has
|
|
34
|
+
// finished synchronously. Before v4.4.6 this caused `alvin-bot doctor` to
|
|
35
|
+
// hang indefinitely when .env was missing — early-return worked, but the
|
|
36
|
+
// process couldn't terminate. Creating rl only when `ask()` is actually
|
|
37
|
+
// called keeps non-interactive commands (audit/doctor/version/start/stop)
|
|
38
|
+
// terminating cleanly.
|
|
39
|
+
let rl = null;
|
|
40
|
+
const ensureRL = () => {
|
|
41
|
+
if (!rl) rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
42
|
+
return rl;
|
|
43
|
+
};
|
|
44
|
+
const closeRL = () => { if (rl) { rl.close(); rl = null; } };
|
|
45
|
+
const ask = (q) => new Promise((r) => ensureRL().question(q, r));
|
|
34
46
|
|
|
35
47
|
const LOGO = `
|
|
36
48
|
╔══════════════════════════════════════╗
|
|
@@ -164,6 +176,17 @@ async function validateProviderKey(providerKey, apiKey) {
|
|
|
164
176
|
}
|
|
165
177
|
|
|
166
178
|
case "claude-sdk": {
|
|
179
|
+
// The Claude Agent SDK can authenticate through multiple paths that
|
|
180
|
+
// `claude auth status` does not see:
|
|
181
|
+
// 1. ANTHROPIC_API_KEY env var (bypasses CLI entirely)
|
|
182
|
+
// 2. An active Claude Code session (IDE-initiated, not via `auth login`)
|
|
183
|
+
// 3. Native binary session cookies
|
|
184
|
+
// Prior to v4.4.6 this check hard-failed on `loggedIn: false` even when
|
|
185
|
+
// the engine ran fine — confusing users whose bot was actually working.
|
|
186
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
187
|
+
return { ok: true, detail: "Claude SDK via ANTHROPIC_API_KEY env var" };
|
|
188
|
+
}
|
|
189
|
+
|
|
167
190
|
// Find claude binary — check PATH and common locations
|
|
168
191
|
let claudeBin = null;
|
|
169
192
|
try {
|
|
@@ -180,10 +203,13 @@ async function validateProviderKey(providerKey, apiKey) {
|
|
|
180
203
|
}
|
|
181
204
|
}
|
|
182
205
|
if (!claudeBin) {
|
|
183
|
-
return { ok: false, error: "Claude CLI not installed" };
|
|
206
|
+
return { ok: false, error: "Claude CLI not installed. Run: curl -fsSL https://claude.ai/install.sh | sh" };
|
|
184
207
|
}
|
|
208
|
+
|
|
209
|
+
// Check `claude auth status`. On mismatch, we DON'T hard-fail:
|
|
210
|
+
// we return a warning so the Agent SDK still gets a chance to run
|
|
211
|
+
// (it has independent auth paths the CLI doesn't know about).
|
|
185
212
|
try {
|
|
186
|
-
// Use `auth status` instead of `-p "ping"` — faster and doesn't require a full query
|
|
187
213
|
const authJson = execSync(`${claudeBin} auth status`, {
|
|
188
214
|
stdio: "pipe", timeout: 10000, encoding: "utf-8",
|
|
189
215
|
});
|
|
@@ -191,7 +217,11 @@ async function validateProviderKey(providerKey, apiKey) {
|
|
|
191
217
|
if (authData.loggedIn) {
|
|
192
218
|
return { ok: true, detail: `Claude SDK authenticated (${authData.authMethod || "OK"})` };
|
|
193
219
|
}
|
|
194
|
-
return {
|
|
220
|
+
return {
|
|
221
|
+
ok: true,
|
|
222
|
+
warning: "Claude CLI reports not logged in, but the Agent SDK may still work via session/env-var. If the bot fails to respond, run: claude auth login",
|
|
223
|
+
detail: "Claude CLI present (not logged in via `auth status` — Agent SDK may still work)",
|
|
224
|
+
};
|
|
195
225
|
} catch (err) {
|
|
196
226
|
const msg = err.stdout?.toString() || err.stderr?.toString() || err.message || "";
|
|
197
227
|
// Try parsing JSON from stdout (auth status exits with code 1 when not logged in)
|
|
@@ -201,7 +231,11 @@ async function validateProviderKey(providerKey, apiKey) {
|
|
|
201
231
|
return { ok: true, detail: `Claude SDK authenticated (${authData.authMethod || "OK"})` };
|
|
202
232
|
}
|
|
203
233
|
} catch {}
|
|
204
|
-
return {
|
|
234
|
+
return {
|
|
235
|
+
ok: true,
|
|
236
|
+
warning: "Claude CLI `auth status` failed. Agent SDK may still work via session/env-var. If the bot fails to respond, run: claude auth login",
|
|
237
|
+
detail: "Claude CLI present (auth status check failed — Agent SDK may still work)",
|
|
238
|
+
};
|
|
205
239
|
}
|
|
206
240
|
}
|
|
207
241
|
|
|
@@ -298,9 +332,148 @@ async function runPostSetupValidation(providerKey, apiKey, botToken, webPort) {
|
|
|
298
332
|
return allGood;
|
|
299
333
|
}
|
|
300
334
|
|
|
335
|
+
// ── Setup: Argument Parsing ─────────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Parse `alvin-bot setup --non-interactive` style CLI args.
|
|
339
|
+
* Returns a flat object with the values found (or null for unset fields).
|
|
340
|
+
*
|
|
341
|
+
* Supports both `--flag=value` and `--flag value` syntax.
|
|
342
|
+
*/
|
|
343
|
+
function parseSetupArgs(argv) {
|
|
344
|
+
const args = {};
|
|
345
|
+
const flags = new Set(["--non-interactive", "-y", "--yes", "--skip-validation"]);
|
|
346
|
+
const valueFlags = [
|
|
347
|
+
"--bot-token", "--allowed-users", "--primary-provider",
|
|
348
|
+
"--groq-key", "--google-key", "--openai-key", "--nvidia-key",
|
|
349
|
+
"--openrouter-key", "--anthropic-key",
|
|
350
|
+
"--fallback-providers", "--web-password", "--platform",
|
|
351
|
+
];
|
|
352
|
+
for (let i = 3; i < argv.length; i++) {
|
|
353
|
+
const a = argv[i];
|
|
354
|
+
if (flags.has(a)) {
|
|
355
|
+
args[a.replace(/^-+/, "")] = true;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
for (const vf of valueFlags) {
|
|
359
|
+
if (a === vf) {
|
|
360
|
+
args[vf.replace(/^-+/, "")] = argv[++i];
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
if (a.startsWith(vf + "=")) {
|
|
364
|
+
args[vf.replace(/^-+/, "")] = a.slice(vf.length + 1);
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return args;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Non-interactive setup path for CI/Docker/automation.
|
|
374
|
+
*
|
|
375
|
+
* Writes ~/.alvin-bot/.env directly from CLI args, with no prompts.
|
|
376
|
+
* The bot's own `ensureDataDirs()` + `seedDefaults()` handle the rest
|
|
377
|
+
* on the next `alvin-bot start`.
|
|
378
|
+
*
|
|
379
|
+
* Usage:
|
|
380
|
+
* alvin-bot setup --non-interactive \
|
|
381
|
+
* --bot-token=123:AAE... \
|
|
382
|
+
* --allowed-users=12345,67890 \
|
|
383
|
+
* --primary-provider=claude-sdk \
|
|
384
|
+
* --groq-key=gsk_... \
|
|
385
|
+
* --fallback-providers=groq,ollama
|
|
386
|
+
*/
|
|
387
|
+
async function setupNonInteractive(args) {
|
|
388
|
+
console.log("🤖 Alvin Bot — Non-interactive setup\n");
|
|
389
|
+
|
|
390
|
+
// Ensure DATA_DIR exists (will also be recreated on bot start, but we
|
|
391
|
+
// need it now to write the .env).
|
|
392
|
+
if (!existsSync(DATA_DIR)) {
|
|
393
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
394
|
+
console.log(`✓ Created ${DATA_DIR}`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const envFile = resolve(DATA_DIR, ".env");
|
|
398
|
+
if (existsSync(envFile)) {
|
|
399
|
+
console.log(`⚠️ ${envFile} already exists — refusing to overwrite.`);
|
|
400
|
+
console.log(` Delete it manually first, or edit it directly.`);
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Validate required fields
|
|
405
|
+
const token = args["bot-token"];
|
|
406
|
+
if (token && !/^\d+:[A-Za-z0-9_-]+$/.test(token)) {
|
|
407
|
+
console.log(`❌ --bot-token format invalid. Expected: 123456789:ABCdef...`);
|
|
408
|
+
process.exit(1);
|
|
409
|
+
}
|
|
410
|
+
const users = args["allowed-users"];
|
|
411
|
+
if (users) {
|
|
412
|
+
const ids = users.split(",").map(s => s.trim());
|
|
413
|
+
const bad = ids.filter(id => !/^\d+$/.test(id));
|
|
414
|
+
if (bad.length > 0) {
|
|
415
|
+
console.log(`❌ --allowed-users must be comma-separated numeric IDs. Got invalid: ${bad.join(", ")}`);
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Optional: validate token with Telegram API unless --skip-validation
|
|
421
|
+
if (token && !args["skip-validation"]) {
|
|
422
|
+
const tgResult = await validateTelegramToken(token);
|
|
423
|
+
if (tgResult.ok) {
|
|
424
|
+
console.log(`✓ Telegram: ${tgResult.botName}`);
|
|
425
|
+
} else {
|
|
426
|
+
console.log(`⚠️ Telegram token validation failed: ${tgResult.error}`);
|
|
427
|
+
console.log(` Writing .env anyway. Pass --skip-validation to suppress this warning.`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const primary = args["primary-provider"] || "groq";
|
|
432
|
+
const fallbacks = args["fallback-providers"] || "";
|
|
433
|
+
const platform = args["platform"] || "telegram";
|
|
434
|
+
|
|
435
|
+
const envLines = [
|
|
436
|
+
"# === Telegram ===",
|
|
437
|
+
`BOT_TOKEN=${token || ""}`,
|
|
438
|
+
`ALLOWED_USERS=${users || ""}`,
|
|
439
|
+
"",
|
|
440
|
+
"# === AI Provider ===",
|
|
441
|
+
`PRIMARY_PROVIDER=${primary}`,
|
|
442
|
+
`FALLBACK_PROVIDERS=${fallbacks}`,
|
|
443
|
+
"",
|
|
444
|
+
"# === API Keys ===",
|
|
445
|
+
];
|
|
446
|
+
if (args["groq-key"]) envLines.push(`GROQ_API_KEY=${args["groq-key"]}`);
|
|
447
|
+
if (args["google-key"]) envLines.push(`GOOGLE_API_KEY=${args["google-key"]}`);
|
|
448
|
+
if (args["openai-key"]) envLines.push(`OPENAI_API_KEY=${args["openai-key"]}`);
|
|
449
|
+
if (args["nvidia-key"]) envLines.push(`NVIDIA_API_KEY=${args["nvidia-key"]}`);
|
|
450
|
+
if (args["openrouter-key"]) envLines.push(`OPENROUTER_API_KEY=${args["openrouter-key"]}`);
|
|
451
|
+
if (args["anthropic-key"]) envLines.push(`ANTHROPIC_API_KEY=${args["anthropic-key"]}`);
|
|
452
|
+
envLines.push("");
|
|
453
|
+
envLines.push("# === Agent ===");
|
|
454
|
+
envLines.push("WORKING_DIR=" + homedir());
|
|
455
|
+
envLines.push("MAX_BUDGET_USD=5.0");
|
|
456
|
+
envLines.push("WEB_PORT=3100");
|
|
457
|
+
if (args["web-password"]) envLines.push(`WEB_PASSWORD=${args["web-password"]}`);
|
|
458
|
+
envLines.push("");
|
|
459
|
+
envLines.push("# === Platforms ===");
|
|
460
|
+
envLines.push(`WHATSAPP_ENABLED=${platform === "whatsapp" ? "true" : "false"}`);
|
|
461
|
+
|
|
462
|
+
writeFileSync(envFile, envLines.join("\n") + "\n", { mode: 0o600 });
|
|
463
|
+
console.log(`✓ Wrote ${envFile} (mode 0600)`);
|
|
464
|
+
console.log(`\n✅ Setup complete. Start the bot with: alvin-bot start\n`);
|
|
465
|
+
}
|
|
466
|
+
|
|
301
467
|
// ── Setup Wizard ────────────────────────────────────────────────────────────
|
|
302
468
|
|
|
303
469
|
async function setup() {
|
|
470
|
+
// Non-interactive path for CI/automation/scripted installs.
|
|
471
|
+
const args = parseSetupArgs(process.argv);
|
|
472
|
+
if (args["non-interactive"] || args["yes"] || args["y"]) {
|
|
473
|
+
await setupNonInteractive(args);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
304
477
|
console.log(LOGO);
|
|
305
478
|
|
|
306
479
|
// ── Prerequisites
|
|
@@ -318,7 +491,7 @@ async function setup() {
|
|
|
318
491
|
|
|
319
492
|
if (!hasNode) {
|
|
320
493
|
console.log(`\n❌ ${t("setup.nodeRequired")}`);
|
|
321
|
-
|
|
494
|
+
closeRL();
|
|
322
495
|
return;
|
|
323
496
|
}
|
|
324
497
|
|
|
@@ -337,7 +510,7 @@ async function setup() {
|
|
|
337
510
|
console.log(` Get one from @BotFather on Telegram.\n`);
|
|
338
511
|
const proceed = (await ask(` Continue anyway? (y/n): `)).trim().toLowerCase();
|
|
339
512
|
if (proceed !== "y" && proceed !== "yes" && proceed !== "j" && proceed !== "ja") {
|
|
340
|
-
|
|
513
|
+
closeRL();
|
|
341
514
|
return;
|
|
342
515
|
}
|
|
343
516
|
}
|
|
@@ -381,7 +554,7 @@ async function setup() {
|
|
|
381
554
|
console.log(` Send /start to @userinfobot on Telegram to get your numeric ID.\n`);
|
|
382
555
|
const proceed = (await ask(` Continue anyway? (y/n): `)).trim().toLowerCase();
|
|
383
556
|
if (proceed !== "y" && proceed !== "yes" && proceed !== "j" && proceed !== "ja") {
|
|
384
|
-
|
|
557
|
+
closeRL();
|
|
385
558
|
return;
|
|
386
559
|
}
|
|
387
560
|
}
|
|
@@ -759,7 +932,7 @@ async function setup() {
|
|
|
759
932
|
console.log(`\n ❌ ${t("setup.buildFailed")}`);
|
|
760
933
|
console.log(` The bot cannot start without a successful build.`);
|
|
761
934
|
console.log(` Try running 'npm run build' manually to see the error.\n`);
|
|
762
|
-
|
|
935
|
+
closeRL();
|
|
763
936
|
return;
|
|
764
937
|
}
|
|
765
938
|
}
|
|
@@ -804,7 +977,7 @@ Bot commands:
|
|
|
804
977
|
${t("setup.haveFun")}
|
|
805
978
|
`);
|
|
806
979
|
|
|
807
|
-
|
|
980
|
+
closeRL();
|
|
808
981
|
}
|
|
809
982
|
|
|
810
983
|
// ── Doctor ──────────────────────────────────────────────────────────────────
|
|
@@ -866,7 +1039,10 @@ async function doctor() {
|
|
|
866
1039
|
|
|
867
1040
|
console.log(` Validating ${primary}...`);
|
|
868
1041
|
const result = await validateProviderKey(primary, key);
|
|
869
|
-
if (result.ok) {
|
|
1042
|
+
if (result.ok && result.warning) {
|
|
1043
|
+
console.log(` ⚠️ ${primary} — ${result.detail}`);
|
|
1044
|
+
console.log(` ${result.warning}`);
|
|
1045
|
+
} else if (result.ok) {
|
|
870
1046
|
console.log(` ✅ ${primary} — ${result.detail}`);
|
|
871
1047
|
} else {
|
|
872
1048
|
console.log(` ❌ ${primary} — ${result.error}`);
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import { execSync } from "child_process";
|
|
3
|
-
import
|
|
4
|
-
import { DATA_DIR } from "../paths.js";
|
|
3
|
+
import dotenv from "dotenv";
|
|
4
|
+
import { DATA_DIR, ENV_FILE } from "../paths.js";
|
|
5
5
|
export function runAudit() {
|
|
6
6
|
const checks = [];
|
|
7
|
+
// `alvin-bot audit` runs in its own process (outside the main bot) and
|
|
8
|
+
// does NOT go through src/config.ts, so process.env is empty by default.
|
|
9
|
+
// We must load the .env ourselves or ALLOWED_USERS/WEB_PASSWORD checks
|
|
10
|
+
// will always report as "not set" — which silently contradicts the bot's
|
|
11
|
+
// actual runtime state (a bug up to v4.4.5).
|
|
12
|
+
if (fs.existsSync(ENV_FILE)) {
|
|
13
|
+
dotenv.config({ path: ENV_FILE });
|
|
14
|
+
}
|
|
7
15
|
// 1. .env file permissions
|
|
8
|
-
const envFile =
|
|
16
|
+
const envFile = ENV_FILE;
|
|
9
17
|
if (fs.existsSync(envFile)) {
|
|
10
18
|
const stat = fs.statSync(envFile);
|
|
11
19
|
const mode = (stat.mode & 0o777).toString(8);
|
package/package.json
CHANGED
package/BACKLOG.md
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
# BACKLOG.md — Alvin Bot Entwicklung
|
|
2
|
-
|
|
3
|
-
> Interne Projektdatei. Wird NICHT ins Git gepusht (.gitignore).
|
|
4
|
-
> Letzte Aktualisierung: 2026-03-03
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Legende
|
|
9
|
-
|
|
10
|
-
| Prio | Bedeutung |
|
|
11
|
-
|------|-----------|
|
|
12
|
-
| P0 | Sicherheitslücke / Kritisch |
|
|
13
|
-
| P1 | Wichtig für Stabilität & Code-Qualität |
|
|
14
|
-
| P2 | Feature / Developer Experience |
|
|
15
|
-
| P3 | Nice-to-Have / Zukunft |
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## P0 — Security
|
|
20
|
-
|
|
21
|
-
### [ ] WebSocket Auth fehlt
|
|
22
|
-
- Aktuell kann jeder, der `localhost:3100` erreicht, über WebSocket chatten
|
|
23
|
-
- Lösung: Auth-Token als Query-Param beim WS-Connect (`ws://localhost:3100?token=xxx`)
|
|
24
|
-
- Token aus WEB_PASSWORD ableiten oder Session-Cookie validieren
|
|
25
|
-
|
|
26
|
-
### [ ] Tool Executor: Unzureichendes Sandboxing
|
|
27
|
-
- `run_shell` Blocklist ist minimal (nur `rm -rf /`, `mkfs`, `dd`)
|
|
28
|
-
- `rm -rf ~/Projects/` würde durchgehen
|
|
29
|
-
- `write_file` kann `.env` und Systemdateien überschreiben
|
|
30
|
-
- `python_execute` führt beliebigen Code ohne Sandbox aus
|
|
31
|
-
- Lösung: Working-Directory-Sandboxing, Blocklist für sensitive Pfade (.env, /etc/, ~/.ssh/)
|
|
32
|
-
|
|
33
|
-
### [ ] Web UI: Kein HTTPS
|
|
34
|
-
- HTTP-only → Passwörter und Chat im Klartext
|
|
35
|
-
- Lösung: Optionaler HTTPS-Modus mit self-signed Cert, oder Warnung wenn WEB_PASSWORD gesetzt aber kein HTTPS
|
|
36
|
-
|
|
37
|
-
### [ ] Sudo-Passwort in CLI-Argument (macOS)
|
|
38
|
-
- `security add-generic-password` bekommt das Passwort als CLI-Arg → sichtbar in `ps aux`
|
|
39
|
-
- Lösung: Passwort über stdin pipen statt als Argument
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## P1 — Code-Qualität & Stabilität
|
|
44
|
-
|
|
45
|
-
### [ ] commands.ts aufteilen (74KB Monolith!)
|
|
46
|
-
- Aktuell: ALLE Telegram-Commands in einer Datei (~1700 Zeilen)
|
|
47
|
-
- Vorschlag: `src/handlers/commands/` Ordner mit je einer Datei pro Bereich:
|
|
48
|
-
- `chat.ts` (help, start, new, cancel)
|
|
49
|
-
- `model.ts` (model, effort, fallback, voice)
|
|
50
|
-
- `tools.ts` (web, imagine, browse, remind)
|
|
51
|
-
- `memory.ts` (recall, remember, reindex, export, memory)
|
|
52
|
-
- `admin.ts` (status, dir, groups, security, users, setup, sudo)
|
|
53
|
-
- `cron.ts` (cron)
|
|
54
|
-
- `extensions.ts` (plugins, mcp, tools, webui)
|
|
55
|
-
- `index.ts` (registriert alle)
|
|
56
|
-
|
|
57
|
-
### [ ] web/server.ts aufteilen (57KB Monolith!)
|
|
58
|
-
- Aktuell: REST-API, WebSocket, Static-Serving, Auth — alles in einer Datei
|
|
59
|
-
- Vorschlag: Router-Module nach Bereich (api/models.ts, api/memory.ts, api/sessions.ts, etc.)
|
|
60
|
-
|
|
61
|
-
### [ ] web/public/js/app.js refactoren (3079 Zeilen)
|
|
62
|
-
- Vanilla JS ohne Struktur → schwer wartbar
|
|
63
|
-
- Option A: Alpine.js oder Petite-Vue für reaktive Bindings (minimaler Overhead)
|
|
64
|
-
- Option B: Web Components für Isolation der Sektionen
|
|
65
|
-
- Option C: Zumindest in Module aufteilen (ES Module Imports)
|
|
66
|
-
|
|
67
|
-
### [ ] Config-Validation mit Zod
|
|
68
|
-
- Aktuell: Kein Validation → `ALLOWED_USERS=""` ergibt `[NaN]`
|
|
69
|
-
- `BOT_TOKEN` fehlt → kryptischer Fehler statt klare Meldung
|
|
70
|
-
- Lösung: `src/config.ts` mit Zod-Schema, Startup-Validation, klare Fehlermeldungen
|
|
71
|
-
|
|
72
|
-
### [ ] MAX_BUDGET_USD wird nie enforced
|
|
73
|
-
- Variable wird gelesen aber nirgendwo geprüft
|
|
74
|
-
- Lösung: In `queryWithFallback()` vor jedem API-Call prüfen, bei Überschreitung blockieren
|
|
75
|
-
|
|
76
|
-
### [ ] Tests einführen
|
|
77
|
-
- Aktuell: Null Tests im gesamten Projekt
|
|
78
|
-
- Priorität für Tests:
|
|
79
|
-
1. Provider-Registry (Fallback-Chain Logik)
|
|
80
|
-
2. Cron-Parser (Edge Cases bei Zeitberechnung)
|
|
81
|
-
3. Tool-Executor (Security-Blocklist)
|
|
82
|
-
4. Config-Validation
|
|
83
|
-
5. Markdown-Sanitizer
|
|
84
|
-
- Framework: Vitest (schnell, ESM-nativ, kein Babel nötig)
|
|
85
|
-
|
|
86
|
-
### [ ] Watch-Mode für Development
|
|
87
|
-
- Aktuell: `npm run dev` = `tsx src/index.ts` (einmal starten, manuell restarten)
|
|
88
|
-
- Lösung: `tsx watch src/index.ts` oder `nodemon` mit TS-Loader
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## P2 — Features & Verbesserungen
|
|
93
|
-
|
|
94
|
-
### [ ] Session-Persistence (optional)
|
|
95
|
-
- Sessions überleben keinen Restart — Chat-History weg nach `pm2 restart`
|
|
96
|
-
- Option A: SQLite-File für Sessions (leichtgewichtig, kein DB-Server)
|
|
97
|
-
- Option B: JSON-Files pro User in `data/sessions/`
|
|
98
|
-
- Opt-in via `SESSION_PERSIST=true` in .env
|
|
99
|
-
|
|
100
|
-
### [ ] Session-Timeout / Cleanup
|
|
101
|
-
- Sessions akkumulieren sich unbegrenzt im Speicher
|
|
102
|
-
- Lösung: Inaktive Sessions nach 24h aus dem RAM entfernen (History bleibt in Memory-Logs)
|
|
103
|
-
|
|
104
|
-
### [ ] Memory-Rotation
|
|
105
|
-
- Daily Logs (`~/.alvin-bot/memory/YYYY-MM-DD.md`) wachsen unbegrenzt
|
|
106
|
-
- Lösung: Nach 30 Tagen alte Logs in `~/.alvin-bot/memory/archive/` verschieben oder komprimieren
|
|
107
|
-
- Optional: Automatische Zusammenfassung alter Logs via AI
|
|
108
|
-
|
|
109
|
-
### [ ] Embedding-Provider erweitern
|
|
110
|
-
- Aktuell: Nur Google `text-embedding-004`
|
|
111
|
-
- Alternativen: OpenAI Embeddings, lokale Ollama-Embeddings, Cohere
|
|
112
|
-
- Fallback-Chain wie bei Chat-Providern
|
|
113
|
-
|
|
114
|
-
### [ ] Plugin-Tools für alle Provider
|
|
115
|
-
- Plugin-Tools funktionieren aktuell nur über Telegram-Commands
|
|
116
|
-
- Sie sollten auch als Agent-Tools in `OpenAICompatibleProvider` injiziert werden
|
|
117
|
-
- Damit können alle LLMs Plugin-Funktionen nutzen (nicht nur Claude SDK)
|
|
118
|
-
|
|
119
|
-
### [ ] MCP HTTP/SSE Transport fertigstellen
|
|
120
|
-
- Aktuell nur stdio-Transport implementiert
|
|
121
|
-
- HTTP/SSE ist stub: `"HTTP/SSE transport not yet supported"`
|
|
122
|
-
- Wichtig für Remote-MCP-Server (z.B. Cloudflare Workers)
|
|
123
|
-
|
|
124
|
-
### [ ] Discord: Richtige Integration
|
|
125
|
-
- `discord.js` fehlt in package.json (muss manuell installiert werden)
|
|
126
|
-
- Kein Support für Discord Slash-Commands
|
|
127
|
-
- Kein Rate-Limiting
|
|
128
|
-
- Lösung: Als optional peer dependency, Slash-Command Registration
|
|
129
|
-
|
|
130
|
-
### [ ] WhatsApp Media Cleanup
|
|
131
|
-
- `data/wa-media/` sammelt empfangene Medien-Dateien und löscht sie nie
|
|
132
|
-
- Lösung: Cron-Job oder TTL-basiertes Cleanup (z.B. nach 7 Tagen löschen)
|
|
133
|
-
|
|
134
|
-
### [ ] Heartbeat-Kosten reduzieren
|
|
135
|
-
- Heartbeat macht echte API-Calls ("Hi") an alle Provider → erzeugt Kosten
|
|
136
|
-
- Lösung: Für Provider mit bekanntem Status-Endpoint nur diesen pingen
|
|
137
|
-
- Für kostenlose Provider (Groq, NVIDIA): OK so lassen
|
|
138
|
-
|
|
139
|
-
### [ ] Kosten-Tracking verbessern
|
|
140
|
-
- `estimateCost()` schätzt nur Output-Tokens, keine Input-Tokens
|
|
141
|
-
- Preise teilweise veraltet
|
|
142
|
-
- Lösung: Aktuelle Preistabelle pflegen, Input+Output getrennt tracken
|
|
143
|
-
|
|
144
|
-
### [ ] Cron-Parser erweitern
|
|
145
|
-
- Kein Support für `@daily`, `@hourly`, `@reboot`, `@weekly`
|
|
146
|
-
- Kein `L` (last day of month), kein `W` (nearest weekday)
|
|
147
|
-
- Lösung: Aliases in `parseSchedule()` hinzufügen
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
|
-
## P3 — Nice-to-Have / Zukunft
|
|
152
|
-
|
|
153
|
-
### [ ] Multi-Language über DE/EN hinaus
|
|
154
|
-
- i18n-System unterstützt nur `de | en`
|
|
155
|
-
- Erweiterbar auf FR, ES, TR, RU etc.
|
|
156
|
-
- Web UI hat bereits ~500 Keys → Übersetzung nötig
|
|
157
|
-
|
|
158
|
-
### [ ] Skill-Matching mit Embeddings
|
|
159
|
-
- Aktuell: Simples `text.includes(trigger)` — sehr unzuverlässig
|
|
160
|
-
- Besser: Embedding-basiertes Similarity-Matching für Skill-Trigger
|
|
161
|
-
- Oder: LLM-basierte Skill-Auswahl ("Welcher Skill passt zu dieser Nachricht?")
|
|
162
|
-
|
|
163
|
-
### [ ] Plugin Hot-Reload über Web UI
|
|
164
|
-
- Aktuell: Plugin-Änderungen erfordern Bot-Restart
|
|
165
|
-
- Lösung: "Reload Plugin" Button in Web UI → `pluginManager.reload(name)`
|
|
166
|
-
|
|
167
|
-
### [ ] Conversation-Export als PDF
|
|
168
|
-
- `/export` gibt aktuell Markdown → könnte auch als formatiertes PDF exportieren
|
|
169
|
-
- Tools vorhanden: `pandoc`, `wkhtmltopdf`
|
|
170
|
-
|
|
171
|
-
### [ ] Streaming für OpenAI-Compatible + Tool-Use
|
|
172
|
-
- Aktuell schließen sich Streaming und Tool-Use gegenseitig aus
|
|
173
|
-
- Lösung: Tool-Calls aus Stream-Chunks akkumulieren (wie OpenAI es unterstützt)
|
|
174
|
-
|
|
175
|
-
### [ ] Windows .exe Build
|
|
176
|
-
- Braucht Windows-Umgebung oder Cross-Compilation
|
|
177
|
-
- electron-builder unterstützt es, aber nicht von macOS aus testbar
|
|
178
|
-
|
|
179
|
-
### [ ] Linux .AppImage Build
|
|
180
|
-
- Braucht Linux-Umgebung oder CI (GitHub Actions)
|
|
181
|
-
- Vorschlag: GitHub Actions Workflow für Multi-Platform Builds
|
|
182
|
-
|
|
183
|
-
### [ ] CI/CD Pipeline (GitHub Actions)
|
|
184
|
-
- Build-Verification auf Push
|
|
185
|
-
- Lint (ESLint) + Type-Check + Tests
|
|
186
|
-
- Auto-Publish zu npm bei Tag
|
|
187
|
-
- Multi-Platform Electron Builds (macOS, Windows, Linux)
|
|
188
|
-
|
|
189
|
-
### [ ] Rate-Limiting für API-Endpoints
|
|
190
|
-
- Web UI API-Endpoints haben kein Rate-Limiting
|
|
191
|
-
- Lösung: Simpler in-memory Counter pro IP (kein Redis nötig)
|
|
192
|
-
|
|
193
|
-
### [ ] Electron: asar wieder aktivieren
|
|
194
|
-
- `asar: false` wegen electron-builder 26.x Bug
|
|
195
|
-
- Bei neuerer Version testen ob es gefixt ist → Source-Code wäre dann nicht mehr exposed
|
|
196
|
-
|
|
197
|
-
### [ ] Provider-Config via Web UI erweitern
|
|
198
|
-
- Neue Provider-Presets (z.B. Anthropic API direkt, DeepSeek, Mistral) über Web UI registrieren
|
|
199
|
-
- Aktuell nur über .env oder Code möglich
|
|
200
|
-
|
|
201
|
-
---
|
|
202
|
-
|
|
203
|
-
## Erledigte Items
|
|
204
|
-
|
|
205
|
-
### [x] i18n English-First (v3.3.0)
|
|
206
|
-
- Alle Telegram-Commands, TUI, Services, Plugins auf Englisch
|
|
207
|
-
- Web UI bleibt bilingual (DE/EN Toggle)
|
|
208
|
-
- TUI nur noch mit explizitem --lang de auf Deutsch
|
|
209
|
-
|
|
210
|
-
### [x] /webui Telegram-Befehl (v3.3.0)
|
|
211
|
-
- Sendet Web UI URL als Text (kein Inline-Button, da Telegram localhost-URLs blockiert)
|
|
212
|
-
|
|
213
|
-
### [x] Smart Port Selection (v3.3.0)
|
|
214
|
-
- Web UI findet automatisch freien Port wenn 3100 belegt
|
|
215
|
-
|
|
216
|
-
### [x] Multi-Platform Support (Phase 7)
|
|
217
|
-
- Telegram, WhatsApp, Discord, Signal
|
|
218
|
-
|
|
219
|
-
### [x] Universal Tool Use (Phase 8)
|
|
220
|
-
- Alle Provider können Shell, File I/O, Web Fetch, Python ausführen
|
|
221
|
-
|
|
222
|
-
### [x] Skill System (Phase 9)
|
|
223
|
-
- SKILL.md-basierte Domain-Expertise
|