zubo 0.1.6 → 0.1.9

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 (80) hide show
  1. package/README.md +5 -4
  2. package/migrations/017_oauth_tokens.sql +11 -0
  3. package/migrations/018_cron_once.sql +1 -0
  4. package/package.json +1 -1
  5. package/site/docs/agents.html +82 -1
  6. package/site/docs/api.html +300 -4
  7. package/site/docs/channels.html +70 -3
  8. package/site/docs/cli.html +39 -8
  9. package/site/docs/config.html +165 -16
  10. package/site/docs/index.html +175 -10
  11. package/site/docs/integrations.html +203 -12
  12. package/site/docs/memory.html +53 -1
  13. package/site/docs/security.html +1 -0
  14. package/site/docs/skills.html +14 -3
  15. package/site/index.html +54 -20
  16. package/site/script.js +191 -64
  17. package/site/style.css +512 -207
  18. package/src/agent/delegate.ts +16 -7
  19. package/src/agent/prompts.ts +78 -71
  20. package/src/channels/dashboard.html.ts +1683 -471
  21. package/src/channels/email.ts +11 -6
  22. package/src/channels/lifecycle.ts +161 -0
  23. package/src/channels/router.ts +24 -3
  24. package/src/channels/webchat.ts +745 -28
  25. package/src/channels/whatsapp.ts +8 -3
  26. package/src/config/schema.ts +30 -0
  27. package/src/db/export.ts +6 -3
  28. package/src/index.ts +27 -0
  29. package/src/llm/claude-code.ts +133 -0
  30. package/src/llm/codex.ts +116 -0
  31. package/src/llm/factory.ts +15 -0
  32. package/src/llm/openai-compat.ts +2 -1
  33. package/src/llm/smart-router.ts +0 -10
  34. package/src/scheduler/cron.ts +26 -11
  35. package/src/scheduler/natural-cron.ts +55 -2
  36. package/src/secrets/store.ts +8 -14
  37. package/src/setup.ts +351 -113
  38. package/src/start.ts +60 -107
  39. package/src/tools/builtin/config-update.ts +148 -0
  40. package/src/tools/builtin/cron.ts +76 -7
  41. package/src/tools/builtin/delegate-task.ts +14 -3
  42. package/src/tools/builtin/delegate.ts +3 -1
  43. package/src/tools/builtin/manage-agents.ts +17 -0
  44. package/src/tools/builtin-integrations/claude-code/claude_code_task/SKILL.md +38 -0
  45. package/src/tools/builtin-integrations/claude-code/claude_code_task/handler.ts +100 -0
  46. package/src/tools/builtin-integrations/codex/codex_task/SKILL.md +33 -0
  47. package/src/tools/builtin-integrations/codex/codex_task/handler.ts +73 -0
  48. package/src/tools/builtin-integrations/github/github_issues/handler.ts +10 -2
  49. package/src/tools/builtin-integrations/github/github_prs/handler.ts +10 -2
  50. package/src/tools/builtin-integrations/github/github_repos/handler.ts +10 -2
  51. package/src/tools/builtin-integrations/google/gmail/handler.ts +7 -0
  52. package/src/tools/builtin-integrations/google/google_calendar/handler.ts +7 -0
  53. package/src/tools/builtin-integrations/google/google_docs/handler.ts +7 -0
  54. package/src/tools/builtin-integrations/google/google_drive/handler.ts +7 -0
  55. package/src/tools/builtin-integrations/google/google_sheets/handler.ts +7 -0
  56. package/src/tools/builtin-integrations/linear/linear_issues/handler.ts +10 -2
  57. package/src/tools/builtin-integrations/linear/linear_projects/handler.ts +10 -2
  58. package/src/tools/builtin-integrations/notion/notion_databases/handler.ts +10 -2
  59. package/src/tools/builtin-integrations/notion/notion_pages/handler.ts +10 -2
  60. package/src/tools/builtin-integrations/notion/notion_search/handler.ts +10 -2
  61. package/src/tools/builtin-integrations/slack/slack_messages/handler.ts +10 -2
  62. package/src/tools/builtin-skills/oauth-manage/SKILL.md +15 -0
  63. package/src/tools/builtin-skills/oauth-manage/handler.ts +156 -0
  64. package/src/tools/executor.ts +11 -23
  65. package/src/tools/integration-installer.ts +2 -0
  66. package/src/tools/mcp-client.ts +48 -7
  67. package/src/tools/mcp-server.ts +176 -0
  68. package/src/tools/oauth.ts +648 -0
  69. package/src/tools/permissions.ts +29 -12
  70. package/src/util/auth.ts +9 -2
  71. package/src/util/costs.ts +4 -0
  72. package/src/util/logger.ts +2 -2
  73. package/src/util/mask.ts +10 -0
  74. package/tests/code-interpreter.test.ts +229 -0
  75. package/tests/config-schema.test.ts +557 -0
  76. package/tests/email.test.ts +122 -0
  77. package/tests/image-generate.test.ts +156 -0
  78. package/tests/knowledge-graph.test.ts +553 -0
  79. package/tests/mcp-client.test.ts +428 -0
  80. package/tests/webhook-manage.test.ts +234 -0
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  <p align="center">
8
8
  <strong>Your AI agent that never forgets.</strong><br>
9
- Persistent memory, 20+ tools, 6 channels, 11+ LLM providers — runs entirely on your machine.
9
+ Persistent memory, 25+ tools, 7 channels, 11+ LLM providers — runs entirely on your machine.
10
10
  </p>
