@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 { z } from "zod"
2
+ import { connectorConfigSchema } from "@/connectors/connector-config-schema"
3
+
4
+ /**
5
+ * Routing mode when multiple WS clients are subscribed to the same channel.
6
+ *
7
+ * - `fanout` (default): every connected client receives every event. Right when each
8
+ * subscriber has its own job (e.g., TUI mirrors, distinct Claude profiles each running
9
+ * their own pipeline against the same source).
10
+ * - `exclusive`: each event is delivered to exactly one connected client, picked
11
+ * round-robin per channel. Right when subscribers are interchangeable workers and you
12
+ * want each event handled once. Tap=all clients (TUI dashboard) always receive,
13
+ * regardless of mode, so they can passively observe.
14
+ */
15
+ export const channelDeliveryModeSchema = z.enum(["fanout", "exclusive"])
16
+
17
+ export type ChannelDeliveryMode = z.infer<typeof channelDeliveryModeSchema>
18
+
19
+ export const channelConfigSchema = z.object({
20
+ id: z.string(),
21
+ name: z.string(),
22
+ delivery: channelDeliveryModeSchema.default("fanout"),
23
+ connectors: z.array(connectorConfigSchema).default([]),
24
+ })
25
+
26
+ export type ChannelConfig = z.infer<typeof channelConfigSchema>
27
+
28
+ export const profileConfigSchema = z.object({
29
+ name: z.string(),
30
+ path: z.string(),
31
+ subAgent: z.string(),
32
+ channelId: z.string(),
33
+ })
34
+
35
+ export type ProfileConfig = z.infer<typeof profileConfigSchema>
36
+
37
+ export const SETTINGS_VERSION = 1
38
+
39
+ export const settingsSchema = z.object({
40
+ /** Schema version. New files always write the current version; older files without one are read as v1. */
41
+ version: z.literal(SETTINGS_VERSION).default(SETTINGS_VERSION),
42
+ channels: z.array(channelConfigSchema).default([]),
43
+ profiles: z.array(profileConfigSchema).default([]),
44
+ })
45
+
46
+ export type Settings = z.infer<typeof settingsSchema>
@@ -0,0 +1,110 @@
1
+ import { homedir } from "node:os"
2
+ import { dirname, join } from "node:path"
3
+ import { FunnelFileSystem } from "@/engine/fs/file-system"
4
+ import { NodeFunnelFileSystem } from "@/engine/fs/node-file-system"
5
+ import { FunnelSettingsReader } from "@/engine/settings/settings-reader"
6
+ import { SETTINGS_VERSION, settingsSchema } from "@/engine/settings/settings-schema"
7
+ import type { Settings } from "@/engine/settings/settings-schema"
8
+
9
+ export const FUNNEL_DIR = join(homedir(), ".funnel")
10
+ export const SETTINGS_PATH = join(FUNNEL_DIR, "settings.json")
11
+
12
+ type Deps = {
13
+ path?: string
14
+ fs?: FunnelFileSystem
15
+ }
16
+
17
+ const defaultFs = new NodeFunnelFileSystem()
18
+
19
+ export class FunnelSettingsStore extends FunnelSettingsReader {
20
+ private readonly path: string
21
+ private readonly fs: FunnelFileSystem
22
+
23
+ constructor(deps: Deps = {}) {
24
+ super()
25
+ this.path = deps.path ?? SETTINGS_PATH
26
+ this.fs = deps.fs ?? defaultFs
27
+ Object.freeze(this)
28
+ }
29
+
30
+ read(): Settings {
31
+ if (!this.fs.existsSync(this.path)) {
32
+ return {
33
+ version: SETTINGS_VERSION,
34
+ channels: [],
35
+ profiles: [],
36
+ }
37
+ }
38
+
39
+ const content = this.fs.readFileSync(this.path)
40
+ const parsed: unknown = JSON.parse(content)
41
+
42
+ if (this.looksLikeLegacy(parsed)) {
43
+ throw new Error(
44
+ `legacy settings.json detected at ${this.path}. The schema changed (channel.connectors are now nested objects with ids; profile fields renamed). Migration is intentionally not provided. Back up and remove the old file:\n mv ${this.path} ${this.path}.bak`,
45
+ )
46
+ }
47
+
48
+ if (
49
+ parsed &&
50
+ typeof parsed === "object" &&
51
+ "version" in parsed &&
52
+ parsed.version !== SETTINGS_VERSION
53
+ ) {
54
+ throw new Error(
55
+ `unsupported settings.json version (${this.path}): expected ${SETTINGS_VERSION}, got ${String(parsed.version)}`,
56
+ )
57
+ }
58
+
59
+ const result = settingsSchema.safeParse(parsed)
60
+
61
+ if (!result.success) {
62
+ throw new Error(
63
+ `invalid settings.json (${this.path}): ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ")}`,
64
+ )
65
+ }
66
+
67
+ return result.data
68
+ }
69
+
70
+ private looksLikeLegacy(parsed: unknown): boolean {
71
+ if (!parsed || typeof parsed !== "object") return false
72
+
73
+ const obj = parsed as Record<string, unknown>
74
+
75
+ if (Array.isArray(obj.channels)) {
76
+ for (const channel of obj.channels) {
77
+ if (!channel || typeof channel !== "object") continue
78
+ const ch = channel as Record<string, unknown>
79
+
80
+ if (Array.isArray(ch.connectors) && ch.connectors.some((x) => typeof x === "string")) {
81
+ return true
82
+ }
83
+
84
+ if (!("id" in ch) && "name" in ch) return true
85
+ }
86
+ }
87
+
88
+ if (Array.isArray(obj.connectors)) return true
89
+ if (Array.isArray(obj.repositories)) return true
90
+
91
+ if (Array.isArray(obj.profiles)) {
92
+ for (const profile of obj.profiles) {
93
+ if (!profile || typeof profile !== "object") continue
94
+ const p = profile as Record<string, unknown>
95
+
96
+ if ("repository" in p || "envFiles" in p || ("channel" in p && !("channelId" in p))) {
97
+ return true
98
+ }
99
+ }
100
+ }
101
+
102
+ return false
103
+ }
104
+
105
+ write(settings: Settings): void {
106
+ this.fs.mkdirSync(dirname(this.path), { recursive: true })
107
+ const versioned: Settings = { ...settings, version: SETTINGS_VERSION }
108
+ this.fs.writeFileSync(this.path, `${JSON.stringify(versioned, null, 2)}\n`)
109
+ }
110
+ }
@@ -1,4 +1,4 @@
1
- import { FunnelClock } from "@/modules/time/funnel-clock"
1
+ import { FunnelClock } from "@/engine/time/clock"
2
2
 
