@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,46 @@
1
+ import { HTTPException } from "hono/http-exception"
2
+ import { z } from "zod"
3
+ import { factory } from "@/cli/factory"
4
+ import { zValidator } from "@/cli/router/validator"
5
+
6
+ export const addHelp = `funnel profiles add — add a profile
7
+
8
+ usage: funnel profiles add <name> --path <path> --sub-agent <agent> --channel <channel-name>
9
+
10
+ options:
11
+ --path working directory passed to claude as cwd
12
+ --sub-agent sub-agent name passed to claude --agent
13
+ --channel channel name (resolved to channel id internally)`
14
+
15
+ export const profilesAddHandler = factory.createHandlers(
16
+ zValidator("param", z.object({ profile: z.string() })),
17
+ zValidator(
18
+ "query",
19
+ z.object({
20
+ path: z.string(),
21
+ "sub-agent": z.string(),
22
+ channel: z.string(),
23
+ }),
24
+ addHelp,
25
+ ),
26
+ (c) => {
27
+ const param = c.req.valid("param")
28
+ const query = c.req.valid("query")
29
+ const funnel = c.var.funnel
30
+
31
+ const channel = funnel.channels.get(query.channel)
32
+
33
+ if (!channel) {
34
+ throw new HTTPException(400, { message: `channel "${query.channel}" not found` })
35
+ }
36
+
37
+ funnel.profiles.add({
38
+ name: param.profile,
39
+ path: query.path,
40
+ subAgent: query["sub-agent"],
41
+ channelId: channel.id,
42
+ })
43
+
44
+ return c.text(`added profile "${param.profile}"`)
45
+ },
46
+ )
@@ -0,0 +1,20 @@
1
+ import { z } from "zod"
2
+ import { factory } from "@/cli/factory"
3
+ import { zValidator } from "@/cli/router/validator"
4
+
5
+ export const removeHelp = `funnel profiles remove — remove a profile
6
+
7
+ usage: funnel profiles remove <name>`
8
+
9
+ export const profilesRemoveHandler = factory.createHandlers(
10
+ zValidator("param", z.object({ profile: z.string() })),
11
+ zValidator("query", z.object({}), removeHelp),
12
+ (c) => {
13
+ const param = c.req.valid("param")
14
+ const funnel = c.var.funnel
15
+
16
+ funnel.profiles.remove(param.profile)
17
+
18
+ return c.text(`removed profile "${param.profile}"`)
19
+ },
20
+ )
@@ -0,0 +1,46 @@
1
+ import { HTTPException } from "hono/http-exception"
2
+ import { z } from "zod"
3
+ import { factory } from "@/cli/factory"
4
+ import { zValidator } from "@/cli/router/validator"
5
+
6
+ export const setHelp = `funnel profiles <name> set — update a profile
7
+
8
+ usage: funnel profiles <name> set [--path <path>] [--sub-agent <agent>] [--channel <channel-name>]`
9
+
10
+ export const profilesSetHandler = factory.createHandlers(
11
+ zValidator("param", z.object({ profile: z.string() })),
12
+ zValidator(
13
+ "query",
14
+ z.object({
15
+ path: z.string().optional(),
16
+ "sub-agent": z.string().optional(),
17
+ channel: z.string().optional(),
18
+ }),
19
+ setHelp,
20
+ ),
21
+ (c) => {
22
+ const param = c.req.valid("param")
23
+ const query = c.req.valid("query")
24
+ const funnel = c.var.funnel
25
+
26
+ let channelId: string | undefined
27
+
28
+ if (query.channel !== undefined) {
29
+ const channel = funnel.channels.get(query.channel)
30
+
31
+ if (!channel) {
32
+ throw new HTTPException(400, { message: `channel "${query.channel}" not found` })
33
+ }
34
+
35
+ channelId = channel.id
36
+ }
37
+
38
+ funnel.profiles.update(param.profile, {
39
+ path: query.path,
40
+ subAgent: query["sub-agent"],
41
+ channelId,
42
+ })
43
+
44
+ return c.text(`updated profile "${param.profile}"`)
45
+ },
46
+ )
@@ -0,0 +1,40 @@
1
+ import { z } from "zod"
2
+ import { factory } from "@/cli/factory"
3
+ import { zValidator } from "@/cli/router/validator"
4
+
5
+ export const groupHelp = `funnel profiles — manage launch profiles
6
+
7
+ usage: funnel profiles [subcommand]
8
+
9
+ subcommands:
10
+ (none) list (first entry is the default)
11
+ add <name> --path <path> --sub-agent <agent> --channel <channel>
12
+ <name> set [--path ...] [--sub-agent ...] [--channel ...]
13
+ <name> as-default move profile to the front (becomes default)
14
+ rename <old> <new> rename
15
+ remove <name> remove
16
+ <name> run launch (sugar for fnl claude -p <name>)
17
+ <name> launch (alias for run)
18
+
19
+ examples:
20
+ funnel profiles add cto --path /repo/myapp --sub-agent cto --channel prod-inbox
21
+ funnel profiles cto as-default
22
+ funnel profiles cto run`
23
+
24
+ export const profilesGroupHandler = factory.createHandlers(
25
+ zValidator("query", z.object({}), groupHelp),
26
+ (c) => {
27
+ const funnel = c.var.funnel
28
+ const profiles = funnel.profiles.list()
29
+
30
+ if (profiles.length === 0) return c.text("no profiles")
31
+
32
+ const lines = profiles.map((profile, index) => {
33
+ const tag = index === 0 ? " (default)" : ""
34
+
35
+ return `${profile.name}${tag} [path=${profile.path}, sub-agent=${profile.subAgent}, channel=${profile.channelId}]`
36
+ })
37
+
38
+ return c.text(lines.join("\n"))
39
+ },
40
+ )
@@ -0,0 +1,93 @@
1
+ import { z } from "zod"
2
+ import { factory } from "@/cli/factory"
3
+ import { zValidator } from "@/cli/router/validator"
4
+
5
+ export const statusHelp = `funnel status — show overall connection status
6
+
7
+ usage: funnel status
8
+
9
+ Lists configured connectors / channels / profiles, gateway running status,
10
+ and active MCP WebSocket clients.`
11
+
12
+ type GatewayClient = { channel: string; connectors: string[] }
13
+
14
+ type GatewayStatus = {
15
+ ok: boolean
16
+ clients: GatewayClient[]
17
+ }
18
+
19
+ const isGatewayStatus = (value: unknown): value is GatewayStatus => {
20
+ if (value === null || typeof value !== "object") return false
21
+ if (!("clients" in value) || !Array.isArray(value.clients)) return false
22
+
23
+ return value.clients.every(
24
+ (client: unknown) =>
25
+ typeof client === "object" &&
26
+ client !== null &&
27
+ "channel" in client &&
28
+ typeof client.channel === "string" &&
29
+ "connectors" in client &&
30
+ Array.isArray(client.connectors),
31
+ )
32
+ }
33
+
34
+ export const statusHandler = factory.createHandlers(
35
+ zValidator("query", z.object({}), statusHelp),
36
+ async (c) => {
37
+ const funnel = c.var.funnel
38
+ const channels = funnel.channels.list()
39
+ const profiles = funnel.profiles.list()
40
+ const gatewayStatus = funnel.gateway.getStatus()
41
+
42
+ const lines: string[] = []
43
+
44
+ lines.push("= funnel status =")
45
+ lines.push("")
46
+
47
+ lines.push(`channels: ${channels.length}`)
48
+ for (const ch of channels) {
49
+ const attached =
50
+ ch.connectors.length > 0
51
+ ? ch.connectors.map((c) => `${c.name}:${c.type}`).join(", ")
52
+ : "(none)"
53
+ lines.push(` - ${ch.name} [${attached}]`)
54
+ }
55
+ lines.push("")
56
+
57
+ lines.push(`profiles: ${profiles.length}`)
58
+ for (const [index, profile] of profiles.entries()) {
59
+ const tag = index === 0 ? " (default)" : ""
60
+ const channel = funnel.channels.getById(profile.channelId)
61
+ const channelLabel = channel ? channel.name : `id:${profile.channelId}`
62
+
63
+ lines.push(
64
+ ` - ${profile.name}${tag} [path=${profile.path}, sub-agent=${profile.subAgent}, channel=${channelLabel}]`,
65
+ )
66
+ }
67
+ lines.push("")
68
+
69
+ if (!gatewayStatus.running) {
70
+ lines.push("gateway: not running")
71
+ } else {
72
+ lines.push(`gateway: running (pid ${gatewayStatus.pid}, port ${gatewayStatus.port})`)
73
+
74
+ const res = await fetch(`http://localhost:${gatewayStatus.port}/status`).catch(() => null)
75
+
76
+ if (res && res.ok) {
77
+ const body: unknown = await res.json()
78
+
79
+ if (isGatewayStatus(body)) {
80
+ lines.push(` clients: ${body.clients.length}`)
81
+
82
+ for (const client of body.clients) {
83
+ const connectorList =
84
+ client.connectors.length > 0 ? client.connectors.join(", ") : "(none)"
85
+ lines.push(` - channel=${client.channel || "(unset)"} [${connectorList}]`)
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ return c.text(lines.join("\n"))
92
+ },
93
+ )
@@ -0,0 +1,27 @@
1
+ import { HTTPException } from "hono/http-exception"
2
+ import { z } from "zod"
3
+ import { factory } from "@/cli/factory"
4
+ import { NodeFunnelProcessRunner } from "@/engine/process/node-process-runner"
5
+ import { zValidator } from "@/cli/router/validator"
6
+
7
+ export const updateHelp = `funnel update — update funnel to the latest version
8
+
9
+ usage: funnel update
10
+
11
+ Runs "bun i -g @interactive-inc/claude-funnel".`
12
+
13
+ const PACKAGE = "@interactive-inc/claude-funnel"
14
+
15
+ export const updateHandler = factory.createHandlers(
16
+ zValidator("query", z.object({}), updateHelp),
17
+ async (c) => {
18
+ const runner = new NodeFunnelProcessRunner()
19
+ const exitCode = await runner.attach(["bun", "i", "-g", PACKAGE])
20
+
21
+ if (exitCode !== 0) {
22
+ throw new HTTPException(500, { message: `update failed (exit ${exitCode})` })
23
+ }
24
+
25
+ return c.text(`updated ${PACKAGE}`)
26
+ },
27
+ )
@@ -0,0 +1,16 @@
1
+ import { z } from "zod"
2
+ import { discordConnectorSchema } from "@/connectors/discord-connector-schema"
3
+ import { ghConnectorSchema } from "@/connectors/gh-connector-schema"
4
+ import { scheduleConnectorSchema } from "@/connectors/schedule-connector-schema"
5
+ import { slackConnectorSchema } from "@/connectors/slack-connector-schema"
6
+
7
+ export const connectorConfigSchema = z.discriminatedUnion("type", [
8
+ slackConnectorSchema,
9
+ ghConnectorSchema,
10
+ discordConnectorSchema,
11
+ scheduleConnectorSchema,
12
+ ])
13
+
14
+ export type ConnectorConfig = z.infer<typeof connectorConfigSchema>
15
+
16
+ export type ConnectorType = ConnectorConfig["type"]
@@ -0,0 +1,94 @@
1
+ import type { FunnelConnectorAdapter } from "@/connectors/connector-adapter"
2
+ import type { ConnectorConfig } from "@/connectors/connector-config-schema"
3
+ import type { FunnelConnectorListener } from "@/connectors/connector-listener"
4
+ import { FunnelDiscordAdapter } from "@/connectors/discord-adapter"
5
+ import { FunnelDiscordListener } from "@/connectors/discord-listener"
6
+ import { FunnelGhAdapter } from "@/connectors/gh-adapter"
7
+ import { FunnelGhListener } from "@/connectors/gh-listener"
8
+ import { FunnelScheduleListener } from "@/connectors/schedule-listener"
9
+ import { ScheduleStateStore } from "@/connectors/schedule-state-store"
10
+ import { FunnelSlackAdapter } from "@/connectors/slack-adapter"
11
+ import { FunnelSlackListener } from "@/connectors/slack-listener"
12
+ import { FunnelFileSystem } from "@/engine/fs/file-system"
13
+ import { NodeFunnelFileSystem } from "@/engine/fs/node-file-system"
14
+ import { FunnelLogger } from "@/engine/logger/logger"
15
+ import { NodeFunnelLogger } from "@/engine/logger/node-logger"
16
+ import { FunnelProcessRunner } from "@/engine/process/process-runner"
17
+ import { NodeFunnelProcessRunner } from "@/engine/process/node-process-runner"
18
+ import { FUNNEL_DIR } from "@/engine/settings/settings-store"
19
+ import { join } from "node:path"
20
+
21
+ type Deps = {
22
+ fs?: FunnelFileSystem
23
+ process?: FunnelProcessRunner
24
+ logger?: FunnelLogger
25
+ dir?: string
26
+ }
27
+
28
+ const defaultFs = new NodeFunnelFileSystem()
29
+ const defaultProcess = new NodeFunnelProcessRunner()
30
+ const defaultLogger = new NodeFunnelLogger()
31
+
32
+ /**
33
+ * Pure factory for per-type listeners and adapters. The factory has no CRUD
34
+ * responsibility — connector configs live inside settings.json under their
35
+ * channel, and FunnelChannels passes them in by value.
36
+ *
37
+ * `dir` is the funnel home (defaults to ~/.funnel); per-connector state files
38
+ * land at `<dir>/channels/<channel-id>/connectors/<connector-id>/state.json`.
39
+ */
40
+ export class FunnelConnectorFactory {
41
+ private readonly fs: FunnelFileSystem
42
+ private readonly process: FunnelProcessRunner
43
+ private readonly logger: FunnelLogger
44
+ private readonly dir: string
45
+
46
+ constructor(deps: Deps = {}) {
47
+ this.fs = deps.fs ?? defaultFs
48
+ this.process = deps.process ?? defaultProcess
49
+ this.logger = deps.logger ?? defaultLogger
50
+ this.dir = deps.dir ?? FUNNEL_DIR
51
+ Object.freeze(this)
52
+ }
53
+
54
+ createListener(channelId: string, config: ConnectorConfig): FunnelConnectorListener {
55
+ if (config.type === "slack") {
56
+ return new FunnelSlackListener({ config, logger: this.logger })
57
+ }
58
+
59
+ if (config.type === "gh") {
60
+ return new FunnelGhListener({ config, process: this.process, logger: this.logger })
61
+ }
62
+
63
+ if (config.type === "discord") {
64
+ return new FunnelDiscordListener({ config, logger: this.logger })
65
+ }
66
+
67
+ const lastFiredStore = new ScheduleStateStore({
68
+ path: join(this.connectorDir(channelId, config.id), "state.json"),
69
+ fs: this.fs,
70
+ })
71
+
72
+ return new FunnelScheduleListener({
73
+ config,
74
+ lastFiredStore,
75
+ logger: this.logger,
76
+ })
77
+ }
78
+
79
+ createAdapter(config: ConnectorConfig): FunnelConnectorAdapter | null {
80
+ if (config.type === "slack") return new FunnelSlackAdapter({ config })
81
+ if (config.type === "gh") return new FunnelGhAdapter({ process: this.process })
82
+ if (config.type === "discord") return new FunnelDiscordAdapter({ config })
83
+
84
+ return null
85
+ }
86
+
87
+ connectorDir(channelId: string, connectorId: string): string {
88
+ return join(this.dir, "channels", channelId, "connectors", connectorId)
89
+ }
90
+
91
+ channelDir(channelId: string): string {
92
+ return join(this.dir, "channels", channelId)
93
+ }
94
+ }
@@ -0,0 +1,20 @@
1
+ export type NotifyFn = (content: string, meta?: Record<string, string>) => Promise<void>
2
+
3
+ /**
4
+ * Long-lived event source for one connector.
5
+ *
6
+ * `start()` opens the underlying connection (Slack Socket Mode, Discord
7
+ * Gateway, GH polling, schedule tick) and pushes events through `notify`.
8
+ * `stop()` releases the resources so the supervisor can recreate the listener
9
+ * with new config without restarting the whole gateway. `isAlive()` lets the
10
+ * supervisor periodically health-check and auto-restart dead listeners; the
11
+ * default optimistic implementation is fine for poll/tick-based listeners
12
+ * that self-heal.
13
+ */
14
+ export abstract class FunnelConnectorListener {
15
+ abstract start(notify: NotifyFn): Promise<void>
16
+ abstract stop(): Promise<void>
17
+ isAlive(): boolean {
18
+ return true
19
+ }
20
+ }
@@ -1,10 +1,7 @@
1
- import {
2
- FunnelConnectorAdapter,
3
- type CallInput,
4
- } from "@/modules/connectors/funnel-connector-adapter"
5
- import { FunnelHttpClient } from "@/modules/http/funnel-http-client"
6
- import { NodeFunnelHttpClient } from "@/modules/http/node-funnel-http-client"
7
- import type { DiscordConnectorConfig } from "@/modules/connectors/discord-connector-schema"
1
+ import { FunnelConnectorAdapter, type CallInput } from "@/connectors/connector-adapter"
2
+ import { FunnelHttpClient } from "@/engine/http/http-client"
3
+ import { NodeFunnelHttpClient } from "@/engine/http/node-http-client"
4
+ import type { DiscordConnectorConfig } from "@/connectors/discord-connector-schema"
8
5
 
9
6
  const DISCORD_API_BASE = "https://discord.com/api/v10"
10
7
 
@@ -29,11 +26,9 @@ export class FunnelDiscordAdapter extends FunnelConnectorAdapter {
29
26
  async call(input: CallInput): Promise<unknown> {
30
27
  const method = (input.method || "GET").toUpperCase()
31
28
  const path = input.path.startsWith("/") ? input.path : `/${input.path}`
29
+ const body = input.body
32
30
  const hasBody =
33
- input.body &&
34
- typeof input.body === "object" &&
35
- method !== "GET" &&
36
- Object.keys(input.body as object).length > 0
31
+ body !== null && typeof body === "object" && method !== "GET" && Object.keys(body).length > 0
37
32
 
38
33
  const res = await this.http.fetch({
39
34
  method,
@@ -1,9 +1,12 @@
1
1
  import { z } from "zod"
2
2
 
3
3
  export const discordConnectorSchema = z.object({
4
- type: z.literal("discord"),
4
+ id: z.string(),
5
5
  name: z.string(),
6
+ type: z.literal("discord"),
6
7
  botToken: z.string().min(10),
8
+ createdAt: z.string().datetime().optional(),
9
+ updatedAt: z.string().datetime().optional(),
7
10
  })
8
11
 
9
12
  export type DiscordConnectorConfig = z.infer<typeof discordConnectorSchema>
@@ -0,0 +1,111 @@
1
+ import { Client, GatewayIntentBits, Partials } from "discord.js"
2
+ import { FunnelConnectorListener, type NotifyFn } from "@/connectors/connector-listener"
3
+ import { FunnelDiscordEventProcessor } from "@/connectors/discord-event-processor"
4
+ import { FunnelLogger } from "@/engine/logger/logger"
5
+ import { NodeFunnelLogger } from "@/engine/logger/node-logger"
6
+ import type { DiscordConnectorConfig } from "@/connectors/discord-connector-schema"
7
+
8
+ type Deps = {
9
+ config: DiscordConnectorConfig
10
+ logger?: FunnelLogger
11
+ }
12
+
13
+ const defaultLogger = new NodeFunnelLogger()
14
+
15
+ export class FunnelDiscordListener extends FunnelConnectorListener {
16
+ private readonly config: DiscordConnectorConfig
17
+ private readonly logger: FunnelLogger
18
+ private client: Client | null = null
19
+
20
+ constructor(deps: Deps) {
21
+ super()
22
+ this.config = deps.config
23
+ this.logger = deps.logger ?? defaultLogger
24
+ }
25
+
26
+ async start(notify: NotifyFn): Promise<void> {
27
+ const client = new Client({
28
+ intents: [
29
+ GatewayIntentBits.Guilds,
30
+ GatewayIntentBits.GuildMessages,
31
+ GatewayIntentBits.MessageContent,
32
+ GatewayIntentBits.DirectMessages,
33
+ ],
34
+ partials: [Partials.Channel],
35
+ })
36
+
37
+ client.on("messageCreate", async (message) => {
38
+ const ownUserId = client.user?.id ?? ""
39
+ const mentionedUserIds = [...message.mentions.users.keys()]
40
+
41
+ this.logger.info("discord messageCreate", {
42
+ author: message.author.id,
43
+ authorIsBot: String(message.author.bot),
44
+ channelId: message.channelId,
45
+ guildId: message.guildId ?? "",
46
+ mentions: mentionedUserIds.join(","),
47
+ ownUserId,
48
+ mentioned: String(mentionedUserIds.includes(ownUserId)),
49
+ })
50
+
51
+ const processor = new FunnelDiscordEventProcessor({ ownUserId })
52
+
53
+ const result = processor.process({
54
+ authorId: message.author.id,
55
+ authorIsBot: message.author.bot,
56
+ channelId: message.channelId,
57
+ guildId: message.guildId,
58
+ mentionedUserIds,
59
+ raw: message.toJSON(),
60
+ })
61
+
62
+ if (result.skip) {
63
+ this.logger.info("discord skip", { reason: "bot author" })
64
+ return
65
+ }
66
+
67
+ try {
68
+ await notify(result.content, result.meta)
69
+ } catch (error) {
70
+ this.logger.error("discord notify error", {
71
+ error: error instanceof Error ? error.message : String(error),
72
+ })
73
+ }
74
+ })
75
+
76
+ client.on("ready", (readyClient) => {
77
+ this.logger.info("discord ready", {
78
+ userId: readyClient.user.id,
79
+ tag: readyClient.user.tag,
80
+ guilds: String(readyClient.guilds.cache.size),
81
+ })
82
+ })
83
+
84
+ client.on("error", (error) => {
85
+ this.logger.error("discord client error", {
86
+ error: error instanceof Error ? error.message : String(error),
87
+ })
88
+ })
89
+
90
+ await client.login(this.config.botToken)
91
+ this.client = client
92
+ }
93
+
94
+ async stop(): Promise<void> {
95
+ if (!this.client) return
96
+
97
+ try {
98
+ await this.client.destroy()
99
+ } catch (error) {
100
+ this.logger.error("discord stop error", {
101
+ error: error instanceof Error ? error.message : String(error),
102
+ })
103
+ } finally {
104
+ this.client = null
105
+ }
106
+ }
107
+
108
+ override isAlive(): boolean {
109
+ return this.client !== null
110
+ }
111
+ }
@@ -1,9 +1,6 @@
1
- import {
2
- FunnelConnectorAdapter,
3
- type CallInput,
4
- } from "@/modules/connectors/funnel-connector-adapter"
5
- import { FunnelProcessRunner } from "@/modules/process/funnel-process-runner"
6
- import { NodeFunnelProcessRunner } from "@/modules/process/node-funnel-process-runner"
1
+ import { FunnelConnectorAdapter, type CallInput } from "@/connectors/connector-adapter"
2
+ import { FunnelProcessRunner } from "@/engine/process/process-runner"
3
+ import { NodeFunnelProcessRunner } from "@/engine/process/node-process-runner"
7
4
 
8
5
  type Deps = {
9
6
  process?: FunnelProcessRunner
@@ -1,9 +1,12 @@
1
1
  import { z } from "zod"
2
2
 
3
3
  export const ghConnectorSchema = z.object({
4
- type: z.literal("gh"),
4
+ id: z.string(),
5
5
  name: z.string(),
6
+ type: z.literal("gh"),
6
7
  pollInterval: z.number().int().positive().optional(),
8
+ createdAt: z.string().datetime().optional(),
9
+ updatedAt: z.string().datetime().optional(),
7
10
  })
8
11
 
9
12
  export type GhConnectorConfig = z.infer<typeof ghConnectorSchema>