@interactive-inc/claude-funnel 0.7.1 → 0.8.1

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 (472) hide show
  1. package/README.md +155 -133
  2. package/dist/bin.js +1344 -0
  3. package/dist/cli/factory.d.ts +7 -0
  4. package/dist/cli/router/query-to-cli-args.d.ts +1 -0
  5. package/dist/cli/router/to-request.d.ts +5 -0
  6. package/dist/cli/router/validator.d.ts +5 -0
  7. package/dist/cli/routes/channels.$channel.connectors.$connector.d.ts +42 -0
  8. package/dist/cli/routes/channels.$channel.connectors.$connector.rename.$newName.d.ts +46 -0
  9. package/dist/cli/routes/channels.$channel.connectors.$connector.request.d.ts +54 -0
  10. package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.d.ts +66 -0
  11. package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.d.ts +42 -0
  12. package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.d.ts +46 -0
  13. package/dist/cli/routes/channels.$channel.connectors.add.$connector.d.ts +90 -0
  14. package/dist/cli/routes/channels.$channel.connectors.d.ts +38 -0
  15. package/dist/cli/routes/channels.$channel.connectors.remove.$connector.d.ts +42 -0
  16. package/dist/cli/routes/channels.$channel.connectors.set.$connector.d.ts +62 -0
  17. package/dist/cli/routes/channels.$channel.d.ts +38 -0
  18. package/dist/cli/routes/channels.$channel.rename.$newName.d.ts +42 -0
  19. package/dist/cli/routes/channels.$channel.set.delivery.$mode.d.ts +28 -0
  20. package/dist/cli/routes/channels.add.$channel.d.ts +46 -0
  21. package/dist/cli/routes/channels.d.ts +16 -0
  22. package/dist/cli/routes/channels.remove.$channel.d.ts +38 -0
  23. package/dist/cli/routes/claude.d.ts +32 -0
  24. package/dist/cli/routes/gateway.d.ts +20 -0
  25. package/dist/cli/routes/gateway.listeners.d.ts +17 -0
  26. package/dist/cli/routes/gateway.logs.d.ts +24 -0
  27. package/dist/cli/routes/gateway.restart.d.ts +24 -0
  28. package/dist/cli/routes/gateway.run.d.ts +24 -0
  29. package/dist/cli/routes/gateway.start.d.ts +24 -0
  30. package/dist/cli/routes/gateway.status.d.ts +13 -0
  31. package/dist/cli/routes/gateway.stop.d.ts +16 -0
  32. package/dist/cli/routes/index.d.ts +1222 -0
  33. package/dist/cli/routes/profiles.$profile.as-default.d.ts +38 -0
  34. package/dist/cli/routes/profiles.$profile.rename.$newName.d.ts +42 -0
  35. package/dist/cli/routes/profiles.$profile.run.d.ts +46 -0
  36. package/dist/cli/routes/profiles.add.$profile.d.ts +54 -0
  37. package/dist/cli/routes/profiles.d.ts +16 -0
  38. package/dist/cli/routes/profiles.remove.$profile.d.ts +38 -0
  39. package/dist/cli/routes/profiles.set.$profile.d.ts +54 -0
  40. package/dist/cli/routes/status.d.ts +16 -0
  41. package/dist/cli/routes/update.d.ts +16 -0
  42. package/dist/connectors/connector-adapter.d.ts +8 -0
  43. package/dist/connectors/connector-config-schema.d.ts +43 -0
  44. package/dist/connectors/connector-factory.d.ts +32 -0
  45. package/dist/connectors/connector-listener.d.ts +17 -0
  46. package/dist/connectors/discord-adapter.d.ts +14 -0
  47. package/dist/connectors/discord-connector-schema.d.ts +10 -0
  48. package/dist/connectors/discord-event-processor.d.ts +26 -0
  49. package/dist/connectors/discord-listener.d.ts +17 -0
  50. package/dist/connectors/gh-adapter.d.ts +11 -0
  51. package/dist/connectors/gh-connector-schema.d.ts +10 -0
  52. package/dist/connectors/gh-listener.d.ts +26 -0
  53. package/dist/connectors/match-cron.d.ts +1 -0
  54. package/dist/connectors/schedule-connector-schema.d.ts +45 -0
  55. package/dist/connectors/schedule-listener.d.ts +30 -0
  56. package/dist/connectors/schedule-state-store.d.ts +19 -0
  57. package/dist/connectors/slack-adapter.d.ts +15 -0
  58. package/dist/connectors/slack-connector-schema.d.ts +11 -0
  59. package/dist/connectors/slack-event-processor.d.ts +27 -0
  60. package/dist/connectors/slack-listener.d.ts +17 -0
  61. package/dist/engine/channels/channels.d.ts +106 -0
  62. package/dist/engine/claude/claude.d.ts +49 -0
  63. package/dist/engine/claude/gateway-controller.d.ts +6 -0
  64. package/dist/engine/fs/file-system.d.ts +24 -0
  65. package/dist/engine/fs/memory-file-system.d.ts +31 -0
  66. package/dist/engine/fs/node-file-system.d.ts +15 -0
  67. package/dist/engine/http/http-client.d.ts +15 -0
  68. package/dist/engine/http/memory-http-client.d.ts +12 -0
  69. package/dist/engine/http/node-http-client.d.ts +5 -0
  70. package/dist/engine/id/id-generator.d.ts +7 -0
  71. package/dist/engine/id/memory-id-generator.d.ts +11 -0
  72. package/dist/engine/id/node-id-generator.d.ts +4 -0
  73. package/dist/engine/logger/logger.d.ts +11 -0
  74. package/dist/engine/logger/memory-logger.d.ts +14 -0
  75. package/dist/engine/logger/node-logger.d.ts +15 -0
  76. package/dist/engine/logger/noop-logger.d.ts +7 -0
  77. package/dist/engine/mcp/channel-server.d.ts +1 -0
  78. package/dist/engine/mcp/mcp.d.ts +22 -0
  79. package/dist/engine/process/memory-process-runner.d.ts +43 -0
  80. package/dist/engine/process/node-process-runner.d.ts +9 -0
  81. package/dist/engine/process/process-runner.d.ts +29 -0
  82. package/dist/engine/profiles/profile-channel-checker.d.ts +7 -0
  83. package/dist/engine/profiles/profiles.d.ts +31 -0
  84. package/dist/engine/settings/mock-settings-reader.d.ts +9 -0
  85. package/dist/engine/settings/settings-reader.d.ts +5 -0
  86. package/dist/engine/settings/settings-schema.d.ts +132 -0
  87. package/dist/engine/settings/settings-store.d.ts +18 -0
  88. package/dist/engine/time/clock.d.ts +9 -0
  89. package/dist/engine/time/memory-clock.d.ts +12 -0
  90. package/dist/engine/time/node-clock.d.ts +4 -0
  91. package/dist/funnel.d.ts +95 -0
  92. package/dist/gateway/auth-middleware.d.ts +14 -0
  93. package/dist/gateway/broadcaster.d.ts +122 -0
  94. package/dist/gateway/daemon.d.ts +2 -0
  95. package/dist/gateway/daemon.js +485 -0
  96. package/dist/gateway/factory.d.ts +7 -0
  97. package/dist/gateway/funnel-event-store.d.ts +81 -0
  98. package/dist/gateway/gateway-server.d.ts +94 -0
  99. package/dist/gateway/gateway-token.d.ts +33 -0
  100. package/dist/gateway/gateway.d.ts +58 -0
  101. package/dist/gateway/kill-competing-slack-gateways.d.ts +9 -0
  102. package/dist/gateway/listener-supervisor.d.ts +85 -0
  103. package/dist/gateway/listeners-client.d.ts +53 -0
  104. package/dist/gateway/resolve-daemon-script.d.ts +11 -0
  105. package/dist/gateway/routes/channels.connectors.call.d.ts +41 -0
  106. package/dist/gateway/routes/health.d.ts +17 -0
  107. package/dist/gateway/routes/index.d.ts +209 -0
  108. package/dist/gateway/routes/listeners.list.d.ts +14 -0
  109. package/dist/gateway/routes/listeners.restart.d.ts +34 -0
  110. package/dist/gateway/routes/listeners.start.d.ts +34 -0
  111. package/dist/gateway/routes/listeners.stop.d.ts +34 -0
  112. package/dist/gateway/routes/route-deps.d.ts +10 -0
  113. package/dist/gateway/routes/status.d.ts +30 -0
  114. package/dist/gateway/routes/validator.d.ts +19 -0
  115. package/dist/highlights-eq9cgrbb.scm +604 -0
  116. package/dist/highlights-ghv9g403.scm +205 -0
  117. package/dist/highlights-hk7bwhj4.scm +284 -0
  118. package/dist/highlights-r812a2qc.scm +150 -0
  119. package/dist/highlights-x6tmsnaa.scm +115 -0
  120. package/dist/index.d.ts +36 -0
  121. package/dist/index.js +3575 -0
  122. package/dist/injections-73j83es3.scm +27 -0
  123. package/dist/logger/leuco-human-file-writer.d.ts +33 -0
  124. package/dist/logger/leuco-human-logger.d.ts +46 -0
  125. package/dist/logger/leuco-human-record.d.ts +15 -0
  126. package/dist/logger/leuco-human-stdout-writer.d.ts +20 -0
  127. package/dist/logger/leuco-human-writer.d.ts +13 -0
  128. package/dist/logger/leuco-logger-memory-sink.d.ts +33 -0
  129. package/dist/logger/leuco-logger-record.d.ts +13 -0
  130. package/dist/logger/leuco-logger-sink.d.ts +34 -0
  131. package/dist/logger/leuco-logger-sqlite-sink.d.ts +102 -0
  132. package/dist/logger/leuco-logger.d.ts +56 -0
  133. package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  134. package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
  135. package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  136. package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  137. package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
  138. package/lib/bin.ts +78 -0
  139. package/lib/{modules → cli}/router/to-request.ts +13 -20
  140. package/lib/cli/routes/channels.$channel.connectors.$connector.rename.$newName.ts +27 -0
  141. package/lib/cli/routes/channels.$channel.connectors.$connector.request.ts +40 -0
  142. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.ts +41 -0
  143. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.ts +22 -0
  144. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.ts +23 -0
  145. package/lib/cli/routes/channels.$channel.connectors.$connector.ts +26 -0
  146. package/lib/cli/routes/channels.$channel.connectors.add.$connector.ts +92 -0
  147. package/lib/cli/routes/channels.$channel.connectors.remove.$connector.ts +22 -0
  148. package/lib/cli/routes/channels.$channel.connectors.set.$connector.ts +63 -0
  149. package/lib/cli/routes/channels.$channel.connectors.ts +26 -0
  150. package/lib/cli/routes/channels.$channel.rename.$newName.ts +22 -0
  151. package/lib/cli/routes/channels.$channel.set.delivery.$mode.ts +34 -0
  152. package/lib/cli/routes/channels.$channel.ts +34 -0
  153. package/lib/cli/routes/channels.add.$channel.ts +33 -0
  154. package/lib/cli/routes/channels.remove.$channel.ts +20 -0
  155. package/lib/cli/routes/channels.ts +39 -0
  156. package/lib/cli/routes/claude.ts +69 -0
  157. package/lib/cli/routes/gateway.listeners.ts +41 -0
  158. package/lib/cli/routes/gateway.logs.ts +123 -0
  159. package/lib/{routes/gateway/restart.ts → cli/routes/gateway.restart.ts} +20 -5
  160. package/lib/cli/routes/gateway.run.ts +41 -0
  161. package/lib/cli/routes/gateway.start.ts +50 -0
  162. package/lib/cli/routes/gateway.status.ts +19 -0
  163. package/lib/cli/routes/gateway.stop.ts +32 -0
  164. package/lib/cli/routes/gateway.ts +55 -0
  165. package/lib/cli/routes/index.ts +202 -0
  166. package/lib/cli/routes/profiles.$profile.as-default.ts +22 -0
  167. package/lib/cli/routes/profiles.$profile.rename.$newName.ts +22 -0
  168. package/lib/cli/routes/profiles.$profile.run.ts +36 -0
  169. package/lib/cli/routes/profiles.add.$profile.ts +46 -0
  170. package/lib/cli/routes/profiles.remove.$profile.ts +20 -0
  171. package/lib/cli/routes/profiles.set.$profile.ts +46 -0
  172. package/lib/cli/routes/profiles.ts +40 -0
  173. package/lib/cli/routes/status.ts +93 -0
  174. package/lib/cli/routes/update.ts +27 -0
  175. package/lib/connectors/connector-config-schema.ts +16 -0
  176. package/lib/connectors/connector-factory.ts +94 -0
  177. package/lib/connectors/connector-listener.ts +20 -0
  178. package/lib/{modules/connectors/funnel-discord-adapter.ts → connectors/discord-adapter.ts} +6 -11
  179. package/lib/{modules/connectors → connectors}/discord-connector-schema.ts +4 -1
  180. package/lib/connectors/discord-listener.ts +111 -0
  181. package/lib/{modules/connectors/funnel-gh-adapter.ts → connectors/gh-adapter.ts} +3 -6
  182. package/lib/{modules/connectors → connectors}/gh-connector-schema.ts +4 -1
  183. package/lib/{modules/connectors/funnel-gh-listener.ts → connectors/gh-listener.ts} +45 -19
  184. package/lib/{modules/connectors → connectors}/match-cron.ts +10 -4
  185. package/lib/connectors/schedule-connector-schema.ts +33 -0
  186. package/lib/connectors/schedule-listener.ts +207 -0
  187. package/lib/connectors/schedule-state-store.ts +54 -0
  188. package/lib/connectors/slack-adapter.ts +36 -0
  189. package/lib/{modules/connectors → connectors}/slack-connector-schema.ts +4 -1
  190. package/lib/{modules/connectors/funnel-slack-event-processor.ts → connectors/slack-event-processor.ts} +15 -9
  191. package/lib/{modules/connectors/funnel-slack-listener.ts → connectors/slack-listener.ts} +33 -14
  192. package/lib/engine/channels/channels.ts +520 -0
  193. package/lib/{modules/claude/funnel-claude.ts → engine/claude/claude.ts} +28 -55
  194. package/lib/engine/claude/gateway-controller.ts +4 -0
  195. package/lib/{modules/fs/funnel-file-system.ts → engine/fs/file-system.ts} +4 -0
  196. package/lib/{modules/fs/memory-funnel-file-system.ts → engine/fs/memory-file-system.ts} +20 -3
  197. package/lib/{modules/fs/node-funnel-file-system.ts → engine/fs/node-file-system.ts} +14 -2
  198. package/lib/{modules/http/memory-funnel-http-client.ts → engine/http/memory-http-client.ts} +1 -5
  199. package/lib/{modules/http/node-funnel-http-client.ts → engine/http/node-http-client.ts} +1 -5
  200. package/lib/{modules/id/memory-funnel-id-generator.ts → engine/id/memory-id-generator.ts} +1 -1
  201. package/lib/{modules/id/node-funnel-id-generator.ts → engine/id/node-id-generator.ts} +1 -1
  202. package/lib/{modules/logger/memory-funnel-logger.ts → engine/logger/memory-logger.ts} +1 -1
  203. package/lib/{modules/logger/node-funnel-logger.ts → engine/logger/node-logger.ts} +1 -1
  204. package/lib/{modules/logger/noop-funnel-logger.ts → engine/logger/noop-logger.ts} +1 -1
  205. package/lib/engine/mcp/channel-server.ts +204 -0
  206. package/lib/{modules/mcp/funnel-mcp.ts → engine/mcp/mcp.ts} +24 -10
  207. package/lib/{modules/process/memory-funnel-process-runner.ts → engine/process/memory-process-runner.ts} +1 -1
  208. package/lib/{modules/process/node-funnel-process-runner.ts → engine/process/node-process-runner.ts} +12 -21
  209. package/lib/engine/profiles/profile-channel-checker.ts +7 -0
  210. package/lib/{modules/profiles/funnel-profiles.ts → engine/profiles/profiles.ts} +41 -43
  211. package/lib/{modules/settings/mock-funnel-settings-reader.ts → engine/settings/mock-settings-reader.ts} +4 -3
  212. package/lib/{modules/settings/funnel-settings-reader.ts → engine/settings/settings-reader.ts} +1 -1
  213. package/lib/engine/settings/settings-schema.ts +46 -0
  214. package/lib/engine/settings/settings-store.ts +110 -0
  215. package/lib/{modules/time/memory-funnel-clock.ts → engine/time/memory-clock.ts} +1 -1
  216. package/lib/{modules/time/node-funnel-clock.ts → engine/time/node-clock.ts} +1 -1
  217. package/lib/funnel.ts +83 -78
  218. package/lib/gateway/auth-middleware.ts +44 -0
  219. package/lib/gateway/broadcaster.ts +319 -0
  220. package/lib/gateway/daemon.ts +47 -0
  221. package/lib/gateway/factory.ts +10 -0
  222. package/lib/gateway/funnel-event-store.ts +155 -0
  223. package/lib/gateway/gateway-server.ts +414 -0
  224. package/lib/gateway/gateway-token.ts +79 -0
  225. package/lib/{modules/gateway/funnel-gateway.ts → gateway/gateway.ts} +27 -13
  226. package/lib/{modules/gateway → gateway}/kill-competing-slack-gateways.ts +4 -4
  227. package/lib/gateway/listener-supervisor.ts +339 -0
  228. package/lib/gateway/listeners-client.ts +128 -0
  229. package/lib/gateway/resolve-daemon-script.ts +26 -0
  230. package/lib/gateway/routes/channels.connectors.call.ts +39 -0
  231. package/lib/gateway/routes/health.ts +13 -0
  232. package/lib/gateway/routes/index.ts +24 -0
  233. package/lib/gateway/routes/listeners.list.ts +6 -0
  234. package/lib/gateway/routes/listeners.restart.ts +15 -0
  235. package/lib/gateway/routes/listeners.start.ts +15 -0
  236. package/lib/gateway/routes/listeners.stop.ts +15 -0
  237. package/lib/gateway/routes/route-deps.ts +11 -0
  238. package/lib/gateway/routes/status.ts +15 -0
  239. package/lib/gateway/routes/validator.ts +17 -0
  240. package/lib/index.ts +52 -92
  241. package/lib/logger/leuco-human-file-writer.ts +65 -0
  242. package/lib/logger/leuco-human-logger.ts +98 -0
  243. package/lib/logger/leuco-human-record.ts +16 -0
  244. package/lib/logger/leuco-human-stdout-writer.ts +26 -0
  245. package/lib/logger/leuco-human-writer.ts +14 -0
  246. package/lib/logger/leuco-logger-memory-sink.ts +67 -0
  247. package/lib/logger/leuco-logger-record.ts +13 -0
  248. package/lib/logger/leuco-logger-sink.ts +33 -0
  249. package/lib/logger/leuco-logger-sqlite-sink.ts +355 -0
  250. package/lib/logger/leuco-logger.ts +135 -0
  251. package/lib/tui/app.tsx +357 -0
  252. package/lib/tui/components/add-row.tsx +18 -0
  253. package/lib/tui/components/brand.tsx +27 -0
  254. package/lib/tui/components/card.tsx +44 -0
  255. package/lib/tui/components/detail-bar.tsx +46 -0
  256. package/lib/tui/components/editable-field.tsx +33 -0
  257. package/lib/tui/components/empty-state.tsx +11 -0
  258. package/lib/tui/components/gateway-status.tsx +66 -0
  259. package/lib/tui/components/keymap.tsx +29 -0
  260. package/lib/tui/components/menu-item.tsx +73 -0
  261. package/lib/tui/components/menu.tsx +26 -0
  262. package/lib/tui/components/panel-header.tsx +22 -0
  263. package/lib/tui/components/readonly-field.tsx +18 -0
  264. package/lib/tui/components/section-header.tsx +25 -0
  265. package/lib/tui/components/selection-accent.tsx +32 -0
  266. package/lib/tui/components/session-item.tsx +33 -0
  267. package/lib/tui/components/session-list.tsx +33 -0
  268. package/lib/tui/components/ui/hascii/accordion-item.tsx +88 -0
  269. package/lib/tui/components/ui/hascii/accordion.tsx +96 -0
  270. package/lib/tui/components/ui/hascii/alert-dialog.tsx +43 -0
  271. package/lib/tui/components/ui/hascii/badge.tsx +51 -0
  272. package/lib/tui/components/ui/hascii/breadcrumb.tsx +58 -0
  273. package/lib/tui/components/ui/hascii/button.tsx +194 -0
  274. package/lib/tui/components/ui/hascii/card-content.tsx +14 -0
  275. package/lib/tui/components/ui/hascii/card-description.tsx +13 -0
  276. package/lib/tui/components/ui/hascii/card-footer.tsx +14 -0
  277. package/lib/tui/components/ui/hascii/card-header.tsx +14 -0
  278. package/lib/tui/components/ui/hascii/card-title.tsx +13 -0
  279. package/lib/tui/components/ui/hascii/card.tsx +27 -0
  280. package/lib/tui/components/ui/hascii/checkbox.tsx +65 -0
  281. package/lib/tui/components/ui/hascii/command.tsx +159 -0
  282. package/lib/tui/components/ui/hascii/dialog-content.tsx +14 -0
  283. package/lib/tui/components/ui/hascii/dialog-description.tsx +13 -0
  284. package/lib/tui/components/ui/hascii/dialog-footer.tsx +14 -0
  285. package/lib/tui/components/ui/hascii/dialog-header.tsx +14 -0
  286. package/lib/tui/components/ui/hascii/dialog-title.tsx +13 -0
  287. package/lib/tui/components/ui/hascii/dialog.tsx +27 -0
  288. package/lib/tui/components/ui/hascii/file-tree.tsx +142 -0
  289. package/lib/tui/components/ui/hascii/focus-group.tsx +62 -0
  290. package/lib/tui/components/ui/hascii/form-item.tsx +43 -0
  291. package/lib/tui/components/ui/hascii/input-otp.tsx +86 -0
  292. package/lib/tui/components/ui/hascii/input.tsx +130 -0
  293. package/lib/tui/components/ui/hascii/pagination.tsx +105 -0
  294. package/lib/tui/components/ui/hascii/progress.tsx +28 -0
  295. package/lib/tui/components/ui/hascii/select.tsx +131 -0
  296. package/lib/tui/components/ui/hascii/separator.tsx +35 -0
  297. package/lib/tui/components/ui/hascii/sidebar-content.tsx +23 -0
  298. package/lib/tui/components/ui/hascii/sidebar-header.tsx +14 -0
  299. package/lib/tui/components/ui/hascii/sidebar-menu-item.tsx +67 -0
  300. package/lib/tui/components/ui/hascii/sidebar.tsx +24 -0
  301. package/lib/tui/components/ui/hascii/skeleton.tsx +60 -0
  302. package/lib/tui/components/ui/hascii/slider.tsx +91 -0
  303. package/lib/tui/components/ui/hascii/snackbar.tsx +75 -0
  304. package/lib/tui/components/ui/hascii/sparkline.tsx +53 -0
  305. package/lib/tui/components/ui/hascii/spinner.tsx +47 -0
  306. package/lib/tui/components/ui/hascii/stepper.tsx +54 -0
  307. package/lib/tui/components/ui/hascii/switch.tsx +66 -0
  308. package/lib/tui/components/ui/hascii/table.tsx +95 -0
  309. package/lib/tui/components/ui/hascii/tabs.tsx +59 -0
  310. package/lib/tui/components/ui/hascii/toggle-group-item.tsx +45 -0
  311. package/lib/tui/components/ui/hascii/toggle-group.tsx +99 -0
  312. package/lib/tui/components/ui/hascii/tree.tsx +104 -0
  313. package/lib/tui/components/view-shell.tsx +44 -0
  314. package/lib/tui/filter-input.tsx +33 -0
  315. package/lib/tui/hooks/hascii/use-pressable.ts +54 -0
  316. package/lib/tui/parse-comma-list.ts +14 -0
  317. package/lib/tui/profile-launcher.tsx +61 -0
  318. package/lib/tui/scrollbar-options.ts +19 -0
  319. package/lib/tui/sidebar.tsx +50 -0
  320. package/lib/tui/theme.ts +40 -0
  321. package/lib/tui/tui.tsx +20 -0
  322. package/lib/tui/types.ts +38 -0
  323. package/lib/tui/unique-name.ts +18 -0
  324. package/lib/tui/use-event-stream.ts +133 -0
  325. package/lib/tui/use-snapshot.ts +99 -0
  326. package/lib/tui/utils/hascii/form-item-context.tsx +23 -0
  327. package/lib/tui/utils/hascii/input-focus-context.tsx +31 -0
  328. package/lib/tui/utils/hascii/theme-context.tsx +26 -0
  329. package/lib/tui/utils/hascii/theme.ts +176 -0
  330. package/lib/tui/views/channels-view.tsx +108 -0
  331. package/lib/tui/views/connectors-view.tsx +164 -0
  332. package/lib/tui/views/events-view.tsx +160 -0
  333. package/lib/tui/views/listeners-view.tsx +80 -0
  334. package/lib/tui/views/profiles-view.tsx +152 -0
  335. package/package.json +55 -44
  336. package/lib/api.ts +0 -54
  337. package/lib/modules/channels/channel-connector-ref-updater.ts +0 -4
  338. package/lib/modules/channels/funnel-channels.ts +0 -160
  339. package/lib/modules/connectors/connector-config-schema.ts +0 -16
  340. package/lib/modules/connectors/connector-existence-checker.ts +0 -3
  341. package/lib/modules/connectors/funnel-callable-connector-store.ts +0 -9
  342. package/lib/modules/connectors/funnel-connector-listener.ts +0 -5
  343. package/lib/modules/connectors/funnel-connector-stores.ts +0 -52
  344. package/lib/modules/connectors/funnel-connector-type-store.ts +0 -24
  345. package/lib/modules/connectors/funnel-connectors.ts +0 -151
  346. package/lib/modules/connectors/funnel-discord-listener.ts +0 -71
  347. package/lib/modules/connectors/funnel-discord-store.ts +0 -88
  348. package/lib/modules/connectors/funnel-gh-store.ts +0 -101
  349. package/lib/modules/connectors/funnel-json-connector-store.ts +0 -100
  350. package/lib/modules/connectors/funnel-schedule-listener.ts +0 -130
  351. package/lib/modules/connectors/funnel-schedule-store.ts +0 -195
  352. package/lib/modules/connectors/funnel-slack-adapter.ts +0 -31
  353. package/lib/modules/connectors/funnel-slack-store.ts +0 -90
  354. package/lib/modules/connectors/migrate-legacy-connectors.ts +0 -81
  355. package/lib/modules/connectors/schedule-connector-schema.ts +0 -18
  356. package/lib/modules/connectors/schedule-last-fired-store.ts +0 -48
  357. package/lib/modules/gateway/daemon.ts +0 -74
  358. package/lib/modules/gateway/funnel-broadcaster.ts +0 -37
  359. package/lib/modules/gateway/funnel-event-logger.ts +0 -59
  360. package/lib/modules/gateway/funnel-gateway-server.ts +0 -241
  361. package/lib/modules/mcp/channel-server.ts +0 -76
  362. package/lib/modules/profiles/profile-channel-checker.ts +0 -3
  363. package/lib/modules/profiles/profile-channel-ref-updater.ts +0 -3
  364. package/lib/modules/repos/funnel-repositories.ts +0 -112
  365. package/lib/modules/schedule/funnel-schedule.ts +0 -39
  366. package/lib/modules/settings/funnel-settings-store.ts +0 -56
  367. package/lib/modules/settings/settings-schema.ts +0 -33
  368. package/lib/modules/tui/app.tsx +0 -44
  369. package/lib/modules/tui/tui.tsx +0 -13
  370. package/lib/routes/channels/add.help.ts +0 -3
  371. package/lib/routes/channels/add.ts +0 -21
  372. package/lib/routes/channels/connectors-attach.help.ts +0 -3
  373. package/lib/routes/channels/connectors-attach.ts +0 -17
  374. package/lib/routes/channels/connectors-detach.help.ts +0 -3
  375. package/lib/routes/channels/connectors-detach.ts +0 -17
  376. package/lib/routes/channels/group.help.ts +0 -16
  377. package/lib/routes/channels/group.ts +0 -22
  378. package/lib/routes/channels/remove.help.ts +0 -3
  379. package/lib/routes/channels/remove.ts +0 -17
  380. package/lib/routes/channels/rename.help.ts +0 -5
  381. package/lib/routes/channels/rename.ts +0 -17
  382. package/lib/routes/channels/routes.ts +0 -19
  383. package/lib/routes/channels/show.help.ts +0 -1
  384. package/lib/routes/channels/show.ts +0 -26
  385. package/lib/routes/claude/claude.help.ts +0 -16
  386. package/lib/routes/claude/claude.ts +0 -76
  387. package/lib/routes/claude/routes.ts +0 -4
  388. package/lib/routes/connectors/add.help.ts +0 -28
  389. package/lib/routes/connectors/add.ts +0 -64
  390. package/lib/routes/connectors/group.help.ts +0 -14
  391. package/lib/routes/connectors/group.ts +0 -18
  392. package/lib/routes/connectors/remove.help.ts +0 -3
  393. package/lib/routes/connectors/remove.ts +0 -17
  394. package/lib/routes/connectors/rename.help.ts +0 -5
  395. package/lib/routes/connectors/rename.ts +0 -17
  396. package/lib/routes/connectors/routes.ts +0 -23
  397. package/lib/routes/connectors/schedules-add.help.ts +0 -11
  398. package/lib/routes/connectors/schedules-add.ts +0 -33
  399. package/lib/routes/connectors/schedules-group.help.ts +0 -1
  400. package/lib/routes/connectors/schedules-group.ts +0 -38
  401. package/lib/routes/connectors/schedules-remove.help.ts +0 -3
  402. package/lib/routes/connectors/schedules-remove.ts +0 -17
  403. package/lib/routes/connectors/set.help.ts +0 -8
  404. package/lib/routes/connectors/set.ts +0 -72
  405. package/lib/routes/connectors/show.help.ts +0 -1
  406. package/lib/routes/connectors/show.ts +0 -41
  407. package/lib/routes/gateway/group.help.ts +0 -15
  408. package/lib/routes/gateway/group.ts +0 -28
  409. package/lib/routes/gateway/logs.help.ts +0 -13
  410. package/lib/routes/gateway/logs.ts +0 -102
  411. package/lib/routes/gateway/restart.help.ts +0 -10
  412. package/lib/routes/gateway/routes.ts +0 -18
  413. package/lib/routes/gateway/run.help.ts +0 -12
  414. package/lib/routes/gateway/run.ts +0 -35
  415. package/lib/routes/gateway/start.help.ts +0 -15
  416. package/lib/routes/gateway/start.ts +0 -32
  417. package/lib/routes/gateway/status.help.ts +0 -9
  418. package/lib/routes/gateway/status.ts +0 -28
  419. package/lib/routes/gateway/stop.help.ts +0 -8
  420. package/lib/routes/gateway/stop.ts +0 -21
  421. package/lib/routes/profiles/add.help.ts +0 -3
  422. package/lib/routes/profiles/add.ts +0 -33
  423. package/lib/routes/profiles/group.help.ts +0 -16
  424. package/lib/routes/profiles/group.ts +0 -25
  425. package/lib/routes/profiles/launch.help.ts +0 -4
  426. package/lib/routes/profiles/launch.ts +0 -36
  427. package/lib/routes/profiles/remove.help.ts +0 -3
  428. package/lib/routes/profiles/remove.ts +0 -17
  429. package/lib/routes/profiles/rename.help.ts +0 -5
  430. package/lib/routes/profiles/rename.ts +0 -17
  431. package/lib/routes/profiles/routes.ts +0 -18
  432. package/lib/routes/profiles/set.help.ts +0 -5
  433. package/lib/routes/profiles/set.ts +0 -32
  434. package/lib/routes/repos/add.help.ts +0 -6
  435. package/lib/routes/repos/add.ts +0 -20
  436. package/lib/routes/repos/group.help.ts +0 -11
  437. package/lib/routes/repos/group.ts +0 -18
  438. package/lib/routes/repos/remove.help.ts +0 -3
  439. package/lib/routes/repos/remove.ts +0 -17
  440. package/lib/routes/repos/rename.help.ts +0 -5
  441. package/lib/routes/repos/rename.ts +0 -17
  442. package/lib/routes/repos/routes.ts +0 -17
  443. package/lib/routes/repos/set.help.ts +0 -5
  444. package/lib/routes/repos/set.ts +0 -21
  445. package/lib/routes/repos/show.help.ts +0 -1
  446. package/lib/routes/repos/show.ts +0 -19
  447. package/lib/routes/request/discord-help.ts +0 -9
  448. package/lib/routes/request/discord.help.ts +0 -19
  449. package/lib/routes/request/discord.ts +0 -65
  450. package/lib/routes/request/group.help.ts +0 -15
  451. package/lib/routes/request/group.ts +0 -9
  452. package/lib/routes/request/routes.ts +0 -14
  453. package/lib/routes/request/slack-help.ts +0 -9
  454. package/lib/routes/request/slack.help.ts +0 -19
  455. package/lib/routes/request/slack.ts +0 -61
  456. package/lib/routes/status/routes.ts +0 -4
  457. package/lib/routes/status/status.help.ts +0 -6
  458. package/lib/routes/status/status.ts +0 -77
  459. package/lib/routes/update/routes.ts +0 -4
  460. package/lib/routes/update/update.help.ts +0 -5
  461. package/lib/routes/update/update.ts +0 -21
  462. package/lib/routes.ts +0 -40
  463. /package/lib/{factory.ts → cli/factory.ts} +0 -0
  464. /package/lib/{modules → cli}/router/query-to-cli-args.ts +0 -0
  465. /package/lib/{modules → cli}/router/validator.ts +0 -0
  466. /package/lib/{modules/connectors/funnel-connector-adapter.ts → connectors/connector-adapter.ts} +0 -0
  467. /package/lib/{modules/connectors/funnel-discord-event-processor.ts → connectors/discord-event-processor.ts} +0 -0
  468. /package/lib/{modules/http/funnel-http-client.ts → engine/http/http-client.ts} +0 -0
  469. /package/lib/{modules/id/funnel-id-generator.ts → engine/id/id-generator.ts} +0 -0
  470. /package/lib/{modules/logger/funnel-logger.ts → engine/logger/logger.ts} +0 -0
  471. /package/lib/{modules/process/funnel-process-runner.ts → engine/process/process-runner.ts} +0 -0
  472. /package/lib/{modules/time/funnel-clock.ts → engine/time/clock.ts} +0 -0
