@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,520 @@
1
+ import type { CallInput } from "@/connectors/connector-adapter"
2
+ import type { ConnectorConfig } from "@/connectors/connector-config-schema"
3
+ import type { FunnelConnectorFactory } from "@/connectors/connector-factory"
4
+ import type { FunnelConnectorListener } from "@/connectors/connector-listener"
5
+ import type { DiscordConnectorConfig } from "@/connectors/discord-connector-schema"
6
+ import type { GhConnectorConfig } from "@/connectors/gh-connector-schema"
7
+ import type { ScheduleConnectorConfig, ScheduleEntry } from "@/connectors/schedule-connector-schema"
8
+ import type { SlackConnectorConfig } from "@/connectors/slack-connector-schema"
9
+ import type { ProfileChannelChecker } from "@/engine/profiles/profile-channel-checker"
10
+ import { FunnelClock } from "@/engine/time/clock"
11
+ import { NodeFunnelClock } from "@/engine/time/node-clock"
12
+ import { FunnelIdGenerator } from "@/engine/id/id-generator"
13
+ import { NodeFunnelIdGenerator } from "@/engine/id/node-id-generator"
14
+ import { FunnelSettingsReader } from "@/engine/settings/settings-reader"
15
+ import type {
16
+ ChannelConfig,
17
+ ChannelDeliveryMode,
18
+ Settings,
19
+ } from "@/engine/settings/settings-schema"
20
+
21
+ type Deps = {
22
+ store: FunnelSettingsReader
23
+ factory: FunnelConnectorFactory
24
+ profileChecker: ProfileChannelChecker
25
+ clock?: FunnelClock
26
+ idGenerator?: FunnelIdGenerator
27
+ }
28
+
29
+ export type ChannelConnectorView = ConnectorConfig & {
30
+ channelId: string
31
+ channelName: string
32
+ }
33
+
34
+ type AddConnectorInput =
35
+ | { type: "slack"; name: string; botToken: string; appToken: string }
36
+ | { type: "gh"; name: string; pollInterval?: number }
37
+ | { type: "discord"; name: string; botToken: string }
38
+ | { type: "schedule"; name: string; entries?: ScheduleEntry[] }
39
+
40
+ const defaultClock = new NodeFunnelClock()
41
+ const defaultIdGenerator = new NodeFunnelIdGenerator()
42
+
43
+ /**
44
+ * Channels own their connectors. Each channel has a stable id (UUID); the
45
+ * `name` is the human-facing label used by the CLI. Connectors live nested
46
+ * inside `channel.connectors[]`, so add/remove/rename are channel-scoped — no
47
+ * global connector namespace exists. Token uniqueness is enforced across all
48
+ * channels at add/update time so the same Slack/Discord credentials cannot
49
+ * be registered twice.
50
+ */
51
+ export class FunnelChannels {
52
+ private readonly store: FunnelSettingsReader
53
+ private readonly factory: FunnelConnectorFactory
54
+ private readonly profileChecker: ProfileChannelChecker
55
+ private readonly clock: FunnelClock
56
+ private readonly idGenerator: FunnelIdGenerator
57
+
58
+ constructor(deps: Deps) {
59
+ this.store = deps.store
60
+ this.factory = deps.factory
61
+ this.profileChecker = deps.profileChecker
62
+ this.clock = deps.clock ?? defaultClock
63
+ this.idGenerator = deps.idGenerator ?? defaultIdGenerator
64
+ Object.freeze(this)
65
+ }
66
+
67
+ list(): ChannelConfig[] {
68
+ return this.store.read().channels
69
+ }
70
+
71
+ get(name: string): ChannelConfig | null {
72
+ return this.list().find((c) => c.name === name) ?? null
73
+ }
74
+
75
+ getById(id: string): ChannelConfig | null {
76
+ return this.list().find((c) => c.id === id) ?? null
77
+ }
78
+
79
+ add(input: { name: string; delivery?: ChannelDeliveryMode }): ChannelConfig {
80
+ const settings = this.store.read()
81
+
82
+ if (settings.channels.some((c) => c.name === input.name)) {
83
+ throw new Error(`channel "${input.name}" already exists`)
84
+ }
85
+
86
+ const channel: ChannelConfig = {
87
+ id: this.idGenerator.generate(),
88
+ name: input.name,
89
+ delivery: input.delivery ?? "fanout",
90
+ connectors: [],
91
+ }
92
+
93
+ settings.channels.push(channel)
94
+ this.store.write(settings)
95
+
96
+ return channel
97
+ }
98
+
99
+ setDelivery(name: string, delivery: ChannelDeliveryMode): void {
100
+ const settings = this.store.read()
101
+ const channel = this.requireChannel(settings, name)
102
+
103
+ channel.delivery = delivery
104
+
105
+ this.store.write(settings)
106
+ }
107
+
108
+ remove(name: string): void {
109
+ const settings = this.store.read()
110
+ const index = settings.channels.findIndex((c) => c.name === name)
111
+
112
+ if (index < 0) throw new Error(`channel "${name}" not found`)
113
+
114
+ const channel = settings.channels[index]
115
+
116
+ if (channel && this.profileChecker.hasChannelRef(channel.id)) {
117
+ throw new Error(`channel "${name}" is referenced by a profile`)
118
+ }
119
+
120
+ settings.channels.splice(index, 1)
121
+ this.store.write(settings)
122
+ }
123
+
124
+ rename(oldName: string, newName: string): void {
125
+ const settings = this.store.read()
126
+ const channel = settings.channels.find((c) => c.name === oldName)
127
+
128
+ if (!channel) throw new Error(`channel "${oldName}" not found`)
129
+ if (settings.channels.some((c) => c.name === newName)) {
130
+ throw new Error(`channel "${newName}" already exists`)
131
+ }
132
+
133
+ channel.name = newName
134
+ this.store.write(settings)
135
+ }
136
+
137
+ listConnectors(channelName: string): ConnectorConfig[] {
138
+ return this.requireChannel(this.store.read(), channelName).connectors
139
+ }
140
+
141
+ getConnector(channelName: string, connectorName: string): ConnectorConfig | null {
142
+ const channel = this.get(channelName)
143
+
144
+ if (!channel) return null
145
+
146
+ return channel.connectors.find((c) => c.name === connectorName) ?? null
147
+ }
148
+
149
+ listAllConnectors(): ChannelConnectorView[] {
150
+ const out: ChannelConnectorView[] = []
151
+
152
+ for (const channel of this.list()) {
153
+ for (const connector of channel.connectors) {
154
+ out.push({ ...connector, channelId: channel.id, channelName: channel.name })
155
+ }
156
+ }
157
+
158
+ return out
159
+ }
160
+
161
+ addConnector(channelName: string, input: AddConnectorInput): ConnectorConfig {
162
+ const settings = this.store.read()
163
+ const channel = this.requireChannel(settings, channelName)
164
+
165
+ if (channel.connectors.some((c) => c.name === input.name)) {
166
+ throw new Error(`connector "${input.name}" already exists in channel "${channelName}"`)
167
+ }
168
+
169
+ const candidate = this.fromInput(input)
170
+
171
+ this.assertNoTokenCollision(settings, candidate)
172
+
173
+ channel.connectors.push(candidate)
174
+ this.store.write(settings)
175
+
176
+ return candidate
177
+ }
178
+
179
+ private fromInput(input: AddConnectorInput): ConnectorConfig {
180
+ const id = this.idGenerator.generate()
181
+ const now = this.clock.iso()
182
+ const createdAt = now
183
+ const updatedAt = now
184
+
185
+ if (input.type === "slack") {
186
+ return {
187
+ id,
188
+ type: "slack",
189
+ name: input.name,
190
+ botToken: input.botToken,
191
+ appToken: input.appToken,
192
+ createdAt,
193
+ updatedAt,
194
+ }
195
+ }
196
+
197
+ if (input.type === "gh") {
198
+ return {
199
+ id,
200
+ type: "gh",
201
+ name: input.name,
202
+ ...(input.pollInterval !== undefined ? { pollInterval: input.pollInterval } : {}),
203
+ createdAt,
204
+ updatedAt,
205
+ }
206
+ }
207
+
208
+ if (input.type === "discord") {
209
+ return {
210
+ id,
211
+ type: "discord",
212
+ name: input.name,
213
+ botToken: input.botToken,
214
+ createdAt,
215
+ updatedAt,
216
+ }
217
+ }
218
+
219
+ return {
220
+ id,
221
+ type: "schedule",
222
+ name: input.name,
223
+ entries: input.entries ?? [],
224
+ createdAt,
225
+ updatedAt,
226
+ }
227
+ }
228
+
229
+ removeConnector(channelName: string, connectorName: string): void {
230
+ const settings = this.store.read()
231
+ const channel = this.requireChannel(settings, channelName)
232
+ const index = channel.connectors.findIndex((c) => c.name === connectorName)
233
+
234
+ if (index < 0) {
235
+ throw new Error(`connector "${connectorName}" not found in channel "${channelName}"`)
236
+ }
237
+
238
+ channel.connectors.splice(index, 1)
239
+ this.store.write(settings)
240
+ }
241
+
242
+ renameConnector(channelName: string, oldName: string, newName: string): void {
243
+ const settings = this.store.read()
244
+ const channel = this.requireChannel(settings, channelName)
245
+ const connector = channel.connectors.find((c) => c.name === oldName)
246
+
247
+ if (!connector) {
248
+ throw new Error(`connector "${oldName}" not found in channel "${channelName}"`)
249
+ }
250
+
251
+ if (channel.connectors.some((c) => c.name === newName)) {
252
+ throw new Error(`connector "${newName}" already exists in channel "${channelName}"`)
253
+ }
254
+
255
+ connector.name = newName
256
+ connector.updatedAt = this.clock.iso()
257
+ this.store.write(settings)
258
+ }
259
+
260
+ updateSlackConnector(
261
+ channelName: string,
262
+ connectorName: string,
263
+ fields: { botToken?: string; appToken?: string },
264
+ ): void {
265
+ const settings = this.store.read()
266
+ const channel = this.requireChannel(settings, channelName)
267
+ const connector = this.requireSlackConnector(channel, connectorName)
268
+
269
+ const updated: SlackConnectorConfig = {
270
+ ...connector,
271
+ botToken: fields.botToken ?? connector.botToken,
272
+ appToken: fields.appToken ?? connector.appToken,
273
+ updatedAt: this.clock.iso(),
274
+ }
275
+
276
+ this.assertNoTokenCollision(settings, updated)
277
+
278
+ Object.assign(connector, updated)
279
+ this.store.write(settings)
280
+ }
281
+
282
+ updateGhConnector(
283
+ channelName: string,
284
+ connectorName: string,
285
+ fields: { pollInterval?: number },
286
+ ): void {
287
+ const settings = this.store.read()
288
+ const channel = this.requireChannel(settings, channelName)
289
+ const connector = this.requireGhConnector(channel, connectorName)
290
+
291
+ if (fields.pollInterval !== undefined) connector.pollInterval = fields.pollInterval
292
+ connector.updatedAt = this.clock.iso()
293
+
294
+ this.store.write(settings)
295
+ }
296
+
297
+ updateDiscordConnector(
298
+ channelName: string,
299
+ connectorName: string,
300
+ fields: { botToken?: string },
301
+ ): void {
302
+ const settings = this.store.read()
303
+ const channel = this.requireChannel(settings, channelName)
304
+ const connector = this.requireDiscordConnector(channel, connectorName)
305
+
306
+ const updated: DiscordConnectorConfig = {
307
+ ...connector,
308
+ botToken: fields.botToken ?? connector.botToken,
309
+ updatedAt: this.clock.iso(),
310
+ }
311
+
312
+ this.assertNoTokenCollision(settings, updated)
313
+
314
+ Object.assign(connector, updated)
315
+ this.store.write(settings)
316
+ }
317
+
318
+ listScheduleEntries(channelName: string, connectorName: string): ScheduleEntry[] {
319
+ const channel = this.requireChannel(this.store.read(), channelName)
320
+ const connector = this.requireScheduleConnector(channel, connectorName)
321
+
322
+ return connector.entries
323
+ }
324
+
325
+ addScheduleEntry(
326
+ channelName: string,
327
+ connectorName: string,
328
+ entry: Pick<ScheduleEntry, "cron" | "prompt"> &
329
+ Partial<Pick<ScheduleEntry, "id" | "enabled" | "catchupPolicy">>,
330
+ ): ScheduleEntry {
331
+ const settings = this.store.read()
332
+ const channel = this.requireChannel(settings, channelName)
333
+ const connector = this.requireScheduleConnector(channel, connectorName)
334
+
335
+ const persisted: ScheduleEntry = {
336
+ id: entry.id ?? this.idGenerator.generate(),
337
+ cron: entry.cron,
338
+ prompt: entry.prompt,
339
+ enabled: entry.enabled ?? true,
340
+ catchupPolicy: entry.catchupPolicy ?? "latest",
341
+ }
342
+
343
+ connector.entries.push(persisted)
344
+ connector.updatedAt = this.clock.iso()
345
+ this.store.write(settings)
346
+
347
+ return persisted
348
+ }
349
+
350
+ removeScheduleEntry(channelName: string, connectorName: string, id: string): void {
351
+ const settings = this.store.read()
352
+ const channel = this.requireChannel(settings, channelName)
353
+ const connector = this.requireScheduleConnector(channel, connectorName)
354
+ const index = connector.entries.findIndex((e) => e.id === id)
355
+
356
+ if (index < 0) throw new Error(`schedule entry "${id}" not found`)
357
+
358
+ connector.entries.splice(index, 1)
359
+ connector.updatedAt = this.clock.iso()
360
+ this.store.write(settings)
361
+ }
362
+
363
+ async call(channelName: string, connectorName: string, input: CallInput): Promise<unknown> {
364
+ const connector = this.getConnector(channelName, connectorName)
365
+
366
+ if (!connector) {
367
+ throw new Error(`connector "${connectorName}" not found in channel "${channelName}"`)
368
+ }
369
+
370
+ const adapter = this.factory.createAdapter(connector)
371
+
372
+ if (!adapter) {
373
+ throw new Error(`connector type "${connector.type}" does not support outbound calls`)
374
+ }
375
+
376
+ return await adapter.call(input)
377
+ }
378
+
379
+ createListener(
380
+ channelName: string,
381
+ connectorName: string,
382
+ ): { config: ConnectorConfig; channelId: string; listener: FunnelConnectorListener } | null {
383
+ const channel = this.get(channelName)
384
+
385
+ if (!channel) return null
386
+
387
+ const connector = channel.connectors.find((c) => c.name === connectorName)
388
+
389
+ if (!connector) return null
390
+
391
+ return {
392
+ config: connector,
393
+ channelId: channel.id,
394
+ listener: this.factory.createListener(channel.id, connector),
395
+ }
396
+ }
397
+
398
+ createAllListeners(): {
399
+ config: ConnectorConfig
400
+ channelId: string
401
+ channelName: string
402
+ listener: FunnelConnectorListener
403
+ }[] {
404
+ const out: {
405
+ config: ConnectorConfig
406
+ channelId: string
407
+ channelName: string
408
+ listener: FunnelConnectorListener
409
+ }[] = []
410
+
411
+ for (const channel of this.list()) {
412
+ for (const connector of channel.connectors) {
413
+ out.push({
414
+ config: connector,
415
+ channelId: channel.id,
416
+ channelName: channel.name,
417
+ listener: this.factory.createListener(channel.id, connector),
418
+ })
419
+ }
420
+ }
421
+
422
+ return out
423
+ }
424
+
425
+ private requireChannel(settings: Settings, name: string): ChannelConfig {
426
+ const channel = settings.channels.find((c) => c.name === name)
427
+
428
+ if (!channel) throw new Error(`channel "${name}" not found`)
429
+
430
+ return channel
431
+ }
432
+
433
+ private requireConnector(channel: ChannelConfig, connectorName: string): ConnectorConfig {
434
+ const connector = channel.connectors.find((c) => c.name === connectorName)
435
+
436
+ if (!connector) {
437
+ throw new Error(`connector "${connectorName}" not found in channel "${channel.name}"`)
438
+ }
439
+
440
+ return connector
441
+ }
442
+
443
+ private requireSlackConnector(
444
+ channel: ChannelConfig,
445
+ connectorName: string,
446
+ ): SlackConnectorConfig {
447
+ const connector = this.requireConnector(channel, connectorName)
448
+
449
+ if (connector.type !== "slack") {
450
+ throw new Error(`connector "${connectorName}" is type "${connector.type}", not "slack"`)
451
+ }
452
+
453
+ return connector
454
+ }
455
+
456
+ private requireGhConnector(channel: ChannelConfig, connectorName: string): GhConnectorConfig {
457
+ const connector = this.requireConnector(channel, connectorName)
458
+
459
+ if (connector.type !== "gh") {
460
+ throw new Error(`connector "${connectorName}" is type "${connector.type}", not "gh"`)
461
+ }
462
+
463
+ return connector
464
+ }
465
+
466
+ private requireDiscordConnector(
467
+ channel: ChannelConfig,
468
+ connectorName: string,
469
+ ): DiscordConnectorConfig {
470
+ const connector = this.requireConnector(channel, connectorName)
471
+
472
+ if (connector.type !== "discord") {
473
+ throw new Error(`connector "${connectorName}" is type "${connector.type}", not "discord"`)
474
+ }
475
+
476
+ return connector
477
+ }
478
+
479
+ private requireScheduleConnector(
480
+ channel: ChannelConfig,
481
+ connectorName: string,
482
+ ): ScheduleConnectorConfig {
483
+ const connector = this.requireConnector(channel, connectorName)
484
+
485
+ if (connector.type !== "schedule") {
486
+ throw new Error(`connector "${connectorName}" is type "${connector.type}", not "schedule"`)
487
+ }
488
+
489
+ return connector
490
+ }
491
+
492
+ private assertNoTokenCollision(settings: Settings, candidate: ConnectorConfig): void {
493
+ const tokens = this.tokensOf(candidate)
494
+
495
+ if (tokens.length === 0) return
496
+
497
+ for (const channel of settings.channels) {
498
+ for (const other of channel.connectors) {
499
+ if (other.id === candidate.id) continue
500
+
501
+ for (const token of this.tokensOf(other)) {
502
+ if (tokens.includes(token)) {
503
+ throw new Error(
504
+ `token already in use by connector "${other.name}" in channel "${channel.name}"`,
505
+ )
506
+ }
507
+ }
508
+ }
509
+ }
510
+ }
511
+
512
+ private tokensOf(connector: ConnectorConfig): string[] {
513
+ if (connector.type === "slack") return [connector.botToken, connector.appToken]
514
+ if (connector.type === "discord") return [connector.botToken]
515
+
516
+ return []
517
+ }
518
+ }
519
+
520
+ export type { ScheduleConnectorConfig }
@@ -1,30 +1,27 @@
1
1
  import { join } from "node:path"
