@interactive-inc/claude-funnel 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (349) hide show
  1. package/README.md +155 -133
  2. package/dist/bin.js +1417 -0
  3. package/dist/gateway/daemon.js +513 -0
  4. package/dist/highlights-eq9cgrbb.scm +604 -0
  5. package/dist/highlights-ghv9g403.scm +205 -0
  6. package/dist/highlights-hk7bwhj4.scm +284 -0
  7. package/dist/highlights-r812a2qc.scm +150 -0
  8. package/dist/highlights-x6tmsnaa.scm +115 -0
  9. package/dist/injections-73j83es3.scm +27 -0
  10. package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  11. package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
  12. package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  13. package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  14. package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
  15. package/lib/bin.ts +78 -0
  16. package/lib/{modules → cli}/router/to-request.ts +13 -20
  17. package/lib/cli/routes/channels.$channel.connectors.$connector.rename.$newName.ts +27 -0
  18. package/lib/cli/routes/channels.$channel.connectors.$connector.request.ts +40 -0
  19. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.ts +41 -0
  20. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.ts +22 -0
  21. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.ts +23 -0
  22. package/lib/cli/routes/channels.$channel.connectors.$connector.ts +26 -0
  23. package/lib/cli/routes/channels.$channel.connectors.add.$connector.ts +92 -0
  24. package/lib/cli/routes/channels.$channel.connectors.remove.$connector.ts +22 -0
  25. package/lib/cli/routes/channels.$channel.connectors.set.$connector.ts +63 -0
  26. package/lib/cli/routes/channels.$channel.connectors.ts +26 -0
  27. package/lib/cli/routes/channels.$channel.rename.$newName.ts +22 -0
  28. package/lib/cli/routes/channels.$channel.set.delivery.$mode.ts +34 -0
  29. package/lib/cli/routes/channels.$channel.ts +34 -0
  30. package/lib/cli/routes/channels.add.$channel.ts +33 -0
  31. package/lib/cli/routes/channels.remove.$channel.ts +20 -0
  32. package/lib/cli/routes/channels.ts +39 -0
  33. package/lib/cli/routes/claude.ts +69 -0
  34. package/lib/cli/routes/gateway.listeners.ts +41 -0
  35. package/lib/cli/routes/gateway.logs.ts +123 -0
  36. package/lib/{routes/gateway/restart.ts → cli/routes/gateway.restart.ts} +20 -5
  37. package/lib/cli/routes/gateway.run.ts +41 -0
  38. package/lib/cli/routes/gateway.start.ts +50 -0
  39. package/lib/cli/routes/gateway.status.ts +19 -0
  40. package/lib/cli/routes/gateway.stop.ts +32 -0
  41. package/lib/cli/routes/gateway.ts +55 -0
  42. package/lib/cli/routes/index.ts +202 -0
  43. package/lib/cli/routes/profiles.$profile.as-default.ts +22 -0
  44. package/lib/cli/routes/profiles.$profile.rename.$newName.ts +22 -0
  45. package/lib/cli/routes/profiles.$profile.run.ts +36 -0
  46. package/lib/cli/routes/profiles.add.$profile.ts +46 -0
  47. package/lib/cli/routes/profiles.remove.$profile.ts +20 -0
  48. package/lib/cli/routes/profiles.set.$profile.ts +46 -0
  49. package/lib/cli/routes/profiles.ts +40 -0
  50. package/lib/cli/routes/status.ts +93 -0
  51. package/lib/cli/routes/update.ts +27 -0
  52. package/lib/connectors/connector-config-schema.ts +16 -0
  53. package/lib/connectors/connector-factory.ts +94 -0
  54. package/lib/connectors/connector-listener.ts +20 -0
  55. package/lib/{modules/connectors/funnel-discord-adapter.ts → connectors/discord-adapter.ts} +6 -11
  56. package/lib/{modules/connectors → connectors}/discord-connector-schema.ts +4 -1
  57. package/lib/connectors/discord-listener.ts +111 -0
  58. package/lib/{modules/connectors/funnel-gh-adapter.ts → connectors/gh-adapter.ts} +3 -6
  59. package/lib/{modules/connectors → connectors}/gh-connector-schema.ts +4 -1
  60. package/lib/{modules/connectors/funnel-gh-listener.ts → connectors/gh-listener.ts} +45 -19
  61. package/lib/{modules/connectors → connectors}/match-cron.ts +10 -4
  62. package/lib/connectors/schedule-connector-schema.ts +33 -0
  63. package/lib/connectors/schedule-listener.ts +207 -0
  64. package/lib/connectors/schedule-state-store.ts +54 -0
  65. package/lib/connectors/slack-adapter.ts +36 -0
  66. package/lib/{modules/connectors → connectors}/slack-connector-schema.ts +4 -1
  67. package/lib/{modules/connectors/funnel-slack-event-processor.ts → connectors/slack-event-processor.ts} +15 -9
  68. package/lib/{modules/connectors/funnel-slack-listener.ts → connectors/slack-listener.ts} +33 -14
  69. package/lib/engine/channels/channels.ts +520 -0
  70. package/lib/{modules/claude/funnel-claude.ts → engine/claude/claude.ts} +28 -55
  71. package/lib/engine/claude/gateway-controller.ts +4 -0
  72. package/lib/{modules/fs/funnel-file-system.ts → engine/fs/file-system.ts} +4 -0
  73. package/lib/{modules/fs/memory-funnel-file-system.ts → engine/fs/memory-file-system.ts} +20 -3
  74. package/lib/{modules/fs/node-funnel-file-system.ts → engine/fs/node-file-system.ts} +14 -2
  75. package/lib/{modules/http/memory-funnel-http-client.ts → engine/http/memory-http-client.ts} +1 -5
  76. package/lib/{modules/http/node-funnel-http-client.ts → engine/http/node-http-client.ts} +1 -5
  77. package/lib/{modules/id/memory-funnel-id-generator.ts → engine/id/memory-id-generator.ts} +1 -1
  78. package/lib/{modules/id/node-funnel-id-generator.ts → engine/id/node-id-generator.ts} +1 -1
  79. package/lib/{modules/logger/memory-funnel-logger.ts → engine/logger/memory-logger.ts} +1 -1
  80. package/lib/{modules/logger/node-funnel-logger.ts → engine/logger/node-logger.ts} +1 -1
  81. package/lib/{modules/logger/noop-funnel-logger.ts → engine/logger/noop-logger.ts} +1 -1
  82. package/lib/engine/mcp/channel-server.ts +204 -0
  83. package/lib/{modules/mcp/funnel-mcp.ts → engine/mcp/mcp.ts} +24 -10
  84. package/lib/{modules/process/memory-funnel-process-runner.ts → engine/process/memory-process-runner.ts} +1 -1
  85. package/lib/{modules/process/node-funnel-process-runner.ts → engine/process/node-process-runner.ts} +12 -21
  86. package/lib/engine/profiles/profile-channel-checker.ts +7 -0
  87. package/lib/{modules/profiles/funnel-profiles.ts → engine/profiles/profiles.ts} +41 -43
  88. package/lib/{modules/settings/mock-funnel-settings-reader.ts → engine/settings/mock-settings-reader.ts} +4 -3
  89. package/lib/{modules/settings/funnel-settings-reader.ts → engine/settings/settings-reader.ts} +1 -1
  90. package/lib/engine/settings/settings-schema.ts +46 -0
  91. package/lib/engine/settings/settings-store.ts +110 -0
  92. package/lib/{modules/time/memory-funnel-clock.ts → engine/time/memory-clock.ts} +1 -1
  93. package/lib/{modules/time/node-funnel-clock.ts → engine/time/node-clock.ts} +1 -1
  94. package/lib/funnel.ts +83 -78
  95. package/lib/gateway/auth-middleware.ts +44 -0
  96. package/lib/gateway/broadcaster.ts +319 -0
  97. package/lib/gateway/daemon.ts +47 -0
  98. package/lib/gateway/factory.ts +10 -0
  99. package/lib/gateway/funnel-event-store.ts +155 -0
  100. package/lib/gateway/gateway-server.ts +414 -0
  101. package/lib/gateway/gateway-token.ts +79 -0
  102. package/lib/{modules/gateway/funnel-gateway.ts → gateway/gateway.ts} +27 -13
  103. package/lib/{modules/gateway → gateway}/kill-competing-slack-gateways.ts +4 -4
  104. package/lib/gateway/listener-supervisor.ts +339 -0
  105. package/lib/gateway/listeners-client.ts +128 -0
  106. package/lib/gateway/resolve-daemon-script.ts +26 -0
  107. package/lib/gateway/routes/channels.connectors.call.ts +39 -0
  108. package/lib/gateway/routes/health.ts +13 -0
  109. package/lib/gateway/routes/index.ts +24 -0
  110. package/lib/gateway/routes/listeners.list.ts +6 -0
  111. package/lib/gateway/routes/listeners.restart.ts +15 -0
  112. package/lib/gateway/routes/listeners.start.ts +15 -0
  113. package/lib/gateway/routes/listeners.stop.ts +15 -0
  114. package/lib/gateway/routes/route-deps.ts +11 -0
  115. package/lib/gateway/routes/status.ts +15 -0
  116. package/lib/gateway/routes/validator.ts +17 -0
  117. package/lib/index.ts +50 -92
  118. package/lib/logger/leuco-human-file-writer.ts +65 -0
  119. package/lib/logger/leuco-human-logger.ts +98 -0
  120. package/lib/logger/leuco-human-record.ts +16 -0
  121. package/lib/logger/leuco-human-stdout-writer.ts +26 -0
  122. package/lib/logger/leuco-human-writer.ts +14 -0
  123. package/lib/logger/leuco-logger-memory-sink.ts +67 -0
  124. package/lib/logger/leuco-logger-record.ts +13 -0
  125. package/lib/logger/leuco-logger-sink.ts +33 -0
  126. package/lib/logger/leuco-logger-sqlite-sink.ts +355 -0
  127. package/lib/logger/leuco-logger.ts +135 -0
  128. package/lib/tui/app.tsx +357 -0
  129. package/lib/tui/components/add-row.tsx +18 -0
  130. package/lib/tui/components/brand.tsx +27 -0
  131. package/lib/tui/components/card.tsx +44 -0
  132. package/lib/tui/components/detail-bar.tsx +46 -0
  133. package/lib/tui/components/editable-field.tsx +33 -0
  134. package/lib/tui/components/empty-state.tsx +11 -0
  135. package/lib/tui/components/gateway-status.tsx +66 -0
  136. package/lib/tui/components/keymap.tsx +29 -0
  137. package/lib/tui/components/menu-item.tsx +73 -0
  138. package/lib/tui/components/menu.tsx +26 -0
  139. package/lib/tui/components/panel-header.tsx +22 -0
  140. package/lib/tui/components/readonly-field.tsx +18 -0
  141. package/lib/tui/components/section-header.tsx +25 -0
  142. package/lib/tui/components/selection-accent.tsx +32 -0
  143. package/lib/tui/components/session-item.tsx +33 -0
  144. package/lib/tui/components/session-list.tsx +33 -0
  145. package/lib/tui/components/ui/hascii/accordion-item.tsx +88 -0
  146. package/lib/tui/components/ui/hascii/accordion.tsx +96 -0
  147. package/lib/tui/components/ui/hascii/alert-dialog.tsx +43 -0
  148. package/lib/tui/components/ui/hascii/badge.tsx +51 -0
  149. package/lib/tui/components/ui/hascii/breadcrumb.tsx +58 -0
  150. package/lib/tui/components/ui/hascii/button.tsx +194 -0
  151. package/lib/tui/components/ui/hascii/card-content.tsx +14 -0
  152. package/lib/tui/components/ui/hascii/card-description.tsx +13 -0
  153. package/lib/tui/components/ui/hascii/card-footer.tsx +14 -0
  154. package/lib/tui/components/ui/hascii/card-header.tsx +14 -0
  155. package/lib/tui/components/ui/hascii/card-title.tsx +13 -0
  156. package/lib/tui/components/ui/hascii/card.tsx +27 -0
  157. package/lib/tui/components/ui/hascii/checkbox.tsx +65 -0
  158. package/lib/tui/components/ui/hascii/command.tsx +159 -0
  159. package/lib/tui/components/ui/hascii/dialog-content.tsx +14 -0
  160. package/lib/tui/components/ui/hascii/dialog-description.tsx +13 -0
  161. package/lib/tui/components/ui/hascii/dialog-footer.tsx +14 -0
  162. package/lib/tui/components/ui/hascii/dialog-header.tsx +14 -0
  163. package/lib/tui/components/ui/hascii/dialog-title.tsx +13 -0
  164. package/lib/tui/components/ui/hascii/dialog.tsx +27 -0
  165. package/lib/tui/components/ui/hascii/file-tree.tsx +142 -0
  166. package/lib/tui/components/ui/hascii/focus-group.tsx +62 -0
  167. package/lib/tui/components/ui/hascii/form-item.tsx +43 -0
  168. package/lib/tui/components/ui/hascii/input-otp.tsx +86 -0
  169. package/lib/tui/components/ui/hascii/input.tsx +130 -0
  170. package/lib/tui/components/ui/hascii/pagination.tsx +105 -0
  171. package/lib/tui/components/ui/hascii/progress.tsx +28 -0
  172. package/lib/tui/components/ui/hascii/select.tsx +131 -0
  173. package/lib/tui/components/ui/hascii/separator.tsx +35 -0
  174. package/lib/tui/components/ui/hascii/sidebar-content.tsx +23 -0
  175. package/lib/tui/components/ui/hascii/sidebar-header.tsx +14 -0
  176. package/lib/tui/components/ui/hascii/sidebar-menu-item.tsx +67 -0
  177. package/lib/tui/components/ui/hascii/sidebar.tsx +24 -0
  178. package/lib/tui/components/ui/hascii/skeleton.tsx +60 -0
  179. package/lib/tui/components/ui/hascii/slider.tsx +91 -0
  180. package/lib/tui/components/ui/hascii/snackbar.tsx +75 -0
  181. package/lib/tui/components/ui/hascii/sparkline.tsx +53 -0
  182. package/lib/tui/components/ui/hascii/spinner.tsx +47 -0
  183. package/lib/tui/components/ui/hascii/stepper.tsx +54 -0
  184. package/lib/tui/components/ui/hascii/switch.tsx +66 -0
  185. package/lib/tui/components/ui/hascii/table.tsx +95 -0
  186. package/lib/tui/components/ui/hascii/tabs.tsx +59 -0
  187. package/lib/tui/components/ui/hascii/toggle-group-item.tsx +45 -0
  188. package/lib/tui/components/ui/hascii/toggle-group.tsx +99 -0
  189. package/lib/tui/components/ui/hascii/tree.tsx +104 -0
  190. package/lib/tui/components/view-shell.tsx +44 -0
  191. package/lib/tui/filter-input.tsx +33 -0
  192. package/lib/tui/hooks/hascii/use-pressable.ts +54 -0
  193. package/lib/tui/parse-comma-list.ts +14 -0
  194. package/lib/tui/profile-launcher.tsx +61 -0
  195. package/lib/tui/scrollbar-options.ts +19 -0
  196. package/lib/tui/sidebar.tsx +50 -0
  197. package/lib/tui/theme.ts +40 -0
  198. package/lib/tui/tui.tsx +20 -0
  199. package/lib/tui/types.ts +38 -0
  200. package/lib/tui/unique-name.ts +18 -0
  201. package/lib/tui/use-event-stream.ts +133 -0
  202. package/lib/tui/use-snapshot.ts +99 -0
  203. package/lib/tui/utils/hascii/form-item-context.tsx +23 -0
  204. package/lib/tui/utils/hascii/input-focus-context.tsx +31 -0
  205. package/lib/tui/utils/hascii/theme-context.tsx +26 -0
  206. package/lib/tui/utils/hascii/theme.ts +176 -0
  207. package/lib/tui/views/channels-view.tsx +108 -0
  208. package/lib/tui/views/connectors-view.tsx +164 -0
  209. package/lib/tui/views/events-view.tsx +160 -0
  210. package/lib/tui/views/listeners-view.tsx +80 -0
  211. package/lib/tui/views/profiles-view.tsx +152 -0
  212. package/package.json +50 -44
  213. package/lib/api.ts +0 -54
  214. package/lib/modules/channels/channel-connector-ref-updater.ts +0 -4
  215. package/lib/modules/channels/funnel-channels.ts +0 -160
  216. package/lib/modules/connectors/connector-config-schema.ts +0 -16
  217. package/lib/modules/connectors/connector-existence-checker.ts +0 -3
  218. package/lib/modules/connectors/funnel-callable-connector-store.ts +0 -9
  219. package/lib/modules/connectors/funnel-connector-listener.ts +0 -5
  220. package/lib/modules/connectors/funnel-connector-stores.ts +0 -52
  221. package/lib/modules/connectors/funnel-connector-type-store.ts +0 -24
  222. package/lib/modules/connectors/funnel-connectors.ts +0 -151
  223. package/lib/modules/connectors/funnel-discord-listener.ts +0 -71
  224. package/lib/modules/connectors/funnel-discord-store.ts +0 -88
  225. package/lib/modules/connectors/funnel-gh-store.ts +0 -101
  226. package/lib/modules/connectors/funnel-json-connector-store.ts +0 -100
  227. package/lib/modules/connectors/funnel-schedule-listener.ts +0 -130
  228. package/lib/modules/connectors/funnel-schedule-store.ts +0 -195
  229. package/lib/modules/connectors/funnel-slack-adapter.ts +0 -31
  230. package/lib/modules/connectors/funnel-slack-store.ts +0 -90
  231. package/lib/modules/connectors/migrate-legacy-connectors.ts +0 -81
  232. package/lib/modules/connectors/schedule-connector-schema.ts +0 -18
  233. package/lib/modules/connectors/schedule-last-fired-store.ts +0 -48
  234. package/lib/modules/gateway/daemon.ts +0 -74
  235. package/lib/modules/gateway/funnel-broadcaster.ts +0 -37
  236. package/lib/modules/gateway/funnel-event-logger.ts +0 -59
  237. package/lib/modules/gateway/funnel-gateway-server.ts +0 -241
  238. package/lib/modules/mcp/channel-server.ts +0 -76
  239. package/lib/modules/profiles/profile-channel-checker.ts +0 -3
  240. package/lib/modules/profiles/profile-channel-ref-updater.ts +0 -3
  241. package/lib/modules/repos/funnel-repositories.ts +0 -112
  242. package/lib/modules/schedule/funnel-schedule.ts +0 -39
  243. package/lib/modules/settings/funnel-settings-store.ts +0 -56
  244. package/lib/modules/settings/settings-schema.ts +0 -33
  245. package/lib/modules/tui/app.tsx +0 -44
  246. package/lib/modules/tui/tui.tsx +0 -13
  247. package/lib/routes/channels/add.help.ts +0 -3
  248. package/lib/routes/channels/add.ts +0 -21
  249. package/lib/routes/channels/connectors-attach.help.ts +0 -3
  250. package/lib/routes/channels/connectors-attach.ts +0 -17
  251. package/lib/routes/channels/connectors-detach.help.ts +0 -3
  252. package/lib/routes/channels/connectors-detach.ts +0 -17
  253. package/lib/routes/channels/group.help.ts +0 -16
  254. package/lib/routes/channels/group.ts +0 -22
  255. package/lib/routes/channels/remove.help.ts +0 -3
  256. package/lib/routes/channels/remove.ts +0 -17
  257. package/lib/routes/channels/rename.help.ts +0 -5
  258. package/lib/routes/channels/rename.ts +0 -17
  259. package/lib/routes/channels/routes.ts +0 -19
  260. package/lib/routes/channels/show.help.ts +0 -1
  261. package/lib/routes/channels/show.ts +0 -26
  262. package/lib/routes/claude/claude.help.ts +0 -16
  263. package/lib/routes/claude/claude.ts +0 -76
  264. package/lib/routes/claude/routes.ts +0 -4
  265. package/lib/routes/connectors/add.help.ts +0 -28
  266. package/lib/routes/connectors/add.ts +0 -64
  267. package/lib/routes/connectors/group.help.ts +0 -14
  268. package/lib/routes/connectors/group.ts +0 -18
  269. package/lib/routes/connectors/remove.help.ts +0 -3
  270. package/lib/routes/connectors/remove.ts +0 -17
  271. package/lib/routes/connectors/rename.help.ts +0 -5
  272. package/lib/routes/connectors/rename.ts +0 -17
  273. package/lib/routes/connectors/routes.ts +0 -23
  274. package/lib/routes/connectors/schedules-add.help.ts +0 -11
  275. package/lib/routes/connectors/schedules-add.ts +0 -33
  276. package/lib/routes/connectors/schedules-group.help.ts +0 -1
  277. package/lib/routes/connectors/schedules-group.ts +0 -38
  278. package/lib/routes/connectors/schedules-remove.help.ts +0 -3
  279. package/lib/routes/connectors/schedules-remove.ts +0 -17
  280. package/lib/routes/connectors/set.help.ts +0 -8
  281. package/lib/routes/connectors/set.ts +0 -72
  282. package/lib/routes/connectors/show.help.ts +0 -1
  283. package/lib/routes/connectors/show.ts +0 -41
  284. package/lib/routes/gateway/group.help.ts +0 -15
  285. package/lib/routes/gateway/group.ts +0 -28
  286. package/lib/routes/gateway/logs.help.ts +0 -13
  287. package/lib/routes/gateway/logs.ts +0 -102
  288. package/lib/routes/gateway/restart.help.ts +0 -10
  289. package/lib/routes/gateway/routes.ts +0 -18
  290. package/lib/routes/gateway/run.help.ts +0 -12
  291. package/lib/routes/gateway/run.ts +0 -35
  292. package/lib/routes/gateway/start.help.ts +0 -15
  293. package/lib/routes/gateway/start.ts +0 -32
  294. package/lib/routes/gateway/status.help.ts +0 -9
  295. package/lib/routes/gateway/status.ts +0 -28
  296. package/lib/routes/gateway/stop.help.ts +0 -8
  297. package/lib/routes/gateway/stop.ts +0 -21
  298. package/lib/routes/profiles/add.help.ts +0 -3
  299. package/lib/routes/profiles/add.ts +0 -33
  300. package/lib/routes/profiles/group.help.ts +0 -16
  301. package/lib/routes/profiles/group.ts +0 -25
  302. package/lib/routes/profiles/launch.help.ts +0 -4
  303. package/lib/routes/profiles/launch.ts +0 -36
  304. package/lib/routes/profiles/remove.help.ts +0 -3
  305. package/lib/routes/profiles/remove.ts +0 -17
  306. package/lib/routes/profiles/rename.help.ts +0 -5
  307. package/lib/routes/profiles/rename.ts +0 -17
  308. package/lib/routes/profiles/routes.ts +0 -18
  309. package/lib/routes/profiles/set.help.ts +0 -5
  310. package/lib/routes/profiles/set.ts +0 -32
  311. package/lib/routes/repos/add.help.ts +0 -6
  312. package/lib/routes/repos/add.ts +0 -20
  313. package/lib/routes/repos/group.help.ts +0 -11
  314. package/lib/routes/repos/group.ts +0 -18
  315. package/lib/routes/repos/remove.help.ts +0 -3
  316. package/lib/routes/repos/remove.ts +0 -17
  317. package/lib/routes/repos/rename.help.ts +0 -5
  318. package/lib/routes/repos/rename.ts +0 -17
  319. package/lib/routes/repos/routes.ts +0 -17
  320. package/lib/routes/repos/set.help.ts +0 -5
  321. package/lib/routes/repos/set.ts +0 -21
  322. package/lib/routes/repos/show.help.ts +0 -1
  323. package/lib/routes/repos/show.ts +0 -19
  324. package/lib/routes/request/discord-help.ts +0 -9
  325. package/lib/routes/request/discord.help.ts +0 -19
  326. package/lib/routes/request/discord.ts +0 -65
  327. package/lib/routes/request/group.help.ts +0 -15
  328. package/lib/routes/request/group.ts +0 -9
  329. package/lib/routes/request/routes.ts +0 -14
  330. package/lib/routes/request/slack-help.ts +0 -9
  331. package/lib/routes/request/slack.help.ts +0 -19
  332. package/lib/routes/request/slack.ts +0 -61
  333. package/lib/routes/status/routes.ts +0 -4
  334. package/lib/routes/status/status.help.ts +0 -6
  335. package/lib/routes/status/status.ts +0 -77
  336. package/lib/routes/update/routes.ts +0 -4
  337. package/lib/routes/update/update.help.ts +0 -5
  338. package/lib/routes/update/update.ts +0 -21
  339. package/lib/routes.ts +0 -40
  340. /package/lib/{factory.ts → cli/factory.ts} +0 -0
  341. /package/lib/{modules → cli}/router/query-to-cli-args.ts +0 -0
  342. /package/lib/{modules → cli}/router/validator.ts +0 -0
  343. /package/lib/{modules/connectors/funnel-connector-adapter.ts → connectors/connector-adapter.ts} +0 -0
  344. /package/lib/{modules/connectors/funnel-discord-event-processor.ts → connectors/discord-event-processor.ts} +0 -0
  345. /package/lib/{modules/http/funnel-http-client.ts → engine/http/http-client.ts} +0 -0
  346. /package/lib/{modules/id/funnel-id-generator.ts → engine/id/id-generator.ts} +0 -0
  347. /package/lib/{modules/logger/funnel-logger.ts → engine/logger/logger.ts} +0 -0
  348. /package/lib/{modules/process/funnel-process-runner.ts → engine/process/process-runner.ts} +0 -0
  349. /package/lib/{modules/time/funnel-clock.ts → engine/time/clock.ts} +0 -0
