maestro-agent 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +316 -2
  2. package/bin/maestro.ts +5 -0
  3. package/dist/maestro +0 -0
  4. package/dist/web/apple-touch-icon.png +0 -0
  5. package/dist/web/assets/Connections-BMA04Ycg.js +11 -0
  6. package/dist/web/assets/GanttView-DXjh0gxg.js +49 -0
  7. package/dist/web/assets/Home-Ct3Ho0Qt.js +1 -0
  8. package/dist/web/assets/HooksCrons--0kyVJcR.js +11 -0
  9. package/dist/web/assets/ProjectDetail-B_IqEpFu.js +1 -0
  10. package/dist/web/assets/Roles-D1tIQzto.js +24 -0
  11. package/dist/web/assets/Settings-yts4LUmH.js +11 -0
  12. package/dist/web/assets/Skills-DbuNLjIV.js +12 -0
  13. package/dist/web/assets/Wizard-vJol8-Y4.js +11 -0
  14. package/dist/web/assets/WorkspaceChat-DrsLs4m2.js +56 -0
  15. package/dist/web/assets/WorkspaceDashboard-B9vgrd2Z.js +6 -0
  16. package/dist/web/assets/WorkspaceNew-DoNGYHCG.js +1 -0
  17. package/dist/web/assets/WorkspaceProjects-DDp3mUse.js +6 -0
  18. package/dist/web/assets/WorkspaceSchedules-BTjmCbYG.js +1 -0
  19. package/dist/web/assets/WorkspaceTasks-mPU-bhKR.js +41 -0
  20. package/dist/web/assets/activity-CIA8bIA4.js +6 -0
  21. package/dist/web/assets/addon-fit-BlxrFPDK.js +1 -0
  22. package/dist/web/assets/arrow-right-S7ID7nDp.js +6 -0
  23. package/dist/web/assets/badge-DDTUzWIi.js +1 -0
  24. package/dist/web/assets/circle-check-B3P1qK0Z.js +6 -0
  25. package/dist/web/assets/clock-f9aYZox0.js +6 -0
  26. package/dist/web/assets/index-BRo4Du_s.js +11 -0
  27. package/dist/web/assets/index-C7kx39S9.js +196 -0
  28. package/dist/web/assets/index-D6LSdZea.css +1 -0
  29. package/dist/web/assets/plus-BHnOxbns.js +6 -0
  30. package/dist/web/assets/refresh-cw-BWX04Hg3.js +6 -0
  31. package/dist/web/assets/save-BLbb_9xz.js +6 -0
  32. package/dist/web/assets/sparkles-CDr6Dw1e.js +6 -0
  33. package/dist/web/assets/trash-2-9-ThEdey.js +6 -0
  34. package/dist/web/assets/useEventStream-DXt2Hmei.js +1 -0
  35. package/dist/web/assets/x-DVdKPXXy.js +6 -0
  36. package/dist/web/assets/xterm-DYP7pi_n.css +32 -0
  37. package/dist/web/assets/xterm-DlVFs1Kw.js +9 -0
  38. package/dist/web/favicon-512.png +0 -0
  39. package/dist/web/favicon.png +0 -0
  40. package/dist/web/index.html +15 -0
  41. package/package.json +49 -6
  42. package/src/api/agents.ts +76 -0
  43. package/src/api/audit.ts +19 -0
  44. package/src/api/autopilot.ts +73 -0
  45. package/src/api/chat.ts +801 -0
  46. package/src/api/chief.ts +84 -0
  47. package/src/api/config.ts +39 -0
  48. package/src/api/gantt.ts +72 -0
  49. package/src/api/hooks.ts +54 -0
  50. package/src/api/inbox.ts +125 -0
  51. package/src/api/lark.ts +32 -0
  52. package/src/api/memory.ts +37 -0
  53. package/src/api/ops.ts +89 -0
  54. package/src/api/projects.ts +105 -0
  55. package/src/api/roles.ts +123 -0
  56. package/src/api/runtimes.ts +62 -0
  57. package/src/api/scheduled-tasks.ts +203 -0
  58. package/src/api/sessions.ts +479 -0
  59. package/src/api/skills.ts +386 -0
  60. package/src/api/tasks.ts +457 -0
  61. package/src/api/telegram.ts +94 -0
  62. package/src/api/templates.ts +36 -0
  63. package/src/api/webhooks.ts +20 -0
  64. package/src/api/workspaces.ts +150 -0
  65. package/src/bridges/lark/index.ts +213 -0
  66. package/src/bridges/telegram/index.ts +273 -0
  67. package/src/bridges/telegram/polling.ts +185 -0
  68. package/src/chat/index.ts +86 -0
  69. package/src/chief/index.ts +461 -0
  70. package/src/core/cli.ts +333 -0
  71. package/src/core/db.ts +53 -0
  72. package/src/core/event-bus.ts +33 -0
  73. package/src/core/index.ts +6 -0
  74. package/src/core/migrations.ts +303 -0
  75. package/src/core/router.ts +69 -0
  76. package/src/core/schema.sql +232 -0
  77. package/src/core/server.ts +308 -0
  78. package/src/core/validate.ts +22 -0
  79. package/src/discovery/index.ts +194 -0
  80. package/src/gateway/adapters/telegram.ts +148 -0
  81. package/src/gateway/index.ts +31 -0
  82. package/src/gateway/manager.ts +176 -0
  83. package/src/gateway/types.ts +77 -0
  84. package/src/inbox/index.ts +500 -0
  85. package/src/ops/artifact-sync.ts +65 -0
  86. package/src/ops/autopilot.ts +338 -0
  87. package/src/ops/gc.ts +252 -0
  88. package/src/ops/index.ts +226 -0
  89. package/src/ops/project-serial.ts +52 -0
  90. package/src/ops/role-dispatch.ts +111 -0
  91. package/src/ops/runtime-scheduler.ts +447 -0
  92. package/src/ops/task-blocking.ts +65 -0
  93. package/src/ops/task-deps.ts +37 -0
  94. package/src/ops/task-workspace.ts +60 -0
  95. package/src/roles/index.ts +258 -0
  96. package/src/roles/prompt-assembler.ts +85 -0
  97. package/src/roles/workspace-role.ts +155 -0
  98. package/src/scheduler/index.ts +461 -0
  99. package/src/session/output-parser.ts +75 -0
  100. package/src/session/realtime-parser.ts +40 -0
  101. package/src/skills/builtin.ts +155 -0
  102. package/src/skills/skill-extractor.ts +452 -0
  103. package/src/skills/skill-md.ts +282 -0
  104. package/src/transport/http-api.ts +75 -0
  105. package/src/transport/index.ts +4 -0
  106. package/src/transport/local-pty.ts +119 -0
  107. package/src/transport/ssh.ts +176 -0
  108. package/src/transport/types.ts +20 -0
  109. package/src/workflows/index.ts +231 -0
  110. package/index.js +0 -1
  111. package/maestro-agent-0.0.1.tgz +0 -0
