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/start.ts
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync, mkdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { loadConfig } from "./config/loader";
|
|
4
|
+
import { ensureDirectories, paths } from "./config/paths";
|
|
5
|
+
import type { ZuboConfig } from "./config/schema";
|
|
6
|
+
import { getDb, closeDb } from "./db/connection";
|
|
7
|
+
import { runMigrations } from "./db/migrations";
|
|
8
|
+
import { createProvider } from "./llm/factory";
|
|
9
|
+
import { registerDatetimeTool } from "./tools/builtin/datetime";
|
|
10
|
+
import { registerMemoryWriteTool } from "./tools/builtin/memory-write";
|
|
11
|
+
import { registerMemorySearchTool } from "./tools/builtin/memory-search";
|
|
12
|
+
import { registerManageSkillsTool } from "./tools/builtin/manage-skills";
|
|
13
|
+
import { registerCronTools } from "./tools/builtin/cron";
|
|
14
|
+
import { registerSecretTools } from "./tools/builtin/secrets";
|
|
15
|
+
import { registerConnectServiceTool } from "./tools/builtin/connect-service";
|
|
16
|
+
import { registerDelegateTool } from "./tools/builtin/delegate";
|
|
17
|
+
import { registerDelegateTaskTool } from "./tools/builtin/delegate-task";
|
|
18
|
+
import { registerDiagnoseTool } from "./tools/builtin/diagnose";
|
|
19
|
+
import { registerGoogleOAuthTool } from "./tools/builtin/google-oauth";
|
|
20
|
+
import { registerManageAgentsTool } from "./tools/builtin/manage-agents";
|
|
21
|
+
import { exposeSecretsRuntime, exposeGoogleTokenRuntime } from "./secrets/store";
|
|
22
|
+
import { loadSkills, watchSkills } from "./tools/skill-loader";
|
|
23
|
+
import { createRouter, type MessageRouter } from "./channels/router";
|
|
24
|
+
import { startHeartbeat } from "./scheduler/heartbeat";
|
|
25
|
+
import { initCronScheduler } from "./scheduler/cron";
|
|
26
|
+
import { initMemory } from "./memory/engine";
|
|
27
|
+
import { logger, enableFileLogging } from "./util/logger";
|
|
28
|
+
|
|
29
|
+
function openBrowser(url: string) {
|
|
30
|
+
try {
|
|
31
|
+
const cmd =
|
|
32
|
+
process.platform === "darwin"
|
|
33
|
+
? ["open", url]
|
|
34
|
+
: process.platform === "win32"
|
|
35
|
+
? ["cmd", "/c", "start", url]
|
|
36
|
+
: ["xdg-open", url];
|
|
37
|
+
Bun.spawn(cmd, { stdio: ["ignore", "ignore", "ignore"] });
|
|
38
|
+
} catch (err: any) {
|
|
39
|
+
logger.warn("Failed to open browser", { error: (err as Error).message });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getTelegramToken(config: ZuboConfig): string | null {
|
|
44
|
+
if (config.channels?.telegram?.enabled !== false && config.channels?.telegram?.botToken) {
|
|
45
|
+
return config.channels.telegram.botToken;
|
|
46
|
+
}
|
|
47
|
+
// Legacy fallback
|
|
48
|
+
if (config.telegramBotToken) {
|
|
49
|
+
return config.telegramBotToken;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getTelegramAllowedUsers(config: ZuboConfig): number[] {
|
|
55
|
+
if (config.channels?.telegram?.allowedUsers?.length) {
|
|
56
|
+
return config.channels.telegram.allowedUsers;
|
|
57
|
+
}
|
|
58
|
+
return config.telegramAllowedUsers ?? [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function startChannels(config: ZuboConfig, router: MessageRouter) {
|
|
62
|
+
const stoppers: (() => void)[] = [];
|
|
63
|
+
|
|
64
|
+
// Telegram
|
|
65
|
+
const tgToken = getTelegramToken(config);
|
|
66
|
+
if (tgToken) {
|
|
67
|
+
const { createTelegramAdapter } = await import("./channels/telegram");
|
|
68
|
+
// Build a compat config object for the telegram adapter
|
|
69
|
+
const tgConfig = {
|
|
70
|
+
...config,
|
|
71
|
+
telegramBotToken: tgToken,
|
|
72
|
+
telegramAllowedUsers: getTelegramAllowedUsers(config),
|
|
73
|
+
};
|
|
74
|
+
const telegram = createTelegramAdapter(tgToken, tgConfig, router);
|
|
75
|
+
router.addAdapter(telegram);
|
|
76
|
+
telegram.start();
|
|
77
|
+
stoppers.push(() => telegram.stop());
|
|
78
|
+
logger.info("Telegram channel started");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Discord
|
|
82
|
+
if (config.channels?.discord?.enabled !== false && config.channels?.discord?.botToken) {
|
|
83
|
+
const { createDiscordAdapter } = await import("./channels/discord");
|
|
84
|
+
const discord = createDiscordAdapter(
|
|
85
|
+
config.channels.discord.botToken,
|
|
86
|
+
config.channels.discord.allowedUsers ?? [],
|
|
87
|
+
router
|
|
88
|
+
);
|
|
89
|
+
router.addAdapter(discord);
|
|
90
|
+
discord.start();
|
|
91
|
+
stoppers.push(() => discord.stop());
|
|
92
|
+
logger.info("Discord channel started");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Slack
|
|
96
|
+
if (config.channels?.slack?.enabled !== false && config.channels?.slack?.botToken) {
|
|
97
|
+
const { createSlackAdapter } = await import("./channels/slack");
|
|
98
|
+
const slack = createSlackAdapter(
|
|
99
|
+
config.channels.slack.botToken,
|
|
100
|
+
config.channels.slack.appToken,
|
|
101
|
+
config.channels.slack.allowedUsers ?? [],
|
|
102
|
+
router
|
|
103
|
+
);
|
|
104
|
+
router.addAdapter(slack);
|
|
105
|
+
slack.start();
|
|
106
|
+
stoppers.push(() => slack.stop());
|
|
107
|
+
logger.info("Slack channel started");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// WhatsApp
|
|
111
|
+
if (config.channels?.whatsapp?.enabled !== false && config.channels?.whatsapp) {
|
|
112
|
+
const { createWhatsAppAdapter } = await import("./channels/whatsapp");
|
|
113
|
+
const whatsapp = createWhatsAppAdapter(
|
|
114
|
+
config.channels.whatsapp.allowedNumbers ?? [],
|
|
115
|
+
config.channels.whatsapp.authDir,
|
|
116
|
+
router
|
|
117
|
+
);
|
|
118
|
+
router.addAdapter(whatsapp);
|
|
119
|
+
whatsapp.start();
|
|
120
|
+
stoppers.push(() => whatsapp.stop());
|
|
121
|
+
logger.info("WhatsApp channel started");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Signal
|
|
125
|
+
if (config.channels?.signal?.enabled !== false && config.channels?.signal?.phoneNumber) {
|
|
126
|
+
const { createSignalAdapter } = await import("./channels/signal");
|
|
127
|
+
const signal = createSignalAdapter(
|
|
128
|
+
config.channels.signal.phoneNumber,
|
|
129
|
+
config.channels.signal.allowedNumbers ?? [],
|
|
130
|
+
config.channels.signal.signalCliPath,
|
|
131
|
+
router
|
|
132
|
+
);
|
|
133
|
+
router.addAdapter(signal);
|
|
134
|
+
signal.start();
|
|
135
|
+
stoppers.push(() => signal.stop());
|
|
136
|
+
logger.info("Signal channel started");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// WebChat + Dashboard (always enabled)
|
|
140
|
+
if (config.channels?.webchat?.enabled !== false) {
|
|
141
|
+
const requestedPort = config.channels?.webchat?.port ?? 0;
|
|
142
|
+
const { createWebChatAdapter } = await import("./channels/webchat");
|
|
143
|
+
const webchat = createWebChatAdapter(requestedPort, router);
|
|
144
|
+
router.addAdapter(webchat);
|
|
145
|
+
webchat.start();
|
|
146
|
+
stoppers.push(() => webchat.stop());
|
|
147
|
+
|
|
148
|
+
// Print URLs with the actual resolved port and auto-open browser
|
|
149
|
+
const actualPort = webchat.getPort();
|
|
150
|
+
const url = `http://localhost:${actualPort}`;
|
|
151
|
+
console.log(`\n Chat: ${url}`);
|
|
152
|
+
console.log(` Dashboard: ${url}/dashboard\n`);
|
|
153
|
+
openBrowser(url);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return () => {
|
|
157
|
+
for (const stop of stoppers) stop();
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function startZubo(isDaemon = false) {
|
|
162
|
+
if (isDaemon) {
|
|
163
|
+
return startDaemon();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
enableFileLogging();
|
|
167
|
+
logger.info("Starting Zubo...");
|
|
168
|
+
|
|
169
|
+
// Load config
|
|
170
|
+
const config = await loadConfig();
|
|
171
|
+
ensureDirectories();
|
|
172
|
+
|
|
173
|
+
// Init DB
|
|
174
|
+
const db = getDb();
|
|
175
|
+
runMigrations(db);
|
|
176
|
+
|
|
177
|
+
// Init memory (embedder + index existing files)
|
|
178
|
+
logger.info("Initializing memory system...");
|
|
179
|
+
await initMemory(db);
|
|
180
|
+
|
|
181
|
+
// Init LLM
|
|
182
|
+
const llm = createProvider(config);
|
|
183
|
+
|
|
184
|
+
// Init voice (STT/TTS) if configured
|
|
185
|
+
if (config.voice?.stt) {
|
|
186
|
+
const { initStt } = await import("./voice/stt");
|
|
187
|
+
initStt(config.voice.stt);
|
|
188
|
+
}
|
|
189
|
+
if (config.voice?.tts) {
|
|
190
|
+
const { initTts } = await import("./voice/tts");
|
|
191
|
+
initTts(config.voice.tts);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Register tools
|
|
195
|
+
registerDatetimeTool();
|
|
196
|
+
registerMemoryWriteTool();
|
|
197
|
+
registerMemorySearchTool(db);
|
|
198
|
+
registerManageSkillsTool();
|
|
199
|
+
registerSecretTools();
|
|
200
|
+
exposeSecretsRuntime();
|
|
201
|
+
|
|
202
|
+
// Expose Google OAuth token helper for installed skill handlers
|
|
203
|
+
const { getGoogleAccessToken } = await import("./util/google-tokens");
|
|
204
|
+
exposeGoogleTokenRuntime(getGoogleAccessToken);
|
|
205
|
+
|
|
206
|
+
registerConnectServiceTool();
|
|
207
|
+
|
|
208
|
+
// Register skill registry tool
|
|
209
|
+
const { registerSkillRegistryTool } = await import("./tools/builtin/skill-registry");
|
|
210
|
+
registerSkillRegistryTool();
|
|
211
|
+
|
|
212
|
+
// Load skills
|
|
213
|
+
try {
|
|
214
|
+
const skillNames = await loadSkills(paths.skills);
|
|
215
|
+
if (skillNames.length) {
|
|
216
|
+
logger.info(`Skills loaded: ${skillNames.join(", ")}`);
|
|
217
|
+
} else {
|
|
218
|
+
logger.info("No skills found in workspace");
|
|
219
|
+
}
|
|
220
|
+
} catch (err: any) {
|
|
221
|
+
logger.error(`Failed to load skills: ${err.message}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Start skill hot-reload watcher
|
|
225
|
+
const stopSkillWatcher = watchSkills();
|
|
226
|
+
|
|
227
|
+
// Create message router
|
|
228
|
+
const router = createRouter(llm, db);
|
|
229
|
+
|
|
230
|
+
// Register cron tools (need router for scheduling)
|
|
231
|
+
registerCronTools(db, router, config, llm);
|
|
232
|
+
|
|
233
|
+
// Register delegation tools
|
|
234
|
+
registerDelegateTool(llm);
|
|
235
|
+
registerDelegateTaskTool(llm);
|
|
236
|
+
registerManageAgentsTool();
|
|
237
|
+
|
|
238
|
+
// Register diagnostics
|
|
239
|
+
registerDiagnoseTool();
|
|
240
|
+
|
|
241
|
+
// Register Google OAuth tool
|
|
242
|
+
registerGoogleOAuthTool();
|
|
243
|
+
|
|
244
|
+
// Register workflow + team tools
|
|
245
|
+
const { registerManageWorkflowsTool } = await import("./tools/builtin/manage-workflows");
|
|
246
|
+
const { registerRunWorkflowTool } = await import("./tools/builtin/run-workflow");
|
|
247
|
+
const { registerManageTeamsTool } = await import("./tools/builtin/manage-teams");
|
|
248
|
+
registerManageWorkflowsTool();
|
|
249
|
+
registerRunWorkflowTool(llm);
|
|
250
|
+
registerManageTeamsTool();
|
|
251
|
+
|
|
252
|
+
// Register proactive intelligence tools
|
|
253
|
+
const { registerManageTriggersTool } = await import("./tools/builtin/manage-triggers");
|
|
254
|
+
registerManageTriggersTool();
|
|
255
|
+
|
|
256
|
+
// Start all configured channels
|
|
257
|
+
const stopChannels = await startChannels(config, router);
|
|
258
|
+
|
|
259
|
+
// Start scheduler
|
|
260
|
+
startHeartbeat(config.heartbeatMinutes);
|
|
261
|
+
initCronScheduler(db, router, config, llm);
|
|
262
|
+
logger.info("Scheduler started");
|
|
263
|
+
|
|
264
|
+
// Register daily backup handler
|
|
265
|
+
const { onHeartbeat } = await import("./scheduler/heartbeat");
|
|
266
|
+
const { backupDatabase } = await import("./db/export");
|
|
267
|
+
let lastBackupDay = "";
|
|
268
|
+
onHeartbeat(async () => {
|
|
269
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
270
|
+
if (today === lastBackupDay) return;
|
|
271
|
+
try {
|
|
272
|
+
const backupDir = join(paths.workspace, "backups");
|
|
273
|
+
mkdirSync(backupDir, { recursive: true });
|
|
274
|
+
backupDatabase(paths.db, backupDir);
|
|
275
|
+
lastBackupDay = today;
|
|
276
|
+
// Prune old backups — keep last 7
|
|
277
|
+
const { readdirSync: ls, unlinkSync: rm } = await import("fs");
|
|
278
|
+
const files = ls(backupDir).filter((f: string) => f.startsWith("zubo-backup-")).sort();
|
|
279
|
+
while (files.length > 7) {
|
|
280
|
+
rm(join(backupDir, files.shift()!));
|
|
281
|
+
}
|
|
282
|
+
logger.info("Daily backup complete");
|
|
283
|
+
} catch (err: any) {
|
|
284
|
+
logger.warn("Daily backup failed", { error: err.message });
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Init proactive intelligence
|
|
289
|
+
const { initProactiveIntelligence } = await import("./scheduler/proactive");
|
|
290
|
+
initProactiveIntelligence(db, router, llm, config);
|
|
291
|
+
|
|
292
|
+
// Ensure morning briefing cron exists
|
|
293
|
+
const { ensureBriefingCron } = await import("./scheduler/briefing");
|
|
294
|
+
ensureBriefingCron(db);
|
|
295
|
+
|
|
296
|
+
// Init performance collector
|
|
297
|
+
const { initPerfCollector } = await import("./util/perf-collector");
|
|
298
|
+
initPerfCollector();
|
|
299
|
+
|
|
300
|
+
logger.info("Zubo is running. Press Ctrl+C to stop.");
|
|
301
|
+
|
|
302
|
+
// Graceful shutdown
|
|
303
|
+
const shutdown = () => {
|
|
304
|
+
logger.info("Shutting down...");
|
|
305
|
+
stopSkillWatcher();
|
|
306
|
+
stopChannels();
|
|
307
|
+
closeDb();
|
|
308
|
+
cleanupPidFile();
|
|
309
|
+
process.exit(0);
|
|
310
|
+
};
|
|
311
|
+
process.on("SIGINT", shutdown);
|
|
312
|
+
process.on("SIGTERM", shutdown);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function cleanupPidFile() {
|
|
316
|
+
try {
|
|
317
|
+
if (existsSync(paths.pidFile)) unlinkSync(paths.pidFile);
|
|
318
|
+
} catch (err: any) {
|
|
319
|
+
logger.warn("Failed to clean up PID file", { error: (err as Error).message });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function startDaemon() {
|
|
324
|
+
ensureDirectories();
|
|
325
|
+
|
|
326
|
+
// Spawn detached child process
|
|
327
|
+
const child = Bun.spawn(["bun", "run", "src/index.ts", "start"], {
|
|
328
|
+
cwd: import.meta.dir.replace(/\/src$/, ""),
|
|
329
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
330
|
+
env: { ...process.env },
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Write child PID
|
|
334
|
+
const pid = child.pid;
|
|
335
|
+
writeFileSync(paths.pidFile, String(pid));
|
|
336
|
+
|
|
337
|
+
console.log(`Zubo started in background (PID ${pid})`);
|
|
338
|
+
console.log(`Logs: ${paths.logFile}`);
|
|
339
|
+
console.log(`Stop: bun run stop`);
|
|
340
|
+
|
|
341
|
+
// Unref so parent can exit
|
|
342
|
+
child.unref();
|
|
343
|
+
process.exit(0);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function stopDaemon() {
|
|
347
|
+
if (!existsSync(paths.pidFile)) {
|
|
348
|
+
console.log("Zubo is not running (no PID file found).");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const pid = parseInt(readFileSync(paths.pidFile, "utf-8").trim(), 10);
|
|
353
|
+
|
|
354
|
+
if (isNaN(pid)) {
|
|
355
|
+
console.log("Invalid PID file. Removing it.");
|
|
356
|
+
unlinkSync(paths.pidFile);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
process.kill(pid, 0); // check if alive
|
|
362
|
+
process.kill(pid, "SIGTERM");
|
|
363
|
+
console.log(`Sent SIGTERM to Zubo (PID ${pid}).`);
|
|
364
|
+
} catch {
|
|
365
|
+
console.log(`Process ${pid} is not running. Cleaning up PID file.`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
unlinkSync(paths.pidFile);
|
|
370
|
+
} catch (err: any) {
|
|
371
|
+
logger.warn("Failed to remove PID file", { error: (err as Error).message });
|
|
372
|
+
}
|
|
373
|
+
}
|
package/src/status.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { paths } from "./config/paths";
|
|
4
|
+
import { configExists } from "./config/loader";
|
|
5
|
+
import { logger } from "./util/logger";
|
|
6
|
+
|
|
7
|
+
function isDaemonRunning(): { running: boolean; pid?: number } {
|
|
8
|
+
if (!existsSync(paths.pidFile)) return { running: false };
|
|
9
|
+
try {
|
|
10
|
+
const pid = parseInt(readFileSync(paths.pidFile, "utf-8").trim(), 10);
|
|
11
|
+
if (isNaN(pid)) return { running: false };
|
|
12
|
+
process.kill(pid, 0); // throws if process doesn't exist
|
|
13
|
+
return { running: true, pid };
|
|
14
|
+
} catch {
|
|
15
|
+
return { running: false };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface UsageStat {
|
|
20
|
+
provider: string;
|
|
21
|
+
model: string;
|
|
22
|
+
total_input: number;
|
|
23
|
+
total_output: number;
|
|
24
|
+
calls: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getDbStats(): {
|
|
28
|
+
messages: number;
|
|
29
|
+
memories: number;
|
|
30
|
+
usage: UsageStat[];
|
|
31
|
+
} | null {
|
|
32
|
+
if (!existsSync(paths.db)) return null;
|
|
33
|
+
try {
|
|
34
|
+
const { Database } = require("bun:sqlite");
|
|
35
|
+
const db = new Database(paths.db, { readonly: true });
|
|
36
|
+
const messages =
|
|
37
|
+
(db.query("SELECT COUNT(*) as count FROM messages").get() as any)
|
|
38
|
+
?.count ?? 0;
|
|
39
|
+
const memories =
|
|
40
|
+
(db.query("SELECT COUNT(*) as count FROM memory_chunks").get() as any)
|
|
41
|
+
?.count ?? 0;
|
|
42
|
+
|
|
43
|
+
let usage: UsageStat[] = [];
|
|
44
|
+
try {
|
|
45
|
+
usage = db
|
|
46
|
+
.query(
|
|
47
|
+
`SELECT provider, model,
|
|
48
|
+
SUM(input_tokens) as total_input,
|
|
49
|
+
SUM(output_tokens) as total_output,
|
|
50
|
+
COUNT(*) as calls
|
|
51
|
+
FROM usage GROUP BY provider, model`
|
|
52
|
+
)
|
|
53
|
+
.all() as UsageStat[];
|
|
54
|
+
} catch {
|
|
55
|
+
// usage table may not exist yet
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
db.close();
|
|
59
|
+
return { messages, memories, usage };
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function showStatus() {
|
|
66
|
+
console.log("\n Zubo Status\n");
|
|
67
|
+
|
|
68
|
+
// Config
|
|
69
|
+
if (configExists()) {
|
|
70
|
+
console.log(" Config: ~/.zubo/config.json ✓");
|
|
71
|
+
} else {
|
|
72
|
+
console.log(" Config: not found (run 'zubo setup')");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Database
|
|
76
|
+
const stats = getDbStats();
|
|
77
|
+
if (stats) {
|
|
78
|
+
console.log(` Database: ~/.zubo/zubo.db ✓`);
|
|
79
|
+
console.log(` Messages: ${stats.messages}`);
|
|
80
|
+
console.log(` Memories: ${stats.memories}`);
|
|
81
|
+
} else {
|
|
82
|
+
console.log(" Database: not found");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Usage
|
|
86
|
+
if (stats?.usage?.length) {
|
|
87
|
+
console.log(" Usage:");
|
|
88
|
+
for (const u of stats.usage) {
|
|
89
|
+
const total = u.total_input + u.total_output;
|
|
90
|
+
console.log(
|
|
91
|
+
` ${u.provider}/${u.model}: ${u.calls} calls, ${total.toLocaleString()} tokens (${u.total_input.toLocaleString()} in / ${u.total_output.toLocaleString()} out)`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// LLM Provider
|
|
97
|
+
if (configExists()) {
|
|
98
|
+
try {
|
|
99
|
+
const config = JSON.parse(readFileSync(paths.config, "utf-8"));
|
|
100
|
+
if (config.providers && config.activeProvider) {
|
|
101
|
+
const active = config.providers[config.activeProvider];
|
|
102
|
+
const failover = config.failover?.length
|
|
103
|
+
? ` → ${config.failover.join(", ")}`
|
|
104
|
+
: "";
|
|
105
|
+
console.log(` Provider: ${config.activeProvider}/${active?.model ?? "?"}${failover}`);
|
|
106
|
+
} else if (config.anthropicApiKey) {
|
|
107
|
+
console.log(` Provider: anthropic/${config.model ?? "claude-sonnet-4-5-20250929"} (legacy)`);
|
|
108
|
+
}
|
|
109
|
+
} catch (err: any) {
|
|
110
|
+
logger.warn("Failed to read provider config for status", { error: (err as Error).message });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Channels
|
|
115
|
+
if (configExists()) {
|
|
116
|
+
try {
|
|
117
|
+
const config = JSON.parse(readFileSync(paths.config, "utf-8"));
|
|
118
|
+
const active: string[] = [];
|
|
119
|
+
if (config.channels?.telegram?.enabled !== false && (config.channels?.telegram?.botToken || config.telegramBotToken)) {
|
|
120
|
+
active.push("telegram");
|
|
121
|
+
}
|
|
122
|
+
if (config.channels?.discord?.enabled !== false && config.channels?.discord?.botToken) {
|
|
123
|
+
active.push("discord");
|
|
124
|
+
}
|
|
125
|
+
if (config.channels?.webchat?.enabled !== false && config.channels?.webchat) {
|
|
126
|
+
const p = config.channels.webchat.port;
|
|
127
|
+
active.push(p ? `webchat(:${p})` : "webchat(auto)");
|
|
128
|
+
}
|
|
129
|
+
console.log(` Channels: ${active.length ? active.join(", ") : "none"}`);
|
|
130
|
+
} catch (err: any) {
|
|
131
|
+
logger.warn("Failed to read channel config for status", { error: (err as Error).message });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Skills
|
|
136
|
+
if (existsSync(paths.skills)) {
|
|
137
|
+
try {
|
|
138
|
+
const skillEntries = readdirSync(paths.skills).filter((e) =>
|
|
139
|
+
existsSync(join(paths.skills, e, "SKILL.md"))
|
|
140
|
+
);
|
|
141
|
+
console.log(` Skills: ${skillEntries.length} installed`);
|
|
142
|
+
} catch {
|
|
143
|
+
console.log(" Skills: error reading");
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
console.log(" Skills: none");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// SYSTEM.md
|
|
150
|
+
if (existsSync(paths.systemPrompt)) {
|
|
151
|
+
console.log(" Prompt: ~/.zubo/workspace/SYSTEM.md ✓");
|
|
152
|
+
} else {
|
|
153
|
+
console.log(" Prompt: using default (no SYSTEM.md)");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Daemon status
|
|
157
|
+
const daemon = isDaemonRunning();
|
|
158
|
+
if (daemon.running) {
|
|
159
|
+
console.log(` Status: running (PID ${daemon.pid})`);
|
|
160
|
+
} else {
|
|
161
|
+
console.log(" Status: not running");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log("");
|
|
165
|
+
}
|