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
package/src/setup.ts
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { paths, ensureDirectories } from "./config/paths";
|
|
2
|
+
import { saveConfig } from "./config/loader";
|
|
3
|
+
import { configSchema } from "./config/schema";
|
|
4
|
+
import type { ProviderConfig } from "./config/schema";
|
|
5
|
+
import { getDb } from "./db/connection";
|
|
6
|
+
import { runMigrations } from "./db/migrations";
|
|
7
|
+
import { logger } from "./util/logger";
|
|
8
|
+
import { existsSync } from "fs";
|
|
9
|
+
import { installBuiltinSkills } from "./tools/skill-installer";
|
|
10
|
+
|
|
11
|
+
async function prompt(question: string): Promise<string> {
|
|
12
|
+
process.stdout.write(question);
|
|
13
|
+
for await (const line of console) {
|
|
14
|
+
return line.trim();
|
|
15
|
+
}
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ProviderOption {
|
|
20
|
+
key: string;
|
|
21
|
+
label: string;
|
|
22
|
+
setup: () => Promise<{ name: string; config: ProviderConfig }>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const PROVIDER_OPTIONS: ProviderOption[] = [
|
|
26
|
+
{
|
|
27
|
+
key: "1",
|
|
28
|
+
label: "Anthropic (Claude)",
|
|
29
|
+
setup: async () => {
|
|
30
|
+
const apiKey = await prompt("Anthropic API key (sk-ant-...): ");
|
|
31
|
+
if (!apiKey.startsWith("sk-ant-")) {
|
|
32
|
+
console.log("Warning: Key doesn't start with 'sk-ant-'. Proceeding anyway.");
|
|
33
|
+
}
|
|
34
|
+
const model = await prompt("Model [claude-sonnet-4-5-20250929]: ");
|
|
35
|
+
return {
|
|
36
|
+
name: "anthropic",
|
|
37
|
+
config: { apiKey, model: model || "claude-sonnet-4-5-20250929" },
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: "2",
|
|
43
|
+
label: "OpenAI (GPT)",
|
|
44
|
+
setup: async () => {
|
|
45
|
+
const apiKey = await prompt("OpenAI API key (sk-...): ");
|
|
46
|
+
const model = await prompt("Model [gpt-4.1]: ");
|
|
47
|
+
return {
|
|
48
|
+
name: "openai",
|
|
49
|
+
config: { apiKey, model: model || "gpt-4.1" },
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
key: "3",
|
|
55
|
+
label: "Ollama (local)",
|
|
56
|
+
setup: async () => {
|
|
57
|
+
const baseUrl = await prompt("Ollama URL [http://localhost:11434/v1]: ");
|
|
58
|
+
const model = await prompt("Model [llama3.3]: ");
|
|
59
|
+
return {
|
|
60
|
+
name: "ollama",
|
|
61
|
+
config: {
|
|
62
|
+
baseUrl: baseUrl || "http://localhost:11434/v1",
|
|
63
|
+
apiKey: "ollama",
|
|
64
|
+
model: model || "llama3.3",
|
|
65
|
+
streaming: false,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
key: "4",
|
|
72
|
+
label: "Groq",
|
|
73
|
+
setup: async () => {
|
|
74
|
+
const apiKey = await prompt("Groq API key (gsk_...): ");
|
|
75
|
+
const model = await prompt("Model [llama-3.3-70b-versatile]: ");
|
|
76
|
+
return {
|
|
77
|
+
name: "groq",
|
|
78
|
+
config: { apiKey, model: model || "llama-3.3-70b-versatile" },
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
key: "5",
|
|
84
|
+
label: "Together AI",
|
|
85
|
+
setup: async () => {
|
|
86
|
+
const apiKey = await prompt("Together API key: ");
|
|
87
|
+
const model = await prompt("Model [meta-llama/Llama-3.3-70B-Instruct-Turbo]: ");
|
|
88
|
+
return {
|
|
89
|
+
name: "together",
|
|
90
|
+
config: {
|
|
91
|
+
apiKey,
|
|
92
|
+
baseUrl: "https://api.together.xyz/v1",
|
|
93
|
+
model: model || "meta-llama/Llama-3.3-70B-Instruct-Turbo",
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
key: "6",
|
|
100
|
+
label: "OpenRouter",
|
|
101
|
+
setup: async () => {
|
|
102
|
+
const apiKey = await prompt("OpenRouter API key: ");
|
|
103
|
+
const model = await prompt("Model [anthropic/claude-sonnet-4-5]: ");
|
|
104
|
+
return {
|
|
105
|
+
name: "openrouter",
|
|
106
|
+
config: {
|
|
107
|
+
apiKey,
|
|
108
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
109
|
+
model: model || "anthropic/claude-sonnet-4-5",
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
key: "7",
|
|
116
|
+
label: "DeepSeek",
|
|
117
|
+
setup: async () => {
|
|
118
|
+
const apiKey = await prompt("DeepSeek API key: ");
|
|
119
|
+
const model = await prompt("Model [deepseek-chat]: ");
|
|
120
|
+
return {
|
|
121
|
+
name: "deepseek",
|
|
122
|
+
config: {
|
|
123
|
+
apiKey,
|
|
124
|
+
baseUrl: "https://api.deepseek.com/v1",
|
|
125
|
+
model: model || "deepseek-chat",
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
key: "8",
|
|
132
|
+
label: "xAI (Grok)",
|
|
133
|
+
setup: async () => {
|
|
134
|
+
const apiKey = await prompt("xAI API key: ");
|
|
135
|
+
const model = await prompt("Model [grok-4.1-fast]: ");
|
|
136
|
+
return {
|
|
137
|
+
name: "xai",
|
|
138
|
+
config: {
|
|
139
|
+
apiKey,
|
|
140
|
+
baseUrl: "https://api.x.ai/v1",
|
|
141
|
+
model: model || "grok-4.1-fast",
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
key: "9",
|
|
148
|
+
label: "Fireworks AI",
|
|
149
|
+
setup: async () => {
|
|
150
|
+
const apiKey = await prompt("Fireworks API key: ");
|
|
151
|
+
const model = await prompt("Model [accounts/fireworks/models/llama-v3p3-70b-instruct]: ");
|
|
152
|
+
return {
|
|
153
|
+
name: "fireworks",
|
|
154
|
+
config: {
|
|
155
|
+
apiKey,
|
|
156
|
+
baseUrl: "https://api.fireworks.ai/inference/v1",
|
|
157
|
+
model: model || "accounts/fireworks/models/llama-v3p3-70b-instruct",
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
key: "10",
|
|
164
|
+
label: "Cerebras",
|
|
165
|
+
setup: async () => {
|
|
166
|
+
const apiKey = await prompt("Cerebras API key: ");
|
|
167
|
+
const model = await prompt("Model [llama-3.3-70b]: ");
|
|
168
|
+
return {
|
|
169
|
+
name: "cerebras",
|
|
170
|
+
config: {
|
|
171
|
+
apiKey,
|
|
172
|
+
baseUrl: "https://api.cerebras.ai/v1",
|
|
173
|
+
model: model || "llama-3.3-70b",
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
key: "11",
|
|
180
|
+
label: "LM Studio (local)",
|
|
181
|
+
setup: async () => {
|
|
182
|
+
const baseUrl = await prompt("LM Studio URL [http://localhost:1234/v1]: ");
|
|
183
|
+
const model = await prompt("Model name: ");
|
|
184
|
+
return {
|
|
185
|
+
name: "lmstudio",
|
|
186
|
+
config: {
|
|
187
|
+
baseUrl: baseUrl || "http://localhost:1234/v1",
|
|
188
|
+
apiKey: "lm-studio",
|
|
189
|
+
model: model || "default",
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
key: "12",
|
|
196
|
+
label: "Other (OpenAI-compatible)",
|
|
197
|
+
setup: async () => {
|
|
198
|
+
const name = await prompt("Provider name: ");
|
|
199
|
+
const baseUrl = await prompt("Base URL (e.g. https://api.example.com/v1): ");
|
|
200
|
+
const apiKey = await prompt("API key (or 'none'): ");
|
|
201
|
+
const model = await prompt("Model name: ");
|
|
202
|
+
return {
|
|
203
|
+
name: name || "custom",
|
|
204
|
+
config: {
|
|
205
|
+
baseUrl,
|
|
206
|
+
apiKey: apiKey === "none" ? undefined : apiKey,
|
|
207
|
+
model: model || "default",
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
function printProviderMenu() {
|
|
215
|
+
for (const opt of PROVIDER_OPTIONS) {
|
|
216
|
+
console.log(` ${opt.key}. ${opt.label}`);
|
|
217
|
+
}
|
|
218
|
+
console.log("");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function pickProvider(): Promise<{ name: string; config: ProviderConfig } | null> {
|
|
222
|
+
const maxKey = PROVIDER_OPTIONS.length;
|
|
223
|
+
const choice = await prompt(`Provider [1-${maxKey}]: `);
|
|
224
|
+
const option = PROVIDER_OPTIONS.find((o) => o.key === choice);
|
|
225
|
+
if (!option) return null;
|
|
226
|
+
console.log("");
|
|
227
|
+
return option.setup();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function setupProvider(): Promise<{
|
|
231
|
+
providers: Record<string, ProviderConfig>;
|
|
232
|
+
activeProvider: string;
|
|
233
|
+
anthropicApiKey?: string;
|
|
234
|
+
}> {
|
|
235
|
+
console.log("Choose your LLM provider:\n");
|
|
236
|
+
printProviderMenu();
|
|
237
|
+
|
|
238
|
+
const result = await pickProvider();
|
|
239
|
+
if (!result) {
|
|
240
|
+
console.log("Invalid choice. Defaulting to Anthropic.\n");
|
|
241
|
+
const apiKey = await prompt("Anthropic API key (sk-ant-...): ");
|
|
242
|
+
const model = "claude-sonnet-4-5-20250929";
|
|
243
|
+
return {
|
|
244
|
+
providers: { anthropic: { apiKey, model } },
|
|
245
|
+
activeProvider: "anthropic",
|
|
246
|
+
anthropicApiKey: apiKey,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const providers: Record<string, ProviderConfig> = {
|
|
251
|
+
[result.name]: result.config,
|
|
252
|
+
};
|
|
253
|
+
const anthropicApiKey =
|
|
254
|
+
result.name === "anthropic" ? result.config.apiKey : undefined;
|
|
255
|
+
|
|
256
|
+
console.log(`\n✓ ${result.name} configured (${result.config.model})`);
|
|
257
|
+
|
|
258
|
+
// Offer to add a fallback
|
|
259
|
+
const addFallback = await prompt("\nAdd a fallback provider? (y/N): ");
|
|
260
|
+
if (addFallback.toLowerCase() === "y") {
|
|
261
|
+
console.log("\nFallback provider:\n");
|
|
262
|
+
printProviderMenu();
|
|
263
|
+
const fb = await pickProvider();
|
|
264
|
+
if (fb) {
|
|
265
|
+
providers[fb.name] = fb.config;
|
|
266
|
+
console.log(`✓ ${fb.name} added as fallback (${fb.config.model})`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return { providers, activeProvider: result.name, anthropicApiKey };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function setupChannels(): Promise<{
|
|
274
|
+
channels: Record<string, any>;
|
|
275
|
+
telegramBotToken?: string;
|
|
276
|
+
}> {
|
|
277
|
+
console.log("Which channels do you want to enable?\n");
|
|
278
|
+
console.log(" 1. Telegram");
|
|
279
|
+
console.log(" 2. Discord");
|
|
280
|
+
console.log(" 3. WebChat (local browser UI)");
|
|
281
|
+
console.log("");
|
|
282
|
+
console.log("Enter numbers separated by commas, e.g. 1,3\n");
|
|
283
|
+
|
|
284
|
+
const choices = await prompt("Channels [1]: ");
|
|
285
|
+
const selected = choices
|
|
286
|
+
? choices.split(",").map((s) => s.trim())
|
|
287
|
+
: ["1"];
|
|
288
|
+
|
|
289
|
+
const channels: Record<string, any> = {};
|
|
290
|
+
let telegramBotToken: string | undefined;
|
|
291
|
+
|
|
292
|
+
if (selected.includes("1")) {
|
|
293
|
+
console.log("\nTo create a Telegram bot:");
|
|
294
|
+
console.log(" 1. Open Telegram and message @BotFather");
|
|
295
|
+
console.log(" 2. Send /newbot and follow the prompts");
|
|
296
|
+
console.log(" 3. Copy the bot token\n");
|
|
297
|
+
const token = await prompt("Telegram bot token: ");
|
|
298
|
+
channels.telegram = { enabled: true, botToken: token, allowedUsers: [] };
|
|
299
|
+
telegramBotToken = token;
|
|
300
|
+
console.log("✓ Telegram configured");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (selected.includes("2")) {
|
|
304
|
+
console.log("\nTo create a Discord bot:");
|
|
305
|
+
console.log(" 1. Go to https://discord.com/developers/applications");
|
|
306
|
+
console.log(" 2. Create an application → Bot → copy token");
|
|
307
|
+
console.log(" 3. Enable MESSAGE CONTENT intent");
|
|
308
|
+
console.log(" 4. Invite bot with messages scope\n");
|
|
309
|
+
const token = await prompt("Discord bot token: ");
|
|
310
|
+
channels.discord = { enabled: true, botToken: token, allowedUsers: [] };
|
|
311
|
+
console.log("✓ Discord configured");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (selected.includes("3")) {
|
|
315
|
+
const portStr = await prompt("\nWebChat port [auto]: ");
|
|
316
|
+
const port = parseInt(portStr, 10) || 0;
|
|
317
|
+
channels.webchat = { enabled: true, port };
|
|
318
|
+
console.log(`✓ WebChat configured${port ? ` on port ${port}` : " (auto port)"}`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (Object.keys(channels).length === 0) {
|
|
322
|
+
console.log("\nNo channels selected. Defaulting to WebChat (auto port).");
|
|
323
|
+
channels.webchat = { enabled: true, port: 0 };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return { channels, telegramBotToken };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export async function runSetup() {
|
|
330
|
+
console.log("\n Zubo Setup Wizard\n");
|
|
331
|
+
console.log("This will configure your Zubo agent.\n");
|
|
332
|
+
|
|
333
|
+
// 1. LLM provider
|
|
334
|
+
const { providers, activeProvider, anthropicApiKey } = await setupProvider();
|
|
335
|
+
|
|
336
|
+
// Build failover list from extra providers
|
|
337
|
+
const failover = Object.keys(providers).filter((k) => k !== activeProvider);
|
|
338
|
+
|
|
339
|
+
// 2. Channels
|
|
340
|
+
const { channels, telegramBotToken } = await setupChannels();
|
|
341
|
+
|
|
342
|
+
// 3. Create directory tree
|
|
343
|
+
console.log("\nCreating ~/.zubo/ directory tree...");
|
|
344
|
+
ensureDirectories();
|
|
345
|
+
|
|
346
|
+
// 4. Write config
|
|
347
|
+
const config = configSchema.parse({
|
|
348
|
+
// Legacy compat
|
|
349
|
+
anthropicApiKey,
|
|
350
|
+
model: providers[activeProvider].model,
|
|
351
|
+
telegramBotToken,
|
|
352
|
+
telegramAllowedUsers: [],
|
|
353
|
+
|
|
354
|
+
// New provider system
|
|
355
|
+
providers,
|
|
356
|
+
activeProvider,
|
|
357
|
+
failover: failover.length ? failover : undefined,
|
|
358
|
+
|
|
359
|
+
// Channels
|
|
360
|
+
channels,
|
|
361
|
+
});
|
|
362
|
+
await saveConfig(config);
|
|
363
|
+
console.log(`Config saved to ${paths.config}`);
|
|
364
|
+
|
|
365
|
+
// 5. Init SQLite DB
|
|
366
|
+
console.log("Initializing database...");
|
|
367
|
+
const db = getDb();
|
|
368
|
+
runMigrations(db);
|
|
369
|
+
db.close();
|
|
370
|
+
console.log(`Database created at ${paths.db}`);
|
|
371
|
+
|
|
372
|
+
// 6. Create initial MEMORY.md
|
|
373
|
+
if (!existsSync(paths.memoryFile)) {
|
|
374
|
+
await Bun.write(
|
|
375
|
+
paths.memoryFile,
|
|
376
|
+
`# Zubo Memory\n\nThis file stores persistent memories about the user.\n`
|
|
377
|
+
);
|
|
378
|
+
console.log(`Created ${paths.memoryFile}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// 7. Create default SYSTEM.md
|
|
382
|
+
if (!existsSync(paths.systemPrompt)) {
|
|
383
|
+
await Bun.write(
|
|
384
|
+
paths.systemPrompt,
|
|
385
|
+
`You are Zubo, a personal AI agent. You are helpful, proactive, and have a persistent memory.
|
|
386
|
+
|
|
387
|
+
## Your capabilities
|
|
388
|
+
- You remember things about the user across conversations using your memory tools.
|
|
389
|
+
- You can check the current date and time.
|
|
390
|
+
- You are conversational and friendly, but concise.
|
|
391
|
+
- When the user tells you something personal (name, preferences, facts about their life), proactively save it to memory.
|
|
392
|
+
- When answering questions that might relate to stored memories, search your memory first.
|
|
393
|
+
|
|
394
|
+
## Guidelines
|
|
395
|
+
- Be concise. Don't over-explain unless asked.
|
|
396
|
+
- Use memory_write to save important facts the user shares.
|
|
397
|
+
- Use memory_search to recall previously stored information.
|
|
398
|
+
- If you're unsure about something, say so.
|
|
399
|
+
`
|
|
400
|
+
);
|
|
401
|
+
console.log(`Created ${paths.systemPrompt}`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 8. Install built-in skills
|
|
405
|
+
const installed = installBuiltinSkills(paths.skills);
|
|
406
|
+
if (installed.length) {
|
|
407
|
+
console.log(`Installed ${installed.length} built-in skills: ${installed.join(", ")}`);
|
|
408
|
+
} else {
|
|
409
|
+
console.log("Built-in skills already installed.");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
console.log("\nSetup complete! Run 'zubo start' to launch Zubo.\n");
|
|
413
|
+
}
|
package/src/skills.ts
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, existsSync, rmSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
4
|
+
import { paths } from "./config/paths";
|
|
5
|
+
import { parseSkillMd, parseSkillExport } from "./tools/skill-loader";
|
|
6
|
+
import { getBuiltinSkillNames, reinstallBuiltinSkill } from "./tools/skill-installer";
|
|
7
|
+
|
|
8
|
+
async function prompt(question: string): Promise<string> {
|
|
9
|
+
process.stdout.write(question);
|
|
10
|
+
for await (const line of console) {
|
|
11
|
+
return line.trim();
|
|
12
|
+
}
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getInstalledSkills(): { name: string; status: string; description: string; folder: string }[] {
|
|
17
|
+
if (!existsSync(paths.skills)) return [];
|
|
18
|
+
|
|
19
|
+
let entries: string[];
|
|
20
|
+
try {
|
|
21
|
+
entries = readdirSync(paths.skills);
|
|
22
|
+
} catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const skills: { name: string; status: string; description: string; folder: string }[] = [];
|
|
27
|
+
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
const dirPath = join(paths.skills, entry);
|
|
30
|
+
const skillMdPath = join(dirPath, "SKILL.md");
|
|
31
|
+
const handlerPath = join(dirPath, "handler.ts");
|
|
32
|
+
|
|
33
|
+
const hasHandler = existsSync(handlerPath);
|
|
34
|
+
const hasSkillMd = existsSync(skillMdPath);
|
|
35
|
+
|
|
36
|
+
if (!hasHandler && !hasSkillMd) continue;
|
|
37
|
+
|
|
38
|
+
let name = entry;
|
|
39
|
+
let desc = "—";
|
|
40
|
+
let status = "error";
|
|
41
|
+
|
|
42
|
+
if (hasSkillMd) {
|
|
43
|
+
const mdContent = readFileSync(skillMdPath, "utf-8");
|
|
44
|
+
const parsed = parseSkillMd(mdContent, dirPath);
|
|
45
|
+
if (parsed) {
|
|
46
|
+
name = parsed.name;
|
|
47
|
+
desc = parsed.description?.split("\n")[0] ?? "—";
|
|
48
|
+
status = hasHandler ? "ok" : "error";
|
|
49
|
+
}
|
|
50
|
+
} else if (hasHandler) {
|
|
51
|
+
// Single-file skill: try to read the exported skill config via regex
|
|
52
|
+
try {
|
|
53
|
+
const handlerContent = readFileSync(handlerPath, "utf-8");
|
|
54
|
+
const nameMatch = handlerContent.match(/name\s*:\s*["']([^"']+)["']/);
|
|
55
|
+
const descMatch = handlerContent.match(/description\s*:\s*["']([^"']+)["']/);
|
|
56
|
+
if (nameMatch) name = nameMatch[1];
|
|
57
|
+
if (descMatch) desc = descMatch[1];
|
|
58
|
+
status = "ok";
|
|
59
|
+
} catch {
|
|
60
|
+
status = "error";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
skills.push({ name, status, description: desc.slice(0, 60), folder: entry });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return skills;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function listSkills() {
|
|
71
|
+
console.log("\n Zubo Skills\n");
|
|
72
|
+
|
|
73
|
+
const skills = getInstalledSkills();
|
|
74
|
+
|
|
75
|
+
if (skills.length === 0) {
|
|
76
|
+
console.log(" No skills installed.\n");
|
|
77
|
+
console.log(" Drop skill folders into ~/.zubo/workspace/skills/");
|
|
78
|
+
console.log(" Each folder needs a handler.ts file\n");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const nameW = Math.max(10, ...skills.map((s) => s.name.length)) + 2;
|
|
83
|
+
const header = ` ${"Name".padEnd(nameW)}${"Status".padEnd(10)}Description`;
|
|
84
|
+
console.log(header);
|
|
85
|
+
console.log(" " + "-".repeat(header.length - 2));
|
|
86
|
+
|
|
87
|
+
for (const s of skills) {
|
|
88
|
+
const statusIcon = s.status === "ok" ? "ok" : "err";
|
|
89
|
+
console.log(` ${s.name.padEnd(nameW)}${statusIcon.padEnd(10)}${s.description}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(`\n ${skills.length} skill(s) installed.\n`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function createSkillWizard() {
|
|
96
|
+
console.log("\n Create New Skill\n");
|
|
97
|
+
|
|
98
|
+
const name = await prompt(" Tool name (lowercase, underscores only): ");
|
|
99
|
+
if (!name || !/^[a-z0-9_]+$/.test(name)) {
|
|
100
|
+
console.log(" Invalid name. Must match [a-z0-9_]+\n");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const destDir = join(paths.skills, name);
|
|
105
|
+
if (existsSync(destDir)) {
|
|
106
|
+
console.log(` Skill "${name}" already exists.\n`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const description = await prompt(" Description: ");
|
|
111
|
+
if (!description) {
|
|
112
|
+
console.log(" Description is required.\n");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Collect parameters
|
|
117
|
+
const params: Record<string, { type: string; description: string; required?: boolean }> = {};
|
|
118
|
+
|
|
119
|
+
console.log("\n Add parameters (press Enter with empty name to finish):\n");
|
|
120
|
+
|
|
121
|
+
while (true) {
|
|
122
|
+
const paramName = await prompt(" Parameter name: ");
|
|
123
|
+
if (!paramName) break;
|
|
124
|
+
|
|
125
|
+
const paramType = await prompt(" Type [string]: ");
|
|
126
|
+
const paramDesc = await prompt(" Description: ");
|
|
127
|
+
const paramReq = await prompt(" Required? (y/N): ");
|
|
128
|
+
|
|
129
|
+
params[paramName] = {
|
|
130
|
+
type: paramType || "string",
|
|
131
|
+
description: paramDesc || "",
|
|
132
|
+
};
|
|
133
|
+
if (paramReq.toLowerCase() === "y") {
|
|
134
|
+
params[paramName].required = true;
|
|
135
|
+
}
|
|
136
|
+
console.log("");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Build params block for the skill config
|
|
140
|
+
const paramEntries = Object.entries(params);
|
|
141
|
+
let paramsBlock = "{}";
|
|
142
|
+
if (paramEntries.length > 0) {
|
|
143
|
+
const paramLines = paramEntries.map(([p, def]) => {
|
|
144
|
+
const parts = [`type: "${def.type}"`];
|
|
145
|
+
if (def.description) parts.push(`description: "${def.description}"`);
|
|
146
|
+
if (def.required) parts.push("required: true");
|
|
147
|
+
return ` ${p}: { ${parts.join(", ")} }`;
|
|
148
|
+
});
|
|
149
|
+
paramsBlock = `{\n${paramLines.join(",\n")}\n }`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Generate single-file handler.ts
|
|
153
|
+
const extractLines = paramEntries
|
|
154
|
+
.map(([p, def]) => ` const ${p} = input.${p} as ${def.type === "number" ? "number" : "string"};`)
|
|
155
|
+
.join("\n");
|
|
156
|
+
|
|
157
|
+
const handlerTs = `export const skill = {
|
|
158
|
+
name: "${name}",
|
|
159
|
+
description: "${description}",
|
|
160
|
+
params: ${paramsBlock}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export default async function (input: Record<string, unknown>): Promise<string> {
|
|
164
|
+
${extractLines || " // Access parameters from input"}
|
|
165
|
+
|
|
166
|
+
// TODO: Implement your skill logic here
|
|
167
|
+
|
|
168
|
+
return JSON.stringify({ result: "Not yet implemented" });
|
|
169
|
+
}
|
|
170
|
+
`;
|
|
171
|
+
|
|
172
|
+
// Write files
|
|
173
|
+
mkdirSync(destDir, { recursive: true });
|
|
174
|
+
writeFileSync(join(destDir, "handler.ts"), handlerTs);
|
|
175
|
+
|
|
176
|
+
console.log(`\n Skill created at ~/.zubo/workspace/skills/${name}/`);
|
|
177
|
+
console.log(" Files:");
|
|
178
|
+
console.log(` handler.ts — skill config + implementation`);
|
|
179
|
+
console.log(`\n Restart Zubo to load the new skill.\n`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function reinstallBuiltins() {
|
|
183
|
+
console.log("\n Reinstall Built-in Skills\n");
|
|
184
|
+
|
|
185
|
+
const builtinNames = getBuiltinSkillNames();
|
|
186
|
+
if (builtinNames.length === 0) {
|
|
187
|
+
console.log(" No built-in skills found.\n");
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log(" Available built-in skills:");
|
|
192
|
+
for (const name of builtinNames) {
|
|
193
|
+
const destDir = join(paths.skills, name);
|
|
194
|
+
const status = existsSync(destDir) ? "(installed)" : "(missing)";
|
|
195
|
+
console.log(` - ${name} ${status}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const confirm = await prompt("\n This will overwrite existing copies. Continue? (y/N): ");
|
|
199
|
+
if (confirm.toLowerCase() !== "y") {
|
|
200
|
+
console.log(" Cancelled.\n");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let count = 0;
|
|
205
|
+
for (const name of builtinNames) {
|
|
206
|
+
if (reinstallBuiltinSkill(paths.skills, name)) {
|
|
207
|
+
count++;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(`\n Reinstalled ${count} built-in skill(s).\n`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function removeSkill() {
|
|
215
|
+
console.log("\n Remove a Skill\n");
|
|
216
|
+
|
|
217
|
+
const skills = getInstalledSkills();
|
|
218
|
+
if (skills.length === 0) {
|
|
219
|
+
console.log(" No skills installed.\n");
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (let i = 0; i < skills.length; i++) {
|
|
224
|
+
console.log(` ${i + 1}. ${skills[i].name}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const choice = await prompt(`\n Remove which skill? [1-${skills.length}]: `);
|
|
228
|
+
const idx = parseInt(choice, 10) - 1;
|
|
229
|
+
if (isNaN(idx) || idx < 0 || idx >= skills.length) {
|
|
230
|
+
console.log(" Invalid choice.\n");
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const skill = skills[idx];
|
|
235
|
+
const confirm = await prompt(` Delete "${skill.name}"? This cannot be undone. (y/N): `);
|
|
236
|
+
if (confirm.toLowerCase() !== "y") {
|
|
237
|
+
console.log(" Cancelled.\n");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const dirPath = join(paths.skills, skill.folder);
|
|
242
|
+
rmSync(dirPath, { recursive: true, force: true });
|
|
243
|
+
console.log(`\n Removed "${skill.name}".\n`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function showMenu() {
|
|
247
|
+
console.log("\n Zubo Skills\n");
|
|
248
|
+
console.log(" 1. List installed skills");
|
|
249
|
+
console.log(" 2. Create new skill");
|
|
250
|
+
console.log(" 3. Reinstall built-in skills");
|
|
251
|
+
console.log(" 4. Remove a skill");
|
|
252
|
+
console.log("");
|
|
253
|
+
|
|
254
|
+
const choice = await prompt(" Choice [1-4]: ");
|
|
255
|
+
|
|
256
|
+
switch (choice) {
|
|
257
|
+
case "1":
|
|
258
|
+
listSkills();
|
|
259
|
+
break;
|
|
260
|
+
case "2":
|
|
261
|
+
await createSkillWizard();
|
|
262
|
+
break;
|
|
263
|
+
case "3":
|
|
264
|
+
await reinstallBuiltins();
|
|
265
|
+
break;
|
|
266
|
+
case "4":
|
|
267
|
+
await removeSkill();
|
|
268
|
+
break;
|
|
269
|
+
default:
|
|
270
|
+
console.log(" Invalid choice.\n");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export async function runSkillsCommand(args: string[] = []) {
|
|
275
|
+
const sub = args[0];
|
|
276
|
+
|
|
277
|
+
switch (sub) {
|
|
278
|
+
case "list":
|
|
279
|
+
listSkills();
|
|
280
|
+
break;
|
|
281
|
+
case "new":
|
|
282
|
+
await createSkillWizard();
|
|
283
|
+
break;
|
|
284
|
+
case "reinstall":
|
|
285
|
+
await reinstallBuiltins();
|
|
286
|
+
break;
|
|
287
|
+
case "remove":
|
|
288
|
+
await removeSkill();
|
|
289
|
+
break;
|
|
290
|
+
default:
|
|
291
|
+
await showMenu();
|
|
292
|
+
}
|
|
293
|
+
}
|