@@ -0,0 +1,202 @@
1
+ import { HTTPException } from "hono/http-exception"
2
+ import { factory } from "@/cli/factory"
3
+ import {
4
+ addHelp as channelsAddHelp,
5
+ channelsAddHandler,
6
+ } from "@/cli/routes/channels.add.$channel"
7
+ import { channelsConnectorsGroupHandler } from "@/cli/routes/channels.$channel.connectors"
8
+ import {
9
+ addHelp as channelsConnectorsAddHelp,
10
+ channelsConnectorsAddHandler,
11
+ } from "@/cli/routes/channels.$channel.connectors.add.$connector"
12
+ import {
13
+ channelsConnectorsRemoveHandler,
14
+ removeHelp as channelsConnectorsRemoveHelp,
15
+ } from "@/cli/routes/channels.$channel.connectors.remove.$connector"
16
+ import {
17
+ channelsConnectorsSetHandler,
18
+ setHelp as channelsConnectorsSetHelp,
19
+ } from "@/cli/routes/channels.$channel.connectors.set.$connector"
20
+ import { channelsConnectorsShowHandler } from "@/cli/routes/channels.$channel.connectors.$connector"
21
+ import {
22
+ channelsConnectorsRenameHandler,
23
+ renameHelp as channelsConnectorsRenameHelp,
24
+ } from "@/cli/routes/channels.$channel.connectors.$connector.rename.$newName"
25
+ import { channelsConnectorsRequestHandler } from "@/cli/routes/channels.$channel.connectors.$connector.request"
26
+ import { channelsConnectorsSchedulesGroupHandler } from "@/cli/routes/channels.$channel.connectors.$connector.schedules"
27
+ import {
28
+ addHelp as channelsConnectorsSchedulesAddHelp,
29
+ channelsConnectorsSchedulesAddHandler,
30
+ } from "@/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id"
31
+ import {
32
+ channelsConnectorsSchedulesRemoveHandler,
33
+ removeHelp as channelsConnectorsSchedulesRemoveHelp,
34
+ } from "@/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id"
35
+ import {
36
+ channelsRemoveHandler,
37
+ removeHelp as channelsRemoveHelp,
38
+ } from "@/cli/routes/channels.remove.$channel"
39
+ import {
40
+ channelsRenameHandler,
41
+ renameHelp as channelsRenameHelp,
42
+ } from "@/cli/routes/channels.$channel.rename.$newName"
43
+ import { channelsSetDeliveryHandler } from "@/cli/routes/channels.$channel.set.delivery.$mode"
44
+ import { channelsShowHandler } from "@/cli/routes/channels.$channel"
45
+ import { channelsGroupHandler } from "@/cli/routes/channels"
46
+ import { claudeHandler } from "@/cli/routes/claude"
47
+ import { gatewayGroupHandler } from "@/cli/routes/gateway"
48
+ import { gatewayListenersHandler } from "@/cli/routes/gateway.listeners"
49
+ import { gatewayLogsHandler } from "@/cli/routes/gateway.logs"
50
+ import { gatewayRestartHandler } from "@/cli/routes/gateway.restart"
51
+ import { gatewayRunHandler } from "@/cli/routes/gateway.run"
52
+ import { gatewayStartHandler } from "@/cli/routes/gateway.start"
53
+ import { gatewayStatusHandler } from "@/cli/routes/gateway.status"
54
+ import { gatewayStopHandler } from "@/cli/routes/gateway.stop"
55
+ import {
56
+ addHelp as profilesAddHelp,
57
+ profilesAddHandler,
58
+ } from "@/cli/routes/profiles.add.$profile"
59
+ import { profilesAsDefaultHandler } from "@/cli/routes/profiles.$profile.as-default"
60
+ import {
61
+ profilesRenameHandler,
62
+ renameHelp as profilesRenameHelp,
63
+ } from "@/cli/routes/profiles.$profile.rename.$newName"
64
+ import { profilesLaunchHandler } from "@/cli/routes/profiles.$profile.run"
65
+ import {
66
+ profilesRemoveHandler,
67
+ removeHelp as profilesRemoveHelp,
68
+ } from "@/cli/routes/profiles.remove.$profile"
69
+ import {
70
+ profilesSetHandler,
71
+ setHelp as profilesSetHelp,
72
+ } from "@/cli/routes/profiles.set.$profile"
73
+ import { profilesGroupHandler } from "@/cli/routes/profiles"
74
+ import { statusHandler } from "@/cli/routes/status"
75
+ import { updateHandler } from "@/cli/routes/update"
76
+ import { Funnel } from "@/funnel"
77
+
78
+ const base = factory.createApp()
79
+
80
+ base.use((c, next) => {
81
+ c.set("funnel", new Funnel())
82
+
83
+ return next()
84
+ })
85
+
86
+ base.onError((error, c) => {
87
+ if (error instanceof HTTPException) {
88
+ return c.text(`error: ${error.message}`, error.status)
89
+ }
90
+
91
+ return c.text(`error: ${error instanceof Error ? error.message : String(error)}`, 400)
92
+ })
93
+
94
+ const helpRoute = (text: string) => factory.createHandlers((c) => c.text(text))
95
+
96
+ // All CLI verbs (`add` / `remove` / `set` / `rename` / `as-default` / `request`) map to POST in
97
+ // to-request.ts and stay in the URL as a literal segment. Read paths (list / show / launch) keep GET.
98
+ // Help shortcuts at parameterless URLs return the help text directly so `funnel <verb>` (no args) is
99
+ // informative instead of 404.
100
+ export const app = base
101
+ .get("/claude", ...claudeHandler)
102
+ .get("/channels", ...channelsGroupHandler)
103
+ .post("/channels/add", ...helpRoute(channelsAddHelp))
104
+ .post("/channels/add/:channel", ...channelsAddHandler)
105
+ .post("/channels/remove", ...helpRoute(channelsRemoveHelp))
106
+ .post("/channels/remove/:channel", ...channelsRemoveHandler)
107
+ .post("/channels/rename/:channel/:newName", ...channelsRenameHandler)
108
+ .post("/channels/:channel/rename/:newName", ...channelsRenameHandler)
109
+ .post("/channels/rename", ...helpRoute(channelsRenameHelp))
110
+ .post("/channels/:channel/rename", ...helpRoute(channelsRenameHelp))
111
+ .post("/channels/:channel/set/delivery/:mode", ...channelsSetDeliveryHandler)
112
+ .get("/channels/:channel", ...channelsShowHandler)
113
+ .get("/channels/:channel/connectors", ...channelsConnectorsGroupHandler)
114
+ .post(
115
+ "/channels/:channel/connectors/add",
116
+ ...helpRoute(channelsConnectorsAddHelp),
117
+ )
118
+ .post(
119
+ "/channels/:channel/connectors/add/:connector",
120
+ ...channelsConnectorsAddHandler,
121
+ )
122
+ .post(
123
+ "/channels/:channel/connectors/remove",
124
+ ...helpRoute(channelsConnectorsRemoveHelp),
125
+ )
126
+ .post(
127
+ "/channels/:channel/connectors/remove/:connector",
128
+ ...channelsConnectorsRemoveHandler,
129
+ )
130
+ .post(
131
+ "/channels/:channel/connectors/set",
132
+ ...helpRoute(channelsConnectorsSetHelp),
133
+ )
134
+ .post(
135
+ "/channels/:channel/connectors/set/:connector",
136
+ ...channelsConnectorsSetHandler,
137
+ )
138
+ .post(
139
+ "/channels/:channel/connectors/rename/:connector/:newName",
140
+ ...channelsConnectorsRenameHandler,
141
+ )
142
+ .post(
143
+ "/channels/:channel/connectors/:connector/rename/:newName",
144
+ ...channelsConnectorsRenameHandler,
145
+ )
146
+ .post(
147
+ "/channels/:channel/connectors/rename",
148
+ ...helpRoute(channelsConnectorsRenameHelp),
149
+ )
150
+ .post(
151
+ "/channels/:channel/connectors/:connector/rename",
152
+ ...helpRoute(channelsConnectorsRenameHelp),
153
+ )
154
+ .post(
155
+ "/channels/:channel/connectors/:connector/request",
156
+ ...channelsConnectorsRequestHandler,
157
+ )
158
+ .get("/channels/:channel/connectors/:connector", ...channelsConnectorsShowHandler)
159
+ .get(
160
+ "/channels/:channel/connectors/:connector/schedules",
161
+ ...channelsConnectorsSchedulesGroupHandler,
162
+ )
163
+ .post(
164
+ "/channels/:channel/connectors/:connector/schedules/add",
165
+ ...helpRoute(channelsConnectorsSchedulesAddHelp),
166
+ )
167
+ .post(
168
+ "/channels/:channel/connectors/:connector/schedules/add/:id",
169
+ ...channelsConnectorsSchedulesAddHandler,
170
+ )
171
+ .post(
172
+ "/channels/:channel/connectors/:connector/schedules/remove",
173
+ ...helpRoute(channelsConnectorsSchedulesRemoveHelp),
174
+ )
175
+ .post(
176
+ "/channels/:channel/connectors/:connector/schedules/remove/:id",
177
+ ...channelsConnectorsSchedulesRemoveHandler,
178
+ )
179
+ .get("/profiles", ...profilesGroupHandler)
180
+ .post("/profiles/add", ...helpRoute(profilesAddHelp))
181
+ .post("/profiles/add/:profile", ...profilesAddHandler)
182
+ .post("/profiles/set", ...helpRoute(profilesSetHelp))
183
+ .post("/profiles/set/:profile", ...profilesSetHandler)
184
+ .post("/profiles/remove", ...helpRoute(profilesRemoveHelp))
185
+ .post("/profiles/remove/:profile", ...profilesRemoveHandler)
186
+ .post("/profiles/rename/:profile/:newName", ...profilesRenameHandler)
187
+ .post("/profiles/:profile/rename/:newName", ...profilesRenameHandler)
188
+ .post("/profiles/rename", ...helpRoute(profilesRenameHelp))
189
+ .post("/profiles/:profile/rename", ...helpRoute(profilesRenameHelp))
190
+ .post("/profiles/:profile/as-default", ...profilesAsDefaultHandler)
191
+ .get("/profiles/:profile/run", ...profilesLaunchHandler)
192
+ .get("/profiles/:profile", ...profilesLaunchHandler)
193
+ .get("/gateway", ...gatewayGroupHandler)
194
+ .get("/gateway/status", ...gatewayStatusHandler)
195
+ .get("/gateway/start", ...gatewayStartHandler)
196
+ .get("/gateway/stop", ...gatewayStopHandler)
197
+ .get("/gateway/restart", ...gatewayRestartHandler)
198
+ .get("/gateway/run", ...gatewayRunHandler)
199
+ .get("/gateway/logs", ...gatewayLogsHandler)
200
+ .get("/gateway/listeners", ...gatewayListenersHandler)
201
+ .get("/status", ...statusHandler)
202
+ .get("/update", ...updateHandler)
@@ -0,0 +1,22 @@
1
+ import { z } from "zod"
2
+ import { factory } from "@/cli/factory"
3
+ import { zValidator } from "@/cli/router/validator"
4
+
5
+ export const asDefaultHelp = `funnel profiles <name> as-default — move profile to the front of the list
6
+
7
+ usage: funnel profiles <name> as-default
8
+
9
+ the first profile in the list is treated as the default for fnl claude.`
10
+
11
+ export const profilesAsDefaultHandler = factory.createHandlers(
12
+ zValidator("param", z.object({ profile: z.string() })),
13
+ zValidator("query", z.object({}), asDefaultHelp),
14
+ (c) => {
15
+ const param = c.req.valid("param")
16
+ const funnel = c.var.funnel
17
+
18
+ funnel.profiles.asDefault(param.profile)
19
+
20
+ return c.text(`profile "${param.profile}" is now the default`)
21
+ },
22
+ )
@@ -0,0 +1,22 @@
1
+ import { z } from "zod"
2
+ import { factory } from "@/cli/factory"
3
+ import { zValidator } from "@/cli/router/validator"
4
+
5
+ export const renameHelp = `funnel profiles rename — rename a profile
6
+
7
+ usage:
8
+ funnel profiles rename <old> <new>
9
+ funnel profiles <old> rename <new>`
10
+
11
+ export const profilesRenameHandler = factory.createHandlers(
12
+ zValidator("param", z.object({ profile: z.string(), newName: z.string() })),
13
+ zValidator("query", z.object({}), renameHelp),
14
+ (c) => {
15
+ const param = c.req.valid("param")
16
+ const funnel = c.var.funnel
17
+
18
+ funnel.profiles.rename(param.profile, param.newName)
19
+
20
+ return c.text(`renamed profile "${param.profile}" to "${param.newName}"`)
21
+ },
22
+ )
@@ -0,0 +1,36 @@
1
+ import { HTTPException } from "hono/http-exception"
2
+ import { z } from "zod"
3
+ import { factory } from "@/cli/factory"
4
+ import { queryToCliArgs } from "@/cli/router/query-to-cli-args"
5
+ import { zValidator } from "@/cli/router/validator"
6
+
7
+ export const launchHelp = `funnel profiles <name> run — launch a profile (sugar for fnl claude)
8
+
9
+ usage: funnel profiles <name> run [additional claude args...]
10
+ funnel profiles <name> (alias)`
11
+
12
+ const RESERVED_KEYS: string[] = []
13
+
14
+ export const profilesLaunchHandler = factory.createHandlers(
15
+ zValidator("param", z.object({ profile: z.string() })),
16
+ zValidator("query", z.object({}).passthrough(), launchHelp),
17
+ async (c) => {
18
+ const param = c.req.valid("param")
19
+ const funnel = c.var.funnel
20
+ const profile = funnel.profiles.get(param.profile)
21
+
22
+ if (!profile) {
23
+ throw new HTTPException(404, { message: `profile "${param.profile}" not found` })
24
+ }
25
+
26
+ const exitCode = await funnel.claude.launch({
27
+ channel: profile.channelId,
28
+ cwd: profile.path,
29
+ subAgent: profile.subAgent,
30
+ userArgs: queryToCliArgs(c.req.url, RESERVED_KEYS),
31
+ profileName: profile.name,
32
+ })
33
+
34
+ process.exit(exitCode)
35
+ },
36
+ )
@@ -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
+ }