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.
- package/.github/workflows/ci.yml +35 -0
- package/README.md +149 -0
- package/bun.lock +216 -0
- package/desktop/README.md +57 -0
- package/desktop/package.json +12 -0
- package/desktop/src-tauri/Cargo.toml +25 -0
- package/desktop/src-tauri/build.rs +3 -0
- package/desktop/src-tauri/icons/README.md +17 -0
- package/desktop/src-tauri/icons/icon.png +0 -0
- package/desktop/src-tauri/src/main.rs +189 -0
- package/desktop/src-tauri/tauri.conf.json +68 -0
- package/docs/ROADMAP.md +490 -0
- package/migrations/001_init.sql +9 -0
- package/migrations/002_memory.sql +33 -0
- package/migrations/003_cron.sql +24 -0
- package/migrations/004_usage.sql +12 -0
- package/migrations/005_secrets.sql +8 -0
- package/migrations/006_agents.sql +1 -0
- package/migrations/007_workflows.sql +22 -0
- package/migrations/008_proactive.sql +24 -0
- package/migrations/009_uploads.sql +9 -0
- package/migrations/010_observability.sql +22 -0
- package/migrations/011_api_keys.sql +7 -0
- package/migrations/012_indexes.sql +5 -0
- package/migrations/013_budget.sql +11 -0
- package/migrations/014_usage_session_idx.sql +2 -0
- package/package.json +39 -0
- package/site/404.html +156 -0
- package/site/CNAME +1 -0
- package/site/docs/agents.html +294 -0
- package/site/docs/api.html +446 -0
- package/site/docs/channels.html +345 -0
- package/site/docs/cli.html +238 -0
- package/site/docs/config.html +1034 -0
- package/site/docs/index.html +433 -0
- package/site/docs/integrations.html +381 -0
- package/site/docs/memory.html +254 -0
- package/site/docs/security.html +375 -0
- package/site/docs/skills.html +322 -0
- package/site/docs.css +412 -0
- package/site/index.html +638 -0
- package/site/install.sh +98 -0
- package/site/logo.svg +1 -0
- package/site/og-image.png +0 -0
- package/site/robots.txt +4 -0
- package/site/script.js +361 -0
- package/site/sitemap.xml +63 -0
- package/site/skills.html +532 -0
- package/site/style.css +1686 -0
- package/src/agent/agents.ts +159 -0
- package/src/agent/compaction.ts +53 -0
- package/src/agent/context.ts +18 -0
- package/src/agent/delegate.ts +118 -0
- package/src/agent/loop.ts +318 -0
- package/src/agent/prompts.ts +111 -0
- package/src/agent/session.ts +87 -0
- package/src/agent/teams.ts +116 -0
- package/src/agent/workflow-executor.ts +192 -0
- package/src/agent/workflow.ts +175 -0
- package/src/channels/adapter.ts +21 -0
- package/src/channels/dashboard.html.ts +2969 -0
- package/src/channels/discord.ts +137 -0
- package/src/channels/optional-deps.d.ts +17 -0
- package/src/channels/router.ts +199 -0
- package/src/channels/signal.ts +133 -0
- package/src/channels/slack.ts +101 -0
- package/src/channels/telegram.ts +102 -0
- package/src/channels/utils.ts +18 -0
- package/src/channels/webchat.ts +1797 -0
- package/src/channels/whatsapp.ts +119 -0
- package/src/config/loader.ts +22 -0
- package/src/config/paths.ts +43 -0
- package/src/config/schema.ts +121 -0
- package/src/db/connection.ts +20 -0
- package/src/db/export.ts +148 -0
- package/src/db/migrations.ts +42 -0
- package/src/index.ts +261 -0
- package/src/llm/claude.ts +193 -0
- package/src/llm/factory.ts +115 -0
- package/src/llm/failover.ts +101 -0
- package/src/llm/openai-compat.ts +409 -0
- package/src/llm/provider.ts +83 -0
- package/src/llm/smart-router.ts +241 -0
- package/src/logs.ts +53 -0
- package/src/memory/chunker.ts +58 -0
- package/src/memory/document-parser.ts +115 -0
- package/src/memory/embedder.ts +235 -0
- package/src/memory/engine.ts +170 -0
- package/src/memory/fts-index.ts +55 -0
- package/src/memory/hybrid-search.ts +72 -0
- package/src/memory/store.ts +56 -0
- package/src/memory/vector-index.ts +72 -0
- package/src/model.ts +118 -0
- package/src/registry/cli.ts +43 -0
- package/src/registry/client.ts +54 -0
- package/src/registry/installer.ts +67 -0
- package/src/scheduler/briefing.ts +71 -0
- package/src/scheduler/cron.ts +258 -0
- package/src/scheduler/heartbeat.ts +58 -0
- package/src/scheduler/memory-triggers.ts +100 -0
- package/src/scheduler/natural-cron.ts +163 -0
- package/src/scheduler/proactive.ts +25 -0
- package/src/scheduler/recipes.ts +110 -0
- package/src/secrets/store.ts +64 -0
- package/src/setup.ts +413 -0
- package/src/skills.ts +293 -0
- package/src/start.ts +373 -0
- package/src/status.ts +165 -0
- package/src/tools/builtin/connect-service.ts +205 -0
- package/src/tools/builtin/cron.ts +126 -0
- package/src/tools/builtin/datetime.ts +36 -0
- package/src/tools/builtin/delegate-task.ts +81 -0
- package/src/tools/builtin/delegate.ts +42 -0
- package/src/tools/builtin/diagnose.ts +41 -0
- package/src/tools/builtin/google-oauth.ts +379 -0
- package/src/tools/builtin/manage-agents.ts +149 -0
- package/src/tools/builtin/manage-skills.ts +294 -0
- package/src/tools/builtin/manage-teams.ts +89 -0
- package/src/tools/builtin/manage-triggers.ts +94 -0
- package/src/tools/builtin/manage-workflows.ts +119 -0
- package/src/tools/builtin/memory-search.ts +38 -0
- package/src/tools/builtin/memory-write.ts +30 -0
- package/src/tools/builtin/run-workflow.ts +36 -0
- package/src/tools/builtin/secrets.ts +122 -0
- package/src/tools/builtin/skill-registry.ts +75 -0
- package/src/tools/builtin-integrations/api-helpers.ts +26 -0
- package/src/tools/builtin-integrations/github/github_issues/SKILL.md +56 -0
- package/src/tools/builtin-integrations/github/github_issues/handler.ts +108 -0
- package/src/tools/builtin-integrations/github/github_prs/SKILL.md +57 -0
- package/src/tools/builtin-integrations/github/github_prs/handler.ts +113 -0
- package/src/tools/builtin-integrations/github/github_repos/SKILL.md +37 -0
- package/src/tools/builtin-integrations/github/github_repos/handler.ts +88 -0
- package/src/tools/builtin-integrations/google/gmail/SKILL.md +51 -0
- package/src/tools/builtin-integrations/google/gmail/handler.ts +125 -0
- package/src/tools/builtin-integrations/google/google_calendar/SKILL.md +35 -0
- package/src/tools/builtin-integrations/google/google_calendar/handler.ts +105 -0
- package/src/tools/builtin-integrations/google/google_docs/SKILL.md +35 -0
- package/src/tools/builtin-integrations/google/google_docs/handler.ts +108 -0
- package/src/tools/builtin-integrations/google/google_drive/SKILL.md +39 -0
- package/src/tools/builtin-integrations/google/google_drive/handler.ts +106 -0
- package/src/tools/builtin-integrations/google/google_sheets/SKILL.md +36 -0
- package/src/tools/builtin-integrations/google/google_sheets/handler.ts +116 -0
- package/src/tools/builtin-integrations/jira/jira_boards/SKILL.md +21 -0
- package/src/tools/builtin-integrations/jira/jira_boards/handler.ts +74 -0
- package/src/tools/builtin-integrations/jira/jira_issues/SKILL.md +28 -0
- package/src/tools/builtin-integrations/jira/jira_issues/handler.ts +140 -0
- package/src/tools/builtin-integrations/linear/linear_issues/SKILL.md +30 -0
- package/src/tools/builtin-integrations/linear/linear_issues/handler.ts +75 -0
- package/src/tools/builtin-integrations/linear/linear_projects/SKILL.md +21 -0
- package/src/tools/builtin-integrations/linear/linear_projects/handler.ts +43 -0
- package/src/tools/builtin-integrations/notion/notion_databases/SKILL.md +39 -0
- package/src/tools/builtin-integrations/notion/notion_databases/handler.ts +83 -0
- package/src/tools/builtin-integrations/notion/notion_pages/SKILL.md +43 -0
- package/src/tools/builtin-integrations/notion/notion_pages/handler.ts +130 -0
- package/src/tools/builtin-integrations/notion/notion_search/SKILL.md +27 -0
- package/src/tools/builtin-integrations/notion/notion_search/handler.ts +69 -0
- package/src/tools/builtin-integrations/slack/slack_messages/SKILL.md +42 -0
- package/src/tools/builtin-integrations/slack/slack_messages/handler.ts +72 -0
- package/src/tools/builtin-integrations/twitter/twitter_posts/SKILL.md +24 -0
- package/src/tools/builtin-integrations/twitter/twitter_posts/handler.ts +133 -0
- package/src/tools/builtin-skills/file-read/SKILL.md +26 -0
- package/src/tools/builtin-skills/file-read/handler.ts +66 -0
- package/src/tools/builtin-skills/file-write/SKILL.md +30 -0
- package/src/tools/builtin-skills/file-write/handler.ts +64 -0
- package/src/tools/builtin-skills/http-request/SKILL.md +34 -0
- package/src/tools/builtin-skills/http-request/handler.ts +87 -0
- package/src/tools/builtin-skills/shell/SKILL.md +26 -0
- package/src/tools/builtin-skills/shell/handler.ts +96 -0
- package/src/tools/builtin-skills/url-fetch/SKILL.md +26 -0
- package/src/tools/builtin-skills/url-fetch/handler.ts +37 -0
- package/src/tools/builtin-skills/web-search/SKILL.md +26 -0
- package/src/tools/builtin-skills/web-search/handler.ts +50 -0
- package/src/tools/executor.ts +205 -0
- package/src/tools/integration-installer.ts +106 -0
- package/src/tools/permissions.ts +45 -0
- package/src/tools/registry.ts +39 -0
- package/src/tools/sandbox-runner.ts +56 -0
- package/src/tools/sandbox.ts +82 -0
- package/src/tools/skill-installer.ts +52 -0
- package/src/tools/skill-loader.ts +259 -0
- package/src/types/optional-deps.d.ts +23 -0
- package/src/util/auth.ts +121 -0
- package/src/util/costs.ts +59 -0
- package/src/util/error-buffer.ts +32 -0
- package/src/util/google-tokens.ts +180 -0
- package/src/util/logger.ts +73 -0
- package/src/util/perf-collector.ts +35 -0
- package/src/util/rate-limiter.ts +70 -0
- package/src/util/tokens.ts +17 -0
- package/src/voice/stt.ts +57 -0
- package/src/voice/tts.ts +103 -0
- package/tests/agent/session.test.ts +109 -0
- package/tests/agent-loop.test.ts +54 -0
- package/tests/auth.test.ts +89 -0
- package/tests/channels.test.ts +67 -0
- package/tests/compaction.test.ts +44 -0
- package/tests/config.test.ts +51 -0
- package/tests/costs.test.ts +19 -0
- package/tests/cron.test.ts +55 -0
- package/tests/db/export.test.ts +219 -0
- package/tests/executor.test.ts +144 -0
- package/tests/export.test.ts +137 -0
- package/tests/helpers/mock-llm.ts +34 -0
- package/tests/helpers/test-db.ts +74 -0
- package/tests/integration/chat-flow.test.ts +48 -0
- package/tests/integrations.test.ts +97 -0
- package/tests/memory/engine.test.ts +114 -0
- package/tests/memory-engine.test.ts +57 -0
- package/tests/permissions.test.ts +21 -0
- package/tests/rate-limiter.test.ts +70 -0
- package/tests/registry.test.ts +67 -0
- package/tests/router.test.ts +36 -0
- package/tests/session.test.ts +58 -0
- package/tests/skill-loader.test.ts +44 -0
- package/tests/tokens.test.ts +30 -0
- package/tests/tools/executor.test.ts +130 -0
- package/tests/util/auth.test.ts +75 -0
- package/tests/util/rate-limiter.test.ts +73 -0
- package/tests/voice.test.ts +60 -0
- package/tests/webchat.test.ts +88 -0
- package/tests/workflow.test.ts +38 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function safeExceptionError(err: any, service: string): string {
|
|
2
|
+
console.error(`[${service}] Request failed: ${err.message}`);
|
|
3
|
+
return JSON.stringify({ error: `${service} request failed. Check logs for details.` });
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const API = "https://api.linear.app/graphql";
|
|
7
|
+
|
|
8
|
+
async function gql(token: string, query: string, variables?: any): Promise<any> {
|
|
9
|
+
const res = await fetch(API, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: { Authorization: token, "Content-Type": "application/json" },
|
|
12
|
+
body: JSON.stringify({ query, variables }),
|
|
13
|
+
});
|
|
14
|
+
if (!res.ok) throw new Error(`Linear API error: ${res.status}`);
|
|
15
|
+
const data = (await res.json()) as any;
|
|
16
|
+
if (data.errors?.length) throw new Error(data.errors[0].message);
|
|
17
|
+
return data.data;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default async function (input: Record<string, unknown>): Promise<string> {
|
|
21
|
+
const token = (globalThis as any).Zubo?.getSecret?.("linear_token");
|
|
22
|
+
if (!token) return JSON.stringify({ error: "Linear token not configured. Use secret_set to store 'linear_token'." });
|
|
23
|
+
|
|
24
|
+
const { action, project_id } = input as { action: string; project_id?: string };
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
switch (action) {
|
|
28
|
+
case "list": {
|
|
29
|
+
const data = await gql(token, `{ projects(first: 25) { nodes { id name state progress startDate targetDate } } }`);
|
|
30
|
+
return JSON.stringify(data.projects.nodes);
|
|
31
|
+
}
|
|
32
|
+
case "get": {
|
|
33
|
+
if (!project_id) return JSON.stringify({ error: "project_id required" });
|
|
34
|
+
const data = await gql(token, `query($id: String!) { project(id: $id) { id name description state progress startDate targetDate teams { nodes { name } } members { nodes { name } } } }`, { id: project_id });
|
|
35
|
+
return JSON.stringify(data.project);
|
|
36
|
+
}
|
|
37
|
+
default:
|
|
38
|
+
return JSON.stringify({ error: `Unknown action: ${action}` });
|
|
39
|
+
}
|
|
40
|
+
} catch (err: any) {
|
|
41
|
+
return safeExceptionError(err, "Linear");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# notion_databases
|
|
2
|
+
|
|
3
|
+
Query and create entries in Notion databases. Requires a Notion integration token stored as `notion_token`.
|
|
4
|
+
|
|
5
|
+
## Input Schema
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"action": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"enum": ["query", "create_entry"],
|
|
14
|
+
"description": "The action to perform"
|
|
15
|
+
},
|
|
16
|
+
"database_id": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "The database ID"
|
|
19
|
+
},
|
|
20
|
+
"filter": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"description": "Notion filter object (for query)"
|
|
23
|
+
},
|
|
24
|
+
"sorts": {
|
|
25
|
+
"type": "array",
|
|
26
|
+
"description": "Sort criteria (for query)"
|
|
27
|
+
},
|
|
28
|
+
"properties": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"description": "Properties for the new entry (for create_entry)"
|
|
31
|
+
},
|
|
32
|
+
"page_size": {
|
|
33
|
+
"type": "number",
|
|
34
|
+
"description": "Number of results (for query, default: 20)"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"required": ["action", "database_id"]
|
|
38
|
+
}
|
|
39
|
+
```
|
|
@@ -0,0 +1,83 @@
|
|
|
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.notion.com/v1";
|
|
13
|
+
|
|
14
|
+
export default async function (input: Record<string, unknown>): Promise<string> {
|
|
15
|
+
const token = (globalThis as any).Zubo?.getSecret?.("notion_token");
|
|
16
|
+
if (!token) {
|
|
17
|
+
return JSON.stringify({
|
|
18
|
+
error: "Notion token not configured. Use secret_set to store a 'notion_token' or connect_service to set up Notion.",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { action, database_id, filter, sorts, properties, page_size } = input as {
|
|
23
|
+
action: string;
|
|
24
|
+
database_id: string;
|
|
25
|
+
filter?: Record<string, unknown>;
|
|
26
|
+
sorts?: unknown[];
|
|
27
|
+
properties?: Record<string, unknown>;
|
|
28
|
+
page_size?: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const headers: Record<string, string> = {
|
|
32
|
+
Authorization: `Bearer ${token}`,
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
"Notion-Version": "2022-06-28",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
switch (action) {
|
|
39
|
+
case "query": {
|
|
40
|
+
const body: any = { page_size: page_size || 20 };
|
|
41
|
+
if (filter) body.filter = filter;
|
|
42
|
+
if (sorts) body.sorts = sorts;
|
|
43
|
+
|
|
44
|
+
const res = await fetch(`${API}/databases/${database_id}/query`, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers,
|
|
47
|
+
body: JSON.stringify(body),
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok) return await safeApiError(res, "Notion");
|
|
50
|
+
const data = (await res.json()) as any;
|
|
51
|
+
return JSON.stringify({
|
|
52
|
+
results: (data.results || []).map((r: any) => ({
|
|
53
|
+
id: r.id,
|
|
54
|
+
url: r.url,
|
|
55
|
+
properties: r.properties,
|
|
56
|
+
created_time: r.created_time,
|
|
57
|
+
})),
|
|
58
|
+
has_more: data.has_more,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
case "create_entry": {
|
|
63
|
+
if (!properties) return JSON.stringify({ error: "properties are required for create_entry" });
|
|
64
|
+
const res = await fetch(`${API}/pages`, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers,
|
|
67
|
+
body: JSON.stringify({
|
|
68
|
+
parent: { database_id },
|
|
69
|
+
properties,
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
if (!res.ok) return await safeApiError(res, "Notion");
|
|
73
|
+
const page = (await res.json()) as any;
|
|
74
|
+
return JSON.stringify({ id: page.id, url: page.url, created_time: page.created_time });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
default:
|
|
78
|
+
return JSON.stringify({ error: `Unknown action: ${action}` });
|
|
79
|
+
}
|
|
80
|
+
} catch (err: any) {
|
|
81
|
+
return safeExceptionError(err, "Notion");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# notion_pages
|
|
2
|
+
|
|
3
|
+
Manage Notion pages: create, read, and update pages. Requires a Notion integration token stored as `notion_token`.
|
|
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
|
+
"page_id": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Page ID (required for read and update)"
|
|
19
|
+
},
|
|
20
|
+
"parent_page_id": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Parent page ID (required for create)"
|
|
23
|
+
},
|
|
24
|
+
"parent_database_id": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Parent database ID (alternative to parent_page_id for create)"
|
|
27
|
+
},
|
|
28
|
+
"title": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Page title (required for create)"
|
|
31
|
+
},
|
|
32
|
+
"content": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "Page content as plain text (for create and update)"
|
|
35
|
+
},
|
|
36
|
+
"properties": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"description": "Properties to set (for create in database, or update)"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"required": ["action"]
|
|
42
|
+
}
|
|
43
|
+
```
|
|
@@ -0,0 +1,130 @@
|
|
|
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.notion.com/v1";
|
|
13
|
+
|
|
14
|
+
export default async function (input: Record<string, unknown>): Promise<string> {
|
|
15
|
+
const token = (globalThis as any).Zubo?.getSecret?.("notion_token");
|
|
16
|
+
if (!token) {
|
|
17
|
+
return JSON.stringify({
|
|
18
|
+
error: "Notion token not configured. Use secret_set to store a 'notion_token' or connect_service to set up Notion.",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { action, page_id, parent_page_id, parent_database_id, title, content, properties } =
|
|
23
|
+
input as {
|
|
24
|
+
action: string;
|
|
25
|
+
page_id?: string;
|
|
26
|
+
parent_page_id?: string;
|
|
27
|
+
parent_database_id?: string;
|
|
28
|
+
title?: string;
|
|
29
|
+
content?: string;
|
|
30
|
+
properties?: Record<string, unknown>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const headers: Record<string, string> = {
|
|
34
|
+
Authorization: `Bearer ${token}`,
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
"Notion-Version": "2022-06-28",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
switch (action) {
|
|
41
|
+
case "create": {
|
|
42
|
+
if (!title) return JSON.stringify({ error: "title is required for create" });
|
|
43
|
+
const parent = parent_database_id
|
|
44
|
+
? { database_id: parent_database_id }
|
|
45
|
+
: parent_page_id
|
|
46
|
+
? { page_id: parent_page_id }
|
|
47
|
+
: null;
|
|
48
|
+
if (!parent) return JSON.stringify({ error: "parent_page_id or parent_database_id is required" });
|
|
49
|
+
|
|
50
|
+
const body: any = {
|
|
51
|
+
parent,
|
|
52
|
+
properties: properties || {
|
|
53
|
+
title: { title: [{ text: { content: title } }] },
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (content) {
|
|
58
|
+
body.children = [
|
|
59
|
+
{
|
|
60
|
+
object: "block",
|
|
61
|
+
type: "paragraph",
|
|
62
|
+
paragraph: {
|
|
63
|
+
rich_text: [{ type: "text", text: { content } }],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const res = await fetch(`${API}/pages`, {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers,
|
|
72
|
+
body: JSON.stringify(body),
|
|
73
|
+
});
|
|
74
|
+
if (!res.ok) return await safeApiError(res, "Notion");
|
|
75
|
+
const page = (await res.json()) as any;
|
|
76
|
+
return JSON.stringify({ id: page.id, url: page.url, created_time: page.created_time });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case "read": {
|
|
80
|
+
if (!page_id) return JSON.stringify({ error: "page_id is required for read" });
|
|
81
|
+
// Get page properties
|
|
82
|
+
const pageRes = await fetch(`${API}/pages/${page_id}`, { headers });
|
|
83
|
+
if (!pageRes.ok) return await safeApiError(pageRes, "Notion");
|
|
84
|
+
const page = (await pageRes.json()) as any;
|
|
85
|
+
|
|
86
|
+
// Get page content (blocks)
|
|
87
|
+
const blocksRes = await fetch(`${API}/blocks/${page_id}/children?page_size=100`, { headers });
|
|
88
|
+
let blocks: any[] = [];
|
|
89
|
+
if (blocksRes.ok) {
|
|
90
|
+
const blocksData = (await blocksRes.json()) as any;
|
|
91
|
+
blocks = blocksData.results || [];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Extract text from blocks
|
|
95
|
+
const textParts: string[] = [];
|
|
96
|
+
for (const block of blocks) {
|
|
97
|
+
const richText = block[block.type]?.rich_text;
|
|
98
|
+
if (richText) {
|
|
99
|
+
textParts.push(richText.map((t: any) => t.plain_text).join(""));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return JSON.stringify({
|
|
104
|
+
id: page.id,
|
|
105
|
+
url: page.url,
|
|
106
|
+
properties: page.properties,
|
|
107
|
+
content: textParts.join("\n"),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
case "update": {
|
|
112
|
+
if (!page_id) return JSON.stringify({ error: "page_id is required for update" });
|
|
113
|
+
if (!properties) return JSON.stringify({ error: "properties are required for update" });
|
|
114
|
+
const res = await fetch(`${API}/pages/${page_id}`, {
|
|
115
|
+
method: "PATCH",
|
|
116
|
+
headers,
|
|
117
|
+
body: JSON.stringify({ properties }),
|
|
118
|
+
});
|
|
119
|
+
if (!res.ok) return await safeApiError(res, "Notion");
|
|
120
|
+
const page = (await res.json()) as any;
|
|
121
|
+
return JSON.stringify({ id: page.id, url: page.url, message: "Page updated." });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
default:
|
|
125
|
+
return JSON.stringify({ error: `Unknown action: ${action}` });
|
|
126
|
+
}
|
|
127
|
+
} catch (err: any) {
|
|
128
|
+
return safeExceptionError(err, "Notion");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# notion_search
|
|
2
|
+
|
|
3
|
+
Search across your Notion workspace for pages and databases. Requires a Notion integration token stored as `notion_token`.
|
|
4
|
+
|
|
5
|
+
## Input Schema
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"query": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "Search query text"
|
|
14
|
+
},
|
|
15
|
+
"filter_type": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"enum": ["page", "database"],
|
|
18
|
+
"description": "Filter results by object type"
|
|
19
|
+
},
|
|
20
|
+
"page_size": {
|
|
21
|
+
"type": "number",
|
|
22
|
+
"description": "Number of results (default: 20)"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": ["query"]
|
|
26
|
+
}
|
|
27
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
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.notion.com/v1";
|
|
13
|
+
|
|
14
|
+
export default async function (input: Record<string, unknown>): Promise<string> {
|
|
15
|
+
const token = (globalThis as any).Zubo?.getSecret?.("notion_token");
|
|
16
|
+
if (!token) {
|
|
17
|
+
return JSON.stringify({
|
|
18
|
+
error: "Notion token not configured. Use secret_set to store a 'notion_token' or connect_service to set up Notion.",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { query, filter_type, page_size } = input as {
|
|
23
|
+
query: string;
|
|
24
|
+
filter_type?: string;
|
|
25
|
+
page_size?: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const headers: Record<string, string> = {
|
|
29
|
+
Authorization: `Bearer ${token}`,
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
"Notion-Version": "2022-06-28",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const body: any = {
|
|
36
|
+
query,
|
|
37
|
+
page_size: page_size || 20,
|
|
38
|
+
};
|
|
39
|
+
if (filter_type) {
|
|
40
|
+
body.filter = { value: filter_type, property: "object" };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const res = await fetch(`${API}/search`, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers,
|
|
46
|
+
body: JSON.stringify(body),
|
|
47
|
+
});
|
|
48
|
+
if (!res.ok) return await safeApiError(res, "Notion");
|
|
49
|
+
const data = (await res.json()) as any;
|
|
50
|
+
|
|
51
|
+
return JSON.stringify({
|
|
52
|
+
results: (data.results || []).map((r: any) => ({
|
|
53
|
+
id: r.id,
|
|
54
|
+
type: r.object,
|
|
55
|
+
url: r.url,
|
|
56
|
+
title:
|
|
57
|
+
r.properties?.title?.title?.[0]?.plain_text ||
|
|
58
|
+
r.properties?.Name?.title?.[0]?.plain_text ||
|
|
59
|
+
r.title?.[0]?.plain_text ||
|
|
60
|
+
"(untitled)",
|
|
61
|
+
created_time: r.created_time,
|
|
62
|
+
last_edited_time: r.last_edited_time,
|
|
63
|
+
})),
|
|
64
|
+
has_more: data.has_more,
|
|
65
|
+
});
|
|
66
|
+
} catch (err: any) {
|
|
67
|
+
return safeExceptionError(err, "Notion");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# slack_messages
|
|
2
|
+
|
|
3
|
+
Send and read Slack messages, list channels, and search messages. Requires a Slack bot token stored as `slack_token`.
|
|
4
|
+
|
|
5
|
+
## Input Schema
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"action": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"enum": ["send", "read", "list_channels", "search"],
|
|
14
|
+
"description": "Action to perform"
|
|
15
|
+
},
|
|
16
|
+
"channel": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Channel ID (for send and read)"
|
|
19
|
+
},
|
|
20
|
+
"text": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Message text (for send)"
|
|
23
|
+
},
|
|
24
|
+
"query": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Search query (for search)"
|
|
27
|
+
},
|
|
28
|
+
"limit": {
|
|
29
|
+
"type": "number",
|
|
30
|
+
"description": "Max results (default 20)"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"required": ["action"]
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage Hints
|
|
38
|
+
|
|
39
|
+
- Use "list_channels" to see available channels.
|
|
40
|
+
- Use "send" with channel and text to post a message.
|
|
41
|
+
- Use "read" with channel to see recent messages.
|
|
42
|
+
- Use "search" with query to search message history.
|
|
@@ -0,0 +1,72 @@
|
|
|
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://slack.com/api";
|
|
13
|
+
|
|
14
|
+
export default async function (input: Record<string, unknown>): Promise<string> {
|
|
15
|
+
const token = (globalThis as any).Zubo?.getSecret?.("slack_token");
|
|
16
|
+
if (!token) {
|
|
17
|
+
return JSON.stringify({ error: "Slack token not configured. Use secret_set to store 'slack_token'." });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { action, channel, text, query, limit } = input as {
|
|
21
|
+
action: string; channel?: string; text?: string; query?: string; limit?: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const headers: Record<string, string> = {
|
|
25
|
+
Authorization: `Bearer ${token}`,
|
|
26
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
switch (action) {
|
|
31
|
+
case "list_channels": {
|
|
32
|
+
const res = await fetch(`${API}/conversations.list?types=public_channel,private_channel&limit=${limit || 50}`, { headers });
|
|
33
|
+
if (!res.ok) return await safeApiError(res, "Slack");
|
|
34
|
+
const data = (await res.json()) as any;
|
|
35
|
+
if (!data.ok) return JSON.stringify({ error: `Slack API error: ${data.error}` });
|
|
36
|
+
return JSON.stringify(data.channels?.map((c: any) => ({ id: c.id, name: c.name, topic: c.topic?.value })) ?? []);
|
|
37
|
+
}
|
|
38
|
+
case "send": {
|
|
39
|
+
if (!channel) return JSON.stringify({ error: "channel is required" });
|
|
40
|
+
if (!text) return JSON.stringify({ error: "text is required" });
|
|
41
|
+
const res = await fetch(`${API}/chat.postMessage`, {
|
|
42
|
+
method: "POST", headers,
|
|
43
|
+
body: JSON.stringify({ channel, text }),
|
|
44
|
+
});
|
|
45
|
+
if (!res.ok) return await safeApiError(res, "Slack");
|
|
46
|
+
const data = (await res.json()) as any;
|
|
47
|
+
if (!data.ok) return JSON.stringify({ error: `Slack API error: ${data.error}` });
|
|
48
|
+
return JSON.stringify({ ok: true, ts: data.ts, channel: data.channel });
|
|
49
|
+
}
|
|
50
|
+
case "read": {
|
|
51
|
+
if (!channel) return JSON.stringify({ error: "channel is required" });
|
|
52
|
+
const res = await fetch(`${API}/conversations.history?channel=${channel}&limit=${limit || 20}`, { headers });
|
|
53
|
+
if (!res.ok) return await safeApiError(res, "Slack");
|
|
54
|
+
const data = (await res.json()) as any;
|
|
55
|
+
if (!data.ok) return JSON.stringify({ error: `Slack API error: ${data.error}` });
|
|
56
|
+
return JSON.stringify(data.messages?.map((m: any) => ({ user: m.user, text: m.text, ts: m.ts })) ?? []);
|
|
57
|
+
}
|
|
58
|
+
case "search": {
|
|
59
|
+
if (!query) return JSON.stringify({ error: "query is required" });
|
|
60
|
+
const res = await fetch(`${API}/search.messages?query=${encodeURIComponent(query)}&count=${limit || 20}`, { headers });
|
|
61
|
+
if (!res.ok) return await safeApiError(res, "Slack");
|
|
62
|
+
const data = (await res.json()) as any;
|
|
63
|
+
if (!data.ok) return JSON.stringify({ error: `Slack API error: ${data.error}` });
|
|
64
|
+
return JSON.stringify(data.messages?.matches?.map((m: any) => ({ text: m.text, user: m.user, channel: m.channel?.name, ts: m.ts })) ?? []);
|
|
65
|
+
}
|
|
66
|
+
default:
|
|
67
|
+
return JSON.stringify({ error: `Unknown action: ${action}` });
|
|
68
|
+
}
|
|
69
|
+
} catch (err: any) {
|
|
70
|
+
return safeExceptionError(err, "Slack");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# twitter_posts
|
|
2
|
+
|
|
3
|
+
Post tweets, read timeline, search, and reply on Twitter/X. Requires `twitter_bearer_token` for reading. For posting also needs `twitter_api_key`, `twitter_api_secret`, `twitter_access_token`, `twitter_access_secret`.
|
|
4
|
+
|
|
5
|
+
## Input Schema
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"action": { "type": "string", "enum": ["post", "timeline", "search", "reply"], "description": "Action to perform" },
|
|
12
|
+
"text": { "type": "string", "description": "Tweet text (for post and reply)" },
|
|
13
|
+
"query": { "type": "string", "description": "Search query" },
|
|
14
|
+
"tweet_id": { "type": "string", "description": "Tweet ID (for reply)" },
|
|
15
|
+
"max_results": { "type": "number", "description": "Max results (default 10)" }
|
|
16
|
+
},
|
|
17
|
+
"required": ["action"]
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage Hints
|
|
22
|
+
|
|
23
|
+
- "post" and "reply" require OAuth credentials and user confirmation.
|
|
24
|
+
- "timeline" and "search" only need the bearer token.
|