11
11
 
12
12
  <p align="center">
@@ -27,11 +27,11 @@
27
27
  ## Features
28
28
 
29
29
  - **11+ LLM providers** — Anthropic, OpenAI, Google Gemini, Ollama, Groq, Together, OpenRouter, DeepSeek, xAI, Fireworks, LM Studio, and any OpenAI-compatible endpoint. Smart routing sends simple queries to fast models automatically.
30
- - **6 channels** — Telegram, Discord, Slack, WhatsApp, Signal, Web Chat
30
+ - **7 channels** — Telegram, Discord, Slack, WhatsApp, Signal, Email, Web Chat
31
31
  - **Persistent memory** — Vector + full-text hybrid search with ONNX embeddings and FTS5. Remembers every conversation, preference, and fact — forever.
32
- - **20+ built-in tools** — Web search, file ops, code execution, APIs, sub-agent delegation, and automatic failover between providers.
32
+ - **25+ built-in tools** — Web search, file ops, code execution, APIs, sub-agent delegation, knowledge graph, reminders, and automatic failover between providers.
33
33
  - **Extensible skills** — Build custom skills in TypeScript. Share them on the registry. Install community skills with one command.
34
- - **7 integrations** — GitHub, Google (Gmail, Calendar, Docs, Drive, Sheets), Notion, Linear, Jira, Slack, Twitter
34
+ - **9 integrations** — GitHub, Google (Gmail, Calendar, Docs, Drive, Sheets), Notion, Linear, Jira, Slack, Twitter + Claude Code and MCP
35
35
  - **Workflows** — Multi-agent pipelines with delegation
36
36
  - **Natural language scheduling** — "Every weekday at 9am" just works. Cron jobs, heartbeat, proactive tasks.
37
37
  - **Voice** — Speech-to-text (Whisper) and text-to-speech (OpenAI, ElevenLabs)