@@ -0,0 +1,355 @@
1
+ import { Database } from "bun:sqlite"
2
+ import type { SQLQueryBindings, Statement } from "bun:sqlite"
3
+ import type { LeucoLoggerRecord } from "@/logger/leuco-logger-record"
4
+ import type { LeucoLoggerPrimarySink, LeucoLoggerSink } from "@/logger/leuco-logger-sink"
5
+
6
+ type IndexValues<I extends ReadonlyArray<string>> = Record<I[number], string | null>
7
+
8
+ /**
9
+ * Constructor props. The shape narrows on `I`: when no indexes are
10
+ * declared (the default), `extractIndexes` is forbidden; when indexes
11
+ * are declared, both `indexes` and `extractIndexes` are required and
12
+ * `extractIndexes` is type-checked against the index keys.
13
+ */
14
+ type Props<E, I extends ReadonlyArray<string>> = I extends readonly []
15
+ ? {
16
+ path: string
17
+ maxRows?: number
18
+ maxAgeMs?: number
19
+ now?: () => number
20
+ indexes?: I
21
+ extractIndexes?: never
22
+ }
23
+ : {
24
+ path: string
25
+ maxRows?: number
26
+ maxAgeMs?: number
27
+ now?: () => number
28
+ indexes: I
29
+ extractIndexes: (event: E) => IndexValues<I>
30
+ }
31
+
32
+ type GetRecordsProps<I extends ReadonlyArray<string>> = {
33
+ /** Return only records with seq strictly greater than this. */
34
+ sinceSeq?: number
35
+ /** Filter by the top-level `event.type` discriminator. */
36
+ type?: string
37
+ /** Filter by indexed columns. Keys are constrained to the declared `indexes`. */
38
+ where?: Partial<IndexValues<I>>
39
+ /** Maximum rows returned. Default 1000. */
40
+ limit?: number
41
+ }
42
+
43
+ type EventRow = {
44
+ seq: number
45
+ ts: number
46
+ type: string | null
47
+ event: string
48
+ }
49
+
50
+ type CountRow = { n: number }
51
+ type MaxRow = { max: number }
52
+ type VersionRow = { user_version: number }
53
+ type ColumnRow = { name: string }
54
+
55
+ /** Conservative whitelist for column names interpolated into SQL. */
56
+ const COLUMN_NAME_RE = /^[a-z_][a-z0-9_]*$/
57
+
58
+ const RESERVED_COLUMNS: ReadonlySet<string> = new Set(["seq", "ts", "type", "event"])
59
+
60
+ /**
61
+ * Schema versions. Each entry is the list of DDL statements that take the
62
+ * database from version i to version i + 1. Migrations run in a transaction
63
+ * so a partial failure rolls back. Adding a new version is append-only —
64
+ * never edit a published one. Caller-defined index columns are added
65
+ * dynamically on construct (independent of versioned migrations) because
66
+ * they are configuration, not schema evolution.
67
+ */
68
+ const MIGRATIONS: ReadonlyArray<ReadonlyArray<string>> = [
69
+ [
70
+ "CREATE TABLE IF NOT EXISTS leuco_log (seq INTEGER PRIMARY KEY, ts INTEGER NOT NULL, type TEXT, event TEXT NOT NULL)",
71
+ "CREATE INDEX IF NOT EXISTS idx_leuco_log_ts ON leuco_log (ts)",
72
+ "CREATE INDEX IF NOT EXISTS idx_leuco_log_type ON leuco_log (type)",
73
+ ],
74
+ ]
75
+
76
+ /**
77
+ * SQLite-backed sink built on `bun:sqlite`. Implements both primary and
78
+ * relay roles so the same instance can own seq generation for one bus and
79
+ * mirror records from another (e.g. cross-process replication, restore
80
+ * from a backup stream).
81
+ *
82
+ * Concurrency model: seq is `INTEGER PRIMARY KEY`, so SQLite assigns it
83
+ * atomically via `lastInsertRowid`. Two `LeucoLogger` instances pointed
84
+ * at the same database file therefore see one monotonically increasing
85
+ * seq stream without any bus-level coordination — the database itself is
86
+ * the synchronization point.
87
+ *
88
+ * Schema is version-managed via `PRAGMA user_version`. Migrations are
89
+ * append-only and run in a transaction on every construct so a partial
90
+ * upgrade rolls back cleanly. Caller-defined `indexes` are layered on top
91
+ * via `ALTER TABLE ADD COLUMN` + `CREATE INDEX IF NOT EXISTS`, so adding
92
+ * a new index to an existing database is a no-downtime operation.
93
+ *
94
+ * Type safety: the second generic parameter `I` is the literal tuple of
95
+ * index column names. `extractIndexes` and `getRecords({ where })` are
96
+ * both type-checked against this tuple, so a typo at the call site is a
97
+ * compile-time error rather than a silent miss at runtime.
98
+ *
99
+ * Retention is bounded by `maxRows` and/or `maxAgeMs`. Both run on every
100
+ * insert as a single indexed DELETE that no-ops below the cap.
101
+ *
102
+ * Bulk inserts use `insertMany`, which wraps the batch in one transaction
103
+ * for ~10–100x throughput at the cost of one fsync per batch instead of
104
+ * one per row.
105
+ */
106
+ export class LeucoLoggerSqliteSink<E, const I extends ReadonlyArray<string> = readonly []>
107
+ implements LeucoLoggerPrimarySink<E>, LeucoLoggerSink<E>
108
+ {
109
+ private readonly db: Database
110
+ private readonly maxRows: number | null
111
+ private readonly maxAgeMs: number | null
112
+ private readonly now: () => number
113
+ private readonly indexes: I
114
+ private readonly extractIndexes: ((event: E) => IndexValues<I>) | null
115
+ private readonly insertStmt: Statement<unknown, SQLQueryBindings[]>
116
+ private readonly insertWithSeqStmt: Statement<unknown, SQLQueryBindings[]>
117
+ private readonly maxSeqStmt: Statement<MaxRow, []>
118
+ private readonly countStmt: Statement<CountRow, []>
119
+ private readonly trimRowsStmt: Statement<unknown, [number]>
120
+ private readonly trimAgeStmt: Statement<unknown, [number]>
121
+
122
+ constructor(props: Props<E, I>) {
123
+ this.db = new Database(props.path)
124
+ this.db.run("PRAGMA journal_mode = WAL")
125
+ this.migrate()
126
+
127
+ this.maxRows = props.maxRows ?? null
128
+ this.maxAgeMs = props.maxAgeMs ?? null
129
+ this.now = props.now ?? (() => Date.now())
130
+
131
+ // The conditional `Props<E, I>` type widens to a union when `I` is a
132
+ // generic, so TS can't narrow `props.indexes` back to `I` after the
133
+ // runtime check. One cast at this boundary brings it back; everything
134
+ // downstream stays I-typed.
135
+ this.indexes = (props.indexes ?? []) as unknown as I
136
+
137
+ if (this.indexes.length > 0) {
138
+ validateIndexNames(this.indexes)
139
+ this.extractIndexes = props.extractIndexes ?? null
140
+ this.syncIndexColumns()
141
+ } else {
142
+ this.extractIndexes = null
143
+ }
144
+
145
+ const cols = ["ts", "type", "event", ...this.indexes]
146
+ const placeholders = cols.map(() => "?").join(", ")
147
+ this.insertStmt = this.db.prepare(
148
+ `INSERT INTO leuco_log (${cols.join(", ")}) VALUES (${placeholders})`,
149
+ )
150
+
151
+ const colsWithSeq = ["seq", ...cols]
152
+ const placeholdersWithSeq = colsWithSeq.map(() => "?").join(", ")
153
+ this.insertWithSeqStmt = this.db.prepare(
154
+ `INSERT INTO leuco_log (${colsWithSeq.join(", ")}) VALUES (${placeholdersWithSeq})`,
155
+ )
156
+
157
+ this.maxSeqStmt = this.db.prepare("SELECT COALESCE(MAX(seq), 0) AS max FROM leuco_log")
158
+ this.countStmt = this.db.prepare("SELECT COUNT(*) AS n FROM leuco_log")
159
+ this.trimRowsStmt = this.db.prepare(
160
+ "DELETE FROM leuco_log WHERE seq <= (SELECT seq FROM leuco_log ORDER BY seq DESC LIMIT 1 OFFSET ?)",
161
+ )
162
+ this.trimAgeStmt = this.db.prepare("DELETE FROM leuco_log WHERE ts < ?")
163
+ }
164
+
165
+ insert(input: { ts: number; event: E }): LeucoLoggerRecord<E> | Error {
166
+ try {
167
+ const params = this.buildInsertParams(input.ts, input.event)
168
+ const result = this.insertStmt.run(...params)
169
+ const seq = Number(result.lastInsertRowid)
170
+ this.trim()
171
+ return { seq, ts: input.ts, event: input.event }
172
+ } catch (e) {
173
+ return e instanceof Error ? e : new Error(String(e))
174
+ }
175
+ }
176
+
177
+ insertMany(inputs: ReadonlyArray<{ ts: number; event: E }>): LeucoLoggerRecord<E>[] | Error {
178
+ if (inputs.length === 0) return []
179
+
180
+ try {
181
+ const records: LeucoLoggerRecord<E>[] = []
182
+ const apply = this.db.transaction((batch: ReadonlyArray<{ ts: number; event: E }>) => {
183
+ for (const input of batch) {
184
+ const params = this.buildInsertParams(input.ts, input.event)
185
+ const result = this.insertStmt.run(...params)
186
+ records.push({
187
+ seq: Number(result.lastInsertRowid),
188
+ ts: input.ts,
189
+ event: input.event,
190
+ })
191
+ }
192
+ })
193
+ apply(inputs)
194
+ this.trim()
195
+ return records
196
+ } catch (e) {
197
+ return e instanceof Error ? e : new Error(String(e))
198
+ }
199
+ }
200
+
201
+ write(record: LeucoLoggerRecord<E>): void | Error {
202
+ try {
203
+ const params: SQLQueryBindings[] = [
204
+ record.seq,
205
+ ...this.buildInsertParams(record.ts, record.event),
206
+ ]
207
+ this.insertWithSeqStmt.run(...params)
208
+ this.trim()
209
+ } catch (e) {
210
+ return e instanceof Error ? e : new Error(String(e))
211
+ }
212
+ }
213
+
214
+ getMaxSeq(): number {
215
+ const row = this.maxSeqStmt.get()
216
+ return row ? row.max : 0
217
+ }
218
+
219
+ getRecords(props: GetRecordsProps<I> = {}): LeucoLoggerRecord<E>[] {
220
+ const conditions: string[] = ["seq > ?"]
221
+ const params: SQLQueryBindings[] = [props.sinceSeq ?? 0]
222
+
223
+ if (typeof props.type === "string") {
224
+ conditions.push("type = ?")
225
+ params.push(props.type)
226
+ }
227
+
228
+ if (props.where) {
229
+ this.appendWhereConditions(props.where, conditions, params)
230
+ }
231
+
232
+ const limit = props.limit ?? 1000
233
+ params.push(limit)
234
+
235
+ const sql = `SELECT seq, ts, type, event FROM leuco_log WHERE ${conditions.join(" AND ")} ORDER BY seq ASC LIMIT ?`
236
+ const stmt = this.db.prepare<EventRow, SQLQueryBindings[]>(sql)
237
+ return stmt.all(...params).map(toRecord<E>)
238
+ }
239
+
240
+ /**
241
+ * Current schema version. Useful for diagnostics and for tests that want
242
+ * to verify migrations ran. Reads `PRAGMA user_version` once per call.
243
+ */
244
+ getSchemaVersion(): number {
245
+ const row = this.db.prepare<VersionRow, []>("PRAGMA user_version").get()
246
+ return row?.user_version ?? 0
247
+ }
248
+
249
+ close(): void {
250
+ this.db.close()
251
+ }
252
+
253
+ private buildInsertParams(ts: number, event: E): SQLQueryBindings[] {
254
+ const type = extractType(event)
255
+ const json = JSON.stringify(event)
256
+ if (this.indexes.length === 0) return [ts, type, json]
257
+
258
+ // The user's typed Record<I[number], V> is structurally a string-keyed
259
+ // object at runtime; widen so we can index by `col: string` from the loop.
260
+ const values = this.extractIndexes
261
+ ? (this.extractIndexes(event) as unknown as Record<string, string | null>)
262
+ : null
263
+ const indexParams = this.indexes.map((col) => values?.[col] ?? null)
264
+ return [ts, type, json, ...indexParams]
265
+ }
266
+
267
+ private appendWhereConditions(
268
+ where: Partial<IndexValues<I>>,
269
+ conditions: string[],
270
+ params: SQLQueryBindings[],
271
+ ): void {
272
+ const widened = where as unknown as Partial<Record<string, string | null>>
273
+ for (const col of this.indexes) {
274
+ const value = widened[col]
275
+ if (value === undefined) continue
276
+ if (value === null) {
277
+ conditions.push(`${col} IS NULL`)
278
+ } else {
279
+ conditions.push(`${col} = ?`)
280
+ params.push(value)
281
+ }
282
+ }
283
+ }
284
+
285
+ private trim(): void {
286
+ if (this.maxRows !== null) {
287
+ const row = this.countStmt.get()
288
+ if (row && row.n > this.maxRows) this.trimRowsStmt.run(this.maxRows)
289
+ }
290
+
291
+ if (this.maxAgeMs !== null) {
292
+ this.trimAgeStmt.run(this.now() - this.maxAgeMs)
293
+ }
294
+ }
295
+
296
+ private syncIndexColumns(): void {
297
+ const existing = new Set(
298
+ this.db
299
+ .prepare<ColumnRow, []>("PRAGMA table_info(leuco_log)")
300
+ .all()
301
+ .map((r) => r.name),
302
+ )
303
+
304
+ for (const col of this.indexes) {
305
+ if (!existing.has(col)) {
306
+ this.db.run(`ALTER TABLE leuco_log ADD COLUMN ${col} TEXT`)
307
+ }
308
+ this.db.run(`CREATE INDEX IF NOT EXISTS idx_leuco_log_${col} ON leuco_log (${col})`)
309
+ }
310
+ }
311
+
312
+ private migrate(): void {
313
+ const row = this.db.prepare<VersionRow, []>("PRAGMA user_version").get()
314
+ const current = row?.user_version ?? 0
315
+ if (current >= MIGRATIONS.length) return
316
+
317
+ const pending = MIGRATIONS.slice(current)
318
+ let version = current
319
+
320
+ for (const stmts of pending) {
321
+ version += 1
322
+ const apply = this.db.transaction(() => {
323
+ for (const stmt of stmts) this.db.run(stmt)
324
+ this.db.run(`PRAGMA user_version = ${version}`)
325
+ })
326
+ apply()
327
+ }
328
+ }
329
+ }
330
+
331
+ function validateIndexNames(names: ReadonlyArray<string>): void {
332
+ for (const name of names) {
333
+ if (!COLUMN_NAME_RE.test(name)) {
334
+ throw new Error(`invalid index column name: ${name}`)
335
+ }
336
+ if (RESERVED_COLUMNS.has(name)) {
337
+ throw new Error(`reserved index column name: ${name}`)
338
+ }
339
+ }
340
+ }
341
+
342
+ function extractType(event: unknown): string | null {
343
+ if (typeof event !== "object" || event === null) return null
344
+ if (!("type" in event)) return null
345
+ const t = event.type
346
+ return typeof t === "string" ? t : null
347
+ }
348
+
349
+ function toRecord<E>(row: EventRow): LeucoLoggerRecord<E> {
350
+ return {
351
+ seq: row.seq,
352
+ ts: row.ts,
353
+ event: JSON.parse(row.event),
354
+ }
355
+ }
@@ -0,0 +1,135 @@
1
+ import type { ZodType } from "zod"
2
+ import type { LeucoLoggerRecord } from "@/logger/leuco-logger-record"
3
+ import type { LeucoLoggerPrimarySink, LeucoLoggerSink } from "@/logger/leuco-logger-sink"
4
+
5
+ type Listener<E> = (record: LeucoLoggerRecord<E>) => void
6
+
7
+ type SinkErrorHandler<E> = (
8
+ error: Error,
9
+ record: LeucoLoggerRecord<E>,
10
+ sink: LeucoLoggerSink<E>,
11
+ ) => void
12
+
13
+ type Props<E> = {
14
+ /** Zod schema for the event union. Validated on every `emit`. */
15
+ schema: ZodType<E>
16
+ /** Owns seq assignment + durability. Use `LeucoLoggerSqliteSink` for multi-process safety. */
17
+ primary: LeucoLoggerPrimarySink<E>
18
+ /** Optional fanout for already-sequenced records (memory ring, stdout, network mirror). */
19
+ relays?: ReadonlyArray<LeucoLoggerSink<E>>
20
+ /** Override for tests. Defaults to `Date.now`. */
21
+ now?: () => number
22
+ /** Observer for relay failures. Default: silently swallow. */
23
+ onSinkError?: SinkErrorHandler<E>
24
+ }
25
+
26
+ /**
27
+ * Schema-validated event log bus. Three responsibilities and nothing else:
28
+ * validate the event, delegate seq + persistence to the primary sink, and
29
+ * fan the resulting record out to relays and live subscribers.
30
+ *
31
+ * Splitting "primary" from "relays" makes the seq invariant honest: there
32
+ * is exactly one source of truth (the primary's atomic insert). Two
33
+ * `LeucoLogger` instances pointed at the same SQLite file therefore see
34
+ * one monotonic stream without bus-level coordination. Relays mirror
35
+ * already-sequenced records, so they can be added or removed without
36
+ * affecting correctness.
37
+ *
38
+ * Failure isolation:
39
+ * - Primary failure short-circuits emit and is returned to the caller.
40
+ * - Relay failures never block the primary path — they surface via the
41
+ * optional `onSinkError` callback so the caller can observe without
42
+ * being interrupted.
43
+ * - A subscriber that throws is contained; the rest of the fanout
44
+ * completes normally.
45
+ */
46
+ export class LeucoLogger<E> {
47
+ private readonly schema: ZodType<E>
48
+ private readonly primary: LeucoLoggerPrimarySink<E>
49
+ private readonly relays: ReadonlyArray<LeucoLoggerSink<E>>
50
+ private readonly now: () => number
51
+ private readonly onSinkError: SinkErrorHandler<E> | null
52
+ private readonly listeners = new Set<Listener<E>>()
53
+
54
+ constructor(props: Props<E>) {
55
+ this.schema = props.schema
56
+ this.primary = props.primary
57
+ this.relays = props.relays ?? []
58
+ this.now = props.now ?? (() => Date.now())
59
+ this.onSinkError = props.onSinkError ?? null
60
+ }
61
+
62
+ emit(event: E): LeucoLoggerRecord<E> | Error {
63
+ const parsed = this.schema.safeParse(event)
64
+ if (!parsed.success) return parsed.error
65
+
66
+ const result = this.callPrimary(parsed.data)
67
+ if (result instanceof Error) return result
68
+
69
+ this.fanOutToRelays(result)
70
+ this.fanOutToListeners(result)
71
+
72
+ return result
73
+ }
74
+
75
+ subscribe(listener: Listener<E>): () => void {
76
+ this.listeners.add(listener)
77
+ return () => {
78
+ this.listeners.delete(listener)
79
+ }
80
+ }
81
+
82
+ getMaxSeq(): number {
83
+ return this.primary.getMaxSeq()
84
+ }
85
+
86
+ close(): void {
87
+ this.listeners.clear()
88
+ this.callClose(this.primary)
89
+ for (const relay of this.relays) this.callClose(relay)
90
+ }
91
+
92
+ private callPrimary(event: E): LeucoLoggerRecord<E> | Error {
93
+ try {
94
+ return this.primary.insert({ ts: this.now(), event })
95
+ } catch (e) {
96
+ return e instanceof Error ? e : new Error(String(e))
97
+ }
98
+ }
99
+
100
+ private fanOutToRelays(record: LeucoLoggerRecord<E>): void {
101
+ for (const relay of this.relays) {
102
+ const error = this.callRelay(relay, record)
103
+ if (!error) continue
104
+ if (this.onSinkError) this.onSinkError(error, record, relay)
105
+ }
106
+ }
107
+
108
+ private callRelay(relay: LeucoLoggerSink<E>, record: LeucoLoggerRecord<E>): Error | null {
109
+ try {
110
+ const outcome = relay.write(record)
111
+ return outcome instanceof Error ? outcome : null
112
+ } catch (e) {
113
+ return e instanceof Error ? e : new Error(String(e))
114
+ }
115
+ }
116
+
117
+ private fanOutToListeners(record: LeucoLoggerRecord<E>): void {
118
+ for (const listener of this.listeners) {
119
+ try {
120
+ listener(record)
121
+ } catch {
122
+ // a faulty subscriber must not derail emission for everyone else
123
+ }
124
+ }
125
+ }
126
+
127
+ private callClose(sink: { close?(): void }): void {
128
+ if (!sink.close) return
129
+ try {
130
+ sink.close()
131
+ } catch {
132
+ // close failures are best-effort by definition
133
+ }
134
+ }
135
+ }