zubo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/.github/workflows/ci.yml +35 -0
  2. package/README.md +149 -0
  3. package/bun.lock +216 -0
  4. package/desktop/README.md +57 -0
  5. package/desktop/package.json +12 -0
  6. package/desktop/src-tauri/Cargo.toml +25 -0
  7. package/desktop/src-tauri/build.rs +3 -0
  8. package/desktop/src-tauri/icons/README.md +17 -0
  9. package/desktop/src-tauri/icons/icon.png +0 -0
  10. package/desktop/src-tauri/src/main.rs +189 -0
  11. package/desktop/src-tauri/tauri.conf.json +68 -0
  12. package/docs/ROADMAP.md +490 -0
  13. package/migrations/001_init.sql +9 -0
  14. package/migrations/002_memory.sql +33 -0
  15. package/migrations/003_cron.sql +24 -0
  16. package/migrations/004_usage.sql +12 -0
  17. package/migrations/005_secrets.sql +8 -0
  18. package/migrations/006_agents.sql +1 -0
  19. package/migrations/007_workflows.sql +22 -0
  20. package/migrations/008_proactive.sql +24 -0
  21. package/migrations/009_uploads.sql +9 -0
  22. package/migrations/010_observability.sql +22 -0
  23. package/migrations/011_api_keys.sql +7 -0
  24. package/migrations/012_indexes.sql +5 -0
  25. package/migrations/013_budget.sql +11 -0
  26. package/migrations/014_usage_session_idx.sql +2 -0
  27. package/package.json +39 -0
  28. package/site/404.html +156 -0
  29. package/site/CNAME +1 -0
  30. package/site/docs/agents.html +294 -0
  31. package/site/docs/api.html +446 -0
  32. package/site/docs/channels.html +345 -0
  33. package/site/docs/cli.html +238 -0
  34. package/site/docs/config.html +1034 -0
  35. package/site/docs/index.html +433 -0
  36. package/site/docs/integrations.html +381 -0
  37. package/site/docs/memory.html +254 -0
  38. package/site/docs/security.html +375 -0
  39. package/site/docs/skills.html +322 -0
  40. package/site/docs.css +412 -0
  41. package/site/index.html +638 -0
  42. package/site/install.sh +98 -0
  43. package/site/logo.svg +1 -0
  44. package/site/og-image.png +0 -0
  45. package/site/robots.txt +4 -0
  46. package/site/script.js +361 -0
  47. package/site/sitemap.xml +63 -0
  48. package/site/skills.html +532 -0
  49. package/site/style.css +1686 -0
  50. package/src/agent/agents.ts +159 -0
  51. package/src/agent/compaction.ts +53 -0
  52. package/src/agent/context.ts +18 -0
  53. package/src/agent/delegate.ts +118 -0
  54. package/src/agent/loop.ts +318 -0
  55. package/src/agent/prompts.ts +111 -0
  56. package/src/agent/session.ts +87 -0
  57. package/src/agent/teams.ts +116 -0
  58. package/src/agent/workflow-executor.ts +192 -0
  59. package/src/agent/workflow.ts +175 -0
  60. package/src/channels/adapter.ts +21 -0
  61. package/src/channels/dashboard.html.ts +2969 -0
  62. package/src/channels/discord.ts +137 -0
  63. package/src/channels/optional-deps.d.ts +17 -0
  64. package/src/channels/router.ts +199 -0
  65. package/src/channels/signal.ts +133 -0
  66. package/src/channels/slack.ts +101 -0
  67. package/src/channels/telegram.ts +102 -0
  68. package/src/channels/utils.ts +18 -0
  69. package/src/channels/webchat.ts +1797 -0
  70. package/src/channels/whatsapp.ts +119 -0
  71. package/src/config/loader.ts +22 -0
  72. package/src/config/paths.ts +43 -0
  73. package/src/config/schema.ts +121 -0
  74. package/src/db/connection.ts +20 -0
  75. package/src/db/export.ts +148 -0
  76. package/src/db/migrations.ts +42 -0
  77. package/src/index.ts +261 -0
  78. package/src/llm/claude.ts +193 -0
  79. package/src/llm/factory.ts +115 -0
  80. package/src/llm/failover.ts +101 -0
  81. package/src/llm/openai-compat.ts +409 -0
  82. package/src/llm/provider.ts +83 -0
  83. package/src/llm/smart-router.ts +241 -0
  84. package/src/logs.ts +53 -0
  85. package/src/memory/chunker.ts +58 -0
  86. package/src/memory/document-parser.ts +115 -0
  87. package/src/memory/embedder.ts +235 -0
  88. package/src/memory/engine.ts +170 -0
  89. package/src/memory/fts-index.ts +55 -0
  90. package/src/memory/hybrid-search.ts +72 -0
  91. package/src/memory/store.ts +56 -0
  92. package/src/memory/vector-index.ts +72 -0
  93. package/src/model.ts +118 -0
  94. package/src/registry/cli.ts +43 -0
  95. package/src/registry/client.ts +54 -0
  96. package/src/registry/installer.ts +67 -0
  97. package/src/scheduler/briefing.ts +71 -0
  98. package/src/scheduler/cron.ts +258 -0
  99. package/src/scheduler/heartbeat.ts +58 -0
  100. package/src/scheduler/memory-triggers.ts +100 -0
  101. package/src/scheduler/natural-cron.ts +163 -0
  102. package/src/scheduler/proactive.ts +25 -0
  103. package/src/scheduler/recipes.ts +110 -0
  104. package/src/secrets/store.ts +64 -0
  105. package/src/setup.ts +413 -0
  106. package/src/skills.ts +293 -0
  107. package/src/start.ts +373 -0
  108. package/src/status.ts +165 -0
  109. package/src/tools/builtin/connect-service.ts +205 -0
  110. package/src/tools/builtin/cron.ts +126 -0
  111. package/src/tools/builtin/datetime.ts +36 -0
  112. package/src/tools/builtin/delegate-task.ts +81 -0
  113. package/src/tools/builtin/delegate.ts +42 -0
  114. package/src/tools/builtin/diagnose.ts +41 -0
  115. package/src/tools/builtin/google-oauth.ts +379 -0
  116. package/src/tools/builtin/manage-agents.ts +149 -0
  117. package/src/tools/builtin/manage-skills.ts +294 -0
  118. package/src/tools/builtin/manage-teams.ts +89 -0
  119. package/src/tools/builtin/manage-triggers.ts +94 -0
  120. package/src/tools/builtin/manage-workflows.ts +119 -0
  121. package/src/tools/builtin/memory-search.ts +38 -0
  122. package/src/tools/builtin/memory-write.ts +30 -0
  123. package/src/tools/builtin/run-workflow.ts +36 -0
  124. package/src/tools/builtin/secrets.ts +122 -0
  125. package/src/tools/builtin/skill-registry.ts +75 -0
  126. package/src/tools/builtin-integrations/api-helpers.ts +26 -0
  127. package/src/tools/builtin-integrations/github/github_issues/SKILL.md +56 -0
  128. package/src/tools/builtin-integrations/github/github_issues/handler.ts +108 -0
  129. package/src/tools/builtin-integrations/github/github_prs/SKILL.md +57 -0
  130. package/src/tools/builtin-integrations/github/github_prs/handler.ts +113 -0
  131. package/src/tools/builtin-integrations/github/github_repos/SKILL.md +37 -0
  132. package/src/tools/builtin-integrations/github/github_repos/handler.ts +88 -0
  133. package/src/tools/builtin-integrations/google/gmail/SKILL.md +51 -0
  134. package/src/tools/builtin-integrations/google/gmail/handler.ts +125 -0
  135. package/src/tools/builtin-integrations/google/google_calendar/SKILL.md +35 -0
  136. package/src/tools/builtin-integrations/google/google_calendar/handler.ts +105 -0
  137. package/src/tools/builtin-integrations/google/google_docs/SKILL.md +35 -0
  138. package/src/tools/builtin-integrations/google/google_docs/handler.ts +108 -0
  139. package/src/tools/builtin-integrations/google/google_drive/SKILL.md +39 -0
  140. package/src/tools/builtin-integrations/google/google_drive/handler.ts +106 -0
  141. package/src/tools/builtin-integrations/google/google_sheets/SKILL.md +36 -0
  142. package/src/tools/builtin-integrations/google/google_sheets/handler.ts +116 -0
  143. package/src/tools/builtin-integrations/jira/jira_boards/SKILL.md +21 -0
  144. package/src/tools/builtin-integrations/jira/jira_boards/handler.ts +74 -0
  145. package/src/tools/builtin-integrations/jira/jira_issues/SKILL.md +28 -0
  146. package/src/tools/builtin-integrations/jira/jira_issues/handler.ts +140 -0
  147. package/src/tools/builtin-integrations/linear/linear_issues/SKILL.md +30 -0
  148. package/src/tools/builtin-integrations/linear/linear_issues/handler.ts +75 -0
  149. package/src/tools/builtin-integrations/linear/linear_projects/SKILL.md +21 -0
  150. package/src/tools/builtin-integrations/linear/linear_projects/handler.ts +43 -0
  151. package/src/tools/builtin-integrations/notion/notion_databases/SKILL.md +39 -0
  152. package/src/tools/builtin-integrations/notion/notion_databases/handler.ts +83 -0
  153. package/src/tools/builtin-integrations/notion/notion_pages/SKILL.md +43 -0
  154. package/src/tools/builtin-integrations/notion/notion_pages/handler.ts +130 -0
  155. package/src/tools/builtin-integrations/notion/notion_search/SKILL.md +27 -0
  156. package/src/tools/builtin-integrations/notion/notion_search/handler.ts +69 -0
  157. package/src/tools/builtin-integrations/slack/slack_messages/SKILL.md +42 -0
  158. package/src/tools/builtin-integrations/slack/slack_messages/handler.ts +72 -0
  159. package/src/tools/builtin-integrations/twitter/twitter_posts/SKILL.md +24 -0
  160. package/src/tools/builtin-integrations/twitter/twitter_posts/handler.ts +133 -0
  161. package/src/tools/builtin-skills/file-read/SKILL.md +26 -0
  162. package/src/tools/builtin-skills/file-read/handler.ts +66 -0
  163. package/src/tools/builtin-skills/file-write/SKILL.md +30 -0
  164. package/src/tools/builtin-skills/file-write/handler.ts +64 -0
  165. package/src/tools/builtin-skills/http-request/SKILL.md +34 -0
  166. package/src/tools/builtin-skills/http-request/handler.ts +87 -0
  167. package/src/tools/builtin-skills/shell/SKILL.md +26 -0
  168. package/src/tools/builtin-skills/shell/handler.ts +96 -0
  169. package/src/tools/builtin-skills/url-fetch/SKILL.md +26 -0
  170. package/src/tools/builtin-skills/url-fetch/handler.ts +37 -0
  171. package/src/tools/builtin-skills/web-search/SKILL.md +26 -0
  172. package/src/tools/builtin-skills/web-search/handler.ts +50 -0
  173. package/src/tools/executor.ts +205 -0
  174. package/src/tools/integration-installer.ts +106 -0
  175. package/src/tools/permissions.ts +45 -0
  176. package/src/tools/registry.ts +39 -0
  177. package/src/tools/sandbox-runner.ts +56 -0
  178. package/src/tools/sandbox.ts +82 -0
  179. package/src/tools/skill-installer.ts +52 -0
  180. package/src/tools/skill-loader.ts +259 -0
  181. package/src/types/optional-deps.d.ts +23 -0
  182. package/src/util/auth.ts +121 -0
  183. package/src/util/costs.ts +59 -0
  184. package/src/util/error-buffer.ts +32 -0
  185. package/src/util/google-tokens.ts +180 -0
  186. package/src/util/logger.ts +73 -0
  187. package/src/util/perf-collector.ts +35 -0
  188. package/src/util/rate-limiter.ts +70 -0
  189. package/src/util/tokens.ts +17 -0
  190. package/src/voice/stt.ts +57 -0
  191. package/src/voice/tts.ts +103 -0
  192. package/tests/agent/session.test.ts +109 -0
  193. package/tests/agent-loop.test.ts +54 -0
  194. package/tests/auth.test.ts +89 -0
  195. package/tests/channels.test.ts +67 -0
  196. package/tests/compaction.test.ts +44 -0
  197. package/tests/config.test.ts +51 -0
  198. package/tests/costs.test.ts +19 -0
  199. package/tests/cron.test.ts +55 -0
  200. package/tests/db/export.test.ts +219 -0
  201. package/tests/executor.test.ts +144 -0
  202. package/tests/export.test.ts +137 -0
  203. package/tests/helpers/mock-llm.ts +34 -0
  204. package/tests/helpers/test-db.ts +74 -0
  205. package/tests/integration/chat-flow.test.ts +48 -0
  206. package/tests/integrations.test.ts +97 -0
  207. package/tests/memory/engine.test.ts +114 -0
  208. package/tests/memory-engine.test.ts +57 -0
  209. package/tests/permissions.test.ts +21 -0
  210. package/tests/rate-limiter.test.ts +70 -0
  211. package/tests/registry.test.ts +67 -0
  212. package/tests/router.test.ts +36 -0
  213. package/tests/session.test.ts +58 -0
  214. package/tests/skill-loader.test.ts +44 -0
  215. package/tests/tokens.test.ts +30 -0
  216. package/tests/tools/executor.test.ts +130 -0
  217. package/tests/util/auth.test.ts +75 -0
  218. package/tests/util/rate-limiter.test.ts +73 -0
  219. package/tests/voice.test.ts +60 -0
  220. package/tests/webchat.test.ts +88 -0
  221. package/tests/workflow.test.ts +38 -0
  222. package/tsconfig.json +16 -0