@@ -98,6 +98,7 @@ See the full [configuration reference](https://zubo.bot/docs/config.html) for al
98
98
  | **Discord** | Add `channels.discord.botToken` from [Developer Portal](https://discord.com/developers) |
99
99
  | **Slack** | Add `channels.slack.botToken` + `appToken` (Socket Mode) |
100
100
  | **WhatsApp** | Add `channels.whatsapp`, authenticate via QR |
101
+ | **Email** | Add `channels.email` with IMAP/SMTP settings |
101
102
  | **Signal** | Install signal-cli, add `channels.signal.phoneNumber` |
102
103
 
103
104
  ## Integrations
@@ -0,0 +1,11 @@
1
+ CREATE TABLE IF NOT EXISTS oauth_tokens (
2
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
3
+ provider TEXT NOT NULL UNIQUE,
4
+ access_token TEXT NOT NULL,
5
+ refresh_token TEXT,
6
+ token_type TEXT DEFAULT 'Bearer',
7
+ expires_at TEXT,
8
+ scope TEXT,
9
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
10
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
11
+ );
@@ -0,0 +1 @@
1
+ ALTER TABLE cron_jobs ADD COLUMN once INTEGER NOT NULL DEFAULT 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zubo",
3
- "version": "0.1.6",
3
+ "version": "0.1.9",
4
4
  "description": "Your AI agent that never forgets. Persistent memory, 25+ tools, 7 channels, 11+ LLM providers — runs entirely on your machine.",
5
5
  "license": "MIT",
6
6
  "author": "thomaskanze",
@@ -66,6 +66,7 @@
66
66
  <div class="docs-sidebar-links">
67
67
  <a href="channels.html">Channel Setup</a>
68
68
  <a href="integrations.html">Integrations</a>
69
+ <a href="integrations.html#oauth">OAuth</a>
69
70
  <a href="security.html">Security &amp; Auth</a>
70
71
  </div>
71
72
  </div>
@@ -115,6 +116,65 @@ You are Zubo, a personal AI assistant for Thomas.
115
116
  - He prefers metric units and Markdown formatting.</code></pre>
116
117
  <p>The system prompt is loaded fresh on every message, so changes take effect immediately without restarting Zubo.</p>
117
118
 
119
+ <h2>Self-Configuration</h2>
120
+ <p>Zubo can modify its own configuration at runtime through the built-in <code>config_update</code> tool. You do not need to edit <code>config.json</code> by hand &mdash; just tell Zubo what you want to change in plain language and it will update the configuration file for you.</p>
121
+
122
+ <h3>What Zubo Can Change</h3>
123
+ <ul>
124
+ <li><strong>Switch LLM providers</strong> &mdash; "Use GPT-4o" updates <code>activeProvider</code> to <code>openai</code>.</li>
125
+ <li><strong>Set budgets</strong> &mdash; "Limit my daily spending to $5" updates <code>budget.dailyLimitUsd</code>.</li>
126
+ <li><strong>Enable smart routing</strong> &mdash; "Enable smart routing with Groq" updates <code>smartRouting.enabled</code> and <code>smartRouting.fastProvider</code>.</li>
127
+ <li><strong>Change its name</strong> &mdash; "Call yourself Jarvis" updates <code>agentName</code>.</li>
128
+ <li><strong>Toggle features</strong> &mdash; Enable or disable channels, change the heartbeat interval, adjust max turns, and more.</li>
129
+ </ul>
130
+
131
+ <h3>What It Cannot Change</h3>
132
+ <ul>
133
+ <li><strong>API keys and tokens</strong> &mdash; For security, secrets are never stored through <code>config_update</code>. Zubo uses the separate <code>secret_set</code> tool to securely store API keys, passwords, and tokens.</li>
134
+ <li><strong>Channel restarts</strong> &mdash; Channel configuration changes (enabling Telegram, changing a bot token) require a restart to take effect.</li>
135
+ </ul>
136
+
137
+ <h3>Example</h3>
138
+ <pre><code>You: I want to switch to Claude Opus for complex tasks but use Groq for simple ones
139
+
140
+ Zubo: Done! I've set your active provider to Anthropic and enabled smart
141
+ routing with Groq as the fast provider. Simple queries like greetings
142
+ will use Groq (fast, inexpensive), while complex tasks like code
143
+ generation and multi-step reasoning use Claude.
144
+
145
+ # Under the hood, Zubo called config_update three times:
146
+ # set activeProvider = "anthropic"
147
+ # set smartRouting.enabled = true
148
+ # set smartRouting.fastProvider = "groq"</code></pre>
149
+
150
+ <h2>How Zubo Learns</h2>
151
+ <p>Zubo's memory is organized into three complementary layers, each serving a different purpose. Together, they give the agent persistent, searchable, and structured knowledge that improves over time.</p>
152
+
153
+ <h3>1. Conversational Memory</h3>
154
+ <p>Every conversation session is automatically persisted. Recent messages are always included in the LLM context, so Zubo remembers what you discussed earlier in the same session without any explicit action on your part.</p>
155
+
156
+ <h3>2. Semantic Memory</h3>
157
+ <p>Facts, notes, and documents saved via the <code>memory_write</code> tool are chunked, embedded as 384-dimensional vectors, and indexed for full-text search. When you send a message, Zubo automatically runs a hybrid search (60% vector similarity + 40% keyword matching) to find relevant memories and inject them into the context. See the <a href="memory.html">Memory System</a> page for full details.</p>
158
+
159
+ <h3>3. Knowledge Graph</h3>
160
+ <p>Structured entity-relationship memory captures discrete facts in a queryable format. Entities (people, projects, organizations, concepts) are linked by labeled relationships (works_on, manages, uses). When you mention a known entity, its graph context is automatically injected into the LLM prompt.</p>
161
+
162
+ <h3>How the Layers Work Together</h3>
163
+ <pre><code>You: I'm working with Alex on the Horizon project. He handles backend,
164
+ I do frontend.
165
+
166
+ Zubo automatically:
167
+ 1. Saves this to semantic memory via memory_write
168
+ 2. Creates KG entities: You (person), Alex (person), Horizon (project)
169
+ 3. Creates KG relations:
170
+ You --works_on--> Horizon
171
+ Alex --works_on--> Horizon
172
+ You --role:frontend--> Horizon
173
+ Alex --role:backend--> Horizon
174
+ 4. Next time you mention "Alex" or "Horizon", this context is
175
+ auto-injected into the LLM prompt</code></pre>
176
+ <p>This means you can teach Zubo facts naturally through conversation, and it will recall them when relevant &mdash; without being asked.</p>
177
+
118
178
  <h2>Creating Custom Agents</h2>
119
179
  <p>Custom agents are defined as directories inside <code>~/.zubo/workspace/agents/</code>. Each directory contains a single <code>AGENT.md</code> file that specifies the agent's name, description, system prompt, and allowed tools.</p>
120
180
  <p>Here is an example <code>AGENT.md</code> for a research specialist:</p>
@@ -137,6 +197,26 @@ to ensure comprehensive coverage.
137
197
  <li><strong>Conversationally</strong> &mdash; Tell Zubo: "Create an agent called research_agent that specializes in web research and has access to web_search, url_fetch, and memory_write." Zubo will generate the <code>AGENT.md</code> for you.</li>
138
198
  <li><strong>Programmatically</strong> &mdash; Use the <code>manage_agents</code> tool to create, update, list, or delete agents.</li>
139
199
  </ul>
200
+ <p>Here is an example of creating and using an agent entirely through conversation:</p>
201
+ <pre><code>You: Create an agent called "researcher" that can only search the web
202
+ and fetch URLs. Its job is deep research.
203
+
204
+ Zubo: Created agent "researcher" with tools: web_search, url_fetch,
205
+ http_request. System prompt focused on thorough research and
206
+ source citation.
207
+
208
+ You: @researcher Find the top 3 competitors to Linear and compare
209
+ their pricing
210
+
211
+ Zubo: [delegates to researcher agent]
212
+
213
+ Here's a comparison of Linear's top competitors:
214
+
215
+ 1. Jira — Free tier for up to 10 users, Standard at $8.15/user/mo...
216
+ 2. Shortcut — Free for up to 10 members, Team at $8.50/user/mo...
217
+ 3. Asana — Free tier available, Premium at $10.99/user/mo...
218
+
219
+ Sources: [jira.com, shortcut.com, asana.com]</code></pre>
140
220
  <p>Agents have restricted tool access &mdash; they can only use the tools explicitly listed in their <code>## Tools</code> section. This is a key security and reliability feature: a research agent does not need access to your calendar, and a writing agent does not need access to the shell.</p>
141
221
 
142
222
  <h3>Agent Fields</h3>
@@ -156,8 +236,9 @@ to ensure comprehensive coverage.
156
236
  <ul>
157
237
  <li><strong>Maximum delegation depth: 2</strong> &mdash; This prevents infinite recursion. The main agent (depth 0) can delegate to a sub-agent (depth 1), which can delegate once more (depth 2), but no further.</li>
158
238
  <li><strong>Sub-agents cannot delegate further or access the <code>manage_agents</code> or <code>delegate</code> tools</strong> &mdash; These tools are automatically stripped from sub-agent tool lists to prevent abuse.</li>
239
+ <li><strong>Immutable security preamble</strong> &mdash; Every sub-agent's system prompt is prefixed with an immutable set of security rules that cannot be overridden by the user-defined agent prompt. These rules prevent sub-agents from revealing secrets, bypassing tool permission levels, delegating to other agents, or modifying their own configuration.</li>
159
240
  <li><strong>Each delegation creates a separate session</strong> &mdash; This provides context isolation. The sub-agent's conversation history does not bleed into the main agent's context.</li>
160
- <li><strong>Memory is shared</strong> &mdash; Sub-agents can search and write to the same memory store as the main agent, so research findings and facts persist across delegations.</li>
241
+ <li><strong>Memory is shared</strong> &mdash; Sub-agents share the same memory store (semantic memory and knowledge graph) as the main agent. Research findings, facts, and knowledge graph entities written by a sub-agent are immediately available to the main agent and all other sub-agents.</li>
161
242
  </ul>
162
243
  <p>Here is an example of how delegation works in practice:</p>
163
244
  <pre><code>User: "Research the latest AI news and summarize it"
@@ -66,6 +66,7 @@
66
66
  <div class="docs-sidebar-links">
67
67
  <a href="channels.html">Channel Setup</a>
68
68
  <a href="integrations.html">Integrations</a>
69
+ <a href="integrations.html#oauth">OAuth</a>
69
70
  <a href="security.html">Security &amp; Auth</a>
70
71
  </div>
71
72
  </div>
@@ -231,9 +232,9 @@ Content-Type: application/json
231
232
  ]
232
233
  }</code></pre>