3
3
  type Props = {
4
4
  start?: Date
@@ -1,4 +1,4 @@
1
- import { FunnelClock } from "@/modules/time/funnel-clock"
1
+ import { FunnelClock } from "@/engine/time/clock"
2
2
 
3
3
  export class NodeFunnelClock extends FunnelClock {
4
4
  now(): Date {
package/lib/funnel.ts CHANGED
@@ -1,27 +1,25 @@
1
1
  import { join } from "node:path"
2
- import { FunnelChannels } from "@/modules/channels/funnel-channels"
3
- import { FunnelClaude } from "@/modules/claude/funnel-claude"
4
- import {
5
- type ConnectorStoresBundle,
6
- createConnectorStores,
7
- } from "@/modules/connectors/funnel-connector-stores"
8
- import { FunnelConnectors } from "@/modules/connectors/funnel-connectors"
9
- import type { FunnelFileSystem } from "@/modules/fs/funnel-file-system"
10
- import { FunnelGateway } from "@/modules/gateway/funnel-gateway"
11
- import { FunnelGatewayServer } from "@/modules/gateway/funnel-gateway-server"
12
- import type { FunnelIdGenerator } from "@/modules/id/funnel-id-generator"
13
- import type { FunnelLogger } from "@/modules/logger/funnel-logger"
14
- import { FunnelMcp } from "@/modules/mcp/funnel-mcp"
15
- import type { FunnelProcessRunner } from "@/modules/process/funnel-process-runner"
16
- import { FunnelProfiles } from "@/modules/profiles/funnel-profiles"
17
- import { FunnelRepositories } from "@/modules/repos/funnel-repositories"
18
- import { FunnelSchedule } from "@/modules/schedule/funnel-schedule"
19
- import { FunnelSettingsReader } from "@/modules/settings/funnel-settings-reader"
20
- import { FUNNEL_DIR, FunnelSettingsStore } from "@/modules/settings/funnel-settings-store"
21
- import type { FunnelClock } from "@/modules/time/funnel-clock"
2
+ import { FunnelConnectorFactory } from "@/connectors/connector-factory"
3
+ import { FunnelChannels } from "@/engine/channels/channels"
4
+ import { FunnelClaude } from "@/engine/claude/claude"
5
+ import type { FunnelFileSystem } from "@/engine/fs/file-system"
6
+ import type { FunnelIdGenerator } from "@/engine/id/id-generator"
7
+ import { FunnelLogger } from "@/engine/logger/logger"
8
+ import { NodeFunnelLogger } from "@/engine/logger/node-logger"
9
+ import { FunnelMcp } from "@/engine/mcp/mcp"
10
+ import { FunnelProcessRunner } from "@/engine/process/process-runner"
11
+ import { NodeFunnelProcessRunner } from "@/engine/process/node-process-runner"
12
+ import { FunnelProfiles } from "@/engine/profiles/profiles"
13
+ import { FunnelSettingsReader } from "@/engine/settings/settings-reader"
14
+ import { FUNNEL_DIR, FunnelSettingsStore } from "@/engine/settings/settings-store"
15
+ import type { FunnelClock } from "@/engine/time/clock"
16
+ import { FunnelGateway } from "@/gateway/gateway"
17
+ import { FunnelGatewayServer } from "@/gateway/gateway-server"
18
+ import { FunnelGatewayToken } from "@/gateway/gateway-token"
19
+ import { FunnelListenersClient } from "@/gateway/listeners-client"
22
20
 
23
21
  type Props = {
24
- /** Settings persistence (channels / repositories / profiles). Defaults to a FunnelSettingsStore rooted at `dir`. */
22
+ /** Settings persistence (channels with nested connectors / profiles). Defaults to a FunnelSettingsStore rooted at `dir`. */
25
23
  store?: FunnelSettingsReader
26
24
  /** Filesystem boundary. Replace with MemoryFunnelFileSystem to sandbox all disk I/O. */
27
25
  fs?: FunnelFileSystem
@@ -31,14 +29,12 @@ type Props = {
31
29
  logger?: FunnelLogger
32
30
  /** Clock used by schedule listener, gh poll watermarks, and gateway timeouts. */
33
31
  clock?: FunnelClock
34
- /** ID generator for schedule entry ids. Use MemoryFunnelIdGenerator for deterministic tests. */
32
+ /** ID generator for channel and connector ids. Use MemoryFunnelIdGenerator for deterministic tests. */
35
33
  idGenerator?: FunnelIdGenerator
36
- /** Funnel home directory (settings.json + connectors/<type>/). Defaults to ~/.funnel. */
34
+ /** Funnel home directory (settings.json + per-channel/per-connector dirs). Defaults to ~/.funnel. */
37
35
  dir?: string
38
36
  /** Temp / runtime directory (gateway logs and PID adjacent files). Defaults to /tmp/funnel. */
39
37
  tmpDir?: string
40
- /** Pre-built connector stores. Useful when sharing stores between multiple Funnel instances. */
41
- connectorStores?: ConnectorStoresBundle
42
38
  }
43
39
 
44
40
  /**
@@ -49,11 +45,14 @@ type Props = {
49
45
  * injectable via `Props` — passing memory implementations gives a fully sandboxed
50
46
  * Funnel that touches no real disk, processes, or wall-clock time.
51
47
  *
48
+ * Connectors live nested inside their owning channel (channels[].connectors[]),
49
+ * so connector CRUD is reached via `funnel.channels.addConnector(...)` etc.
50
+ *
52
51
  * @example
53
52
  * ```ts
54
53
  * const funnel = new Funnel({})
55
- * funnel.connectors.add({ type: "slack", name: "ops", botToken, appToken })
56
- * funnel.channels.add({ name: "inbox", connectors: ["ops"] })
54
+ * const channel = funnel.channels.add({ name: "inbox" })
55
+ * funnel.channels.addConnector("inbox", { type: "slack", name: "ops", botToken, appToken })
57
56
  * await funnel.gatewayServer({ port: 9742 }).start()
58
57
  * ```
59
58
  */
@@ -73,51 +72,51 @@ export class Funnel {
73
72
  )
74
73
  }
75
74
 
76
- /** Per-type connector stores (slack / gh / discord / schedule), all DI'd from Props. */
77
- get stores(): ConnectorStoresBundle {
78
- return (
79
- this.props.connectorStores ??
80
- createConnectorStores({
81
- fs: this.props.fs,
82
- process: this.props.process,
83
- logger: this.props.logger,
84
- clock: this.props.clock,
85
- idGenerator: this.props.idGenerator,
86
- dir: this.props.dir,
87
- })
88
- )
75
+ /** Process runner boundary. Defaults to NodeFunnelProcessRunner. */
76
+ get process(): FunnelProcessRunner {
77
+ return this.props.process ?? new NodeFunnelProcessRunner()
89
78
  }
90
79
 
91
- /** Connector CRUD + per-type call/listener APIs. Wired with channels in a forward-const closure. */
92
- get connectors(): FunnelConnectors {
93
- return this.wirePair().connectors
80
+ /** Logger boundary. Defaults to NodeFunnelLogger. */
81
+ get logger(): FunnelLogger {
82
+ return this.props.logger ?? new NodeFunnelLogger()
94
83
  }
95
84
 
96
- /** Channel CRUD + connector attach/detach. Wired with connectors and profiles via DI. */
97
- get channels(): FunnelChannels {
98
- return this.wirePair().channels
85
+ /** Pure factory that constructs per-type listeners and adapters from connector configs. */
86
+ get factory(): FunnelConnectorFactory {
87
+ return new FunnelConnectorFactory({
88
+ fs: this.props.fs,
89
+ process: this.props.process,
90
+ logger: this.props.logger,
91
+ dir: this.props.dir,
92
+ })
99
93
  }
100
94
 
101
- /** Schedule connector entry CRUD (cron lines). */
102
- get schedule(): FunnelSchedule {
103
- return new FunnelSchedule({ store: this.stores.schedule })
95
+ /** Channel CRUD + nested connector CRUD + schedule entries + listener/adapter dispatch. */
96
+ get channels(): FunnelChannels {
97
+ return new FunnelChannels({
98
+ store: this.store,
99
+ factory: this.factory,
100
+ profileChecker: this.profiles,
101
+ clock: this.props.clock,
102
+ idGenerator: this.props.idGenerator,
103
+ })
104
104
  }
105
105
 
106
- /** Launch profiles (named presets for `fnl claude`). */
106
+ /** Launch profiles (named presets for `fnl claude`: path + sub-agent + channel id). */
107
107
  get profiles(): FunnelProfiles {
108
108
  return new FunnelProfiles({ store: this.store })
109
109
  }
110
110
 
111
- /** Repository registry; writes the funnel MCP entry into each repo's .mcp.json. */
112
- get repositories(): FunnelRepositories {
113
- return new FunnelRepositories({ store: this.store, mcp: this.mcp })
111
+ /** funnel MCP installer (writes/removes `.mcp.json` entries in target repos). */
112
+ get mcp(): FunnelMcp {
113
+ return new FunnelMcp({ fs: this.props.fs })
114
114
  }
115
115
 
116
116
  /** Launch Claude Code with a channel injected via env, MCP installed, gateway ensured. */
117
117
  get claude(): FunnelClaude {
118
118
  return new FunnelClaude({
119
119
  channels: this.channels,
120
- repositories: this.repositories,
121
120
  mcp: this.mcp,
122
121
  gateway: this.gateway,
123
122
  fs: this.props.fs,
@@ -138,45 +137,51 @@ export class Funnel {
138
137
  })
139
138
  }
140
139
 
140
+ /** Read / generate the daemon's gateway token (mode 0600 file under `dir`). */
141
+ get gatewayToken(): FunnelGatewayToken {
142
+ return new FunnelGatewayToken({ fs: this.props.fs, dir: this.props.dir })
143
+ }
144
+
145
+ /**
146
+ * HTTP client for listener operations on the running gateway daemon.
147
+ * Returns `{ state: "offline" }` when the daemon is offline so hot-reload
148
+ * paths stay write-only without parsing strings.
149
+ */
150
+ get listeners(): FunnelListenersClient {
151
+ const gateway = this.gateway
152
+ const token = this.gatewayToken
153
+
154
+ return new FunnelListenersClient({
155
+ port: gateway.getPort(),
156
+ isDaemonRunning: () => gateway.isRunning(),
157
+ getToken: () => token.read(),
158
+ })
159
+ }
160
+
141
161
  /**
142
162
  * In-process gateway server. Unlike `gateway.start()` (which spawns a daemon),
143
163
  * this returns a class that runs `Bun.serve` + listeners inside the current process —
144
164
  * useful for tests, embedding, or custom hosts.
145
165
  */
146
166
  gatewayServer(
147
- options: { port?: number; logDir?: string; killCompetingSlack?: boolean } = {},
167
+ options: {
168
+ port?: number
169
+ logDir?: string
170
+ killCompetingSlack?: boolean
171
+ /** Override the auth token. Defaults to the persisted gateway.token. Pass "" to disable auth (tests). */
172
+ token?: string
173
+ } = {},
148
174
  ): FunnelGatewayServer {
149
175
  return new FunnelGatewayServer({
150
- connectors: this.connectors,
176
+ channels: this.channels,
151
177
  settings: this.store,
152
178
  port: options.port,
153
179
  logDir: options.logDir,
154
- fs: this.props.fs,
155
180
  process: this.props.process,
156
181
  clock: this.props.clock,
157
182
  logger: this.props.logger,
158
183
  killCompetingSlack: options.killCompetingSlack,
184
+ token: options.token ?? this.gatewayToken.ensure(),
159
185
  })
160
186
  }
161
-
162
- /** funnel MCP installer (writes/removes `.mcp.json` entries in target repos). */
163
- get mcp(): FunnelMcp {
164
- return new FunnelMcp({ fs: this.props.fs })
165
- }
166
-
167
- private wirePair(): { channels: FunnelChannels; connectors: FunnelConnectors } {
168
- const stores = this.stores
169
- const profiles = this.profiles
170
- const channels: FunnelChannels = new FunnelChannels({
171
- store: this.store,
172
- connectorChecker: { has: (name) => connectors.has(name) },
173
- profileChecker: profiles,
174
- profileRefUpdater: profiles,
175
- })
176
- const connectors: FunnelConnectors = new FunnelConnectors({
177
- ...stores,
178
- refUpdater: channels,
179
- })
180
- return { channels, connectors }
181
- }
182
187
  }
@@ -0,0 +1,44 @@
1
+ import { timingSafeEqual } from "node:crypto"
2
+ import type { MiddlewareHandler } from "hono"
3
+ import type { Env } from "@/gateway/factory"
4
+
5
+ type Deps = {
6
+ expected: string
7
+ }
8
+
9
+ /**
10
+ * Verifies `Authorization: Bearer <token>` against the daemon's gateway token.
11
+ * Mounted on the routes that mutate listener state or expose detailed status.
12
+ * `/health` is intentionally left unauthenticated so the daemon manager can
13
+ * probe liveness without needing the token.
14
+ */
15
+ export const requireBearerToken = (deps: Deps): MiddlewareHandler<Env> => {
16
+ return async (c, next) => {
17
+ const header = c.req.header("authorization") ?? ""
18
+ const match = header.match(/^Bearer\s+(.+)$/i)
19
+ const presented = match?.[1] ?? ""
20
+
21
+ if (!constantTimeEqual(presented, deps.expected)) {
22
+ return c.text("unauthorized", 401)
23
+ }
24
+
25
+ return await next()
26
+ }
27
+ }
28
+
29
+ export const constantTimeEqual = (a: string, b: string): boolean => {
30
+ const bufA = Buffer.from(a, "utf-8")
31
+ const bufB = Buffer.from(b, "utf-8")
32
+ const maxLen = Math.max(bufA.length, bufB.length, 1)
33
+ const padA = Buffer.alloc(maxLen)
34
+ const padB = Buffer.alloc(maxLen)
35
+
36
+ bufA.copy(padA)
37
+ bufB.copy(padB)
38
+
39
+ // timingSafeEqual on equal-length padded buffers, then AND with length match
40
+ // so a length-only probe still requires the full comparison time.
41
+ const equal = timingSafeEqual(padA, padB)
42
+
43
+ return equal && bufA.length === bufB.length
44
+ }