2
- import type { FunnelChannels } from "@/modules/channels/funnel-channels"
3
- import { FunnelFileSystem } from "@/modules/fs/funnel-file-system"
4
- import { NodeFunnelFileSystem } from "@/modules/fs/node-funnel-file-system"
5
- import type { FunnelGateway } from "@/modules/gateway/funnel-gateway"
6
- import { FunnelLogger } from "@/modules/logger/funnel-logger"
7
- import { NodeFunnelLogger } from "@/modules/logger/node-funnel-logger"
8
- import type { FunnelMcp } from "@/modules/mcp/funnel-mcp"
9
- import { FunnelProcessRunner } from "@/modules/process/funnel-process-runner"
10
- import { NodeFunnelProcessRunner } from "@/modules/process/node-funnel-process-runner"
11
- import type { FunnelRepositories } from "@/modules/repos/funnel-repositories"
12
- import { FUNNEL_DIR } from "@/modules/settings/funnel-settings-store"
2
+ import type { FunnelChannels } from "@/engine/channels/channels"
3
+ import type { GatewayController } from "@/engine/claude/gateway-controller"
4
+ import { FunnelFileSystem } from "@/engine/fs/file-system"
5
+ import { NodeFunnelFileSystem } from "@/engine/fs/node-file-system"
6
+ import { FunnelLogger } from "@/engine/logger/logger"
7
+ import { NodeFunnelLogger } from "@/engine/logger/node-logger"
8
+ import type { FunnelMcp } from "@/engine/mcp/mcp"
9
+ import { FunnelProcessRunner } from "@/engine/process/process-runner"
10
+ import { NodeFunnelProcessRunner } from "@/engine/process/node-process-runner"
11
+ import { FUNNEL_DIR } from "@/engine/settings/settings-store"
13
12
 