233
234
 
234
- <h3>Cron Jobs</h3>
235
+ <h3>Cron Jobs &amp; Reminders</h3>
235
236
  <pre><code>GET /api/dashboard/cron</code></pre>
236
- <p>List all configured cron jobs and their status.</p>
237
+ <p>List all configured cron jobs and one-shot reminders.</p>
237
238
  <p><strong>Response:</strong></p>
238
239
  <pre><code>{
239
240
  "jobs": [
@@ -242,18 +243,313 @@ Content-Type: application/json
242
243
  "schedule": "0 9 * * *",
243
244
  "task": "Send a daily briefing",
244
245
  "enabled": true,
246
+ "once": false,
245
247
  "last_run": "2026-02-12T09:00:00Z"
248
+ },
249
+ {
250
+ "name": "marketing-followup",
251
+ "schedule": "2026-02-14T15:30:00.000Z",
252
+ "task": "Hey! Have you worked on marketing yet?",
253
+ "enabled": true,
254
+ "once": true,
255
+ "last_run": null
246
256
  }
247
257
  ]
248
258
  }</code></pre>
259
+ <p>Jobs with <code>"once": true</code> are one-shot reminders that auto-delete after firing.</p>
260
+
261
+ <h3>Providers</h3>
262
+ <pre><code>GET /api/dashboard/providers</code></pre>
263
+ <p>List all configured LLM providers with masked API keys, the active provider, and the failover order.</p>
264
+ <p><strong>Response:</strong></p>
265
+ <pre><code>{
266
+ "providers": [
267
+ {
268
+ "name": "anthropic",
269
+ "model": "claude-sonnet-4-5-20250929",
270
+ "apiKey": "sk-ant-...xxxx",
271
+ "baseUrl": "",
272
+ "contextWindow": null,
273
+ "streaming": true
274
+ },
275
+ {
276
+ "name": "openai",
277
+ "model": "gpt-4o",
278
+ "apiKey": "sk-...xxxx",
279
+ "baseUrl": "",
280
+ "contextWindow": null,
281
+ "streaming": true
282
+ }
283
+ ],
284
+ "activeProvider": "anthropic",
285
+ "failover": ["openai"]
286
+ }</code></pre>
287
+ <pre><code>PUT /api/dashboard/providers/:name
288
+ Content-Type: application/json
289
+
290
+ {
291
+ "model": "gpt-4o",
292
+ "apiKey": "sk-...",
293
+ "baseUrl": "",
294
+ "streaming": true
295
+ }</code></pre>
296
+ <p>Add or update a provider configuration. If this is the first provider, it is automatically set as active. Masked API key values (containing <code>...</code>) are ignored to preserve the existing key.</p>
297
+ <pre><code>DELETE /api/dashboard/providers/:name</code></pre>
298
+ <p>Remove a provider. If the deleted provider was active, the first remaining provider is selected. The provider is also removed from the failover list.</p>
299
+ <pre><code>PUT /api/dashboard/providers/active
300
+ Content-Type: application/json
301
+
302
+ {
303
+ "provider": "openai"
304
+ }</code></pre>
305
+ <p>Set the active LLM provider. The change is hot-reloaded into the running agent &mdash; no restart is needed.</p>
306
+ <pre><code>PUT /api/dashboard/providers/failover
307
+ Content-Type: application/json
308
+
309
+ {
310
+ "failover": ["openai", "anthropic"]
311
+ }</code></pre>
312
+ <p>Update the provider failover order. When the active provider fails, the agent tries each failover provider in the order listed.</p>
313
+
314
+ <h3>MCP Servers</h3>
315
+ <pre><code>GET /api/dashboard/mcp/servers</code></pre>
316
+ <p>List all configured MCP (Model Context Protocol) server connections with their current status and tool count.</p>
317
+ <p><strong>Response:</strong></p>
318
+ <pre><code>{
319
+ "servers": [
320
+ {
321
+ "name": "filesystem",
322
+ "command": "npx",
323
+ "args": ["-y", "@modelcontextprotocol/server-filesystem"],
324
+ "env": ["HOME"],
325
+ "enabled": true,
326
+ "connected": true,
327
+ "tools": 5
328
+ }
329
+ ]
330
+ }</code></pre>
331
+ <pre><code>POST /api/dashboard/mcp/servers
332
+ Content-Type: application/json
333
+
334
+ {
335
+ "name": "filesystem",
336
+ "command": "npx",
337
+ "args": ["-y", "@modelcontextprotocol/server-filesystem"],
338
+ "env": { "HOME": "/Users/me" },
339
+ "enabled": true
340
+ }</code></pre>
341
+ <p>Add and connect a new MCP server. If a server with the same name already exists, it is replaced. The server is hot-connected immediately if <code>enabled</code> is <code>true</code> (or omitted).</p>
342
+ <pre><code>DELETE /api/dashboard/mcp/servers/:name</code></pre>
343
+ <p>Disconnect and remove an MCP server. The server process is terminated and the configuration is deleted.</p>
344
+ <pre><code>POST /api/dashboard/mcp/servers/:name/restart</code></pre>
345
+ <p>Restart an MCP server connection. The server is disconnected and then reconnected using its saved configuration. Returns <code>404</code> if the server is not found.</p>
346
+
347
+ <h3>Channels Configuration</h3>
348
+ <pre><code>GET /api/dashboard/channels/config</code></pre>
349
+ <p>Get all channel configurations with masked tokens and running status. Returns an object keyed by channel name (telegram, discord, slack, whatsapp, signal, email), each containing <code>enabled</code>, <code>configured</code>, <code>running</code>, and <code>config</code> fields.</p>
350
+ <pre><code>PUT /api/dashboard/channels/:name/config
351
+ Content-Type: application/json
352
+
353
+ {
354
+ "botToken": "123456:ABC-DEF...",
355
+ "allowedUsers": ["user1"],
356
+ "enabled": true
357
+ }</code></pre>
358
+ <p>Update a channel's configuration. Valid channel names are <code>telegram</code>, <code>discord</code>, <code>slack</code>, <code>whatsapp</code>, <code>signal</code>, and <code>email</code>. If <code>enabled</code> is <code>true</code>, the channel is hot-started immediately. If <code>false</code>, the channel is stopped. Masked token values are preserved.</p>
359
+ <pre><code>PUT /api/dashboard/channels/:name/toggle
360
+ Content-Type: application/json
361
+
362
+ {
363
+ "enabled": true
364
+ }</code></pre>
365
+ <p>Enable or disable a channel with immediate start or stop. The channel must already be configured.</p>
366
+
367
+ <h3>OAuth</h3>
368
+ <pre><code>GET /api/dashboard/oauth/status</code></pre>
369
+ <p>Get OAuth connection status for all supported providers. Returns a list of supported providers and their current connection state, including whether each provider has credentials configured.</p>
370
+ <pre><code>PUT /api/dashboard/oauth/:provider/config
371
+ Content-Type: application/json
372
+
373
+ {
374
+ "clientId": "your-client-id",
375
+ "clientSecret": "your-client-secret"
376
+ }</code></pre>
377
+ <p>Save OAuth client credentials for a provider (e.g. <code>google</code>, <code>github</code>). These credentials are stored in the config file and used when initiating OAuth authorization flows.</p>
378
+ <pre><code>DELETE /api/dashboard/oauth/:provider/config</code></pre>
379
+ <p>Remove OAuth client credentials for a provider from the configuration.</p>
380
+ <pre><code>DELETE /api/dashboard/oauth/:provider</code></pre>
381
+ <p>Disconnect an OAuth provider by revoking its stored tokens. The client credentials remain in the configuration.</p>
382
+
383
+ <h3>Budget</h3>
384
+ <pre><code>GET /api/dashboard/budget</code></pre>
385
+ <p>Get budget configuration and current usage, including today's spend, this month's spend, and a daily breakdown for the last 7 days.</p>
386
+ <p><strong>Response:</strong></p>
387
+ <pre><code>{
388
+ "daily_limit_usd": 5.00,
389
+ "monthly_limit_usd": 100.00,
390
+ "alert_threshold": 0.8,
391
+ "paused": false,
392
+ "today_spend_usd": 1.2345,
393
+ "month_spend_usd": 42.50,
394
+ "daily_breakdown": [
395
+ { "day": "2026-02-13", "cost": 1.50, "tokens": 45000 },
396
+ { "day": "2026-02-14", "cost": 1.23, "tokens": 38000 }
397
+ ]
398
+ }</code></pre>
399
+ <pre><code>PUT /api/dashboard/budget
400
+ Content-Type: application/json
401
+
402
+ {
403
+ "daily_limit_usd": 5.00,
404
+ "monthly_limit_usd": 100.00,
405
+ "alert_threshold": 0.8
406
+ }</code></pre>
407
+ <p>Update budget limits. Set a field to <code>null</code> to remove that limit. The <code>alert_threshold</code> is a fraction (0&ndash;1) of the limit at which a warning is triggered. Updating the budget also resets the <code>paused</code> state.</p>
408
+
409
+ <h3>Privacy &amp; Data</h3>
410
+ <pre><code>GET /api/dashboard/privacy/summary</code></pre>
411
+ <p>Get a summary of all stored data, including counts of memories, messages, sessions, secrets, cron jobs, API calls, tool calls, and total tokens sent and received.</p>
412
+ <p><strong>Response:</strong></p>
413
+ <pre><code>{
414
+ "memoryCount": 567,
415
+ "messageCount": 1234,
416
+ "sessionCount": 15,
417
+ "secretCount": 3,
418
+ "cronCount": 2,
419
+ "apiCallCount": 890,
420
+ "toolCallCount": 456,
421
+ "totalTokensSent": 1500000,
422
+ "totalTokensReceived": 750000,
423
+ "providerBreakdown": [
424
+ { "provider": "anthropic", "calls": 800, "tokens_sent": 1200000 }
425
+ ]
426
+ }</code></pre>
427
+ <pre><code>GET /api/dashboard/privacy/api-log?limit=50&amp;offset=0</code></pre>
428
+ <p>View API call history with session ID, provider, model, token counts, cost, and response time. Supports pagination via <code>limit</code> (max 100) and <code>offset</code> query parameters.</p>
429
+ <pre><code>GET /api/dashboard/privacy/tool-log?limit=50&amp;offset=0</code></pre>
430
+ <p>View tool call history with tool name, session ID, duration, and success status. Supports pagination via <code>limit</code> (max 100) and <code>offset</code> query parameters.</p>
431
+ <pre><code>POST /api/dashboard/privacy/wipe-memories
432
+ Content-Type: application/json
433
+
434
+ { "confirm": "DELETE" }</code></pre>
435
+ <p>Delete all memory chunks and full-text search index. Requires <code>{ "confirm": "DELETE" }</code> in the request body.</p>
436
+ <pre><code>POST /api/dashboard/privacy/wipe-messages
437
+ Content-Type: application/json
438
+
439
+ { "confirm": "DELETE" }</code></pre>
440
+ <p>Delete all stored messages. Requires <code>{ "confirm": "DELETE" }</code> in the request body.</p>
441
+ <pre><code>POST /api/dashboard/privacy/wipe-usage
442
+ Content-Type: application/json
443
+
444
+ { "confirm": "DELETE" }</code></pre>
445
+ <p>Delete all usage analytics and tool metrics. Requires <code>{ "confirm": "DELETE" }</code> in the request body.</p>
446
+ <pre><code>POST /api/dashboard/privacy/wipe-all
447
+ Content-Type: application/json
448
+
449
+ { "confirm": "DELETE_ALL" }</code></pre>
450
+ <p>Complete data wipe: deletes all memories, messages, usage analytics, tool metrics, secrets, and cron logs. Requires <code>{ "confirm": "DELETE_ALL" }</code> in the request body.</p>
451
+
452
+ <h3>Threads</h3>
453
+ <pre><code>GET /api/dashboard/threads</code></pre>
454
+ <p>List all conversation threads, ordered by most recently updated.</p>
455
+ <pre><code>POST /api/dashboard/threads
456
+ Content-Type: application/json
457
+
458
+ {
459
+ "title": "Project discussion"
460
+ }</code></pre>
461
+ <p>Create a new conversation thread. The <code>title</code> field is optional and defaults to "New conversation". Returns the generated thread <code>id</code> and <code>title</code>.</p>
462
+ <pre><code>PUT /api/dashboard/threads/:id
463
+ Content-Type: application/json
464
+
465
+ {
466
+ "title": "Renamed thread"
467
+ }</code></pre>
468
+ <p>Rename a thread by its ID.</p>
469
+ <pre><code>DELETE /api/dashboard/threads/:id</code></pre>
470
+ <p>Delete a thread and its associated session file.</p>
471
+ <pre><code>GET /api/dashboard/threads/:id/messages</code></pre>
472
+ <p>Get the messages in a thread (up to 100 messages). Returns the session's message array.</p>
473
+ <pre><code>GET /api/dashboard/threads/:id/export</code></pre>
474
+ <p>Export a thread as a Markdown file. Returns a <code>text/markdown</code> response with a <code>Content-Disposition</code> header for download.</p>
475
+
476
+ <h3>Recipes</h3>
477
+ <pre><code>GET /api/dashboard/recipes</code></pre>
478
+ <p>List all available workflow recipe templates with their install status. Each recipe includes its <code>id</code>, metadata, and an <code>installed</code> flag indicating whether it has already been added as a cron job.</p>
479
+ <pre><code>POST /api/dashboard/recipes/install
480
+ Content-Type: application/json
481
+
482
+ {
483
+ "recipeId": "daily-summary"
484
+ }</code></pre>
485
+ <p>Install a recipe as a cron job. The recipe is looked up by <code>recipeId</code> and inserted into the cron jobs table. Returns an error if the recipe is already installed.</p>
486
+ <pre><code>POST /api/dashboard/recipes/uninstall
487
+ Content-Type: application/json
488
+
489
+ {
490
+ "recipeId": "daily-summary"
491
+ }</code></pre>
492
+ <p>Uninstall a recipe by removing its cron job.</p>
493
+
494
+ <h3>Smart Routing</h3>
495
+ <pre><code>GET /api/dashboard/smart-routing</code></pre>
496
+ <p>Get the current smart routing configuration. Smart routing automatically directs simple queries to a faster, cheaper model while using the primary model for complex tasks.</p>
497
+ <p><strong>Response:</strong></p>
498
+ <pre><code>{
499
+ "enabled": false,
500
+ "fastProvider": "openai",
501
+ "fastModel": "gpt-4o-mini"
502
+ }</code></pre>
503
+ <pre><code>PUT /api/dashboard/smart-routing
504
+ Content-Type: application/json
505
+
506
+ {
507
+ "enabled": true,
508
+ "fastProvider": "openai",
509
+ "fastModel": "gpt-4o-mini"
510
+ }</code></pre>
511
+ <p>Update smart routing settings. All fields are optional &mdash; only the fields you include will be updated.</p>
249
512
 
