alvin-bot 4.4.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.
Files changed (136) hide show
  1. package/.env.example +43 -0
  2. package/BACKLOG.md +223 -0
  3. package/CHANGELOG.md +63 -0
  4. package/CLAUDE.example.md +152 -0
  5. package/CODE_OF_CONDUCT.md +52 -0
  6. package/CONTRIBUTING.md +72 -0
  7. package/LICENSE +21 -0
  8. package/README.md +529 -0
  9. package/SECURITY.md +38 -0
  10. package/SOUL.example.md +60 -0
  11. package/TOOLS.example.md +42 -0
  12. package/alvin-bot.config.example.json +24 -0
  13. package/bin/cli.js +1088 -0
  14. package/dist/.metadata_never_index +0 -0
  15. package/dist/claude.js +102 -0
  16. package/dist/config.js +65 -0
  17. package/dist/engine.js +90 -0
  18. package/dist/find-claude-binary.js +98 -0
  19. package/dist/handlers/commands.js +1489 -0
  20. package/dist/handlers/document.js +187 -0
  21. package/dist/handlers/message.js +200 -0
  22. package/dist/handlers/photo.js +154 -0
  23. package/dist/handlers/platform-message.js +275 -0
  24. package/dist/handlers/video.js +237 -0
  25. package/dist/handlers/voice.js +148 -0
  26. package/dist/i18n.js +299 -0
  27. package/dist/index.js +442 -0
  28. package/dist/init-data-dir.js +81 -0
  29. package/dist/middleware/auth.js +215 -0
  30. package/dist/migrate.js +139 -0
  31. package/dist/paths.js +87 -0
  32. package/dist/platforms/discord.js +161 -0
  33. package/dist/platforms/index.js +130 -0
  34. package/dist/platforms/signal.js +205 -0
  35. package/dist/platforms/slack.js +318 -0
  36. package/dist/platforms/telegram.js +111 -0
  37. package/dist/platforms/types.js +8 -0
  38. package/dist/platforms/whatsapp.js +648 -0
  39. package/dist/providers/claude-sdk-provider.js +173 -0
  40. package/dist/providers/codex-cli-provider.js +121 -0
  41. package/dist/providers/index.js +7 -0
  42. package/dist/providers/openai-compatible.js +388 -0
  43. package/dist/providers/registry.js +209 -0
  44. package/dist/providers/tool-executor.js +450 -0
  45. package/dist/providers/types.js +205 -0
  46. package/dist/services/access.js +144 -0
  47. package/dist/services/asset-index.js +230 -0
  48. package/dist/services/browser-manager.js +161 -0
  49. package/dist/services/browser.js +121 -0
  50. package/dist/services/compaction.js +129 -0
  51. package/dist/services/cron.js +462 -0
  52. package/dist/services/custom-tools.js +317 -0
  53. package/dist/services/delivery-queue.js +154 -0
  54. package/dist/services/elevenlabs.js +58 -0
  55. package/dist/services/embeddings.js +386 -0
  56. package/dist/services/exec-guard.js +46 -0
  57. package/dist/services/fallback-order.js +151 -0
  58. package/dist/services/heartbeat.js +192 -0
  59. package/dist/services/hooks.js +44 -0
  60. package/dist/services/imagegen.js +72 -0
  61. package/dist/services/language-detect.js +144 -0
  62. package/dist/services/markdown.js +63 -0
  63. package/dist/services/mcp.js +252 -0
  64. package/dist/services/memory.js +133 -0
  65. package/dist/services/personality.js +227 -0
  66. package/dist/services/plugins.js +171 -0
  67. package/dist/services/reminders.js +97 -0
  68. package/dist/services/restart.js +48 -0
  69. package/dist/services/security-audit.js +66 -0
  70. package/dist/services/self-search.js +129 -0
  71. package/dist/services/session.js +93 -0
  72. package/dist/services/skills.js +287 -0
  73. package/dist/services/standing-orders.js +29 -0
  74. package/dist/services/subagents.js +142 -0
  75. package/dist/services/sudo.js +243 -0
  76. package/dist/services/telegram.js +113 -0
  77. package/dist/services/tool-discovery.js +214 -0
  78. package/dist/services/usage-tracker.js +137 -0
  79. package/dist/services/users.js +199 -0
  80. package/dist/services/voice.js +95 -0
  81. package/dist/tui/index.js +507 -0
  82. package/dist/web/canvas.js +30 -0
  83. package/dist/web/doctor-api.js +606 -0
  84. package/dist/web/openai-compat.js +252 -0
  85. package/dist/web/server.js +1351 -0
  86. package/dist/web/setup-api.js +1078 -0
  87. package/docs/mcp.example.json +16 -0
  88. package/docs/screenshots/00-Login.png +0 -0
  89. package/docs/screenshots/01-Chat-Dark-Conversation.png +0 -0
  90. package/docs/screenshots/02-Chat.png +0 -0
  91. package/docs/screenshots/03-Dashboard-Overview.png +0 -0
  92. package/docs/screenshots/04-AI-Models-and-Providers.png +0 -0
  93. package/docs/screenshots/05-Personality-Editor.png +0 -0
  94. package/docs/screenshots/06-Memory-Manager.png +0 -0
  95. package/docs/screenshots/07-Active-Sessions.png +0 -0
  96. package/docs/screenshots/08-File-Browser.png +0 -0
  97. package/docs/screenshots/09-Scheduled-Jobs.png +0 -0
  98. package/docs/screenshots/10-Custom-Tools.png +0 -0
  99. package/docs/screenshots/11-Plugins-and-MCP.png +0 -0
  100. package/docs/screenshots/12-Messaging-Platforms.png +0 -0
  101. package/docs/screenshots/12.1-Messaging-Platforms-WhatsApp-Groups-List.png +0 -0
  102. package/docs/screenshots/12.2-Messaging-Platforms-WA-Group-Details.png +0 -0
  103. package/docs/screenshots/13-User-Management.png +0 -0
  104. package/docs/screenshots/14-Web-Terminal.png +0 -0
  105. package/docs/screenshots/15-Maintenance-and-Health.png +0 -0
  106. package/docs/screenshots/16-Settings-and-Env.png +0 -0
  107. package/docs/screenshots/TG-commands.png +0 -0
  108. package/docs/screenshots/TG.png +0 -0
  109. package/docs/screenshots/_Mac-Installer.png +0 -0
  110. package/docs/tools.example.json +33 -0
  111. package/install.sh +165 -0
  112. package/package.json +190 -0
  113. package/plugins/calendar/index.js +270 -0
  114. package/plugins/email/index.js +231 -0
  115. package/plugins/finance/index.js +254 -0
  116. package/plugins/notes/index.js +227 -0
  117. package/plugins/smarthome/index.js +230 -0
  118. package/plugins/weather/index.js +122 -0
  119. package/skills/apple-notes/SKILL.md +31 -0
  120. package/skills/browse/SKILL.md +136 -0
  121. package/skills/code-project/SKILL.md +43 -0
  122. package/skills/data-analysis/SKILL.md +39 -0
  123. package/skills/document-creation/SKILL.md +48 -0
  124. package/skills/email-summary/SKILL.md +46 -0
  125. package/skills/github/SKILL.md +42 -0
  126. package/skills/summarize/SKILL.md +28 -0
  127. package/skills/system-admin/SKILL.md +39 -0
  128. package/skills/weather/SKILL.md +34 -0
  129. package/skills/web-research/SKILL.md +35 -0
  130. package/web/public/canvas.html +52 -0
  131. package/web/public/css/style.css +555 -0
  132. package/web/public/index.html +189 -0
  133. package/web/public/js/app.js +3102 -0
  134. package/web/public/js/i18n.js +1048 -0
  135. package/web/public/js/icons.js +104 -0
  136. package/web/public/login.html +48 -0