@@ -0,0 +1,88 @@
1
+ async function safeApiError(res: Response, service: string): Promise<string> {
2
+ const body = await res.text().catch(() => "");
3
+ console.error(`[${service}] API error ${res.status}: ${body.slice(0, 500)}`);
4
+ return JSON.stringify({ error: `${service} API error: ${res.status} ${res.statusText}` });
5
+ }
6
+
7
+ function safeExceptionError(err: any, service: string): string {
8
+ console.error(`[${service}] Request failed: ${err.message}`);
9
+ return JSON.stringify({ error: `${service} request failed. Check logs for details.` });
10
+ }
11
+
12
+ const API = "https://api.github.com";
13
+
14
+ export default async function (input: Record<string, unknown>): Promise<string> {
15
+ const token = (globalThis as any).Zubo?.getSecret?.("github_token");
16
+ if (!token) {
17
+ return JSON.stringify({
18
+ error: "GitHub token not configured. Use secret_set to store a 'github_token' or connect_service to set up GitHub.",
19
+ });
20
+ }
21
+
22
+ const { action, owner, repo, type, sort } = input as {
23
+ action: string;
24
+ owner?: string;
25
+ repo?: string;
26
+ type?: string;
27
+ sort?: string;
28
+ };
29
+
30
+ const headers: Record<string, string> = {
31
+ Authorization: `Bearer ${token}`,
32
+ Accept: "application/vnd.github+json",
33
+ "User-Agent": "Zubo-Agent",
34
+ };
35
+
36
+ try {
37
+ switch (action) {
38
+ case "list": {
39
+ const endpoint = owner ? `${API}/users/${owner}/repos` : `${API}/user/repos`;
40
+ const qs = new URLSearchParams({
41
+ type: type || "owner",
42
+ sort: sort || "updated",
43
+ per_page: "30",
44
+ });
45
+ const res = await fetch(`${endpoint}?${qs}`, { headers });
46
+ if (!res.ok) return await safeApiError(res, "GitHub");
47
+ const repos = (await res.json()) as any[];
48
+ return JSON.stringify(
49
+ repos.map((r) => ({
50
+ full_name: r.full_name,
51
+ description: r.description,
52
+ language: r.language,
53
+ stars: r.stargazers_count,
54
+ forks: r.forks_count,
55
+ private: r.private,
56
+ updated_at: r.updated_at,
57
+ url: r.html_url,
58
+ }))
59
+ );
60
+ }
61
+
62
+ case "get": {
63
+ if (!owner || !repo) return JSON.stringify({ error: "owner and repo are required for get" });
64
+ const res = await fetch(`${API}/repos/${owner}/${repo}`, { headers });
65
+ if (!res.ok) return await safeApiError(res, "GitHub");
66
+ const r = (await res.json()) as any;
67
+ return JSON.stringify({
68
+ full_name: r.full_name,
69
+ description: r.description,
70
+ language: r.language,
71
+ stars: r.stargazers_count,
72
+ forks: r.forks_count,
73
+ open_issues: r.open_issues_count,
74
+ private: r.private,
75
+ default_branch: r.default_branch,
76
+ created_at: r.created_at,
77
+ updated_at: r.updated_at,
78
+ url: r.html_url,
79
+ });
80
+ }
81
+
82
+ default:
83
+ return JSON.stringify({ error: `Unknown action: ${action}` });
84
+ }
85
+ } catch (err: any) {
86
+ return safeExceptionError(err, "GitHub");
87
+ }
88
+ }
@@ -0,0 +1,51 @@
1
+ # gmail
2
+
3
+ Manage Gmail: list, read, send, search, and reply to emails. Requires Google OAuth 2.0 connection (use google_oauth tool to connect).
4
+
5
+ ## Input Schema
6
+
7
+ ```json
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "action": {
12
+ "type": "string",
13
+ "enum": ["list", "read", "send", "search", "reply"],
14
+ "description": "Action to perform"
15
+ },
16
+ "message_id": {
17
+ "type": "string",
18
+ "description": "Message ID (for read and reply)"
19
+ },
20
+ "to": {
21
+ "type": "string",
22
+ "description": "Recipient email (for send)"
23
+ },
24
+ "subject": {
25
+ "type": "string",
26
+ "description": "Email subject (for send)"
27
+ },
28
+ "body": {
29
+ "type": "string",
30
+ "description": "Email body (for send and reply)"
31
+ },
32
+ "query": {
33
+ "type": "string",
34
+ "description": "Search query (for search)"
35
+ },
36
+ "max_results": {
37
+ "type": "number",
38
+ "description": "Max results (default 10)"
39
+ }
40
+ },
41
+ "required": ["action"]
42
+ }
43
+ ```
44
+
45
+ ## Usage Hints
46
+
47
+ - Use "list" to see recent emails.
48
+ - Use "read" with message_id to view full email content.
49
+ - Use "send" with to, subject, and body to send an email.
50
+ - Use "search" with Gmail search query syntax.
51
+ - Use "reply" with message_id and body to reply.
@@ -0,0 +1,125 @@
1
+ const API = "https://gmail.googleapis.com/gmail/v1/users/me";
2
+
3
+ async function getToken(): Promise<string> {
4
+ const Zubo = (globalThis as any).Zubo;
5
+ if (Zubo?.getGoogleToken) return Zubo.getGoogleToken();
6
+ throw new Error("Google is NOT connected. Use google_oauth with action 'start' to set up Google.");
7
+ }
8
+
9
+ async function safeApiErr(res: Response, service: string): Promise<string> {
10
+ const body = await res.text().catch(() => "");
11
+ console.error(`[${service}] API error ${res.status}: ${body.slice(0, 500)}`);
12
+ // Parse Google error for actionable info
13
+ let hint = "";
14
+ if (res.status === 403) {
15
+ if (body.includes("Gmail API has not been used") || body.includes("accessNotConfigured")) {
16
+ hint = " The Gmail API is not enabled in your Google Cloud Console. Go to console.cloud.google.com > APIs & Services > Library > search 'Gmail API' > Enable it.";
17
+ } else if (body.includes("insufficientPermissions") || body.includes("Insufficient Permission")) {
18
+ hint = " The access token does not have Gmail permissions. You may need to disconnect and reconnect Google (use google_oauth action 'disconnect' then 'start') to re-authorize with Gmail scope.";
19
+ }
20
+ }
21
+ return JSON.stringify({ error: `${service} API error: ${res.status} ${res.statusText}.${hint}` });
22
+ }
23
+
24
+ export default async function (input: Record<string, unknown>): Promise<string> {
25
+ let token: string;
26
+ try {
27
+ token = await getToken();
28
+ } catch (err: any) {
29
+ return JSON.stringify({
30
+ error: err.message,
31
+ action_required: "Google is NOT connected. The user needs to complete the OAuth 2.0 flow. " +
32
+ "Ask the user for their Google OAuth client_id (ends with .apps.googleusercontent.com) " +
33
+ "and client_secret (starts with GOCSPX-), then use google_oauth tool with action 'start'.",
34
+ });
35
+ }
36
+
37
+ const { action, message_id, to, subject, body, query, max_results } = input as {
38
+ action: string; message_id?: string; to?: string; subject?: string;
39
+ body?: string; query?: string; max_results?: number;
40
+ };
41
+
42
+ const headers: Record<string, string> = {
43
+ Authorization: `Bearer ${token}`,
44
+ "Content-Type": "application/json",
45
+ };
46
+
47
+ try {
48
+ switch (action) {
49
+ case "list": {
50
+ const res = await fetch(`${API}/messages?maxResults=${max_results || 10}`, { headers });
51
+ if (!res.ok) return await safeApiErr(res, "Gmail");
52
+ const data = (await res.json()) as any;
53
+ const summaries = [];
54
+ for (const msg of (data.messages || []).slice(0, 10)) {
55
+ const detail = await fetch(`${API}/messages/${msg.id}?format=metadata&metadataHeaders=Subject&metadataHeaders=From&metadataHeaders=Date`, { headers });
56
+ if (detail.ok) {
57
+ const d = (await detail.json()) as any;
58
+ const getHeader = (name: string) => d.payload?.headers?.find((h: any) => h.name === name)?.value ?? "";
59
+ summaries.push({ id: msg.id, subject: getHeader("Subject"), from: getHeader("From"), date: getHeader("Date"), snippet: d.snippet });
60
+ }
61
+ }
62
+ return JSON.stringify(summaries);
63
+ }
64
+ case "read": {
65
+ if (!message_id) return JSON.stringify({ error: "message_id required" });
66
+ const res = await fetch(`${API}/messages/${message_id}?format=full`, { headers });
67
+ if (!res.ok) return await safeApiErr(res, "Gmail");
68
+ const data = (await res.json()) as any;
69
+ const getHeader = (name: string) => data.payload?.headers?.find((h: any) => h.name === name)?.value ?? "";
70
+ let emailBody = data.snippet || "";
71
+ const parts = data.payload?.parts || [data.payload];
72
+ for (const part of parts) {
73
+ if (part?.mimeType === "text/plain" && part?.body?.data) {
74
+ emailBody = Buffer.from(part.body.data, "base64url").toString("utf-8");
75
+ break;
76
+ }
77
+ }
78
+ return JSON.stringify({ id: data.id, subject: getHeader("Subject"), from: getHeader("From"), date: getHeader("Date"), body: emailBody });
79
+ }
80
+ case "send": {
81
+ if (!to) return JSON.stringify({ error: "to is required" });
82
+ if (!subject) return JSON.stringify({ error: "subject is required" });
83
+ const raw = Buffer.from(`To: ${to}\r\nSubject: ${subject}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n${body || ""}`).toString("base64url");
84
+ const res = await fetch(`${API}/messages/send`, {
85
+ method: "POST", headers,
86
+ body: JSON.stringify({ raw }),
87
+ });
88
+ if (!res.ok) return await safeApiErr(res, "Gmail");
89
+ const data = (await res.json()) as any;
90
+ return JSON.stringify({ sent: true, id: data.id });
91
+ }
92
+ case "search": {
93
+ if (!query) return JSON.stringify({ error: "query is required" });
94
+ const res = await fetch(`${API}/messages?q=${encodeURIComponent(query)}&maxResults=${max_results || 10}`, { headers });
95
+ if (!res.ok) return await safeApiErr(res, "Gmail");
96
+ const data = (await res.json()) as any;
97
+ return JSON.stringify({ resultCount: data.resultSizeEstimate, messages: data.messages?.map((m: any) => m.id) ?? [] });
98
+ }
99
+ case "reply": {
100
+ if (!message_id) return JSON.stringify({ error: "message_id required" });
101
+ if (!body) return JSON.stringify({ error: "body required" });
102
+ const orig = await fetch(`${API}/messages/${message_id}?format=metadata&metadataHeaders=Subject&metadataHeaders=From&metadataHeaders=Message-ID`, { headers });
103
+ if (!orig.ok) return await safeApiErr(orig, "Gmail");
104
+ const origData = (await orig.json()) as any;
105
+ const getHeader = (name: string) => origData.payload?.headers?.find((h: any) => h.name === name)?.value ?? "";
106
+ const replyTo = getHeader("From");
107
+ const subj = getHeader("Subject").startsWith("Re:") ? getHeader("Subject") : `Re: ${getHeader("Subject")}`;
108
+ const msgId = getHeader("Message-ID");
109
+ const raw = Buffer.from(`To: ${replyTo}\r\nSubject: ${subj}\r\nIn-Reply-To: ${msgId}\r\nReferences: ${msgId}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n${body}`).toString("base64url");
110
+ const res = await fetch(`${API}/messages/send`, {
111
+ method: "POST", headers,
112
+ body: JSON.stringify({ raw, threadId: origData.threadId }),
113
+ });
114
+ if (!res.ok) return await safeApiErr(res, "Gmail");
115
+ const data = (await res.json()) as any;
116
+ return JSON.stringify({ replied: true, id: data.id });
117
+ }
118
+ default:
119
+ return JSON.stringify({ error: `Unknown action: ${action}` });
120
+ }
121
+ } catch (err: any) {
122
+ console.error(`[Gmail] Request failed: ${err.message}`);
123
+ return JSON.stringify({ error: "Gmail request failed. Check logs for details." });
124
+ }
125
+ }
@@ -0,0 +1,35 @@
1
+ # google_calendar
2
+
3
+ Manage Google Calendar events: list, create, get, update, delete. Requires a Google Calendar token stored as `google_calendar_token`.
4
+
5
+ ## Input Schema
6
+
7
+ ```json
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "action": {
12
+ "type": "string",
13
+ "enum": ["list", "create", "get", "update", "delete"],
14
+ "description": "Action to perform"
15
+ },
16
+ "event_id": { "type": "string", "description": "Event ID (for get, update, delete)" },
17
+ "summary": { "type": "string", "description": "Event title" },
18
+ "description": { "type": "string", "description": "Event description" },
19
+ "start": { "type": "string", "description": "Start datetime (ISO 8601)" },
20
+ "end": { "type": "string", "description": "End datetime (ISO 8601)" },
21
+ "time_min": { "type": "string", "description": "Filter start (ISO 8601, for list)" },
22
+ "time_max": { "type": "string", "description": "Filter end (ISO 8601, for list)" },
23
+ "calendar_id": { "type": "string", "description": "Calendar ID (default: primary)" }
24
+ },
25
+ "required": ["action"]
26
+ }
27
+ ```
28
+
29
+ ## Usage Hints
30
+
31
+ - Use "list" to see upcoming events. Defaults to next 7 days.
32
+ - Use "create" with summary, start, and end to create an event.
33
+ - Use "get" with event_id to see event details.
34
+ - Use "update" with event_id and fields to modify.
35
+ - Use "delete" with event_id to remove an event.
@@ -0,0 +1,105 @@
1
+ const API = "https://www.googleapis.com/calendar/v3";
2
+
3
+ async function getToken(): Promise<string> {
4
+ const Zubo = (globalThis as any).Zubo;
5
+ if (Zubo?.getGoogleToken) return Zubo.getGoogleToken();
6
+ throw new Error("Google is NOT connected. Use google_oauth with action 'start' to set up Google.");
7
+ }
8
+
9
+ function safeApiErr(status: number, statusText: string, service: string): string {
10
+ return JSON.stringify({ error: `${service} API error: ${status} ${statusText}` });
11
+ }
12
+
13
+ export default async function (input: Record<string, unknown>): Promise<string> {
14
+ let token: string;
15
+ try {
16
+ token = await getToken();
17
+ } catch (err: any) {
18
+ return JSON.stringify({
19
+ error: err.message,
20
+ action_required: "Google is NOT connected. The user needs to complete the OAuth 2.0 flow. " +
21
+ "Ask the user for their Google OAuth client_id (ends with .apps.googleusercontent.com) " +
22
+ "and client_secret (starts with GOCSPX-), then use google_oauth tool with action 'start'.",
23
+ });
24
+ }
25
+
26
+ const { action, event_id, summary, description, start, end, time_min, time_max, calendar_id } = input as {
27
+ action: string; event_id?: string; summary?: string; description?: string;
28
+ start?: string; end?: string; time_min?: string; time_max?: string; calendar_id?: string;
29
+ };
30
+
31
+ const calId = calendar_id || "primary";
32
+ const headers: Record<string, string> = {
33
+ Authorization: `Bearer ${token}`,
34
+ "Content-Type": "application/json",
35
+ };
36
+
37
+ try {
38
+ switch (action) {
39
+ case "list": {
40
+ const now = new Date();
41
+ const tMin = time_min || now.toISOString();
42
+ const tMax = time_max || new Date(now.getTime() + 7 * 86400_000).toISOString();
43
+ const qs = `timeMin=${encodeURIComponent(tMin)}&timeMax=${encodeURIComponent(tMax)}&singleEvents=true&orderBy=startTime&maxResults=20`;
44
+ const res = await fetch(`${API}/calendars/${calId}/events?${qs}`, { headers });
45
+ if (!res.ok) return safeApiErr(res.status, res.statusText, "Google Calendar");
46
+ const data = (await res.json()) as any;
47
+ return JSON.stringify(data.items?.map((e: any) => ({
48
+ id: e.id, summary: e.summary, start: e.start?.dateTime || e.start?.date,
49
+ end: e.end?.dateTime || e.end?.date, status: e.status,
50
+ })) ?? []);
51
+ }
52
+ case "create": {
53
+ if (!summary) return JSON.stringify({ error: "summary required" });
54
+ if (!start || !end) return JSON.stringify({ error: "start and end required" });
55
+ const res = await fetch(`${API}/calendars/${calId}/events`, {
56
+ method: "POST", headers,
57
+ body: JSON.stringify({
58
+ summary, description: description || "",
59
+ start: { dateTime: start }, end: { dateTime: end },
60
+ }),
61
+ });
62
+ if (!res.ok) return safeApiErr(res.status, res.statusText, "Google Calendar");
63
+ const data = (await res.json()) as any;
64
+ return JSON.stringify({ created: true, id: data.id, summary: data.summary, htmlLink: data.htmlLink });
65
+ }
66
+ case "get": {
67
+ if (!event_id) return JSON.stringify({ error: "event_id required" });
68
+ const res = await fetch(`${API}/calendars/${calId}/events/${event_id}`, { headers });
69
+ if (!res.ok) return safeApiErr(res.status, res.statusText, "Google Calendar");
70
+ const e = (await res.json()) as any;
71
+ return JSON.stringify({
72
+ id: e.id, summary: e.summary, description: e.description,
73
+ start: e.start?.dateTime || e.start?.date, end: e.end?.dateTime || e.end?.date,
74
+ status: e.status, attendees: e.attendees?.map((a: any) => a.email),
75
+ });
76
+ }
77
+ case "update": {
78
+ if (!event_id) return JSON.stringify({ error: "event_id required" });
79
+ const patch: any = {};
80
+ if (summary) patch.summary = summary;
81
+ if (description) patch.description = description;
82
+ if (start) patch.start = { dateTime: start };
83
+ if (end) patch.end = { dateTime: end };
84
+ const res = await fetch(`${API}/calendars/${calId}/events/${event_id}`, {
85
+ method: "PATCH", headers,
86
+ body: JSON.stringify(patch),
87
+ });
88
+ if (!res.ok) return safeApiErr(res.status, res.statusText, "Google Calendar");
89
+ const data = (await res.json()) as any;
90
+ return JSON.stringify({ updated: true, id: data.id, summary: data.summary });
91
+ }
92
+ case "delete": {
93
+ if (!event_id) return JSON.stringify({ error: "event_id required" });
94
+ const res = await fetch(`${API}/calendars/${calId}/events/${event_id}`, { method: "DELETE", headers });
95
+ if (!res.ok) return safeApiErr(res.status, res.statusText, "Google Calendar");
96
+ return JSON.stringify({ deleted: true });
97
+ }
98
+ default:
99
+ return JSON.stringify({ error: `Unknown action: ${action}` });
100
+ }
101
+ } catch (err: any) {
102
+ console.error(`[Google Calendar] Request failed: ${err.message}`);
103
+ return JSON.stringify({ error: "Google Calendar request failed. Check logs for details." });
104
+ }
105
+ }
@@ -0,0 +1,35 @@
1
+ # google_docs
2
+
3
+ Manage Google Docs: create, read, and update documents. Requires a Google API key stored as `google_api_key`.
4
+
5
+ ## Input Schema
6
+
7
+ ```json
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "action": {
12
+ "type": "string",
13
+ "enum": ["create", "read", "update"],
14
+ "description": "The action to perform"
15
+ },
16
+ "document_id": {
17
+ "type": "string",
18
+ "description": "The document ID (required for read, update)"
19
+ },
20
+ "title": {
21
+ "type": "string",
22
+ "description": "Document title (required for create)"
23
+ },
24
+ "text": {
25
+ "type": "string",
26
+ "description": "Text to insert (for update)"
27
+ },
28
+ "index": {
29
+ "type": "number",
30
+ "description": "Insert position index (for update, default: 1 = start)"
31
+ }
32
+ },
33
+ "required": ["action"]
34
+ }
35
+ ```
@@ -0,0 +1,108 @@
1
+ const API = "https://docs.googleapis.com/v1/documents";
2
+
3
+ async function getToken(): Promise<string> {
4
+ const Zubo = (globalThis as any).Zubo;
5
+ if (Zubo?.getGoogleToken) return Zubo.getGoogleToken();
6
+ throw new Error("Google is NOT connected. Use google_oauth with action 'start' to set up Google.");
7
+ }
8
+
9
+ function safeApiErr(status: number, statusText: string, service: string): string {
10
+ return JSON.stringify({ error: `${service} API error: ${status} ${statusText}` });
11
+ }
12
+
13
+ export default async function (input: Record<string, unknown>): Promise<string> {
14
+ let token: string;
15
+ try {
16
+ token = await getToken();
17
+ } catch (err: any) {
18
+ return JSON.stringify({
19
+ error: err.message,
20
+ action_required: "Google is NOT connected. The user needs to complete the OAuth 2.0 flow. " +
21
+ "Ask the user for their Google OAuth client_id (ends with .apps.googleusercontent.com) " +
22
+ "and client_secret (starts with GOCSPX-), then use google_oauth tool with action 'start'.",
23
+ });
24
+ }
25
+
26
+ const { action, document_id, title, text, index } = input as {
27
+ action: string;
28
+ document_id?: string;
29
+ title?: string;
30
+ text?: string;
31
+ index?: number;
32
+ };
33
+
34
+ const headers: Record<string, string> = {
35
+ Authorization: `Bearer ${token}`,
36
+ "Content-Type": "application/json",
37
+ };
38
+
39
+ try {
40
+ switch (action) {
41
+ case "create": {
42
+ if (!title) return JSON.stringify({ error: "title is required for create" });
43
+ const res = await fetch(API, {
44
+ method: "POST",
45
+ headers,
46
+ body: JSON.stringify({ title }),
47
+ });
48
+ if (!res.ok) return safeApiErr(res.status, res.statusText, "Google Docs");
49
+ const doc = (await res.json()) as any;
50
+ return JSON.stringify({
51
+ document_id: doc.documentId,
52
+ title: doc.title,
53
+ url: `https://docs.google.com/document/d/${doc.documentId}/edit`,
54
+ });
55
+ }
56
+
57
+ case "read": {
58
+ if (!document_id) return JSON.stringify({ error: "document_id is required for read" });
59
+ const res = await fetch(`${API}/${document_id}`, { headers });
60
+ if (!res.ok) return safeApiErr(res.status, res.statusText, "Google Docs");
61
+ const doc = (await res.json()) as any;
62
+ // Extract plain text from doc body
63
+ let content = "";
64
+ if (doc.body?.content) {
65
+ for (const element of doc.body.content) {
66
+ if (element.paragraph?.elements) {
67
+ for (const e of element.paragraph.elements) {
68
+ if (e.textRun?.content) content += e.textRun.content;
69
+ }
70
+ }
71
+ }
72
+ }
73
+ return JSON.stringify({
74
+ document_id: doc.documentId,
75
+ title: doc.title,
76
+ content: content.trim(),
77
+ });
78
+ }
79
+
80
+ case "update": {
81
+ if (!document_id) return JSON.stringify({ error: "document_id is required for update" });
82
+ if (!text) return JSON.stringify({ error: "text is required for update" });
83
+ const res = await fetch(`${API}/${document_id}:batchUpdate`, {
84
+ method: "POST",
85
+ headers,
86
+ body: JSON.stringify({
87
+ requests: [
88
+ {
89
+ insertText: {
90
+ text,
91
+ location: { index: index ?? 1 },
92
+ },
93
+ },
94
+ ],
95
+ }),
96
+ });
97
+ if (!res.ok) return safeApiErr(res.status, res.statusText, "Google Docs");
98
+ return JSON.stringify({ success: true, message: "Document updated." });
99
+ }
100
+
101
+ default:
102
+ return JSON.stringify({ error: `Unknown action: ${action}` });
103
+ }
104
+ } catch (err: any) {
105
+ console.error(`[Google Docs] Request failed: ${err.message}`);
106
+ return JSON.stringify({ error: "Google Docs request failed. Check logs for details." });
107
+ }
108
+ }
@@ -0,0 +1,39 @@
1
+ # google_drive
2
+
3
+ Manage Google Drive: list files, create folders, and get file info. Requires a Google API key stored as `google_api_key`.
4
+
5
+ ## Input Schema
6
+
7
+ ```json
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "action": {
12
+ "type": "string",
13
+ "enum": ["list", "create_folder", "get"],
14
+ "description": "The action to perform"
15
+ },
16
+ "file_id": {
17
+ "type": "string",
18
+ "description": "File or folder ID (required for get)"
19
+ },
20
+ "name": {
21
+ "type": "string",
22
+ "description": "Folder name (required for create_folder)"
23
+ },
24
+ "parent_id": {
25
+ "type": "string",
26
+ "description": "Parent folder ID (optional for create_folder)"
27
+ },
28
+ "query": {
29
+ "type": "string",
30
+ "description": "Search query (for list, uses Google Drive query syntax)"
31
+ },
32
+ "page_size": {
33
+ "type": "number",
34
+ "description": "Number of results (for list, default: 20)"
35
+ }
36
+ },
37
+ "required": ["action"]
38
+ }
39
+ ```