250
513
  <h3>Logs</h3>
251
514
  <pre><code>GET /api/dashboard/logs</code></pre>
252
515
  <p>Returns the last 100 log lines from the agent's log output.</p>
253
516
 
254
- <h3>Config</h3>
517
+ <h3>Configuration</h3>
255
518
  <pre><code>GET /api/dashboard/config</code></pre>
256
- <p>Returns the current configuration (sensitive values are masked).</p>
519
+ <p>Returns the current configuration summary with API keys masked.</p>
520
+ <p><strong>Response:</strong></p>
521
+ <pre><code>{
522
+ "agentName": "Zubo",
523
+ "activeProvider": "anthropic",
524
+ "providers": {
525
+ "anthropic": { "model": "claude-sonnet-4-5-20250929", "hasApiKey": true },
526
+ "groq": { "model": "llama-3.3-70b-versatile", "hasApiKey": true }
527
+ },
528
+ "failover": ["groq"],
529
+ "channels": {
530
+ "webchat": { "enabled": true },
531
+ "telegram": { "enabled": true }
532
+ },
533
+ "smartRouting": { "enabled": true, "fastProvider": "groq" },
534
+ "budget": { "dailyLimitUsd": 5, "monthlyLimitUsd": 50, "alertThreshold": 0.8 },
535
+ "maxTurns": 50,
536
+ "heartbeatMinutes": 30
537
+ }</code></pre>
538
+ <pre><code>PUT /api/dashboard/config
539
+ Content-Type: application/json
540
+
541
+ {
542
+ "key": "budget.dailyLimitUsd",
543
+ "value": 10
544
+ }</code></pre>
545
+ <p>Updates a configuration value using dot-notation paths. The value is validated against the config schema before saving. API keys and tokens cannot be set through this endpoint &mdash; use the secrets API instead.</p>
546
+ <p><strong>Response:</strong></p>
547
+ <pre><code>{
548
+ "success": true,
549
+ "key": "budget.dailyLimitUsd",
550
+ "value": 10,
551
+ "message": "Config updated. Changes take effect on next restart."
552
+ }</code></pre>
257
553
  <pre><code>PUT /api/dashboard/config/model