14
13
  export type LaunchOptions = {
15
14
  channel: string
16
- repo?: string
15
+ cwd?: string
17
16
  subAgent?: string
18
- envFiles?: string[]
19
17
  userArgs?: string[]
20
18
  profileName?: string
21
19
  }
22
20
 
23
21
  type Deps = {
24
22
  channels: FunnelChannels
25
- repositories: FunnelRepositories
26
23
  mcp: FunnelMcp
27
- gateway: FunnelGateway
24
+ gateway: GatewayController
28
25
  process?: FunnelProcessRunner
29
26
  fs?: FunnelFileSystem
30
27
  logger?: FunnelLogger
@@ -43,9 +40,8 @@ const defaultLogger = new NodeFunnelLogger()
43
40
  */
44
41
  export class FunnelClaude {
45
42
  private readonly channels: FunnelChannels
46
- private readonly repositories: FunnelRepositories
47
43
  private readonly mcp: FunnelMcp
48
- private readonly gateway: FunnelGateway
44
+ private readonly gateway: GatewayController
49
45
  private readonly process: FunnelProcessRunner
50
46
  private readonly fs: FunnelFileSystem
51
47
  private readonly logger: FunnelLogger
@@ -53,7 +49,6 @@ export class FunnelClaude {
53
49
 
54
50
  constructor(deps: Deps) {
55
51
  this.channels = deps.channels
56
- this.repositories = deps.repositories
57
52
  this.mcp = deps.mcp
58
53
  this.gateway = deps.gateway
59
54
  this.process = deps.process ?? defaultProcess
@@ -64,7 +59,7 @@ export class FunnelClaude {
64
59
  }
65
60
 
66
61
  async launch(options: LaunchOptions): Promise<number> {
67
- const channel = this.channels.get(options.channel)
62
+ const channel = this.channels.get(options.channel) ?? this.channels.getById(options.channel)
68
63
 
69
64
  if (!channel) {
70
65
  throw new Error(`channel "${options.channel}" not found`)
@@ -74,9 +69,7 @@ export class FunnelClaude {
74
69
  throw new Error(`profile "${options.profileName}" is already running`)
75
70
  }
76
71
 
77
- const cwd = options.repo
78
- ? this.repositories.resolvePath(options.repo)
79
- : globalThis.process.cwd()
72
+ const cwd = options.cwd ?? globalThis.process.cwd()
80
73
 
81
74
  if (!this.mcp.findInstalledName(cwd)) {
82
75
  this.mcp.install(cwd)
@@ -95,11 +88,11 @@ export class FunnelClaude {
95
88
  }
96
89
 
97
90
  const claudeArgs = this.buildArgs(options, cwd)
98
- const env = this.buildEnv(options, cwd)
91
+ const env = this.buildEnv(channel.id)
99
92
 
100
93
  this.logger.info(`claude launch`, {
101
94
  channel: options.channel,
102
- repo: options.repo,
95
+ channelId: channel.id,
103
96
  subAgent: options.subAgent,
104
97
  cwd,
105
98
  })
@@ -152,11 +145,12 @@ export class FunnelClaude {
152
145
  }
153
146
 
154
147
  private installCleanup(profileName: string): void {
155
- const cleanup = () => this.removePidFile(profileName)
156
-
157
- globalThis.process.once("exit", cleanup)
158
- globalThis.process.once("SIGINT", cleanup)
159
- globalThis.process.once("SIGTERM", cleanup)
148
+ // Default Bun behavior on SIGINT/SIGTERM is process.exit(130/143), which
149
+ // fires the "exit" event. Hooking only "exit" keeps the PID file cleanup
150
+ // running while letting the signal terminate the process normally —
151
+ // adding our own SIGINT handler would suppress the default exit and leave
152
+ // funnel hanging until claude responds.
153
+ globalThis.process.once("exit", () => this.removePidFile(profileName))
160
154
  }
161
155
 
162
156
  private isProcessAlive(pid: number): boolean {
@@ -191,35 +185,14 @@ export class FunnelClaude {
191
185
  return result
192
186
  }
193
187
 
194
- private buildEnv(options: LaunchOptions, cwd: string): Record<string, string> {
195
- const env: Record<string, string> = { ...globalThis.process.env } as Record<string, string>
196
-
197
- if (options.envFiles) {
198
- for (const file of options.envFiles) {
199
- const filePath = `${cwd}/${file}`
200
-
201
- if (!this.fs.existsSync(filePath)) continue
202
-
203
- const content = this.fs.readFileSync(filePath)
204
-
205
- for (const line of content.split("\n")) {
206
- const trimmed = line.trim()
207
-
208
- if (!trimmed || trimmed.startsWith("#")) continue
209
-
210
- const eqIndex = trimmed.indexOf("=")
211
-
212
- if (eqIndex < 0) continue
213
-
214
- const key = trimmed.slice(0, eqIndex)
215
- const value = trimmed.slice(eqIndex + 1).replace(/^["']|["']$/g, "")
188
+ private buildEnv(channelId: string): Record<string, string> {
189
+ const env: Record<string, string> = {}
216
190
 
217
- env[key] = value
218
- }
219
- }
191
+ for (const [key, value] of Object.entries(globalThis.process.env)) {
192
+ if (typeof value === "string") env[key] = value
220
193
  }
221
194
 
222
- env.FUNNEL_CHANNEL_ID = options.channel
195
+ env.FUNNEL_CHANNEL_ID = channelId
223
196
 
224
197
  return env
225
198
  }
@@ -0,0 +1,4 @@
1
+ export type GatewayController = {
2
+ isRunning(): boolean
3
+ start(options?: { caffeinate?: boolean }): Promise<boolean>
4
+ }
@@ -1,5 +1,7 @@
1
1
  export type FileStat = {
2
2
  mtimeMs: number
3
+ /** POSIX mode bits (e.g. 0o600). `null` when the underlying FS does not expose mode. */
4
+ mode: number | null
3
5
  }
4
6
 
5
7
  /**
@@ -11,6 +13,8 @@ export abstract class FunnelFileSystem {
11
13
  abstract existsSync(path: string): boolean
12
14
  abstract readFileSync(path: string): string
13
15
  abstract writeFileSync(path: string, data: string): void
16
+ /** Write `data` and ensure the resulting file is owner-only (0600). Use for tokens and any file that may contain secrets. */
17
+ abstract writeSecretFileSync(path: string, data: string): void
14
18
  abstract appendFileSync(path: string, data: string): void
15
19
  abstract unlink(path: string): void
16
20
  abstract mkdirSync(path: string, options?: { recursive?: boolean }): void