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
@@ -0,0 +1,252 @@
1
+ /**
2
+ * MCP (Model Context Protocol) Client — Connect to any MCP-compatible tool server.
3
+ *
4
+ * Supports:
5
+ * - stdio transport (local processes)
6
+ * - HTTP/SSE transport (remote servers)
7
+ *
8
+ * Configuration via docs/mcp.json:
9
+ * {
10
+ * "servers": {
11
+ * "filesystem": {
12
+ * "command": "npx",
13
+ * "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
14
+ * "env": {}
15
+ * },
16
+ * "remote-server": {
17
+ * "url": "https://mcp.example.com/sse",
18
+ * "headers": { "Authorization": "Bearer ..." }
19
+ * }
20
+ * }
21
+ * }
22
+ */
23
+ import { spawn } from "child_process";
24
+ import fs from "fs";
25
+ import { MCP_CONFIG } from "../paths.js";
26
+ // ── MCP Client ──────────────────────────────────────────
27
+ const servers = new Map();
28
+ /**
29
+ * Load MCP configuration from docs/mcp.json.
30
+ */
31
+ function loadConfig() {
32
+ try {
33
+ const raw = fs.readFileSync(MCP_CONFIG, "utf-8");
34
+ return JSON.parse(raw);
35
+ }
36
+ catch {
37
+ return { servers: {} };
38
+ }
39
+ }
40
+ /**
41
+ * Send a JSON-RPC message to a stdio MCP server.
42
+ */
43
+ function sendMessage(server, method, params) {
44
+ return new Promise((resolve, reject) => {
45
+ if (!server.process?.stdin?.writable) {
46
+ reject(new Error(`Server ${server.name} not connected`));
47
+ return;
48
+ }
49
+ const id = ++server.requestId;
50
+ const message = JSON.stringify({
51
+ jsonrpc: "2.0",
52
+ id,
53
+ method,
54
+ params: params || {},
55
+ });
56
+ server.pendingRequests.set(id, { resolve, reject });
57
+ // Timeout after 30s
58
+ setTimeout(() => {
59
+ if (server.pendingRequests.has(id)) {
60
+ server.pendingRequests.delete(id);
61
+ reject(new Error(`Request ${id} timed out`));
62
+ }
63
+ }, 30000);
64
+ server.process.stdin.write(message + "\n");
65
+ });
66
+ }
67
+ /**
68
+ * Handle incoming JSON-RPC responses from a stdio server.
69
+ */
70
+ function handleResponse(server, line) {
71
+ try {
72
+ const msg = JSON.parse(line);
73
+ if (msg.id && server.pendingRequests.has(msg.id)) {
74
+ const pending = server.pendingRequests.get(msg.id);
75
+ server.pendingRequests.delete(msg.id);
76
+ if (msg.error) {
77
+ pending.reject(new Error(msg.error.message || JSON.stringify(msg.error)));
78
+ }
79
+ else {
80
+ pending.resolve(msg.result);
81
+ }
82
+ }
83
+ }
84
+ catch {
85
+ // Not valid JSON — skip
86
+ }
87
+ }
88
+ /**
89
+ * Connect to a stdio MCP server.
90
+ */
91
+ async function connectStdio(name, config) {
92
+ const server = {
93
+ name,
94
+ config,
95
+ tools: [],
96
+ connected: false,
97
+ requestId: 0,
98
+ pendingRequests: new Map(),
99
+ buffer: "",
100
+ };
101
+ return new Promise((resolve, reject) => {
102
+ const proc = spawn(config.command, config.args || [], {
103
+ env: { ...process.env, ...config.env },
104
+ stdio: ["pipe", "pipe", "pipe"],
105
+ });
106
+ server.process = proc;
107
+ proc.stdout.on("data", (data) => {
108
+ server.buffer += data.toString();
109
+ const lines = server.buffer.split("\n");
110
+ server.buffer = lines.pop() || "";
111
+ for (const line of lines) {
112
+ if (line.trim())
113
+ handleResponse(server, line.trim());
114
+ }
115
+ });
116
+ proc.stderr.on("data", (data) => {
117
+ console.error(`MCP ${name} stderr:`, data.toString().trim());
118
+ });
119
+ proc.on("error", (err) => {
120
+ console.error(`MCP ${name} process error:`, err);
121
+ server.connected = false;
122
+ });
123
+ proc.on("close", (code) => {
124
+ console.log(`MCP ${name} exited with code ${code}`);
125
+ server.connected = false;
126
+ });
127
+ // Initialize the connection
128
+ setTimeout(async () => {
129
+ try {
130
+ // Send initialize
131
+ await sendMessage(server, "initialize", {
132
+ protocolVersion: "2024-11-05",
133
+ capabilities: {},
134
+ clientInfo: { name: "alvin-bot", version: "2.2.0" },
135
+ });
136
+ // Send initialized notification
137
+ server.process.stdin.write(JSON.stringify({
138
+ jsonrpc: "2.0",
139
+ method: "notifications/initialized",
140
+ }) + "\n");
141
+ // List tools
142
+ const result = await sendMessage(server, "tools/list");
143
+ server.tools = result?.tools || [];
144
+ server.connected = true;
145
+ console.log(`MCP ${name}: connected, ${server.tools.length} tools`);
146
+ resolve(server);
147
+ }
148
+ catch (err) {
149
+ reject(err);
150
+ }
151
+ }, 500);
152
+ });
153
+ }
154
+ // ── Public API ──────────────────────────────────────────
155
+ /**
156
+ * Initialize all configured MCP servers.
157
+ */
158
+ export async function initMCP() {
159
+ const config = loadConfig();
160
+ const connected = [];
161
+ const errors = [];
162
+ for (const [name, serverConfig] of Object.entries(config.servers)) {
163
+ try {
164
+ if (serverConfig.command) {
165
+ const server = await connectStdio(name, serverConfig);
166
+ servers.set(name, server);
167
+ connected.push(name);
168
+ }
169
+ else if (serverConfig.url) {
170
+ // HTTP/SSE transport — not yet implemented
171
+ errors.push({ name, error: "HTTP/SSE transport not yet supported" });
172
+ }
173
+ }
174
+ catch (err) {
175
+ const msg = err instanceof Error ? err.message : String(err);
176
+ errors.push({ name, error: msg });
177
+ }
178
+ }
179
+ return { connected, errors };
180
+ }
181
+ /**
182
+ * Call a tool on an MCP server.
183
+ */
184
+ export async function callMCPTool(serverName, toolName, args) {
185
+ const server = servers.get(serverName);
186
+ if (!server)
187
+ throw new Error(`MCP server "${serverName}" not found`);
188
+ if (!server.connected)
189
+ throw new Error(`MCP server "${serverName}" not connected`);
190
+ const result = await sendMessage(server, "tools/call", {
191
+ name: toolName,
192
+ arguments: args,
193
+ });
194
+ // Extract text from content array
195
+ const texts = (result?.content || [])
196
+ .filter((c) => c.type === "text")
197
+ .map((c) => c.text || "");
198
+ return texts.join("\n") || JSON.stringify(result);
199
+ }
200
+ /**
201
+ * Get all available MCP tools across all servers.
202
+ */
203
+ export function getMCPTools() {
204
+ const tools = [];
205
+ for (const [serverName, server] of servers) {
206
+ for (const tool of server.tools) {
207
+ tools.push({
208
+ server: serverName,
209
+ name: tool.name,
210
+ description: tool.description,
211
+ });
212
+ }
213
+ }
214
+ return tools;
215
+ }
216
+ /**
217
+ * Get MCP server status.
218
+ */
219
+ export function getMCPStatus() {
220
+ const result = [];
221
+ for (const [name, server] of servers) {
222
+ result.push({
223
+ name,
224
+ connected: server.connected,
225
+ tools: server.tools.length,
226
+ });
227
+ }
228
+ return result;
229
+ }
230
+ /**
231
+ * Disconnect all MCP servers.
232
+ */
233
+ export async function disconnectMCP() {
234
+ for (const [name, server] of servers) {
235
+ try {
236
+ if (server.process) {
237
+ server.process.kill();
238
+ }
239
+ console.log(`MCP ${name} disconnected`);
240
+ }
241
+ catch (err) {
242
+ console.error(`MCP ${name} disconnect error:`, err);
243
+ }
244
+ }
245
+ servers.clear();
246
+ }
247
+ /**
248
+ * Check if MCP config exists.
249
+ */
250
+ export function hasMCPConfig() {
251
+ return fs.existsSync(MCP_CONFIG);
252
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Memory Service — Persistent memory across sessions.
3
+ *
4
+ * Manages:
5
+ * - MEMORY.md (long-term, curated knowledge)
6
+ * - memory/YYYY-MM-DD.md (daily session logs)
7
+ * - Auto-write session summaries on /new
8
+ * - Load context at session start
9
+ */
10
+ import fs from "fs";
11
+ import { resolve } from "path";
12
+ import { MEMORY_DIR, MEMORY_FILE } from "../paths.js";
13
+ import { reindexMemory } from "./embeddings.js";
14
+ // Ensure dirs exist
15
+ if (!fs.existsSync(MEMORY_DIR))
16
+ fs.mkdirSync(MEMORY_DIR, { recursive: true });
17
+ /** Get today's date as YYYY-MM-DD */
18
+ function today() {
19
+ return new Date().toISOString().slice(0, 10);
20
+ }
21
+ /** Get current time as HH:MM */
22
+ function now() {
23
+ return new Date().toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
24
+ }
25
+ /**
26
+ * Load long-term memory (MEMORY.md).
27
+ */
28
+ export function loadLongTermMemory() {
29
+ try {
30
+ return fs.readFileSync(MEMORY_FILE, "utf-8");
31
+ }
32
+ catch {
33
+ return "";
34
+ }
35
+ }
36
+ /**
37
+ * Load today's daily log.
38
+ */
39
+ export function loadDailyLog(date) {
40
+ const d = date || today();
41
+ const filePath = resolve(MEMORY_DIR, `${d}.md`);
42
+ try {
43
+ return fs.readFileSync(filePath, "utf-8");
44
+ }
45
+ catch {
46
+ return "";
47
+ }
48
+ }
49
+ /**
50
+ * Append an entry to today's daily log.
51
+ */
52
+ export function appendDailyLog(entry) {
53
+ const filePath = resolve(MEMORY_DIR, `${today()}.md`);
54
+ const header = `# ${today()} — Session Log\n\n`;
55
+ let content = "";
56
+ try {
57
+ content = fs.readFileSync(filePath, "utf-8");
58
+ }
59
+ catch {
60
+ content = header;
61
+ }
62
+ content += `\n## ~${now()}\n\n${entry}\n`;
63
+ fs.writeFileSync(filePath, content);
64
+ // Trigger incremental reindex in background (non-blocking)
65
+ reindexMemory().catch(() => { });
66
+ }
67
+ /**
68
+ * Build memory context for injection into non-SDK prompts.
69
+ * Returns relevant memory as a compact string.
70
+ */
71
+ export function buildMemoryContext() {
72
+ const parts = [];
73
+ // Long-term memory (truncate if too long)
74
+ const ltm = loadLongTermMemory();
75
+ if (ltm) {
76
+ const truncated = ltm.length > 2000 ? ltm.slice(0, 2000) + "\n[...truncated]" : ltm;
77
+ parts.push(`## Long-term Memory\n${truncated}`);
78
+ }
79
+ // Today's log
80
+ const todayLog = loadDailyLog();
81
+ if (todayLog) {
82
+ const truncated = todayLog.length > 1500 ? todayLog.slice(-1500) : todayLog;
83
+ parts.push(`## Today's Log\n${truncated}`);
84
+ }
85
+ // Yesterday's log (for continuity)
86
+ const yesterday = new Date(Date.now() - 86_400_000).toISOString().slice(0, 10);
87
+ const yesterdayLog = loadDailyLog(yesterday);
88
+ if (yesterdayLog) {
89
+ const truncated = yesterdayLog.length > 500 ? yesterdayLog.slice(-500) : yesterdayLog;
90
+ parts.push(`## Yesterday's Log (summary)\n${truncated}`);
91
+ }
92
+ if (parts.length === 0)
93
+ return "";
94
+ return `\n\n---\n## Your Memory (auto-loaded)\n\n${parts.join("\n\n")}`;
95
+ }
96
+ /**
97
+ * Write a session summary to daily log.
98
+ * Called when user does /new or session is long enough.
99
+ */
100
+ export function writeSessionSummary(summary) {
101
+ const lines = [
102
+ `**Session Summary:**`,
103
+ `- Messages: ${summary.messageCount}`,
104
+ `- Tool Calls: ${summary.toolUseCount}`,
105
+ `- Cost: $${summary.costUsd.toFixed(4)}`,
106
+ `- Provider: ${summary.provider}`,
107
+ ];
108
+ if (summary.topics && summary.topics.length > 0) {
109
+ lines.push(`- Topics: ${summary.topics.join(", ")}`);
110
+ }
111
+ appendDailyLog(lines.join("\n"));
112
+ }
113
+ /**
114
+ * Get memory stats for /status.
115
+ */
116
+ export function getMemoryStats() {
117
+ let longTermSize = 0;
118
+ try {
119
+ longTermSize = fs.statSync(MEMORY_FILE).size;
120
+ }
121
+ catch { /* empty */ }
122
+ let dailyLogs = 0;
123
+ try {
124
+ dailyLogs = fs.readdirSync(MEMORY_DIR).filter(f => f.endsWith(".md") && f !== ".gitkeep").length;
125
+ }
126
+ catch { /* empty */ }
127
+ let todayEntries = 0;
128
+ const todayLog = loadDailyLog();
129
+ if (todayLog) {
130
+ todayEntries = (todayLog.match(/^## ~/gm) || []).length;
131
+ }
132
+ return { longTermSize, dailyLogs, todayEntries };
133
+ }
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Personality Service — Loads SOUL.md and builds system prompts.
3
+ *
4
+ * SOUL.md defines Alvin Bot's personality and is injected into every system prompt.
5
+ * This ensures consistent personality across ALL providers (SDK + non-SDK).
6
+ */
7
+ import { readFileSync } from "fs";
8
+ import { buildMemoryContext } from "./memory.js";
9
+ import { searchMemory } from "./embeddings.js";
10
+ import { getToolSummary } from "./tool-discovery.js";
11
+ import { SOUL_FILE } from "../paths.js";
12
+ import { loadStandingOrders, getStandingOrders } from "./standing-orders.js";
13
+ import { getAssetIndexMd } from "./asset-index.js";
14
+ // Resolve display name for the active provider
15
+ function getActiveProviderLabel() {
16
+ try {
17
+ const primary = process.env.PRIMARY_PROVIDER || "claude-sdk";
18
+ const labels = {
19
+ "claude-sdk": "Claude (Anthropic Agent SDK) — Opus/Sonnet Klasse",
20
+ "openai": "OpenAI GPT",
21
+ "groq": "Groq (Llama 3.3 70B)",
22
+ "gemini-2.5-flash": "Google Gemini 2.5 Flash",
23
+ "google": "Google Gemini",
24
+ "nvidia-llama-3.3-70b": "NVIDIA NIM (Llama 3.3 70B)",
25
+ "nvidia-kimi-k2.5": "NVIDIA NIM (Kimi K2.5)",
26
+ };
27
+ return labels[primary] || primary;
28
+ }
29
+ catch {
30
+ return "AI language model";
31
+ }
32
+ }
33
+ let soulContent = "";
34
+ try {
35
+ soulContent = readFileSync(SOUL_FILE, "utf-8");
36
+ }
37
+ catch {
38
+ console.warn("SOUL.md not found — using default personality");
39
+ }
40
+ loadStandingOrders();
41
+ /** Base system prompt — adapts to user language */
42
+ function getBasePrompt(lang) {
43
+ return lang === "de"
44
+ ? `You are Alvin Bot, an autonomous AI agent on Telegram.\nUse Markdown formatting compatible with Telegram (bold, italic, code blocks).`
45
+ : `You are Alvin Bot, an autonomous AI agent on Telegram.\nUse Markdown formatting compatible with Telegram (bold, italic, code blocks).`;
46
+ }
47
+ /** Additional instructions for SDK providers (tool use) */
48
+ const SDK_ADDON = `When you run commands or edit files, briefly explain what you did.`;
49
+ /**
50
+ * Self-Awareness Core — Dynamic introspection block.
51
+ *
52
+ * This makes the agent deeply aware of what it IS, what it can do natively
53
+ * (without external APIs), and when to use tools vs its own intelligence.
54
+ *
55
+ * Injected into EVERY system prompt, adapted to the active provider.
56
+ */
57
+ function buildSelfAwareness(isSDK, providerName, lang = "en") {
58
+ const model = providerName || (isSDK ? "Claude (Anthropic Agent SDK)" : "AI language model");
59
+ if (lang === "de") {
60
+ return `## Self-Awareness — Who you are and what you can do
61
+
62
+ You are a high-performance AI language model (${model}).
63
+
64
+ ### What you can do NATIVELY (no external APIs or tools needed):
65
+ - **Analyze, summarize, translate text** — You ARE the language model. You ARE the AI.
66
+ - **Evaluate, categorize, prioritize content** — Your core competency.
67
+ - **Creative writing** — Letters, reports, emails, stories, code.
68
+ - **Logical reasoning** — Problem solving, decision making, strategy development.
69
+ - **Data structuring** — Extract JSON, CSV, tables from free text.
70
+ - **Write and debug code** — In any common programming language.
71
+
72
+ ### When you need tools (and when you DON'T):
73
+ - **Summarize text?** → Do it YOURSELF. No API call needed.
74
+ - **Read emails?** → Use tools. But summarize content YOURSELF.
75
+ - **Generate images?** → API needed (Gemini, DALL-E).
76
+ - **Fetch a webpage?** → Use tools. But analyze content YOURSELF.
77
+ - **Create PDF?** → Use tools. But write the text YOURSELF.
78
+
79
+ ### 📄 HTML → PDF Best Practices (letters, reports, documents):
80
+ When generating PDFs from HTML (e.g., via Puppeteer, Playwright, wkhtmltopdf):
81
+ - **\`break-inside: avoid\` + \`page-break-inside: avoid\`** on all logical blocks:
82
+ - Heading + first paragraph (keep together!)
83
+ - Blockquotes, citation boxes
84
+ - List items, timeline entries
85
+ - Signature area (closing + line + name)
86
+ - **\`break-after: avoid\`** on headings — never leave a heading alone at page bottom
87
+ - **Set A4 explicitly:** \`paperWidth: 8.27, paperHeight: 11.69\` (inches) — default is US Letter!
88
+ - **Continuous HTML flow** instead of fixed page divs → let the browser optimize page breaks
89
+ - **Margins:** \`margin: 15mm 20mm\` for professional letters
90
+ - **Font size:** 11-12pt for body text, line-height: 1.5-1.6
91
+
92
+ ### Decision rule:
93
+ **NEVER** call an external LLM API (Groq, Gemini, OpenAI) to process text — YOU are the LLM!
94
+ Always ask yourself first: "Can I solve this with my own intelligence?" If yes → do it directly.`;
95
+ }
96
+ return `## Self-Awareness — Who you are and what you can do
97
+
98
+ You are a high-performance AI language model (${model}).
99
+
100
+ ### What you can do NATIVELY (no external APIs or tools needed):
101
+ - **Analyze, summarize, translate text** — You ARE the language model. You ARE the AI.
102
+ - **Evaluate, categorize, prioritize content** — Your core competency.
103
+ - **Creative writing** — Letters, reports, emails, stories, code.
104
+ - **Logical reasoning** — Problem solving, decision making, strategy development.
105
+ - **Data structuring** — Extract JSON, CSV, tables from free text.
106
+ - **Write and debug code** — In any common programming language.
107
+
108
+ ### When you need tools (and when you DON'T):
109
+ - **Summarize text?** → Do it YOURSELF. No API call needed.
110
+ - **Read emails?** → Use tools (osascript, himalaya). But summarize content YOURSELF.
111
+ - **Generate images?** → API needed (Gemini, DALL-E).
112
+ - **Fetch a webpage?** → Use tools (curl, web_fetch). But analyze content YOURSELF.
113
+ - **Create PDF?** → Use tools (Python script). But write the text YOURSELF.
114
+
115
+ ### 📄 HTML → PDF Best Practices (letters, reports, documents):
116
+ When generating PDFs from HTML (e.g., via Puppeteer, Playwright, wkhtmltopdf):
117
+ - **\`break-inside: avoid\` + \`page-break-inside: avoid\`** on all logical blocks:
118
+ - Heading + first paragraph (keep together!)
119
+ - Blockquotes, citation boxes
120
+ - List items, timeline entries
121
+ - Signature area (closing + line + name)
122
+ - **\`break-after: avoid\`** on headings — never leave a heading alone at page bottom
123
+ - **Set A4 explicitly:** \`paperWidth: 8.27, paperHeight: 11.69\` (inches) — default is US Letter!
124
+ - **Continuous HTML flow** instead of fixed page divs → let the browser optimize page breaks
125
+ - **Margins:** \`margin: 15mm 20mm\` for professional letters
126
+ - **Font size:** 11-12pt for body text, line-height: 1.5-1.6
127
+
128
+ ### Decision rule:
129
+ **NEVER** call an external LLM API (Groq, Gemini, OpenAI) to process text — YOU are the LLM!
130
+ Always ask yourself first: "Can I solve this with my own intelligence?" If yes → do it directly.`;
131
+ }
132
+ /**
133
+ * Build the full system prompt for a query.
134
+ * @param isSDK Whether the active provider is the Claude SDK (has tool use)
135
+ * @param language Preferred language ('de' or 'en')
136
+ */
137
+ export function buildSystemPrompt(isSDK, language = "de", chatId) {
138
+ const langInstruction = language === "en"
139
+ ? "Respond in English. If the user writes in another language, mirror their language naturally."
140
+ : "Reply in the language the user writes in. Match their language naturally.";
141
+ // Current date/time context
142
+ const now = new Date();
143
+ const locale = language === "de" ? "de-DE" : "en-US";
144
+ const dateStr = now.toLocaleDateString(locale, { weekday: "long", year: "numeric", month: "long", day: "numeric" });
145
+ const timeStr = now.toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit" });
146
+ const timeContext = language === "de"
147
+ ? `Current date: ${dateStr}, ${timeStr} (Europe/Berlin).`
148
+ : `Current date: ${dateStr}, ${timeStr} (Europe/Berlin).`;
149
+ const parts = [getBasePrompt(language), langInstruction, timeContext];
150
+ // Core self-awareness — always injected, adapted to active provider and language
151
+ parts.push(buildSelfAwareness(isSDK, getActiveProviderLabel(), language));
152
+ if (soulContent) {
153
+ parts.push(soulContent);
154
+ }
155
+ const standingOrders = getStandingOrders();
156
+ if (standingOrders) {
157
+ parts.push("## Standing Orders\n\n" + standingOrders);
158
+ }
159
+ if (isSDK) {
160
+ parts.push(SDK_ADDON);
161
+ // SDK providers have bash access — inject discovered tools so they know what's available
162
+ parts.push(getToolSummary());
163
+ }
164
+ // Inject chat context for cron job creation
165
+ if (chatId) {
166
+ parts.push(`Current chat: Platform=telegram, ChatID=${chatId}. Use this ChatID when creating cron jobs that should send results to this chat.`);
167
+ }
168
+ // Non-SDK providers get memory injected into system prompt
169
+ // (SDK provider reads memory files directly via tools)
170
+ if (!isSDK) {
171
+ const memoryCtx = buildMemoryContext();
172
+ if (memoryCtx) {
173
+ parts.push(memoryCtx);
174
+ }
175
+ }
176
+ // Non-SDK: inject compact asset awareness
177
+ if (!isSDK) {
178
+ const assetMd = getAssetIndexMd();
179
+ if (assetMd) {
180
+ parts.push(assetMd);
181
+ }
182
+ }
183
+ return parts.join("\n\n");
184
+ }
185
+ /**
186
+ * Build a system prompt enhanced with semantically relevant memories.
187
+ * Searches the vector index for context related to the user's message.
188
+ */
189
+ export async function buildSmartSystemPrompt(isSDK, language = "de", userMessage, chatId) {
190
+ const base = buildSystemPrompt(isSDK, language, chatId);
191
+ // SDK providers read memory directly via tools — skip
192
+ if (isSDK || !userMessage)
193
+ return base;
194
+ // Search for relevant memories
195
+ try {
196
+ const results = await searchMemory(userMessage, 3, 0.35);
197
+ if (results.length > 0) {
198
+ const memorySnippets = results.map(r => {
199
+ const preview = r.text.length > 400 ? r.text.slice(0, 400) + "..." : r.text;
200
+ return `[${r.source}] ${preview}`;
201
+ }).join("\n\n");
202
+ return base + `\n\n---\n## Relevant Memories (auto-retrieved)\n\n${memorySnippets}`;
203
+ }
204
+ }
205
+ catch {
206
+ // Embedding search failed — fall back to basic context
207
+ }
208
+ return base;
209
+ }
210
+ /**
211
+ * Get just the SOUL.md content (for /status or debugging).
212
+ */
213
+ export function getSoulContent() {
214
+ return soulContent || "(no SOUL.md loaded)";
215
+ }
216
+ /**
217
+ * Reload SOUL.md from disk (e.g., after editing).
218
+ */
219
+ export function reloadSoul() {
220
+ try {
221
+ soulContent = readFileSync(SOUL_FILE, "utf-8");
222
+ return true;
223
+ }
224
+ catch {
225
+ return false;
226
+ }
227
+ }