258
554
  Content-Type: application/json
259
555
 
@@ -4,18 +4,18 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Channel Setup — Zubo Docs</title>
7
- <meta name="description" content="Connect Zubo to Telegram, Discord, Slack, WhatsApp, Signal, and Web Chat. Step-by-step setup guides for every supported messaging channel.">
7
+ <meta name="description" content="Connect Zubo to Telegram, Discord, Slack, WhatsApp, Signal, Email, and Web Chat. Step-by-step setup guides for every supported messaging channel.">
8
8
  <meta name="theme-color" content="#060608">
9
9
  <link rel="canonical" href="https://zubo.bot/docs/channels.html">
10
10
  <meta property="og:title" content="Channel Setup — Zubo Docs">
11
- <meta property="og:description" content="Connect Zubo to Telegram, Discord, Slack, WhatsApp, Signal, and Web Chat. Step-by-step setup guides.">
11
+ <meta property="og:description" content="Connect Zubo to Telegram, Discord, Slack, WhatsApp, Signal, Email, and Web Chat. Step-by-step setup guides.">
12
12
  <meta property="og:type" content="article">
13
13
  <meta property="og:url" content="https://zubo.bot/docs/channels.html">
14
14
  <meta property="og:image" content="https://zubo.bot/og-image.png">