@@ -0,0 +1,333 @@
1
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+ import { runChatCommand } from "../chat";
4
+ import { discoverRuntimes, formatRuntimeList, registerDiscoveredRuntimes } from "../discovery";
5
+ import { runInboxCommand } from "../inbox";
6
+ import { ensureBuiltinSkills } from "../skills/builtin";
7
+ import { ensureDefaultWorkflow } from "../workflows";
8
+ import { getDb, initSchema } from "./db";
9
+ import { startServer } from "./server";
10
+
11
+ const HUB_DIR = join(process.cwd(), ".hub");
12
+ const VERSION = "0.0.1";
13
+ const PID_FILE = join(HUB_DIR, "hub.pid");
14
+ const LOG_FILE = join(HUB_DIR, "hub.log");
15
+
16
+ export async function main(args: string[]) {
17
+ const command = args[0];
18
+
19
+ if (command === "-v" || command === "--version") {
20
+ console.log(`maestro v${VERSION}`);
21
+ return;
22
+ }
23
+
24
+ switch (command) {
25
+ case "init":
26
+ await cmdInit();
27
+ break;
28
+ case "start":
29
+ await cmdStart(args.slice(1));
30
+ break;
31
+ case "doctor":
32
+ await cmdDoctor();
33
+ break;
34
+ case "stop":
35
+ cmdStop();
36
+ break;
37
+ case "inbox":
38
+ await cmdInbox(args.slice(1));
39
+ break;
40
+ case "chat":
41
+ await cmdChat(args.slice(1));
42
+ break;
43
+ case "help":
44
+ case "--help":
45
+ case "-h":
46
+ printHelp();
47
+ break;
48
+ default:
49
+ if (command) {
50
+ console.error(`\x1b[31mError:\x1b[0m Unknown command "${command}"\n`);
51
+ }
52
+ printHelp();
53
+ if (command) process.exit(1);
54
+ }
55
+ }
56
+
57
+ async function cmdInit() {
58
+ if (existsSync(HUB_DIR)) {
59
+ console.log("\x1b[33m⚠\x1b[0m .hub/ already exists, skipping initialization");
60
+ return;
61
+ }
62
+
63
+ console.log("\x1b[36m🎼\x1b[0m Initializing MAESTRO Hub...\n");
64
+
65
+ // 创建目录结构
66
+ mkdirSync(join(HUB_DIR, "transcripts"), { recursive: true });
67
+ mkdirSync(join(HUB_DIR, "artifacts"), { recursive: true });
68
+ mkdirSync(join(HUB_DIR, "roles"), { recursive: true });
69
+
70
+ // 复制出厂角色
71
+ const rolesSource = join(import.meta.dir, "../../roles");
72
+ if (existsSync(rolesSource)) {
73
+ cpSync(rolesSource, join(HUB_DIR, "roles"), { recursive: true });
74
+ }
75
+
76
+ // 初始化数据库
77
+ const db = getDb(HUB_DIR);
78
+ initSchema(db, HUB_DIR);
79
+ console.log(" \x1b[32m✓\x1b[0m Database initialized (16 tables)");
80
+
81
+ const builtinSkills = ensureBuiltinSkills(db, HUB_DIR);
82
+ ensureDefaultWorkflow(db);
83
+ if (builtinSkills.installed.length > 0 || builtinSkills.updated.length > 0) {
84
+ console.log(` \x1b[32m✓\x1b[0m Built-in skills ready (${builtinSkills.installed.length + builtinSkills.updated.length})`);
85
+ }
86
+
87
+ await discoverAndRegisterRuntimes(db);
88
+
89
+ console.log(`\n\x1b[32m✨ Done!\x1b[0m Hub initialized.`);
90
+ }
91
+
92
+ async function cmdStart(args: string[]) {
93
+ // 首次启动时自动初始化
94
+ if (!existsSync(HUB_DIR)) {
95
+ await cmdInit();
96
+ }
97
+
98
+ // 自动构建前端(如果 dist/web 不存在)
99
+ const webDistDir = join(import.meta.dir, "../../dist/web");
100
+ if (!existsSync(webDistDir)) {
101
+ const webDir = join(import.meta.dir, "../../web");
102
+ if (existsSync(join(webDir, "package.json"))) {
103
+ console.log("\x1b[36m⟳\x1b[0m Building Web UI...");
104
+ const result = Bun.spawnSync(["bun", "run", "build"], { cwd: webDir, stdio: ["ignore", "pipe", "pipe"] });
105
+ if (result.exitCode === 0) {
106
+ console.log("\x1b[32m✓\x1b[0m Web UI built");
107
+ } else {
108
+ console.log("\x1b[33m⚠\x1b[0m Web UI build failed (API still works, no UI)");
109
+ }
110
+ }
111
+ }
112
+
113
+ const portFlag = args.indexOf("--port");
114
+ const port = portFlag !== -1 ? parseInt(args[portFlag + 1], 10) : 7423;
115
+
116
+ if (isNaN(port) || port < 1 || port > 65535) {
117
+ console.error("\x1b[31m✗\x1b[0m Invalid port number");
118
+ process.exit(1);
119
+ }
120
+
121
+ const db = getDb(HUB_DIR);
122
+ initSchema(db, HUB_DIR);
123
+ ensureBuiltinSkills(db, HUB_DIR);
124
+ ensureDefaultWorkflow(db);
125
+ await discoverAndRegisterRuntimes(db);
126
+
127
+ const isDaemon = args.includes("--daemon") || args.includes("-d");
128
+
129
+ if (isDaemon) {
130
+ const binPath = join(import.meta.dir, "../../bin/maestro.ts");
131
+
132
+ const child = Bun.spawn(["bun", "run", binPath, "start", "--port", String(port)], {
133
+ cwd: process.cwd(),
134
+ stdio: ["ignore", "ignore", "ignore"],
135
+ env: { ...process.env },
136
+ });
137
+
138
+ writeFileSync(PID_FILE, String(child.pid));
139
+ console.log(`\x1b[32m✓\x1b[0m Hub started in background (pid: ${child.pid})`);
140
+ console.log(` Log: ${LOG_FILE}`);
141
+ console.log(` PID: ${PID_FILE}`);
142
+ child.unref();
143
+ process.exit(0);
144
+ }
145
+
146
+ // Foreground mode — write pidfile for stop command
147
+ writeFileSync(PID_FILE, String(process.pid));
148
+ process.on("SIGTERM", () => {
149
+ try { unlinkSync(PID_FILE); } catch {}
150
+ process.exit(0);
151
+ });
152
+ process.on("SIGINT", () => {
153
+ try { unlinkSync(PID_FILE); } catch {}
154
+ process.exit(0);
155
+ });
156
+
157
+ try {
158
+ await startServer(HUB_DIR, port);
159
+ } catch (err) {
160
+ try { unlinkSync(PID_FILE); } catch {}
161
+ throw err;
162
+ }
163
+ }
164
+
165
+ async function cmdDoctor() {
166
+ console.log(`\x1b[36m🩺\x1b[0m MAESTRO Doctor (v${VERSION})\n`);
167
+
168
+ const hubExists = existsSync(HUB_DIR);
169
+ console.log(` ${hubExists ? "\x1b[32m✓\x1b[0m" : "\x1b[31m✗\x1b[0m"} .hub/ directory ${hubExists ? "exists" : "NOT FOUND"}`);
170
+
171
+ if (!hubExists) {
172
+ console.log(`\n Run \x1b[1mmaestro init\x1b[0m to create it.`);
173
+ return;
174
+ }
175
+
176
+ const dbPath = join(HUB_DIR, "maestro.db");
177
+ const dbExists = existsSync(dbPath);
178
+ console.log(` ${dbExists ? "\x1b[32m✓\x1b[0m" : "\x1b[31m✗\x1b[0m"} maestro.db ${dbExists ? "ok" : "MISSING"}`);
179
+
180
+ if (dbExists) {
181
+ const db = getDb(HUB_DIR);
182
+ const tables = db.query("SELECT name FROM sqlite_master WHERE type='table'").all() as { name: string }[];
183
+ console.log(` \x1b[32m✓\x1b[0m ${tables.length} tables`);
184
+
185
+ const runtimes = db.query("SELECT * FROM agent_runtime").all() as any[];
186
+ console.log(` \x1b[32m✓\x1b[0m ${runtimes.length} agent runtime(s) registered`);
187
+ }
188
+
189
+ console.log("\n \x1b[36mAgent Runtime Discovery:\x1b[0m");
190
+ const runtimes = await discoverRuntimes();
191
+ if (runtimes.length === 0) {
192
+ console.log(" \x1b[33m⚠\x1b[0m No CLI agents found");
193
+ } else {
194
+ for (const line of formatRuntimeList(runtimes)) {
195
+ console.log(` \x1b[32m✓\x1b[0m ${line}`);
196
+ }
197
+ }
198
+
199
+ // Version & schema check
200
+ if (dbExists) {
201
+ const db = getDb(HUB_DIR);
202
+ const schemaVersion = (db.query("SELECT MAX(version) as v FROM schema_version").get() as any)?.v ?? -1;
203
+ const { migrations } = require("./migrations");
204
+ const latestMigration = migrations.length > 0 ? migrations[migrations.length - 1].version : 0;
205
+ const migrationStatus = schemaVersion >= latestMigration ? "\x1b[32m✓\x1b[0m" : "\x1b[33m⚠\x1b[0m";
206
+ console.log(`\n \x1b[36mSchema:\x1b[0m`);
207
+ console.log(` ${migrationStatus} schema_version: ${schemaVersion} (latest: ${latestMigration})`);
208
+ }
209
+
210
+ // Role diff detection
211
+ console.log(`\n \x1b[36mRole Updates:\x1b[0m`);
212
+ const factoryRolesDir = join(import.meta.dir, "../../roles");
213
+ const hubRolesDir = join(HUB_DIR, "roles");
214
+ if (existsSync(factoryRolesDir) && existsSync(hubRolesDir)) {
215
+ const { createHash } = require("crypto");
216
+ const factoryFiles = readdirSync(factoryRolesDir).filter((f: string) => f.endsWith(".md"));
217
+ let diffs = 0;
218
+ for (const file of factoryFiles) {
219
+ const factoryPath = join(factoryRolesDir, file);
220
+ const hubPath = join(hubRolesDir, file);
221
+ if (!existsSync(hubPath)) {
222
+ console.log(` \x1b[33m⚠\x1b[0m ${file} — new upstream role available`);
223
+ diffs++;
224
+ continue;
225
+ }
226
+ const factoryHash = createHash("md5").update(readFileSync(factoryPath)).digest("hex");
227
+ const hubHash = createHash("md5").update(readFileSync(hubPath)).digest("hex");
228
+ if (factoryHash !== hubHash) {
229
+ console.log(` \x1b[33m⚠\x1b[0m ${file} — upstream has changes`);
230
+ diffs++;
231
+ }
232
+ }
233
+ if (diffs === 0) console.log(` \x1b[32m✓\x1b[0m All roles up to date`);
234
+ } else {
235
+ console.log(` \x1b[90m- Skipped (roles directory missing)\x1b[0m`);
236
+ }
237
+
238
+ console.log(`\n \x1b[90mRuntime: Bun ${Bun.version} (${process.platform} ${process.arch})\x1b[0m`);
239
+ }
240
+
241
+ async function discoverAndRegisterRuntimes(db: ReturnType<typeof getDb>) {
242
+ console.log(" \x1b[36m⟳\x1b[0m Discovering agent runtimes...");
243
+ const result = await registerDiscoveredRuntimes(db);
244
+
245
+ if (result.discovered.length === 0) {
246
+ console.log(" \x1b[33m⚠\x1b[0m No CLI agents found (claude/claude-code/codex/opencode/openclaw/blade/hermes)");
247
+ console.log(" Install one of these CLIs or add a runtime manually with the API/UI.");
248
+ return;
249
+ }
250
+
251
+ for (const line of formatRuntimeList(result.added)) {
252
+ console.log(` \x1b[32m✓\x1b[0m ${line}`);
253
+ }
254
+ if (result.added.length === 0) {
255
+ console.log(` \x1b[32m✓\x1b[0m ${result.discovered.length} CLI agent runtime(s) already registered`);
256
+ } else if (result.skipped.length > 0) {
257
+ console.log(` \x1b[90m-\x1b[0m ${result.skipped.length} discovered runtime(s) kept from existing configuration`);
258
+ }
259
+ }
260
+
261
+ function printHelp() {
262
+ console.log(`\x1b[1mMAESTRO\x1b[0m v${VERSION} — The Conductor for AI Agents
263
+
264
+ \x1b[36mUsage:\x1b[0m
265
+ maestro <command> [options]
266
+
267
+ \x1b[36mCommands:\x1b[0m
268
+ init Initialize a new workspace (.hub/)
269
+ start [--port N] Start the Hub (default: 7423)
270
+ [--daemon] Run in background (detach)
271
+ stop Stop the running Hub daemon
272
+ doctor Run system diagnostics
273
+ inbox <command> Run agent inbox commands
274
+ help Show this help message
275
+
276
+ \x1b[36mOptions:\x1b[0m
277
+ -v, --version Show version number
278
+ -h, --help Show help
279
+
280
+ \x1b[90mDocs: https://github.com/user/maestro-agent\x1b[0m
281
+ `);
282
+ }
283
+
284
+ function cmdStop() {
285
+ if (!existsSync(PID_FILE)) {
286
+ console.error("\x1b[31m✗\x1b[0m No pidfile found. Hub is not running (or was not started with pidfile).");
287
+ process.exit(1);
288
+ }
289
+ const pid = parseInt(readFileSync(PID_FILE, "utf-8").trim(), 10);
290
+ if (isNaN(pid)) {
291
+ console.error("\x1b[31m✗\x1b[0m Invalid pidfile content.");
292
+ unlinkSync(PID_FILE);
293
+ process.exit(1);
294
+ }
295
+ try {
296
+ process.kill(pid, "SIGTERM");
297
+ console.log(`\x1b[32m✓\x1b[0m Sent SIGTERM to Hub (pid: ${pid})`);
298
+ } catch (err: any) {
299
+ if (err.code === "ESRCH") {
300
+ console.log(`\x1b[33m⚠\x1b[0m Process ${pid} not found (already stopped?)`);
301
+ } else {
302
+ throw err;
303
+ }
304
+ }
305
+ try { unlinkSync(PID_FILE); } catch {}
306
+ }
307
+
308
+ async function cmdInbox(args: string[]) {
309
+ if (!existsSync(HUB_DIR)) {
310
+ console.error("\x1b[31m✗\x1b[0m .hub/ not found. Run \x1b[1mmaestro start\x1b[0m first.");
311
+ process.exit(1);
312
+ }
313
+
314
+ const agentFlag = args.indexOf("--agent");
315
+ const agentId = agentFlag === -1 ? process.env.MAESTRO_AGENT_ID : args[agentFlag + 1];
316
+ const filteredArgs = agentFlag === -1 ? args : args.filter((_, index) => index !== agentFlag && index !== agentFlag + 1);
317
+ const result = await runInboxCommand(filteredArgs, { hubDir: HUB_DIR, agentId });
318
+ if (result.stdout) process.stdout.write(result.stdout);
319
+ if (result.stderr) process.stderr.write(result.stderr);
320
+ if (result.status !== 0) process.exit(result.status);
321
+ }
322
+
323
+ async function cmdChat(args: string[]) {
324
+ if (!existsSync(HUB_DIR)) {
325
+ console.error("\x1b[31m✗\x1b[0m .hub/ not found. Run \x1b[1mmaestro start\x1b[0m first.");
326
+ process.exit(1);
327
+ }
328
+
329
+ const result = await runChatCommand(args, { hubDir: HUB_DIR });
330
+ if (result.stdout) process.stdout.write(result.stdout);
331
+ if (result.stderr) process.stderr.write(result.stderr);
332
+ if (result.status !== 0) process.exit(result.status);
333
+ }
package/src/core/db.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { readFileSync } from "fs";
3
+ import { join, resolve } from "path";
4
+ import { runMigrations } from "./migrations";
5
+
6
+ const dbCache = new Map<string, Database>();
7
+
8
+ export function getDb(hubDir: string): Database {
9
+ const dbPath = resolve(join(hubDir, "maestro.db"));
10
+ const cached = dbCache.get(dbPath);
11
+ if (cached) {
12
+ try {
13
+ cached.query("SELECT 1").get();
14
+ return cached;
15
+ } catch {
16
+ dbCache.delete(dbPath);
17
+ }
18
+ }
19
+
20
+ const db = new Database(dbPath, { create: true });
21
+ db.exec("PRAGMA journal_mode=WAL");
22
+ db.exec("PRAGMA foreign_keys=ON");
23
+
24
+ const originalClose = db.close.bind(db);
25
+ db.close = () => {
26
+ dbCache.delete(dbPath);
27
+ return originalClose();
28
+ };
29
+
30
+ dbCache.set(dbPath, db);
31
+ return db;
32
+ }
33
+
34
+ export function initSchema(db: Database, hubDir?: string): void {
35
+ const schemaPath = join(import.meta.dir, "schema.sql");
36
+ const sql = readFileSync(schemaPath, "utf-8");
37
+ db.exec(sql);
38
+ runMigrations(db, hubDir);
39
+ }
40
+
41
+ export function closeDb(hubDir: string): void {
42
+ const dbPath = resolve(join(hubDir, "maestro.db"));
43
+ const cached = dbCache.get(dbPath);
44
+ if (cached) cached.close();
45
+ }
46
+
47
+ export function generateId(prefix: string): string {
48
+ return `${prefix}_${crypto.randomUUID().replace(/-/g, "").slice(0, 8)}`;
49
+ }
50
+
51
+ export function now(): number {
52
+ return Date.now();
53
+ }
@@ -0,0 +1,33 @@
1
+ import { EventEmitter } from "events";
2
+ import type { Database } from "bun:sqlite";
3
+ import { generateId, now } from "./db";
4
+
5
+ export type BroadcastFn = (type: string, payload: unknown, ts: number) => void;
6
+
7
+ export class EventBus extends EventEmitter {
8
+ private broadcast?: BroadcastFn;
9
+
10
+ constructor(private db: Database) {
11
+ super();
12
+ this.setMaxListeners(100);
13
+ }
14
+
15
+ setDb(db: Database) {
16
+ this.db = db;
17
+ }
18
+
19
+ setBroadcast(fn: BroadcastFn) {
20
+ this.broadcast = fn;
21
+ }
22
+
23
+ publish(type: string, payload: unknown): boolean {
24
+ const id = generateId("evt");
25
+ const ts = now();
26
+ this.db.run(
27
+ "INSERT INTO event_log (id, type, payload_json, created_at) VALUES (?, ?, ?, ?)",
28
+ [id, type, JSON.stringify(payload), ts]
29
+ );
30
+ this.broadcast?.(type, payload, ts);
31
+ return super.emit(type, payload);
32
+ }
33
+ }
@@ -0,0 +1,6 @@
1
+ export { main } from "./cli";
2
+ export { startServer } from "./server";
3
+ export { getDb, initSchema, generateId, now } from "./db";
4
+ export { Router, json, body } from "./router";
5
+ export { EventBus } from "./event-bus";
6
+ export type { HubContext } from "./server";