@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
@@ -1,31 +0,0 @@
1
- import { WebClient } from "@slack/web-api"
2
- import {
3
- FunnelConnectorAdapter,
4
- type CallInput,
5
- } from "@/modules/connectors/funnel-connector-adapter"
6
- import type { SlackConnectorConfig } from "@/modules/connectors/slack-connector-schema"
7
-
8
- export type SlackWebClientLike = {
9
- apiCall: (method: string, options: Record<string, unknown>) => Promise<unknown>
10
- }
11
-
12
- type Deps = {
13
- config: SlackConnectorConfig
14
- client?: SlackWebClientLike
15
- }
16
-
17
- export class FunnelSlackAdapter extends FunnelConnectorAdapter {
18
- private readonly client: SlackWebClientLike
19
-
20
- constructor(deps: Deps) {
21
- super()
22
- this.client = deps.client ?? new WebClient(deps.config.botToken)
23
- Object.freeze(this)
24
- }
25
-
26
- async call(input: CallInput): Promise<unknown> {
27
- const body = input.body && typeof input.body === "object" ? input.body : {}
28
-
29
- return await this.client.apiCall(input.path, body as Record<string, unknown>)
30
- }
31
- }
@@ -1,90 +0,0 @@
1
- import { FunnelCallableConnectorStore } from "@/modules/connectors/funnel-callable-connector-store"
2
- import type { FunnelConnectorAdapter } from "@/modules/connectors/funnel-connector-adapter"
3
- import type { FunnelConnectorListener } from "@/modules/connectors/funnel-connector-listener"
4
- import {
5
- DEFAULT_FUNNEL_DIR,
6
- FunnelJsonConnectorStore,
7
- } from "@/modules/connectors/funnel-json-connector-store"
8
- import { FunnelSlackAdapter } from "@/modules/connectors/funnel-slack-adapter"
9
- import { FunnelSlackListener } from "@/modules/connectors/funnel-slack-listener"
10
- import {
11
- type SlackConnectorConfig,
12
- slackConnectorSchema,
13
- } from "@/modules/connectors/slack-connector-schema"
14
- import { FunnelFileSystem } from "@/modules/fs/funnel-file-system"
15
- import type { FunnelLogger } from "@/modules/logger/funnel-logger"
16
-
17
- type Deps = {
18
- fs?: FunnelFileSystem
19
- dir?: string
20
- logger?: FunnelLogger
21
- }
22
-
23
- export type SlackUpdateFields = {
24
- botToken?: string
25
- appToken?: string
26
- }
27
-
28
- export class FunnelSlackStore extends FunnelCallableConnectorStore<SlackConnectorConfig> {
29
- readonly type = "slack" as const
30
- private readonly store: FunnelJsonConnectorStore<SlackConnectorConfig>
31
- private readonly logger?: FunnelLogger
32
-
33
- constructor(deps: Deps = {}) {
34
- super()
35
- this.store = new FunnelJsonConnectorStore<SlackConnectorConfig>({
36
- type: "slack",
37
- schema: slackConnectorSchema,
38
- fs: deps.fs,
39
- dir: deps.dir ?? DEFAULT_FUNNEL_DIR,
40
- })
41
- this.logger = deps.logger
42
- Object.freeze(this)
43
- }
44
-
45
- list(): SlackConnectorConfig[] {
46
- return this.store.list()
47
- }
48
-
49
- get(name: string): SlackConnectorConfig | null {
50
- return this.store.get(name)
51
- }
52
-
53
- has(name: string): boolean {
54
- return this.store.has(name)
55
- }
56
-
57
- add(config: SlackConnectorConfig): void {
58
- if (this.has(config.name)) throw new Error(`connector "${config.name}" already exists`)
59
-
60
- this.store.write(config)
61
- }
62
-
63
- update(name: string, fields: SlackUpdateFields): void {
64
- const current = this.store.get(name)
65
-
66
- if (!current) throw new Error(`connector "${name}" not found`)
67
-
68
- this.store.write({
69
- ...current,
70
- botToken: fields.botToken ?? current.botToken,
71
- appToken: fields.appToken ?? current.appToken,
72
- })
73
- }
74
-
75
- remove(name: string): void {
76
- this.store.remove(name)
77
- }
78
-
79
- rename(oldName: string, newName: string): void {
80
- this.store.rename(oldName, newName)
81
- }
82
-
83
- createListener(config: SlackConnectorConfig): FunnelConnectorListener {
84
- return new FunnelSlackListener({ config, logger: this.logger })
85
- }
86
-
87
- createAdapter(config: SlackConnectorConfig): FunnelConnectorAdapter {
88
- return new FunnelSlackAdapter({ config })
89
- }
90
- }
@@ -1,81 +0,0 @@
1
- import { join } from "node:path"
2
- import { connectorConfigSchema } from "@/modules/connectors/connector-config-schema"
3
- import type { ConnectorStoresBundle } from "@/modules/connectors/funnel-connector-stores"
4
- import { DEFAULT_FUNNEL_DIR } from "@/modules/connectors/funnel-json-connector-store"
5
- import { FunnelFileSystem } from "@/modules/fs/funnel-file-system"
6
- import { NodeFunnelFileSystem } from "@/modules/fs/node-funnel-file-system"
7
- import { FunnelLogger } from "@/modules/logger/funnel-logger"
8
- import { NodeFunnelLogger } from "@/modules/logger/node-funnel-logger"
9
-
10
- type Props = {
11
- stores: ConnectorStoresBundle
12
- fs?: FunnelFileSystem
13
- dir?: string
14
- logger?: FunnelLogger
15
- }
16
-
17
- const defaultFs = new NodeFunnelFileSystem()
18
- const defaultLogger = new NodeFunnelLogger()
19
-
20
- export const migrateLegacyConnectors = (props: Props): number => {
21
- const fs = props.fs ?? defaultFs
22
- const base = props.dir ?? DEFAULT_FUNNEL_DIR
23
- const path = join(base, "settings.json")
24
- const logger = props.logger ?? defaultLogger
25
-
26
- if (!fs.existsSync(path)) return 0
27
-
28
- const content = fs.readFileSync(path)
29
- const raw = JSON.parse(content) as Record<string, unknown>
30
- const legacy = raw.connectors
31
-
32
- if (!Array.isArray(legacy) || legacy.length === 0) {
33
- if (legacy !== undefined) {
34
- const stripped = { ...raw }
35
- delete stripped.connectors
36
- fs.writeFileSync(path, `${JSON.stringify(stripped, null, 2)}\n`)
37
- }
38
- return 0
39
- }
40
-
41
- let migrated = 0
42
-
43
- for (const entry of legacy) {
44
- const parsed = connectorConfigSchema.safeParse(entry)
45
-
46
- if (!parsed.success) {
47
- logger.warn("skipping invalid legacy connector", {
48
- error: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", "),
49
- })
50
- continue
51
- }
52
-
53
- const config = parsed.data
54
-
55
- if (config.type === "slack") {
56
- if (props.stores.slack.has(config.name)) continue
57
- props.stores.slack.add(config)
58
- } else if (config.type === "gh") {
59
- if (props.stores.gh.has(config.name)) continue
60
- props.stores.gh.add(config)
61
- } else if (config.type === "discord") {
62
- if (props.stores.discord.has(config.name)) continue
63
- props.stores.discord.add(config)
64
- } else {
65
- if (props.stores.schedule.has(config.name)) continue
66
- props.stores.schedule.add(config)
67
- }
68
-
69
- migrated++
70
- }
71
-
72
- const stripped = { ...raw }
73
- delete stripped.connectors
74
- fs.writeFileSync(path, `${JSON.stringify(stripped, null, 2)}\n`)
75
-
76
- if (migrated > 0) {
77
- logger.info("migrated legacy connectors from settings.json", { count: migrated })
78
- }
79
-
80
- return migrated
81
- }
@@ -1,18 +0,0 @@
1
- import { z } from "zod"
2
-
3
- export const scheduleEntrySchema = z.object({
4
- id: z.string(),
5
- cron: z.string(),
6
- prompt: z.string(),
7
- enabled: z.boolean().default(true),
8
- })
9
-
10
- export type ScheduleEntry = z.infer<typeof scheduleEntrySchema>
11
-
12
- export const scheduleConnectorSchema = z.object({
13
- type: z.literal("schedule"),
14
- name: z.string(),
15
- entries: z.array(scheduleEntrySchema).default([]),
16
- })
17
-
18
- export type ScheduleConnectorConfig = z.infer<typeof scheduleConnectorSchema>
@@ -1,48 +0,0 @@
1
- import { dirname, join } from "node:path"
2
- import { DEFAULT_FUNNEL_DIR } from "@/modules/connectors/funnel-json-connector-store"
3
- import { FunnelFileSystem } from "@/modules/fs/funnel-file-system"
4
- import { NodeFunnelFileSystem } from "@/modules/fs/node-funnel-file-system"
5
-
6
- type Deps = {
7
- connector: string
8
- fs?: FunnelFileSystem
9
- dir?: string
10
- }
11
-
12
- const defaultFs = new NodeFunnelFileSystem()
13
-
14
- export class ScheduleLastFiredStore {
15
- private readonly path: string
16
- private readonly fs: FunnelFileSystem
17
-
18
- constructor(deps: Deps) {
19
- this.fs = deps.fs ?? defaultFs
20
- const base = deps.dir ?? DEFAULT_FUNNEL_DIR
21
- this.path = join(base, "connectors", "schedule", `${deps.connector}.state.json`)
22
- Object.freeze(this)
23
- }
24
-
25
- load(): Map<string, Date> {
26
- if (!this.fs.existsSync(this.path)) return new Map()
27
-
28
- const raw = JSON.parse(this.fs.readFileSync(this.path)) as Record<string, string>
29
- const map = new Map<string, Date>()
30
-
31
- for (const [id, iso] of Object.entries(raw)) {
32
- map.set(id, new Date(iso))
33
- }
34
-
35
- return map
36
- }
37
-
38
- save(state: Map<string, Date>): void {
39
- const obj: Record<string, string> = {}
40
-
41
- for (const [id, date] of state) {
42
- obj[id] = date.toISOString()
43
- }
44
-
45
- this.fs.mkdirSync(dirname(this.path), { recursive: true })
46
- this.fs.writeFileSync(this.path, `${JSON.stringify(obj, null, 2)}\n`)
47
- }
48
- }
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs"
3
- import { join } from "node:path"
4
- import { FunnelChannels } from "@/modules/channels/funnel-channels"
5
- import { FunnelConnectors } from "@/modules/connectors/funnel-connectors"
6
- import { createConnectorStores } from "@/modules/connectors/funnel-connector-stores"
7
- import { migrateLegacyConnectors } from "@/modules/connectors/migrate-legacy-connectors"
8
- import { FunnelGatewayServer } from "@/modules/gateway/funnel-gateway-server"
9
- import { NodeFunnelLogger } from "@/modules/logger/node-funnel-logger"
10
- import { FunnelProfiles } from "@/modules/profiles/funnel-profiles"
11
- import { FUNNEL_DIR, FunnelSettingsStore } from "@/modules/settings/funnel-settings-store"
12
-
13
- const PORT = Number(process.env.FUNNEL_PORT) || 9742
14
- const PID_FILE = join(FUNNEL_DIR, "gateway.pid")
15
- const LOG_DIR = "/tmp/funnel/events"
16
-
17
- const logger = new NodeFunnelLogger()
18
-
19
- mkdirSync(FUNNEL_DIR, { recursive: true })
20
-
21
- if (existsSync(PID_FILE)) {
22
- const existing = Number(readFileSync(PID_FILE, "utf-8").trim())
23
-
24
- if (existing > 0) {
25
- const check = Bun.spawnSync(["ps", "-p", String(existing), "-o", "state="], {
26
- stdout: "pipe",
27
- stderr: "pipe",
28
- })
29
-
30
- if (check.exitCode === 0 && check.stdout.toString().trim()) {
31
- logger.error(`funnel gateway already running`, { pid: existing })
32
- process.exit(1)
33
- }
34
- }
35
- }
36
-
37
- writeFileSync(PID_FILE, String(process.pid))
38
-
39
- process.on("exit", () => {
40
- try {
41
- unlinkSync(PID_FILE)
42
- } catch {
43
- // ignore
44
- }
45
- })
46
- process.on("SIGINT", () => process.exit(130))
47
- process.on("SIGTERM", () => process.exit(143))
48
-
49
- const store = new FunnelSettingsStore()
50
- const connectorStores = createConnectorStores()
51
-
52
- migrateLegacyConnectors({ stores: connectorStores })
53
-
54
- const profiles = new FunnelProfiles({ store })
55
- const channels: FunnelChannels = new FunnelChannels({
56
- store,
57
- connectorChecker: { has: (name: string) => connectors.has(name) },
58
- profileChecker: profiles,
59
- profileRefUpdater: profiles,
60
- })
61
- const connectors: FunnelConnectors = new FunnelConnectors({
62
- ...connectorStores,
63
- refUpdater: channels,
64
- })
65
-
66
- const server = new FunnelGatewayServer({
67
- connectors,
68
- settings: store,
69
- port: PORT,
70
- logDir: LOG_DIR,
71
- logger,
72
- })
73
-
74
- await server.start()
@@ -1,37 +0,0 @@
1
- import type { ServerWebSocket } from "bun"
2
-
3
- type ClientData = {
4
- channel: string
5
- connectors: string[]
6
- }
7
-
8
- export class FunnelBroadcaster {
9
- private readonly clients: Map<ServerWebSocket<unknown>, ClientData> = new Map()
10
-
11
- addClient(ws: ServerWebSocket<unknown>, data: ClientData): void {
12
- this.clients.set(ws, data)
13
- }
14
-
15
- removeClient(ws: ServerWebSocket<unknown>): void {
16
- this.clients.delete(ws)
17
- }
18
-
19
- getClientCount(): number {
20
- return this.clients.size
21
- }
22
-
23
- listChannels(): { channel: string; connectors: string[] }[] {
24
- return [...this.clients.values()].map((d) => ({ ...d }))
25
- }
26
-
27
- broadcast(content: string, meta?: Record<string, string>): void {
28
- const payload = JSON.stringify({ content, meta })
29
- const connector = meta?.connector
30
-
31
- for (const [ws, data] of this.clients) {
32
- if (connector && !data.connectors.includes(connector)) continue
33
-
34
- ws.send(payload)
35
- }
36
- }
37
- }
@@ -1,59 +0,0 @@
1
- import { join } from "node:path"
2
- import { FunnelFileSystem } from "@/modules/fs/funnel-file-system"
3
- import { NodeFunnelFileSystem } from "@/modules/fs/node-funnel-file-system"
4
-
5
- const MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000 // 30 days
6
-
7
- type Deps = {
8
- logDir: string
9
- fs?: FunnelFileSystem
10
- now?: () => number
11
- }
12
-
13
- const defaultFs = new NodeFunnelFileSystem()
14
-
15
- export class FunnelEventLogger {
16
- private readonly logDir: string
17
- private readonly fs: FunnelFileSystem
18
- private readonly now: () => number
19
-
20
- constructor(deps: Deps) {
21
- this.logDir = deps.logDir
22
- this.fs = deps.fs ?? defaultFs
23
- this.now = deps.now ?? (() => Date.now())
24
- this.fs.mkdirSync(this.logDir, { recursive: true })
25
- this.rotate()
26
- Object.freeze(this)
27
- }
28
-
29
- log(content: string, meta?: Record<string, string>): void {
30
- const entry = {
31
- timestamp: new Date(this.now()).toISOString(),
32
- eventType: meta?.event_type ?? "unknown",
33
- content: content.length > 2000 ? `${content.slice(0, 2000)}...` : content,
34
- meta,
35
- }
36
- const dateStr = new Date(this.now()).toISOString().slice(0, 10)
37
- const logFile = join(this.logDir, `${dateStr}.jsonl`)
38
-
39
- this.fs.appendFileSync(logFile, `${JSON.stringify(entry)}\n`)
40
- }
41
-
42
- private rotate(): void {
43
- const now = this.now()
44
-
45
- for (const name of this.fs.readdirSync(this.logDir)) {
46
- if (!name.endsWith(".jsonl")) continue
47
-
48
- const path = join(this.logDir, name)
49
-
50
- try {
51
- const stat = this.fs.statSync(path)
52
-
53
- if (now - stat.mtimeMs > MAX_AGE_MS) this.fs.unlink(path)
54
- } catch {
55
- // ignore
56
- }
57
- }
58
- }
59
- }
@@ -1,241 +0,0 @@
1
- import type { Server, ServerWebSocket } from "bun"
2
- import { Hono } from "hono"
3
- import type { FunnelConnectors } from "@/modules/connectors/funnel-connectors"
4
- import type { FunnelFileSystem } from "@/modules/fs/funnel-file-system"
5
- import { FunnelBroadcaster } from "@/modules/gateway/funnel-broadcaster"
6
- import { FunnelEventLogger } from "@/modules/gateway/funnel-event-logger"
7
- import { killCompetingSlackGateways } from "@/modules/gateway/kill-competing-slack-gateways"
8
- import { FunnelLogger } from "@/modules/logger/funnel-logger"
9
- import { NodeFunnelLogger } from "@/modules/logger/node-funnel-logger"
10
- import type { FunnelProcessRunner } from "@/modules/process/funnel-process-runner"
11
- import type { FunnelSettingsReader } from "@/modules/settings/funnel-settings-reader"
12
- import type { FunnelClock } from "@/modules/time/funnel-clock"
13
-
14
- const DEFAULT_PORT = 9742
15
- const DEFAULT_LOG_DIR = "/tmp/funnel/events"
16
-
17
- type Deps = {
18
- connectors: FunnelConnectors
19
- settings: FunnelSettingsReader
20
- port?: number
21
- logDir?: string
22
- fs?: FunnelFileSystem
23
- process?: FunnelProcessRunner
24
- clock?: FunnelClock
25
- logger?: FunnelLogger
26
- selfPid?: number
27
- killCompetingSlack?: boolean
28
- }
29
-
30
- type WsData = { channel: string; connectors: string[] }
31
-
32
- const defaultLogger = new NodeFunnelLogger()
33
-
34
- /**
35
- * In-process gateway: runs `Bun.serve` (HTTP + WebSocket /ws), boots all
36
- * connector listeners, fans events out via FunnelBroadcaster, and persists
37
- * them via FunnelEventLogger. Useful for embedding the gateway in a custom
38
- * host or driving it from tests.
39
- */
40
- export class FunnelGatewayServer {
41
- private readonly connectors: FunnelConnectors
42
- private readonly settings: FunnelSettingsReader
43
- private readonly port: number
44
- private readonly logDir: string
45
- private readonly fs?: FunnelFileSystem
46
- private readonly process?: FunnelProcessRunner
47
- private readonly logger: FunnelLogger
48
- private readonly selfPid: number
49
- private readonly killCompetingSlack: boolean
50
- private readonly broadcaster: FunnelBroadcaster
51
- private readonly eventLogger: FunnelEventLogger
52
- private server: Server<WsData> | null = null
53
-
54
- constructor(deps: Deps) {
55
- this.connectors = deps.connectors
56
- this.settings = deps.settings
57
- this.port = deps.port ?? DEFAULT_PORT
58
- this.logDir = deps.logDir ?? DEFAULT_LOG_DIR
59
- this.fs = deps.fs
60
- this.process = deps.process
61
- this.logger = deps.logger ?? defaultLogger
62
- this.selfPid = deps.selfPid ?? globalThis.process.pid
63
- this.killCompetingSlack = deps.killCompetingSlack ?? true
64
- this.broadcaster = new FunnelBroadcaster()
65
- this.eventLogger = new FunnelEventLogger({
66
- logDir: this.logDir,
67
- fs: this.fs,
68
- now: deps.clock ? () => deps.clock!.millis() : undefined,
69
- })
70
- }
71
-
72
- async start(): Promise<Server<WsData>> {
73
- if (this.server) return this.server
74
-
75
- const app = this.buildApp()
76
-
77
- this.server = Bun.serve<WsData>({
78
- port: this.port,
79
- fetch: (request, server) => {
80
- const url = new URL(request.url)
81
-
82
- if (url.pathname === "/ws" && request.headers.get("upgrade") === "websocket") {
83
- const channelName = url.searchParams.get("channel") ?? ""
84
- const connectors = channelName ? this.resolveConnectors(channelName) : []
85
- const data: WsData = { channel: channelName, connectors }
86
- const upgraded = server.upgrade(request, { data })
87
-
88
- if (upgraded) return undefined
89
-
90
- return new Response("WebSocket upgrade failed", { status: 400 })
91
- }
92
-
93
- return app.fetch(request)
94
- },
95
- websocket: {
96
- open: (ws: ServerWebSocket<WsData>) => {
97
- const data = ws.data
98
-
99
- this.broadcaster.addClient(ws, data)
100
- this.eventLogger.log("channel connected", {
101
- event_type: "system",
102
- action: "channel_connect",
103
- channel: data.channel,
104
- connectors: data.connectors.join(","),
105
- total: String(this.broadcaster.getClientCount()),
106
- })
107
- },
108
- close: (ws: ServerWebSocket<WsData>) => {
109
- this.broadcaster.removeClient(ws)
110
- this.eventLogger.log("channel disconnected", {
111
- event_type: "system",
112
- action: "channel_disconnect",
113
- total: String(this.broadcaster.getClientCount()),
114
- })
115
- },
116
- message() {
117
- // future: client → gateway messages
118
- },
119
- },
120
- })
121
-
122
- this.eventLogger.log("gateway started", {
123
- event_type: "system",
124
- action: "gateway_start",
125
- port: String(this.port),
126
- pid: String(this.selfPid),
127
- })
128
-
129
- this.logger.info("funnel gateway listening", {
130
- url: `http://localhost:${this.port}`,
131
- websocket: `ws://localhost:${this.port}/ws`,
132
- health: `http://localhost:${this.port}/health`,
133
- })
134
-
135
- await this.bootListeners()
136
-
137
- return this.server
138
- }
139
-
140
- stop(): void {
141
- if (!this.server) return
142
-
143
- this.server.stop()
144
- this.server = null
145
- }
146
-
147
- getStatus(): { clients: number; channels: { channel: string; connectors: string[] }[] } {
148
- return {
149
- clients: this.broadcaster.getClientCount(),
150
- channels: this.broadcaster.listChannels(),
151
- }
152
- }
153
-
154
- getBroadcaster(): FunnelBroadcaster {
155
- return this.broadcaster
156
- }
157
-
158
- private buildApp(): Hono {
159
- const app = new Hono()
160
-
161
- app.get("/health", (c) =>
162
- c.json({
163
- ok: true,
164
- pid: this.selfPid,
165
- clients: this.broadcaster.getClientCount(),
166
- }),
167
- )
168
-
169
- app.get("/status", (c) =>
170
- c.json({
171
- ok: true,
172
- clients: this.broadcaster.listChannels(),
173
- }),
174
- )
175
-
176
- return app
177
- }
178
-
179
- private resolveConnectors(channelName: string): string[] {
180
- const settings = this.settings.read()
181
- const channel = settings?.channels.find((c) => c.name === channelName)
182
-
183
- return channel?.connectors ?? []
184
- }
185
-
186
- private async bootListeners(): Promise<void> {
187
- const allConnectors = this.connectors.list()
188
-
189
- if (this.killCompetingSlack && allConnectors.some((c) => c.type === "slack")) {
190
- const killed = await killCompetingSlackGateways({
191
- selfPid: this.selfPid,
192
- process: this.process,
193
- logger: this.logger,
194
- })
195
-
196
- if (killed.length > 0) {
197
- this.eventLogger.log("killed competing Slack gateway processes", {
198
- event_type: "system",
199
- action: "kill_competing",
200
- pids: killed.join(","),
201
- })
202
- }
203
- }
204
-
205
- for (const { config, listener } of this.connectors.createListeners()) {
206
- const bind = (content: string, meta?: Record<string, string>) =>
207
- this.notify(config.name, content, meta)
208
-
209
- try {
210
- await listener.start(bind)
211
-
212
- this.eventLogger.log(`${config.type} listener started: ${config.name}`, {
213
- event_type: "system",
214
- action: `${config.type}_connect`,
215
- connector: config.name,
216
- })
217
-
218
- this.logger.info(`${config.type} listener started`, { connector: config.name })
219
- } catch (error) {
220
- this.logger.error(`${config.type} listener failed`, {
221
- connector: config.name,
222
- error: error instanceof Error ? error.message : String(error),
223
- })
224
- }
225
- }
226
-
227
- this.logger.info(`event logs: ${this.logDir}`)
228
- this.logger.info("funnel gateway running")
229
- }
230
-
231
- private async notify(
232
- connectorName: string,
233
- content: string,
234
- meta?: Record<string, string>,
235
- ): Promise<void> {
236
- const withConnector: Record<string, string> = { ...meta, connector: connectorName }
237
-
238
- this.eventLogger.log(content, withConnector)
239
- this.broadcaster.broadcast(content, withConnector)
240
- }
241
- }