15
15
  <meta property="og:site_name" content="Zubo">
16
16
  <meta name="twitter:card" content="summary_large_image">
17
17
  <meta name="twitter:title" content="Channel Setup — Zubo Docs">
18
- <meta name="twitter:description" content="Connect Zubo to Telegram, Discord, Slack, WhatsApp, Signal, and Web Chat.">
18
+ <meta name="twitter:description" content="Connect Zubo to Telegram, Discord, Slack, WhatsApp, Signal, Email, and Web Chat.">
19
19
  <meta name="twitter:image" content="https://zubo.bot/og-image.png">
20
20
  <meta name="twitter:creator" content="@thomaskanze">
21
21
  <link rel="preconnect" href="https://fonts.googleapis.com">
@@ -72,6 +72,7 @@
72
72
  <div class="docs-sidebar-links">
73
73
  <a href="channels.html" class="active">Channel Setup</a>
74
74
  <a href="integrations.html">Integrations</a>
75
+ <a href="integrations.html#oauth">OAuth</a>
75
76
  <a href="security.html">Security &amp; Auth</a>
76
77
  </div>
77
78
  </div>
@@ -285,6 +286,72 @@ brew install signal-cli
285
286
  </tbody>
286
287
  </table>
287
288
 
289
+ <h2>Email</h2>
290
+ <p>Connect Zubo to an email account via IMAP/SMTP so it can receive and reply to emails. Great for workflows like processing incoming emails, auto-replies, or email-based task management.</p>
291
+ <h3>Prerequisites</h3>
292
+ <ul>
293
+ <li>An email account with IMAP and SMTP access (Gmail, Outlook, FastMail, etc.)</li>
294
+ <li>For Gmail, an <strong>App Password</strong> is required &mdash; your regular Google account password will not work</li>
295
+ </ul>
296
+ <h3>Setup Steps</h3>
297
+ <ol>
298
+ <li>Gather your IMAP and SMTP server details from your email provider (host, port, username, password)</li>
299
+ <li>Add the email channel to your Zubo config:
300
+ <pre><code>{
301
+ "channels": {
302
+ "email": {
303
+ "enabled": true,
304
+ "imap": {
305
+ "host": "imap.gmail.com",
306
+ "port": 993,
307
+ "user": "you@gmail.com",
308
+ "password": "your-app-password",
309
+ "tls": true
310
+ },
311
+ "smtp": {
312
+ "host": "smtp.gmail.com",
313
+ "port": 587,
314
+ "user": "you@gmail.com",
315
+ "password": "your-app-password",
316
+ "tls": true
317
+ },
318
+ "pollIntervalSeconds": 60,
319
+ "allowedSenders": ["boss@company.com", "team@company.com"],
320
+ "fromName": "Zubo Assistant"
321
+ }
322
+ }
323
+ }</code></pre>
324
+ </li>
325
+ <li>Restart Zubo: <code>zubo restart</code></li>
326
+ </ol>
327
+
328
+ <h3>Config Fields</h3>
329
+ <table>
330
+ <thead>
331
+ <tr><th>Field</th><th>Type</th><th>Default</th><th>Description</th></tr>
332
+ </thead>
333
+ <tbody>
334
+ <tr><td><code>enabled</code></td><td>boolean</td><td><code>true</code></td><td>Enable or disable the Email channel</td></tr>
335
+ <tr><td><code>imap.host</code></td><td>string</td><td>&mdash;</td><td>IMAP server hostname (e.g., <code>imap.gmail.com</code>)</td></tr>
336
+ <tr><td><code>imap.port</code></td><td>number</td><td><code>993</code></td><td>IMAP server port</td></tr>
337
+ <tr><td><code>imap.user</code></td><td>string</td><td>&mdash;</td><td>IMAP login username (usually your email address)</td></tr>
338
+ <tr><td><code>imap.password</code></td><td>string</td><td>&mdash;</td><td>IMAP login password or app password</td></tr>
339
+ <tr><td><code>imap.tls</code></td><td>boolean</td><td><code>true</code></td><td>Use TLS for the IMAP connection</td></tr>
340
+ <tr><td><code>smtp.host</code></td><td>string</td><td>&mdash;</td><td>SMTP server hostname (e.g., <code>smtp.gmail.com</code>)</td></tr>
341
+ <tr><td><code>smtp.port</code></td><td>number</td><td><code>587</code></td><td>SMTP server port (587 for STARTTLS, 465 for implicit TLS)</td></tr>
342
+ <tr><td><code>smtp.user</code></td><td>string</td><td>&mdash;</td><td>SMTP login username (usually your email address)</td></tr>
343
+ <tr><td><code>smtp.password</code></td><td>string</td><td>&mdash;</td><td>SMTP login password or app password</td></tr>
344
+ <tr><td><code>smtp.tls</code></td><td>boolean</td><td><code>true</code></td><td>Use TLS for the SMTP connection</td></tr>
345
+ <tr><td><code>pollIntervalSeconds</code></td><td>number</td><td><code>60</code></td><td>How often (in seconds) to check for new emails. Minimum is 10.</td></tr>
346
+ <tr><td><code>allowedSenders</code></td><td>string[]</td><td>&mdash;</td><td>Email addresses allowed to message. If omitted or empty, all incoming emails are processed.</td></tr>
347
+ <tr><td><code>fromName</code></td><td>string</td><td><code>"Zubo"</code></td><td>Display name used in the <code>From</code> header of outgoing replies</td></tr>
348
+ </tbody>
349
+ </table>
350
+
351
+ <p><strong>Security note &mdash; <code>allowedSenders</code>:</strong> If set, only emails from the listed addresses will be processed. All other incoming emails are marked as read and skipped. If <code>allowedSenders</code> is omitted or empty, Zubo will process every incoming email &mdash; use this only for private or low-traffic mailboxes.</p>
352
+
353
+ <p><strong>Gmail setup:</strong> Gmail requires an App Password for third-party IMAP/SMTP access. To create one, go to <a href="https://myaccount.google.com/apppasswords">Google Account &rarr; Security &rarr; App Passwords</a> (requires 2-Step Verification to be enabled). Generate a new app password, then use that 16-character password in both the <code>imap.password</code> and <code>smtp.password</code> fields above.</p>
354
+
288
355
  <h2>Running Multiple Channels</h2>
289
356
  <p>Zubo is designed to run all channels simultaneously. There is no limit to how many channels you can enable at once.</p>
290
357
  <ul>