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,36 @@
1
+ import { registerTool } from "../registry";
2
+ import type { LlmProvider } from "../../llm/provider";
3
+ import { executeWorkflow } from "../../agent/workflow-executor";
4
+
5
+ export function registerRunWorkflowTool(llm: LlmProvider) {
6
+ registerTool({
7
+ definition: {
8
+ name: "run_workflow",
9
+ description:
10
+ "Execute a multi-agent workflow by name. Provide the workflow name and an input string. Steps execute respecting dependencies.",
11
+ input_schema: {
12
+ type: "object",
13
+ properties: {
14
+ workflow: {
15
+ type: "string",
16
+ description: "Name of the workflow to execute",
17
+ },
18
+ input: {
19
+ type: "string",
20
+ description: "Input text for the workflow (available as $input in step tasks)",
21
+ },
22
+ },
23
+ required: ["workflow", "input"],
24
+ },
25
+ },
26
+ async execute(input) {
27
+ const workflowName = input.workflow as string;
28
+ const workflowInput = input.input as string;
29
+
30
+ if (!workflowName) return JSON.stringify({ error: "workflow name is required" });
31
+
32
+ const result = await executeWorkflow(llm, workflowName, workflowInput);
33
+ return JSON.stringify(result);
34
+ },
35
+ });
36
+ }
@@ -0,0 +1,122 @@
1
+ import { registerTool } from "../registry";
2
+ import { setSecret, listSecrets, deleteSecret } from "../../secrets/store";
3
+
4
+ export function registerSecretTools() {
5
+ registerTool({
6
+ definition: {
7
+ name: "secret_set",
8
+ description:
9
+ "Store a secret (API key, token, credential). The value is saved securely and never shown in conversation. Use this when the user provides credentials.",
10
+ input_schema: {
11
+ type: "object",
12
+ properties: {
13
+ name: {
14
+ type: "string",
15
+ description:
16
+ "Secret name (e.g., 'github_token', 'google_api_key'). Use lowercase with underscores.",
17
+ },
18
+ value: {
19
+ type: "string",
20
+ description: "The secret value (API key, token, etc.)",
21
+ },
22
+ service: {
23
+ type: "string",
24
+ description:
25
+ "Optional service name this secret belongs to (e.g., 'github', 'google', 'notion')",
26
+ },
27
+ },
28
+ required: ["name", "value"],
29
+ },
30
+ },
31
+ execute: async (input) => {
32
+ const { name, value, service } = input as {
33
+ name: string;
34
+ value: string;
35
+ service?: string;
36
+ };
37
+
38
+ if (!name || !/^[a-z0-9_]+$/.test(name)) {
39
+ return JSON.stringify({ error: "Invalid name. Must match [a-z0-9_]+" });
40
+ }
41
+ if (!value) {
42
+ return JSON.stringify({ error: "Value is required." });
43
+ }
44
+
45
+ setSecret(name, value, service);
46
+ return JSON.stringify({
47
+ success: true,
48
+ name,
49
+ service: service ?? null,
50
+ message: `Secret "${name}" stored securely.`,
51
+ });
52
+ },
53
+ });
54
+
55
+ registerTool({
56
+ definition: {
57
+ name: "secret_list",
58
+ description:
59
+ "List stored secret names (never shows values). Use this to check what credentials are available.",
60
+ input_schema: {
61
+ type: "object",
62
+ properties: {
63
+ service: {
64
+ type: "string",
65
+ description: "Optional: filter by service name (e.g., 'github')",
66
+ },
67
+ },
68
+ },
69
+ },
70
+ execute: async (input) => {
71
+ const { service } = input as { service?: string };
72
+ const secrets = listSecrets(service);
73
+
74
+ if (secrets.length === 0) {
75
+ return service
76
+ ? `No secrets found for service "${service}".`
77
+ : "No secrets stored yet.";
78
+ }
79
+
80
+ return JSON.stringify({
81
+ secrets: secrets.map((s) => ({
82
+ name: s.name,
83
+ service: s.service,
84
+ updated_at: s.updated_at,
85
+ })),
86
+ count: secrets.length,
87
+ });
88
+ },
89
+ });
90
+
91
+ registerTool({
92
+ definition: {
93
+ name: "secret_delete",
94
+ description: "Delete a stored secret by name.",
95
+ input_schema: {
96
+ type: "object",
97
+ properties: {
98
+ name: {
99
+ type: "string",
100
+ description: "The name of the secret to delete",
101
+ },
102
+ },
103
+ required: ["name"],
104
+ },
105
+ },
106
+ execute: async (input) => {
107
+ const { name } = input as { name: string };
108
+ if (!name) {
109
+ return JSON.stringify({ error: "Secret name is required." });
110
+ }
111
+
112
+ const deleted = deleteSecret(name);
113
+ if (deleted) {
114
+ return JSON.stringify({
115
+ success: true,
116
+ message: `Secret "${name}" deleted.`,
117
+ });
118
+ }
119
+ return JSON.stringify({ error: `No secret found with name "${name}".` });
120
+ },
121
+ });
122
+ }
@@ -0,0 +1,75 @@
1
+ import { registerTool } from "../registry";
2
+ import { searchRegistry } from "../../registry/client";
3
+ import { installFromRegistry } from "../../registry/installer";
4
+ import { loadSkills } from "../skill-loader";
5
+ import { paths } from "../../config/paths";
6
+ import { logger } from "../../util/logger";
7
+
8
+ export function registerSkillRegistryTool() {
9
+ registerTool({
10
+ definition: {
11
+ name: "skill_registry",
12
+ description:
13
+ "Search and install skills from the Zubo skill registry. Use action 'search' to find skills, 'install' to install one.",
14
+ input_schema: {
15
+ type: "object",
16
+ properties: {
17
+ action: {
18
+ type: "string",
19
+ enum: ["search", "install"],
20
+ description: "Action to perform",
21
+ },
22
+ query: {
23
+ type: "string",
24
+ description: "Search query (for action=search)",
25
+ },
26
+ name: {
27
+ type: "string",
28
+ description: "Skill name to install (for action=install)",
29
+ },
30
+ },
31
+ required: ["action"],
32
+ },
33
+ },
34
+ async execute(input) {
35
+ const action = input.action as string;
36
+
37
+ if (action === "search") {
38
+ const query = (input.query as string) ?? "";
39
+ if (!query) return JSON.stringify({ error: "query is required for search" });
40
+ const results = await searchRegistry(query);
41
+ if (results.length === 0) return JSON.stringify({ results: [], message: "No skills found" });
42
+ return JSON.stringify({
43
+ results: results.map((r) => ({
44
+ name: r.name,
45
+ description: r.description,
46
+ tags: r.tags,
47
+ secrets: r.secrets,
48
+ })),
49
+ });
50
+ }
51
+
52
+ if (action === "install") {
53
+ const name = (input.name as string) ?? "";
54
+ if (!name) return JSON.stringify({ error: "name is required for install" });
55
+ const result = await installFromRegistry(name);
56
+ if (result.success) {
57
+ // Reload skills so the new skill is available immediately
58
+ try {
59
+ await loadSkills(paths.skills);
60
+ } catch (err: any) {
61
+ logger.warn("Failed to reload skills after install", { error: (err as Error).message });
62
+ }
63
+ return JSON.stringify({
64
+ installed: true,
65
+ name: result.name,
66
+ requiredSecrets: result.requiredSecrets,
67
+ });
68
+ }
69
+ return JSON.stringify({ installed: false, error: result.error });
70
+ }
71
+
72
+ return JSON.stringify({ error: `Unknown action: ${action}` });
73
+ },
74
+ });
75
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared helpers for integration handlers.
3
+ * Prevents leaking secrets/tokens via API error responses or exception messages.
4
+ */
5
+
6
+ /**
7
+ * Safely handle an API response error. Logs the full error but returns
8
+ * only the status code to the conversation — never the response body,
9
+ * which may echo tokens or internal details.
10
+ */
11
+ export async function safeApiError(res: Response, service: string): Promise<string> {
12
+ // Read body once so it doesn't hang
13
+ const body = await res.text().catch(() => "");
14
+ // Log full details for debugging (goes to zubo.log, not conversation)
15
+ console.error(`[${service}] API error ${res.status}: ${body.slice(0, 500)}`);
16
+ return JSON.stringify({ error: `${service} API error: ${res.status} ${res.statusText}` });
17
+ }
18
+
19
+ /**
20
+ * Safely handle an exception. Never expose raw error messages which
21
+ * may contain tokens, URLs with credentials, or internal details.
22
+ */
23
+ export function safeExceptionError(err: any, service: string): string {
24
+ console.error(`[${service}] Request failed: ${err.message}`);
25
+ return JSON.stringify({ error: `${service} request failed. Check logs for details.` });
26
+ }
@@ -0,0 +1,56 @@
1
+ # github_issues
2
+
3
+ Manage GitHub issues: list, create, get details, and add comments. Requires a GitHub personal access token stored as `github_token`.
4
+
5
+ ## Input Schema
6
+
7
+ ```json
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "action": {
12
+ "type": "string",
13
+ "enum": ["list", "create", "get", "comment"],
14
+ "description": "The action to perform"
15
+ },
16
+ "owner": {
17
+ "type": "string",
18
+ "description": "Repository owner (user or org)"
19
+ },
20
+ "repo": {
21
+ "type": "string",
22
+ "description": "Repository name"
23
+ },
24
+ "issue_number": {
25
+ "type": "number",
26
+ "description": "Issue number (required for get and comment)"
27
+ },
28
+ "title": {
29
+ "type": "string",
30
+ "description": "Issue title (required for create)"
31
+ },
32
+ "body": {
33
+ "type": "string",
34
+ "description": "Issue body or comment text"
35
+ },
36
+ "labels": {
37
+ "type": "array",
38
+ "items": { "type": "string" },
39
+ "description": "Labels to apply (for create)"
40
+ },
41
+ "state": {
42
+ "type": "string",
43
+ "enum": ["open", "closed", "all"],
44
+ "description": "Filter by state (for list, default: open)"
45
+ }
46
+ },
47
+ "required": ["action", "owner", "repo"]
48
+ }
49
+ ```
50
+
51
+ ## Usage Hints
52
+
53
+ - Use action "list" to see open issues for a repo.
54
+ - Use action "create" with title and body to open a new issue.
55
+ - Use action "get" with issue_number to see issue details.
56
+ - Use action "comment" with issue_number and body to add a comment.
@@ -0,0 +1,108 @@
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, issue_number, title, body, labels, state } = input as {
23
+ action: string;
24
+ owner: string;
25
+ repo: string;
26
+ issue_number?: number;
27
+ title?: string;
28
+ body?: string;
29
+ labels?: string[];
30
+ state?: string;
31
+ };
32
+
33
+ const headers: Record<string, string> = {
34
+ Authorization: `Bearer ${token}`,
35
+ Accept: "application/vnd.github+json",
36
+ "User-Agent": "Zubo-Agent",
37
+ };
38
+
39
+ try {
40
+ switch (action) {
41
+ case "list": {
42
+ const qs = new URLSearchParams({ state: state || "open", per_page: "30" });
43
+ const res = await fetch(`${API}/repos/${owner}/${repo}/issues?${qs}`, { headers });
44
+ if (!res.ok) return await safeApiError(res, "GitHub");
45
+ const issues = (await res.json()) as any[];
46
+ return JSON.stringify(
47
+ issues.map((i) => ({
48
+ number: i.number,
49
+ title: i.title,
50
+ state: i.state,
51
+ user: i.user?.login,
52
+ labels: i.labels?.map((l: any) => l.name),
53
+ created_at: i.created_at,
54
+ comments: i.comments,
55
+ }))
56
+ );
57
+ }
58
+
59
+ case "create": {
60
+ if (!title) return JSON.stringify({ error: "title is required for create" });
61
+ const res = await fetch(`${API}/repos/${owner}/${repo}/issues`, {
62
+ method: "POST",
63
+ headers: { ...headers, "Content-Type": "application/json" },
64
+ body: JSON.stringify({ title, body: body || "", labels: labels || [] }),
65
+ });
66
+ if (!res.ok) return await safeApiError(res, "GitHub");
67
+ const issue = (await res.json()) as any;
68
+ return JSON.stringify({ number: issue.number, title: issue.title, url: issue.html_url });
69
+ }
70
+
71
+ case "get": {
72
+ if (!issue_number) return JSON.stringify({ error: "issue_number is required for get" });
73
+ const res = await fetch(`${API}/repos/${owner}/${repo}/issues/${issue_number}`, { headers });
74
+ if (!res.ok) return await safeApiError(res, "GitHub");
75
+ const issue = (await res.json()) as any;
76
+ return JSON.stringify({
77
+ number: issue.number,
78
+ title: issue.title,
79
+ state: issue.state,
80
+ body: issue.body,
81
+ user: issue.user?.login,
82
+ labels: issue.labels?.map((l: any) => l.name),
83
+ created_at: issue.created_at,
84
+ comments: issue.comments,
85
+ url: issue.html_url,
86
+ });
87
+ }
88
+
89
+ case "comment": {
90
+ if (!issue_number) return JSON.stringify({ error: "issue_number is required for comment" });
91
+ if (!body) return JSON.stringify({ error: "body is required for comment" });
92
+ const res = await fetch(`${API}/repos/${owner}/${repo}/issues/${issue_number}/comments`, {
93
+ method: "POST",
94
+ headers: { ...headers, "Content-Type": "application/json" },
95
+ body: JSON.stringify({ body }),
96
+ });
97
+ if (!res.ok) return await safeApiError(res, "GitHub");
98
+ const comment = (await res.json()) as any;
99
+ return JSON.stringify({ id: comment.id, url: comment.html_url, created_at: comment.created_at });
100
+ }
101
+
102
+ default:
103
+ return JSON.stringify({ error: `Unknown action: ${action}` });
104
+ }
105
+ } catch (err: any) {
106
+ return safeExceptionError(err, "GitHub");
107
+ }
108
+ }
@@ -0,0 +1,57 @@
1
+ # github_prs
2
+
3
+ Manage GitHub pull requests: list, create, get details, and review. Requires a GitHub personal access token stored as `github_token`.
4
+
5
+ ## Input Schema
6
+
7
+ ```json
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "action": {
12
+ "type": "string",
13
+ "enum": ["list", "create", "get", "review"],
14
+ "description": "The action to perform"
15
+ },
16
+ "owner": {
17
+ "type": "string",
18
+ "description": "Repository owner (user or org)"
19
+ },
20
+ "repo": {
21
+ "type": "string",
22
+ "description": "Repository name"
23
+ },
24
+ "pr_number": {
25
+ "type": "number",
26
+ "description": "PR number (required for get and review)"
27
+ },
28
+ "title": {
29
+ "type": "string",
30
+ "description": "PR title (required for create)"
31
+ },
32
+ "body": {
33
+ "type": "string",
34
+ "description": "PR body or review comment"
35
+ },
36
+ "head": {
37
+ "type": "string",
38
+ "description": "Head branch (required for create)"
39
+ },
40
+ "base": {
41
+ "type": "string",
42
+ "description": "Base branch (required for create, default: main)"
43
+ },
44
+ "event": {
45
+ "type": "string",
46
+ "enum": ["APPROVE", "REQUEST_CHANGES", "COMMENT"],
47
+ "description": "Review event type (for review, default: COMMENT)"
48
+ },
49
+ "state": {
50
+ "type": "string",
51
+ "enum": ["open", "closed", "all"],
52
+ "description": "Filter by state (for list, default: open)"
53
+ }
54
+ },
55
+ "required": ["action", "owner", "repo"]
56
+ }
57
+ ```
@@ -0,0 +1,113 @@
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, pr_number, title, body, head, base, event, state } = input as {
23
+ action: string;
24
+ owner: string;
25
+ repo: string;
26
+ pr_number?: number;
27
+ title?: string;
28
+ body?: string;
29
+ head?: string;
30
+ base?: string;
31
+ event?: string;
32
+ state?: string;
33
+ };
34
+
35
+ const headers: Record<string, string> = {
36
+ Authorization: `Bearer ${token}`,
37
+ Accept: "application/vnd.github+json",
38
+ "User-Agent": "Zubo-Agent",
39
+ };
40
+
41
+ try {
42
+ switch (action) {
43
+ case "list": {
44
+ const qs = new URLSearchParams({ state: state || "open", per_page: "30" });
45
+ const res = await fetch(`${API}/repos/${owner}/${repo}/pulls?${qs}`, { headers });
46
+ if (!res.ok) return await safeApiError(res, "GitHub");
47
+ const prs = (await res.json()) as any[];
48
+ return JSON.stringify(
49
+ prs.map((p) => ({
50
+ number: p.number,
51
+ title: p.title,
52
+ state: p.state,
53
+ user: p.user?.login,
54
+ head: p.head?.ref,
55
+ base: p.base?.ref,
56
+ created_at: p.created_at,
57
+ url: p.html_url,
58
+ }))
59
+ );
60
+ }
61
+
62
+ case "create": {
63
+ if (!title) return JSON.stringify({ error: "title is required for create" });
64
+ if (!head) return JSON.stringify({ error: "head branch is required for create" });
65
+ const res = await fetch(`${API}/repos/${owner}/${repo}/pulls`, {
66
+ method: "POST",
67
+ headers: { ...headers, "Content-Type": "application/json" },
68
+ body: JSON.stringify({ title, body: body || "", head, base: base || "main" }),
69
+ });
70
+ if (!res.ok) return await safeApiError(res, "GitHub");
71
+ const pr = (await res.json()) as any;
72
+ return JSON.stringify({ number: pr.number, title: pr.title, url: pr.html_url });
73
+ }
74
+
75
+ case "get": {
76
+ if (!pr_number) return JSON.stringify({ error: "pr_number is required for get" });
77
+ const res = await fetch(`${API}/repos/${owner}/${repo}/pulls/${pr_number}`, { headers });
78
+ if (!res.ok) return await safeApiError(res, "GitHub");
79
+ const pr = (await res.json()) as any;
80
+ return JSON.stringify({
81
+ number: pr.number,
82
+ title: pr.title,
83
+ state: pr.state,
84
+ body: pr.body,
85
+ user: pr.user?.login,
86
+ head: pr.head?.ref,
87
+ base: pr.base?.ref,
88
+ mergeable: pr.mergeable,
89
+ merged: pr.merged,
90
+ created_at: pr.created_at,
91
+ url: pr.html_url,
92
+ });
93
+ }
94
+
95
+ case "review": {
96
+ if (!pr_number) return JSON.stringify({ error: "pr_number is required for review" });
97
+ const res = await fetch(`${API}/repos/${owner}/${repo}/pulls/${pr_number}/reviews`, {
98
+ method: "POST",
99
+ headers: { ...headers, "Content-Type": "application/json" },
100
+ body: JSON.stringify({ body: body || "", event: event || "COMMENT" }),
101
+ });
102
+ if (!res.ok) return await safeApiError(res, "GitHub");
103
+ const review = (await res.json()) as any;
104
+ return JSON.stringify({ id: review.id, state: review.state, url: review.html_url });
105
+ }
106
+
107
+ default:
108
+ return JSON.stringify({ error: `Unknown action: ${action}` });
109
+ }
110
+ } catch (err: any) {
111
+ return safeExceptionError(err, "GitHub");
112
+ }
113
+ }
@@ -0,0 +1,37 @@
1
+ # github_repos
2
+
3
+ List and get information about GitHub repositories. Requires a GitHub personal access token stored as `github_token`.
4
+
5
+ ## Input Schema
6
+
7
+ ```json
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "action": {
12
+ "type": "string",
13
+ "enum": ["list", "get"],
14
+ "description": "The action to perform"
15
+ },
16
+ "owner": {
17
+ "type": "string",
18
+ "description": "Repository owner or user whose repos to list"
19
+ },
20
+ "repo": {
21
+ "type": "string",
22
+ "description": "Repository name (required for get)"
23
+ },
24
+ "type": {
25
+ "type": "string",
26
+ "enum": ["all", "owner", "member"],
27
+ "description": "Filter repos by type (for list, default: owner)"
28
+ },
29
+ "sort": {
30
+ "type": "string",
31
+ "enum": ["created", "updated", "pushed", "full_name"],
32
+ "description": "Sort field (for list, default: updated)"
33
+ }
34
+ },
35
+ "required": ["action"]
36
+ }
37
+ ```