File without changes
package/dist/claude.js ADDED
@@ -0,0 +1,102 @@
1
+ import { query } from "@anthropic-ai/claude-agent-sdk";
2
+ import { readFileSync } from "fs";
3
+ import { resolve, dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { findClaudeBinary } from "./find-claude-binary.js";
6
+ // Bot project root (one level up from src/)
7
+ const BOT_PROJECT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
8
+ // Load bot's CLAUDE.md at startup — personality, rules, memory instructions
9
+ let botClaudeMd = "";
10
+ try {
11
+ botClaudeMd = readFileSync(resolve(BOT_PROJECT_ROOT, "CLAUDE.md"), "utf-8");
12
+ // Replace relative docs/ paths with absolute paths so memory works from any CWD
13
+ botClaudeMd = botClaudeMd.replaceAll("docs/", `${BOT_PROJECT_ROOT}/docs/`);
14
+ }
15
+ catch {
16
+ // CLAUDE.md not found — continue without bot-specific instructions
17
+ }
18
+ // Checkpoint reminder thresholds
19
+ const CHECKPOINT_TOOL_THRESHOLD = 15; // After N tool uses → inject checkpoint reminder
20
+ const CHECKPOINT_MSG_THRESHOLD = 10; // After N messages → inject checkpoint reminder
21
+ export async function runClaudeAgent(opts) {
22
+ // Remove env vars that prevent nested Claude Code sessions
23
+ const cleanEnv = { ...process.env };
24
+ delete cleanEnv.CLAUDECODE;
25
+ delete cleanEnv.CLAUDE_CODE_ENTRYPOINT;
26
+ // Build prompt — inject checkpoint reminder if thresholds exceeded
27
+ let prompt = opts.prompt;
28
+ const needsCheckpoint = opts.toolUseCount >= CHECKPOINT_TOOL_THRESHOLD ||
29
+ opts.messageCount >= CHECKPOINT_MSG_THRESHOLD;
30
+ if (needsCheckpoint) {
31
+ prompt = `[CHECKPOINT] Du hast bereits ${opts.toolUseCount} Tool-Aufrufe und ${opts.messageCount} Nachrichten in dieser Session. Schreibe jetzt einen Checkpoint in deine Memory-Datei (docs/memory/YYYY-MM-DD.md) bevor du diese Anfrage bearbeitest — fasse den bisherigen Kontext kurz zusammen.\n\n${prompt}`;
32
+ }
33
+ // Find the claude native binary path
34
+ const claudePath = findClaudeBinary();
35
+ const q = query({
36
+ prompt,
37
+ options: {
38
+ cwd: opts.workingDir,
39
+ abortController: opts.abortController,
40
+ resume: opts.sessionId ?? undefined,
41
+ pathToClaudeCodeExecutable: claudePath,
42
+ permissionMode: "bypassPermissions",
43
+ allowDangerouslySkipPermissions: true,
44
+ env: cleanEnv,
45
+ settingSources: ["user", "project"],
46
+ allowedTools: [
47
+ "Read", "Write", "Edit", "Bash", "Glob", "Grep",
48
+ "WebSearch", "WebFetch", "Task",
49
+ ],
50
+ systemPrompt: `You are an autonomous AI agent, controlled via Telegram.
51
+ Keep answers short and concise, but thorough.
52
+ Use Markdown formatting compatible with Telegram (bold, italic, code blocks).
53
+ When you execute commands or edit files, briefly explain what you did.
54
+ Reply in the language the user writes in.
55
+
56
+ ${botClaudeMd}`,
57
+ effort: opts.effort,
58
+ maxTurns: 50,
59
+ },
60
+ });
61
+ let accumulatedText = "";
62
+ let capturedSessionId = opts.sessionId || "";
63
+ let localToolUseCount = 0;
64
+ for await (const message of q) {
65
+ // System init message — capture session ID
66
+ if (message.type === "system" && "subtype" in message && message.subtype === "init") {
67
+ const sysMsg = message;
68
+ capturedSessionId = sysMsg.session_id;
69
+ }
70
+ // Assistant message — extract text and tool use
71
+ if (message.type === "assistant") {
72
+ const assistantMsg = message;
73
+ capturedSessionId = assistantMsg.session_id;
74
+ if (assistantMsg.message?.content) {
75
+ for (const block of assistantMsg.message.content) {
76
+ if ("text" in block && block.text) {
77
+ accumulatedText += block.text;
78
+ await opts.onText(accumulatedText);
79
+ }
80
+ if ("name" in block) {
81
+ localToolUseCount++;
82
+ if (opts.onToolUse) {
83
+ await opts.onToolUse(block.name);
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ // Result message — complete
90
+ if (message.type === "result") {
91
+ const resultMsg = message;
92
+ // Report tool use count back to caller for session tracking
93
+ if (opts.onToolUseCount) {
94
+ opts.onToolUseCount(localToolUseCount);
95
+ }
96
+ opts.onComplete({
97
+ sessionId: resultMsg.session_id || capturedSessionId,
98
+ cost: "total_cost_usd" in resultMsg ? resultMsg.total_cost_usd : 0,
99
+ });
100
+ }
101
+ }
102
+ }
package/dist/config.js ADDED
@@ -0,0 +1,65 @@
1
+ import dotenv from "dotenv";
2
+ import { resolve } from "path";
3
+ import os from "os";
4
+ import { existsSync } from "fs";
5
+ // Load .env from ~/.alvin-bot/.env (primary) with cwd fallback (dev mode)
6
+ const dataEnv = resolve(process.env.ALVIN_DATA_DIR || resolve(os.homedir(), ".alvin-bot"), ".env");
7
+ const cwdEnv = resolve(process.cwd(), ".env");
8
+ if (existsSync(dataEnv)) {
9
+ dotenv.config({ path: dataEnv });
10
+ }
11
+ else if (existsSync(cwdEnv)) {
12
+ dotenv.config({ path: cwdEnv });
13
+ }
14
+ else {
15
+ dotenv.config(); // default behavior
16
+ }
17
+ export const config = {
18
+ // Telegram
19
+ botToken: process.env.BOT_TOKEN || "",
20
+ allowedUsers: (process.env.ALLOWED_USERS || "")
21
+ .split(",")
22
+ .map(Number)
23
+ .filter(Boolean),
24
+ telegramMaxLength: 4096,
25
+ streamThrottleMs: 1500,
26
+ // Agent
27
+ defaultWorkingDir: process.env.WORKING_DIR || os.homedir(),
28
+ maxBudgetUsd: Number(process.env.MAX_BUDGET_USD) || 5.0,
29
+ // Model provider (primary)
30
+ primaryProvider: process.env.PRIMARY_PROVIDER || "claude-sdk",
31
+ fallbackProviders: (process.env.FALLBACK_PROVIDERS || "")
32
+ .split(",")
33
+ .map(s => s.trim())
34
+ .filter(Boolean),
35
+ // API Keys (for multi-model support)
36
+ apiKeys: {
37
+ anthropic: process.env.ANTHROPIC_API_KEY || "",
38
+ groq: process.env.GROQ_API_KEY || "",
39
+ openai: process.env.OPENAI_API_KEY || "",
40
+ google: process.env.GOOGLE_API_KEY || "",
41
+ nvidia: process.env.NVIDIA_API_KEY || "",
42
+ openrouter: process.env.OPENROUTER_API_KEY || "",
43
+ },
44
+ // Compaction
45
+ compactionThreshold: Number(process.env.COMPACTION_THRESHOLD) || 80000,
46
+ // Sub-Agents
47
+ maxSubAgents: Number(process.env.MAX_SUBAGENTS) || 4,
48
+ subAgentTimeout: Number(process.env.SUBAGENT_TIMEOUT) || 300000, // 5 min
49
+ // TTS Provider
50
+ ttsProvider: (process.env.TTS_PROVIDER || "edge"),
51
+ elevenlabs: {
52
+ apiKey: process.env.ELEVENLABS_API_KEY || "",
53
+ voiceId: process.env.ELEVENLABS_VOICE_ID || "iP95p4xoKVk53GoZ742B",
54
+ modelId: process.env.ELEVENLABS_MODEL_ID || "eleven_v3",
55
+ },
56
+ authMode: (process.env.AUTH_MODE || "allowlist"),
57
+ sessionMode: (process.env.SESSION_MODE || "per-user"),
58
+ webhookEnabled: process.env.WEBHOOK_ENABLED === "true",
59
+ webhookToken: process.env.WEBHOOK_TOKEN || "",
60
+ // Browser
61
+ cdpUrl: process.env.CDP_URL || "",
62
+ browseServerPort: Number(process.env.BROWSE_SERVER_PORT) || 3800,
63
+ // Exec Security
64
+ execSecurity: (process.env.EXEC_SECURITY || "full"),
65
+ };
package/dist/engine.js ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Engine — Central AI query dispatcher.
3
+ *
4
+ * Bridges the gap between Telegram handlers and the provider system.
5
+ * Handlers call engine.query(), engine routes to the right provider.
6
+ */
7
+ import fs from "fs";
8
+ import { config } from "./config.js";
9
+ import { createRegistry } from "./providers/index.js";
10
+ import { CUSTOM_MODELS as CUSTOM_MODELS_FILE } from "./paths.js";
11
+ let registry = null;
12
+ /**
13
+ * Load custom models from docs/custom-models.json
14
+ */
15
+ function loadCustomProviders() {
16
+ try {
17
+ const models = JSON.parse(fs.readFileSync(CUSTOM_MODELS_FILE, "utf-8"));
18
+ const result = {};
19
+ for (const m of models) {
20
+ result[m.key] = {
21
+ type: "openai-compatible",
22
+ name: m.name,
23
+ model: m.model,
24
+ baseUrl: m.baseUrl,
25
+ apiKey: m.apiKeyEnv ? process.env[m.apiKeyEnv] : undefined,
26
+ supportsVision: m.supportsVision ?? false,
27
+ supportsStreaming: m.supportsStreaming ?? true,
28
+ maxTokens: m.maxTokens,
29
+ temperature: m.temperature,
30
+ };
31
+ }
32
+ return result;
33
+ }
34
+ catch {
35
+ return {};
36
+ }
37
+ }
38
+ /**
39
+ * Initialize the provider registry from config.
40
+ * Called once at bot startup.
41
+ */
42
+ export function initEngine() {
43
+ const customProviders = loadCustomProviders();
44
+ registry = createRegistry({
45
+ primary: config.primaryProvider,
46
+ fallbacks: config.fallbackProviders.length > 0 ? config.fallbackProviders : undefined,
47
+ apiKeys: {
48
+ anthropic: config.apiKeys.anthropic || undefined,
49
+ groq: config.apiKeys.groq || undefined,
50
+ openai: config.apiKeys.openai || undefined,
51
+ google: config.apiKeys.google || undefined,
52
+ nvidia: config.apiKeys.nvidia || undefined,
53
+ openrouter: config.apiKeys.openrouter || undefined,
54
+ },
55
+ customProviders: Object.keys(customProviders).length > 0 ? customProviders : undefined,
56
+ });
57
+ if (Object.keys(customProviders).length > 0) {
58
+ console.log(`Custom models loaded: ${Object.keys(customProviders).join(", ")}`);
59
+ }
60
+ return registry;
61
+ }
62
+ /**
63
+ * Get the provider registry. Must call initEngine() first.
64
+ */
65
+ export function getRegistry() {
66
+ if (!registry) {
67
+ throw new Error("Engine not initialized. Call initEngine() first.");
68
+ }
69
+ return registry;
70
+ }
71
+ /**
72
+ * Run a query through the active provider (with fallback).
73
+ * This is the main entry point for handlers.
74
+ */
75
+ export async function* engineQuery(options) {
76
+ const reg = getRegistry();
77
+ yield* reg.queryWithFallback(options);
78
+ }
79
+ /**
80
+ * Get info about the current model setup for /status.
81
+ */
82
+ export async function getEngineStatus() {
83
+ const reg = getRegistry();
84
+ const providers = await reg.listAll();
85
+ const lines = providers.map(p => {
86
+ const marker = p.active ? "→" : " ";
87
+ return `${marker} ${p.key}: ${p.name} (${p.model}) ${p.status}`;
88
+ });
89
+ return `Model: ${reg.getActiveKey()}\n\n${lines.join("\n")}`;
90
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Find the Claude Code native binary path.
3
+ *
4
+ * The Agent SDK requires the native Mach-O/ELF binary, NOT the npm/node wrapper.
5
+ * Native installer: ~/.local/bin/claude → ~/.local/share/claude/versions/<ver>
6
+ */
7
+ import { existsSync, openSync, readSync, closeSync, readdirSync, realpathSync, statSync } from "fs";
8
+ import { homedir } from "os";
9
+ import { join } from "path";
10
+ import { execSync } from "child_process";
11
+ /** Check if a file is a node/shell script (NOT a native binary) */
12
+ function isScript(filePath) {
13
+ try {
14
+ const buf = Buffer.alloc(64);
15
+ const fd = openSync(filePath, "r");
16
+ readSync(fd, buf, 0, 64, 0);
17
+ closeSync(fd);
18
+ const hdr = buf.toString("utf-8", 0, 64);
19
+ return hdr.startsWith("#!") && (hdr.includes("node") || hdr.includes("sh"));
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ /** Resolve a candidate path to the native binary, or undefined */
26
+ function tryCandidate(p, label) {
27
+ try {
28
+ if (!existsSync(p))
29
+ return undefined;
30
+ const resolved = realpathSync(p);
31
+ if (!statSync(resolved).isFile())
32
+ return undefined;
33
+ if (isScript(resolved)) {
34
+ console.error(`[claude] ${label}: ${resolved} is a script wrapper, skipping`);
35
+ return undefined;
36
+ }
37
+ return resolved;
38
+ }
39
+ catch {
40
+ return undefined;
41
+ }
42
+ }
43
+ /** Find the native Claude Code binary. Returns the path or undefined. */
44
+ export function findClaudeBinary() {
45
+ try {
46
+ const home = homedir();
47
+ // Strategy 1: ~/.local/bin/claude (native installer symlink)
48
+ let result = tryCandidate(join(home, ".local", "bin", "claude"), "Strategy 1 (~/.local/bin/claude)");
49
+ if (result) {
50
+ console.error(`[claude] Native binary: ${result}`);
51
+ return result;
52
+ }
53
+ // Strategy 2: Scan ~/.local/share/claude/versions/ (newest first)
54
+ const versionsDir = join(home, ".local", "share", "claude", "versions");
55
+ if (existsSync(versionsDir)) {
56
+ try {
57
+ const entries = readdirSync(versionsDir)
58
+ .filter((f) => !f.startsWith("."))
59
+ .sort()
60
+ .reverse();
61
+ for (const entry of entries) {
62
+ const entryPath = join(versionsDir, entry);
63
+ // Entry might be the binary itself OR a directory containing it
64
+ result = tryCandidate(entryPath, `Strategy 2 (versions/${entry})`);
65
+ if (!result) {
66
+ result = tryCandidate(join(entryPath, "claude"), `Strategy 2 (versions/${entry}/claude)`);
67
+ }
68
+ if (result) {
69
+ console.error(`[claude] Native binary: ${result}`);
70
+ return result;
71
+ }
72
+ }
73
+ }
74
+ catch (e) {
75
+ console.error(`[claude] Strategy 2: can't read versions dir: ${e}`);
76
+ }
77
+ }
78
+ // Strategy 3: which claude → resolve → verify not a script
79
+ try {
80
+ const p = execSync("which claude", { encoding: "utf-8", stdio: "pipe" }).trim();
81
+ if (p) {
82
+ result = tryCandidate(p, `Strategy 3 (which → ${p})`);
83
+ if (result) {
84
+ console.error(`[claude] Native binary: ${result}`);
85
+ return result;
86
+ }
87
+ }
88
+ }
89
+ catch { /* not in PATH */ }
90
+ console.error("[claude] WARNING: Native binary not found — SDK will use default (may fail)");
91
+ console.error(`[claude] Checked: ~/.local/bin/claude, ~/.local/share/claude/versions/, which claude`);
92
+ return undefined;
93
+ }
94
+ catch (err) {
95
+ console.error(`[claude] Binary search error: ${err instanceof Error ? err.message : err}`);
96
+ return undefined;
97
+ }
98
+ }