@interactive-inc/claude-funnel 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (472) hide show
  1. package/README.md +155 -133
  2. package/dist/bin.js +1344 -0
  3. package/dist/cli/factory.d.ts +7 -0
  4. package/dist/cli/router/query-to-cli-args.d.ts +1 -0
  5. package/dist/cli/router/to-request.d.ts +5 -0
  6. package/dist/cli/router/validator.d.ts +5 -0
  7. package/dist/cli/routes/channels.$channel.connectors.$connector.d.ts +42 -0
  8. package/dist/cli/routes/channels.$channel.connectors.$connector.rename.$newName.d.ts +46 -0
  9. package/dist/cli/routes/channels.$channel.connectors.$connector.request.d.ts +54 -0
  10. package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.d.ts +66 -0
  11. package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.d.ts +42 -0
  12. package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.d.ts +46 -0
  13. package/dist/cli/routes/channels.$channel.connectors.add.$connector.d.ts +90 -0
  14. package/dist/cli/routes/channels.$channel.connectors.d.ts +38 -0
  15. package/dist/cli/routes/channels.$channel.connectors.remove.$connector.d.ts +42 -0
  16. package/dist/cli/routes/channels.$channel.connectors.set.$connector.d.ts +62 -0
  17. package/dist/cli/routes/channels.$channel.d.ts +38 -0
  18. package/dist/cli/routes/channels.$channel.rename.$newName.d.ts +42 -0
  19. package/dist/cli/routes/channels.$channel.set.delivery.$mode.d.ts +28 -0
  20. package/dist/cli/routes/channels.add.$channel.d.ts +46 -0
  21. package/dist/cli/routes/channels.d.ts +16 -0
  22. package/dist/cli/routes/channels.remove.$channel.d.ts +38 -0
  23. package/dist/cli/routes/claude.d.ts +32 -0
  24. package/dist/cli/routes/gateway.d.ts +20 -0
  25. package/dist/cli/routes/gateway.listeners.d.ts +17 -0
  26. package/dist/cli/routes/gateway.logs.d.ts +24 -0
  27. package/dist/cli/routes/gateway.restart.d.ts +24 -0
  28. package/dist/cli/routes/gateway.run.d.ts +24 -0
  29. package/dist/cli/routes/gateway.start.d.ts +24 -0
  30. package/dist/cli/routes/gateway.status.d.ts +13 -0
  31. package/dist/cli/routes/gateway.stop.d.ts +16 -0
  32. package/dist/cli/routes/index.d.ts +1222 -0
  33. package/dist/cli/routes/profiles.$profile.as-default.d.ts +38 -0
  34. package/dist/cli/routes/profiles.$profile.rename.$newName.d.ts +42 -0
  35. package/dist/cli/routes/profiles.$profile.run.d.ts +46 -0
  36. package/dist/cli/routes/profiles.add.$profile.d.ts +54 -0
  37. package/dist/cli/routes/profiles.d.ts +16 -0
  38. package/dist/cli/routes/profiles.remove.$profile.d.ts +38 -0
  39. package/dist/cli/routes/profiles.set.$profile.d.ts +54 -0
  40. package/dist/cli/routes/status.d.ts +16 -0
  41. package/dist/cli/routes/update.d.ts +16 -0
  42. package/dist/connectors/connector-adapter.d.ts +8 -0
  43. package/dist/connectors/connector-config-schema.d.ts +43 -0
  44. package/dist/connectors/connector-factory.d.ts +32 -0
  45. package/dist/connectors/connector-listener.d.ts +17 -0
  46. package/dist/connectors/discord-adapter.d.ts +14 -0
  47. package/dist/connectors/discord-connector-schema.d.ts +10 -0
  48. package/dist/connectors/discord-event-processor.d.ts +26 -0
  49. package/dist/connectors/discord-listener.d.ts +17 -0
  50. package/dist/connectors/gh-adapter.d.ts +11 -0
  51. package/dist/connectors/gh-connector-schema.d.ts +10 -0
  52. package/dist/connectors/gh-listener.d.ts +26 -0
  53. package/dist/connectors/match-cron.d.ts +1 -0
  54. package/dist/connectors/schedule-connector-schema.d.ts +45 -0
  55. package/dist/connectors/schedule-listener.d.ts +30 -0
  56. package/dist/connectors/schedule-state-store.d.ts +19 -0
  57. package/dist/connectors/slack-adapter.d.ts +15 -0
  58. package/dist/connectors/slack-connector-schema.d.ts +11 -0
  59. package/dist/connectors/slack-event-processor.d.ts +27 -0
  60. package/dist/connectors/slack-listener.d.ts +17 -0
  61. package/dist/engine/channels/channels.d.ts +106 -0
  62. package/dist/engine/claude/claude.d.ts +49 -0
  63. package/dist/engine/claude/gateway-controller.d.ts +6 -0
  64. package/dist/engine/fs/file-system.d.ts +24 -0
  65. package/dist/engine/fs/memory-file-system.d.ts +31 -0
  66. package/dist/engine/fs/node-file-system.d.ts +15 -0
  67. package/dist/engine/http/http-client.d.ts +15 -0
  68. package/dist/engine/http/memory-http-client.d.ts +12 -0
  69. package/dist/engine/http/node-http-client.d.ts +5 -0
  70. package/dist/engine/id/id-generator.d.ts +7 -0
  71. package/dist/engine/id/memory-id-generator.d.ts +11 -0
  72. package/dist/engine/id/node-id-generator.d.ts +4 -0
  73. package/dist/engine/logger/logger.d.ts +11 -0
  74. package/dist/engine/logger/memory-logger.d.ts +14 -0
  75. package/dist/engine/logger/node-logger.d.ts +15 -0
  76. package/dist/engine/logger/noop-logger.d.ts +7 -0
  77. package/dist/engine/mcp/channel-server.d.ts +1 -0
  78. package/dist/engine/mcp/mcp.d.ts +22 -0
  79. package/dist/engine/process/memory-process-runner.d.ts +43 -0
  80. package/dist/engine/process/node-process-runner.d.ts +9 -0
  81. package/dist/engine/process/process-runner.d.ts +29 -0
  82. package/dist/engine/profiles/profile-channel-checker.d.ts +7 -0
  83. package/dist/engine/profiles/profiles.d.ts +31 -0
  84. package/dist/engine/settings/mock-settings-reader.d.ts +9 -0
  85. package/dist/engine/settings/settings-reader.d.ts +5 -0
  86. package/dist/engine/settings/settings-schema.d.ts +132 -0
  87. package/dist/engine/settings/settings-store.d.ts +18 -0
  88. package/dist/engine/time/clock.d.ts +9 -0
  89. package/dist/engine/time/memory-clock.d.ts +12 -0
  90. package/dist/engine/time/node-clock.d.ts +4 -0
  91. package/dist/funnel.d.ts +95 -0
  92. package/dist/gateway/auth-middleware.d.ts +14 -0
  93. package/dist/gateway/broadcaster.d.ts +122 -0
  94. package/dist/gateway/daemon.d.ts +2 -0
  95. package/dist/gateway/daemon.js +485 -0
  96. package/dist/gateway/factory.d.ts +7 -0
  97. package/dist/gateway/funnel-event-store.d.ts +81 -0
  98. package/dist/gateway/gateway-server.d.ts +94 -0
  99. package/dist/gateway/gateway-token.d.ts +33 -0
  100. package/dist/gateway/gateway.d.ts +58 -0
  101. package/dist/gateway/kill-competing-slack-gateways.d.ts +9 -0
  102. package/dist/gateway/listener-supervisor.d.ts +85 -0
  103. package/dist/gateway/listeners-client.d.ts +53 -0
  104. package/dist/gateway/resolve-daemon-script.d.ts +11 -0
  105. package/dist/gateway/routes/channels.connectors.call.d.ts +41 -0
  106. package/dist/gateway/routes/health.d.ts +17 -0
  107. package/dist/gateway/routes/index.d.ts +209 -0
  108. package/dist/gateway/routes/listeners.list.d.ts +14 -0
  109. package/dist/gateway/routes/listeners.restart.d.ts +34 -0
  110. package/dist/gateway/routes/listeners.start.d.ts +34 -0
  111. package/dist/gateway/routes/listeners.stop.d.ts +34 -0
  112. package/dist/gateway/routes/route-deps.d.ts +10 -0
  113. package/dist/gateway/routes/status.d.ts +30 -0
  114. package/dist/gateway/routes/validator.d.ts +19 -0
  115. package/dist/highlights-eq9cgrbb.scm +604 -0
  116. package/dist/highlights-ghv9g403.scm +205 -0
  117. package/dist/highlights-hk7bwhj4.scm +284 -0
  118. package/dist/highlights-r812a2qc.scm +150 -0
  119. package/dist/highlights-x6tmsnaa.scm +115 -0
  120. package/dist/index.d.ts +36 -0
  121. package/dist/index.js +3575 -0
  122. package/dist/injections-73j83es3.scm +27 -0
  123. package/dist/logger/leuco-human-file-writer.d.ts +33 -0
  124. package/dist/logger/leuco-human-logger.d.ts +46 -0
  125. package/dist/logger/leuco-human-record.d.ts +15 -0
  126. package/dist/logger/leuco-human-stdout-writer.d.ts +20 -0
  127. package/dist/logger/leuco-human-writer.d.ts +13 -0
  128. package/dist/logger/leuco-logger-memory-sink.d.ts +33 -0
  129. package/dist/logger/leuco-logger-record.d.ts +13 -0
  130. package/dist/logger/leuco-logger-sink.d.ts +34 -0
  131. package/dist/logger/leuco-logger-sqlite-sink.d.ts +102 -0
  132. package/dist/logger/leuco-logger.d.ts +56 -0
  133. package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  134. package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
  135. package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  136. package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  137. package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
  138. package/lib/bin.ts +78 -0
  139. package/lib/{modules → cli}/router/to-request.ts +13 -20
  140. package/lib/cli/routes/channels.$channel.connectors.$connector.rename.$newName.ts +27 -0
  141. package/lib/cli/routes/channels.$channel.connectors.$connector.request.ts +40 -0
  142. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.ts +41 -0
  143. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.ts +22 -0
  144. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.ts +23 -0
  145. package/lib/cli/routes/channels.$channel.connectors.$connector.ts +26 -0
  146. package/lib/cli/routes/channels.$channel.connectors.add.$connector.ts +92 -0
  147. package/lib/cli/routes/channels.$channel.connectors.remove.$connector.ts +22 -0
  148. package/lib/cli/routes/channels.$channel.connectors.set.$connector.ts +63 -0
  149. package/lib/cli/routes/channels.$channel.connectors.ts +26 -0
  150. package/lib/cli/routes/channels.$channel.rename.$newName.ts +22 -0
  151. package/lib/cli/routes/channels.$channel.set.delivery.$mode.ts +34 -0
  152. package/lib/cli/routes/channels.$channel.ts +34 -0
  153. package/lib/cli/routes/channels.add.$channel.ts +33 -0
  154. package/lib/cli/routes/channels.remove.$channel.ts +20 -0
  155. package/lib/cli/routes/channels.ts +39 -0
  156. package/lib/cli/routes/claude.ts +69 -0
  157. package/lib/cli/routes/gateway.listeners.ts +41 -0
  158. package/lib/cli/routes/gateway.logs.ts +123 -0
  159. package/lib/{routes/gateway/restart.ts → cli/routes/gateway.restart.ts} +20 -5
  160. package/lib/cli/routes/gateway.run.ts +41 -0
  161. package/lib/cli/routes/gateway.start.ts +50 -0
  162. package/lib/cli/routes/gateway.status.ts +19 -0
  163. package/lib/cli/routes/gateway.stop.ts +32 -0
  164. package/lib/cli/routes/gateway.ts +55 -0
  165. package/lib/cli/routes/index.ts +202 -0
  166. package/lib/cli/routes/profiles.$profile.as-default.ts +22 -0
  167. package/lib/cli/routes/profiles.$profile.rename.$newName.ts +22 -0
  168. package/lib/cli/routes/profiles.$profile.run.ts +36 -0
  169. package/lib/cli/routes/profiles.add.$profile.ts +46 -0
  170. package/lib/cli/routes/profiles.remove.$profile.ts +20 -0
  171. package/lib/cli/routes/profiles.set.$profile.ts +46 -0
  172. package/lib/cli/routes/profiles.ts +40 -0
  173. package/lib/cli/routes/status.ts +93 -0
  174. package/lib/cli/routes/update.ts +27 -0
  175. package/lib/connectors/connector-config-schema.ts +16 -0
  176. package/lib/connectors/connector-factory.ts +94 -0
  177. package/lib/connectors/connector-listener.ts +20 -0
  178. package/lib/{modules/connectors/funnel-discord-adapter.ts → connectors/discord-adapter.ts} +6 -11
  179. package/lib/{modules/connectors → connectors}/discord-connector-schema.ts +4 -1
  180. package/lib/connectors/discord-listener.ts +111 -0
  181. package/lib/{modules/connectors/funnel-gh-adapter.ts → connectors/gh-adapter.ts} +3 -6
  182. package/lib/{modules/connectors → connectors}/gh-connector-schema.ts +4 -1
  183. package/lib/{modules/connectors/funnel-gh-listener.ts → connectors/gh-listener.ts} +45 -19
  184. package/lib/{modules/connectors → connectors}/match-cron.ts +10 -4
  185. package/lib/connectors/schedule-connector-schema.ts +33 -0
  186. package/lib/connectors/schedule-listener.ts +207 -0
  187. package/lib/connectors/schedule-state-store.ts +54 -0
  188. package/lib/connectors/slack-adapter.ts +36 -0
  189. package/lib/{modules/connectors → connectors}/slack-connector-schema.ts +4 -1
  190. package/lib/{modules/connectors/funnel-slack-event-processor.ts → connectors/slack-event-processor.ts} +15 -9
  191. package/lib/{modules/connectors/funnel-slack-listener.ts → connectors/slack-listener.ts} +33 -14
  192. package/lib/engine/channels/channels.ts +520 -0
  193. package/lib/{modules/claude/funnel-claude.ts → engine/claude/claude.ts} +28 -55
  194. package/lib/engine/claude/gateway-controller.ts +4 -0
  195. package/lib/{modules/fs/funnel-file-system.ts → engine/fs/file-system.ts} +4 -0
  196. package/lib/{modules/fs/memory-funnel-file-system.ts → engine/fs/memory-file-system.ts} +20 -3
  197. package/lib/{modules/fs/node-funnel-file-system.ts → engine/fs/node-file-system.ts} +14 -2
  198. package/lib/{modules/http/memory-funnel-http-client.ts → engine/http/memory-http-client.ts} +1 -5
  199. package/lib/{modules/http/node-funnel-http-client.ts → engine/http/node-http-client.ts} +1 -5
  200. package/lib/{modules/id/memory-funnel-id-generator.ts → engine/id/memory-id-generator.ts} +1 -1
  201. package/lib/{modules/id/node-funnel-id-generator.ts → engine/id/node-id-generator.ts} +1 -1
  202. package/lib/{modules/logger/memory-funnel-logger.ts → engine/logger/memory-logger.ts} +1 -1
  203. package/lib/{modules/logger/node-funnel-logger.ts → engine/logger/node-logger.ts} +1 -1
  204. package/lib/{modules/logger/noop-funnel-logger.ts → engine/logger/noop-logger.ts} +1 -1
  205. package/lib/engine/mcp/channel-server.ts +204 -0
  206. package/lib/{modules/mcp/funnel-mcp.ts → engine/mcp/mcp.ts} +24 -10
  207. package/lib/{modules/process/memory-funnel-process-runner.ts → engine/process/memory-process-runner.ts} +1 -1
  208. package/lib/{modules/process/node-funnel-process-runner.ts → engine/process/node-process-runner.ts} +12 -21
  209. package/lib/engine/profiles/profile-channel-checker.ts +7 -0
  210. package/lib/{modules/profiles/funnel-profiles.ts → engine/profiles/profiles.ts} +41 -43
  211. package/lib/{modules/settings/mock-funnel-settings-reader.ts → engine/settings/mock-settings-reader.ts} +4 -3
  212. package/lib/{modules/settings/funnel-settings-reader.ts → engine/settings/settings-reader.ts} +1 -1
  213. package/lib/engine/settings/settings-schema.ts +46 -0
  214. package/lib/engine/settings/settings-store.ts +110 -0
  215. package/lib/{modules/time/memory-funnel-clock.ts → engine/time/memory-clock.ts} +1 -1
  216. package/lib/{modules/time/node-funnel-clock.ts → engine/time/node-clock.ts} +1 -1
  217. package/lib/funnel.ts +83 -78
  218. package/lib/gateway/auth-middleware.ts +44 -0
  219. package/lib/gateway/broadcaster.ts +319 -0
  220. package/lib/gateway/daemon.ts +47 -0
  221. package/lib/gateway/factory.ts +10 -0
  222. package/lib/gateway/funnel-event-store.ts +155 -0
  223. package/lib/gateway/gateway-server.ts +414 -0
  224. package/lib/gateway/gateway-token.ts +79 -0
  225. package/lib/{modules/gateway/funnel-gateway.ts → gateway/gateway.ts} +27 -13
  226. package/lib/{modules/gateway → gateway}/kill-competing-slack-gateways.ts +4 -4
  227. package/lib/gateway/listener-supervisor.ts +339 -0
  228. package/lib/gateway/listeners-client.ts +128 -0
  229. package/lib/gateway/resolve-daemon-script.ts +26 -0
  230. package/lib/gateway/routes/channels.connectors.call.ts +39 -0
  231. package/lib/gateway/routes/health.ts +13 -0
  232. package/lib/gateway/routes/index.ts +24 -0
  233. package/lib/gateway/routes/listeners.list.ts +6 -0
  234. package/lib/gateway/routes/listeners.restart.ts +15 -0
  235. package/lib/gateway/routes/listeners.start.ts +15 -0
  236. package/lib/gateway/routes/listeners.stop.ts +15 -0
  237. package/lib/gateway/routes/route-deps.ts +11 -0
  238. package/lib/gateway/routes/status.ts +15 -0
  239. package/lib/gateway/routes/validator.ts +17 -0
  240. package/lib/index.ts +52 -92
  241. package/lib/logger/leuco-human-file-writer.ts +65 -0
  242. package/lib/logger/leuco-human-logger.ts +98 -0
  243. package/lib/logger/leuco-human-record.ts +16 -0
  244. package/lib/logger/leuco-human-stdout-writer.ts +26 -0
  245. package/lib/logger/leuco-human-writer.ts +14 -0
  246. package/lib/logger/leuco-logger-memory-sink.ts +67 -0
  247. package/lib/logger/leuco-logger-record.ts +13 -0
  248. package/lib/logger/leuco-logger-sink.ts +33 -0
  249. package/lib/logger/leuco-logger-sqlite-sink.ts +355 -0
  250. package/lib/logger/leuco-logger.ts +135 -0
  251. package/lib/tui/app.tsx +357 -0
  252. package/lib/tui/components/add-row.tsx +18 -0
  253. package/lib/tui/components/brand.tsx +27 -0
  254. package/lib/tui/components/card.tsx +44 -0
  255. package/lib/tui/components/detail-bar.tsx +46 -0
  256. package/lib/tui/components/editable-field.tsx +33 -0
  257. package/lib/tui/components/empty-state.tsx +11 -0
  258. package/lib/tui/components/gateway-status.tsx +66 -0
  259. package/lib/tui/components/keymap.tsx +29 -0
  260. package/lib/tui/components/menu-item.tsx +73 -0
  261. package/lib/tui/components/menu.tsx +26 -0
  262. package/lib/tui/components/panel-header.tsx +22 -0
  263. package/lib/tui/components/readonly-field.tsx +18 -0
  264. package/lib/tui/components/section-header.tsx +25 -0
  265. package/lib/tui/components/selection-accent.tsx +32 -0
  266. package/lib/tui/components/session-item.tsx +33 -0
  267. package/lib/tui/components/session-list.tsx +33 -0
  268. package/lib/tui/components/ui/hascii/accordion-item.tsx +88 -0
  269. package/lib/tui/components/ui/hascii/accordion.tsx +96 -0
  270. package/lib/tui/components/ui/hascii/alert-dialog.tsx +43 -0
  271. package/lib/tui/components/ui/hascii/badge.tsx +51 -0
  272. package/lib/tui/components/ui/hascii/breadcrumb.tsx +58 -0
  273. package/lib/tui/components/ui/hascii/button.tsx +194 -0
  274. package/lib/tui/components/ui/hascii/card-content.tsx +14 -0
  275. package/lib/tui/components/ui/hascii/card-description.tsx +13 -0
  276. package/lib/tui/components/ui/hascii/card-footer.tsx +14 -0
  277. package/lib/tui/components/ui/hascii/card-header.tsx +14 -0
  278. package/lib/tui/components/ui/hascii/card-title.tsx +13 -0
  279. package/lib/tui/components/ui/hascii/card.tsx +27 -0
  280. package/lib/tui/components/ui/hascii/checkbox.tsx +65 -0
  281. package/lib/tui/components/ui/hascii/command.tsx +159 -0
  282. package/lib/tui/components/ui/hascii/dialog-content.tsx +14 -0
  283. package/lib/tui/components/ui/hascii/dialog-description.tsx +13 -0
  284. package/lib/tui/components/ui/hascii/dialog-footer.tsx +14 -0
  285. package/lib/tui/components/ui/hascii/dialog-header.tsx +14 -0
  286. package/lib/tui/components/ui/hascii/dialog-title.tsx +13 -0
  287. package/lib/tui/components/ui/hascii/dialog.tsx +27 -0
  288. package/lib/tui/components/ui/hascii/file-tree.tsx +142 -0
  289. package/lib/tui/components/ui/hascii/focus-group.tsx +62 -0
  290. package/lib/tui/components/ui/hascii/form-item.tsx +43 -0
  291. package/lib/tui/components/ui/hascii/input-otp.tsx +86 -0
  292. package/lib/tui/components/ui/hascii/input.tsx +130 -0
  293. package/lib/tui/components/ui/hascii/pagination.tsx +105 -0
  294. package/lib/tui/components/ui/hascii/progress.tsx +28 -0
  295. package/lib/tui/components/ui/hascii/select.tsx +131 -0
  296. package/lib/tui/components/ui/hascii/separator.tsx +35 -0
  297. package/lib/tui/components/ui/hascii/sidebar-content.tsx +23 -0
  298. package/lib/tui/components/ui/hascii/sidebar-header.tsx +14 -0
  299. package/lib/tui/components/ui/hascii/sidebar-menu-item.tsx +67 -0
  300. package/lib/tui/components/ui/hascii/sidebar.tsx +24 -0
  301. package/lib/tui/components/ui/hascii/skeleton.tsx +60 -0
  302. package/lib/tui/components/ui/hascii/slider.tsx +91 -0
  303. package/lib/tui/components/ui/hascii/snackbar.tsx +75 -0
  304. package/lib/tui/components/ui/hascii/sparkline.tsx +53 -0
  305. package/lib/tui/components/ui/hascii/spinner.tsx +47 -0
  306. package/lib/tui/components/ui/hascii/stepper.tsx +54 -0
  307. package/lib/tui/components/ui/hascii/switch.tsx +66 -0
  308. package/lib/tui/components/ui/hascii/table.tsx +95 -0
  309. package/lib/tui/components/ui/hascii/tabs.tsx +59 -0
  310. package/lib/tui/components/ui/hascii/toggle-group-item.tsx +45 -0
  311. package/lib/tui/components/ui/hascii/toggle-group.tsx +99 -0
  312. package/lib/tui/components/ui/hascii/tree.tsx +104 -0
  313. package/lib/tui/components/view-shell.tsx +44 -0
  314. package/lib/tui/filter-input.tsx +33 -0
  315. package/lib/tui/hooks/hascii/use-pressable.ts +54 -0
  316. package/lib/tui/parse-comma-list.ts +14 -0
  317. package/lib/tui/profile-launcher.tsx +61 -0
  318. package/lib/tui/scrollbar-options.ts +19 -0
  319. package/lib/tui/sidebar.tsx +50 -0
  320. package/lib/tui/theme.ts +40 -0
  321. package/lib/tui/tui.tsx +20 -0
  322. package/lib/tui/types.ts +38 -0
  323. package/lib/tui/unique-name.ts +18 -0
  324. package/lib/tui/use-event-stream.ts +133 -0
  325. package/lib/tui/use-snapshot.ts +99 -0
  326. package/lib/tui/utils/hascii/form-item-context.tsx +23 -0
  327. package/lib/tui/utils/hascii/input-focus-context.tsx +31 -0
  328. package/lib/tui/utils/hascii/theme-context.tsx +26 -0
  329. package/lib/tui/utils/hascii/theme.ts +176 -0
  330. package/lib/tui/views/channels-view.tsx +108 -0
  331. package/lib/tui/views/connectors-view.tsx +164 -0
  332. package/lib/tui/views/events-view.tsx +160 -0
  333. package/lib/tui/views/listeners-view.tsx +80 -0
  334. package/lib/tui/views/profiles-view.tsx +152 -0
  335. package/package.json +55 -44
  336. package/lib/api.ts +0 -54
  337. package/lib/modules/channels/channel-connector-ref-updater.ts +0 -4
  338. package/lib/modules/channels/funnel-channels.ts +0 -160
  339. package/lib/modules/connectors/connector-config-schema.ts +0 -16
  340. package/lib/modules/connectors/connector-existence-checker.ts +0 -3
  341. package/lib/modules/connectors/funnel-callable-connector-store.ts +0 -9
  342. package/lib/modules/connectors/funnel-connector-listener.ts +0 -5
  343. package/lib/modules/connectors/funnel-connector-stores.ts +0 -52
  344. package/lib/modules/connectors/funnel-connector-type-store.ts +0 -24
  345. package/lib/modules/connectors/funnel-connectors.ts +0 -151
  346. package/lib/modules/connectors/funnel-discord-listener.ts +0 -71
  347. package/lib/modules/connectors/funnel-discord-store.ts +0 -88
  348. package/lib/modules/connectors/funnel-gh-store.ts +0 -101
  349. package/lib/modules/connectors/funnel-json-connector-store.ts +0 -100
  350. package/lib/modules/connectors/funnel-schedule-listener.ts +0 -130
  351. package/lib/modules/connectors/funnel-schedule-store.ts +0 -195
  352. package/lib/modules/connectors/funnel-slack-adapter.ts +0 -31
  353. package/lib/modules/connectors/funnel-slack-store.ts +0 -90
  354. package/lib/modules/connectors/migrate-legacy-connectors.ts +0 -81
  355. package/lib/modules/connectors/schedule-connector-schema.ts +0 -18
  356. package/lib/modules/connectors/schedule-last-fired-store.ts +0 -48
  357. package/lib/modules/gateway/daemon.ts +0 -74
  358. package/lib/modules/gateway/funnel-broadcaster.ts +0 -37
  359. package/lib/modules/gateway/funnel-event-logger.ts +0 -59
  360. package/lib/modules/gateway/funnel-gateway-server.ts +0 -241
  361. package/lib/modules/mcp/channel-server.ts +0 -76
  362. package/lib/modules/profiles/profile-channel-checker.ts +0 -3
  363. package/lib/modules/profiles/profile-channel-ref-updater.ts +0 -3
  364. package/lib/modules/repos/funnel-repositories.ts +0 -112
  365. package/lib/modules/schedule/funnel-schedule.ts +0 -39
  366. package/lib/modules/settings/funnel-settings-store.ts +0 -56
  367. package/lib/modules/settings/settings-schema.ts +0 -33
  368. package/lib/modules/tui/app.tsx +0 -44
  369. package/lib/modules/tui/tui.tsx +0 -13
  370. package/lib/routes/channels/add.help.ts +0 -3
  371. package/lib/routes/channels/add.ts +0 -21
  372. package/lib/routes/channels/connectors-attach.help.ts +0 -3
  373. package/lib/routes/channels/connectors-attach.ts +0 -17
  374. package/lib/routes/channels/connectors-detach.help.ts +0 -3
  375. package/lib/routes/channels/connectors-detach.ts +0 -17
  376. package/lib/routes/channels/group.help.ts +0 -16
  377. package/lib/routes/channels/group.ts +0 -22
  378. package/lib/routes/channels/remove.help.ts +0 -3
  379. package/lib/routes/channels/remove.ts +0 -17
  380. package/lib/routes/channels/rename.help.ts +0 -5
  381. package/lib/routes/channels/rename.ts +0 -17
  382. package/lib/routes/channels/routes.ts +0 -19
  383. package/lib/routes/channels/show.help.ts +0 -1
  384. package/lib/routes/channels/show.ts +0 -26
  385. package/lib/routes/claude/claude.help.ts +0 -16
  386. package/lib/routes/claude/claude.ts +0 -76
  387. package/lib/routes/claude/routes.ts +0 -4
  388. package/lib/routes/connectors/add.help.ts +0 -28
  389. package/lib/routes/connectors/add.ts +0 -64
  390. package/lib/routes/connectors/group.help.ts +0 -14
  391. package/lib/routes/connectors/group.ts +0 -18
  392. package/lib/routes/connectors/remove.help.ts +0 -3
  393. package/lib/routes/connectors/remove.ts +0 -17
  394. package/lib/routes/connectors/rename.help.ts +0 -5
  395. package/lib/routes/connectors/rename.ts +0 -17
  396. package/lib/routes/connectors/routes.ts +0 -23
  397. package/lib/routes/connectors/schedules-add.help.ts +0 -11
  398. package/lib/routes/connectors/schedules-add.ts +0 -33
  399. package/lib/routes/connectors/schedules-group.help.ts +0 -1
  400. package/lib/routes/connectors/schedules-group.ts +0 -38
  401. package/lib/routes/connectors/schedules-remove.help.ts +0 -3
  402. package/lib/routes/connectors/schedules-remove.ts +0 -17
  403. package/lib/routes/connectors/set.help.ts +0 -8
  404. package/lib/routes/connectors/set.ts +0 -72
  405. package/lib/routes/connectors/show.help.ts +0 -1
  406. package/lib/routes/connectors/show.ts +0 -41
  407. package/lib/routes/gateway/group.help.ts +0 -15
  408. package/lib/routes/gateway/group.ts +0 -28
  409. package/lib/routes/gateway/logs.help.ts +0 -13
  410. package/lib/routes/gateway/logs.ts +0 -102
  411. package/lib/routes/gateway/restart.help.ts +0 -10
  412. package/lib/routes/gateway/routes.ts +0 -18
  413. package/lib/routes/gateway/run.help.ts +0 -12
  414. package/lib/routes/gateway/run.ts +0 -35
  415. package/lib/routes/gateway/start.help.ts +0 -15
  416. package/lib/routes/gateway/start.ts +0 -32
  417. package/lib/routes/gateway/status.help.ts +0 -9
  418. package/lib/routes/gateway/status.ts +0 -28
  419. package/lib/routes/gateway/stop.help.ts +0 -8
  420. package/lib/routes/gateway/stop.ts +0 -21
  421. package/lib/routes/profiles/add.help.ts +0 -3
  422. package/lib/routes/profiles/add.ts +0 -33
  423. package/lib/routes/profiles/group.help.ts +0 -16
  424. package/lib/routes/profiles/group.ts +0 -25
  425. package/lib/routes/profiles/launch.help.ts +0 -4
  426. package/lib/routes/profiles/launch.ts +0 -36
  427. package/lib/routes/profiles/remove.help.ts +0 -3
  428. package/lib/routes/profiles/remove.ts +0 -17
  429. package/lib/routes/profiles/rename.help.ts +0 -5
  430. package/lib/routes/profiles/rename.ts +0 -17
  431. package/lib/routes/profiles/routes.ts +0 -18
  432. package/lib/routes/profiles/set.help.ts +0 -5
  433. package/lib/routes/profiles/set.ts +0 -32
  434. package/lib/routes/repos/add.help.ts +0 -6
  435. package/lib/routes/repos/add.ts +0 -20
  436. package/lib/routes/repos/group.help.ts +0 -11
  437. package/lib/routes/repos/group.ts +0 -18
  438. package/lib/routes/repos/remove.help.ts +0 -3
  439. package/lib/routes/repos/remove.ts +0 -17
  440. package/lib/routes/repos/rename.help.ts +0 -5
  441. package/lib/routes/repos/rename.ts +0 -17
  442. package/lib/routes/repos/routes.ts +0 -17
  443. package/lib/routes/repos/set.help.ts +0 -5
  444. package/lib/routes/repos/set.ts +0 -21
  445. package/lib/routes/repos/show.help.ts +0 -1
  446. package/lib/routes/repos/show.ts +0 -19
  447. package/lib/routes/request/discord-help.ts +0 -9
  448. package/lib/routes/request/discord.help.ts +0 -19
  449. package/lib/routes/request/discord.ts +0 -65
  450. package/lib/routes/request/group.help.ts +0 -15
  451. package/lib/routes/request/group.ts +0 -9
  452. package/lib/routes/request/routes.ts +0 -14
  453. package/lib/routes/request/slack-help.ts +0 -9
  454. package/lib/routes/request/slack.help.ts +0 -19
  455. package/lib/routes/request/slack.ts +0 -61
  456. package/lib/routes/status/routes.ts +0 -4
  457. package/lib/routes/status/status.help.ts +0 -6
  458. package/lib/routes/status/status.ts +0 -77
  459. package/lib/routes/update/routes.ts +0 -4
  460. package/lib/routes/update/update.help.ts +0 -5
  461. package/lib/routes/update/update.ts +0 -21
  462. package/lib/routes.ts +0 -40
  463. /package/lib/{factory.ts → cli/factory.ts} +0 -0
  464. /package/lib/{modules → cli}/router/query-to-cli-args.ts +0 -0
  465. /package/lib/{modules → cli}/router/validator.ts +0 -0
  466. /package/lib/{modules/connectors/funnel-connector-adapter.ts → connectors/connector-adapter.ts} +0 -0
  467. /package/lib/{modules/connectors/funnel-discord-event-processor.ts → connectors/discord-event-processor.ts} +0 -0
  468. /package/lib/{modules/http/funnel-http-client.ts → engine/http/http-client.ts} +0 -0
  469. /package/lib/{modules/id/funnel-id-generator.ts → engine/id/id-generator.ts} +0 -0
  470. /package/lib/{modules/logger/funnel-logger.ts → engine/logger/logger.ts} +0 -0
  471. /package/lib/{modules/process/funnel-process-runner.ts → engine/process/process-runner.ts} +0 -0
  472. /package/lib/{modules/time/funnel-clock.ts → engine/time/clock.ts} +0 -0
@@ -1,71 +0,0 @@
1
- import { Client, GatewayIntentBits, Partials } from "discord.js"
2
- import {
3
- FunnelConnectorListener,
4
- type NotifyFn,
5
- } from "@/modules/connectors/funnel-connector-listener"
6
- import { FunnelDiscordEventProcessor } from "@/modules/connectors/funnel-discord-event-processor"
7
- import { FunnelLogger } from "@/modules/logger/funnel-logger"
8
- import { NodeFunnelLogger } from "@/modules/logger/node-funnel-logger"
9
- import type { DiscordConnectorConfig } from "@/modules/connectors/discord-connector-schema"
10
-
11
- type Deps = {
12
- config: DiscordConnectorConfig
13
- logger?: FunnelLogger
14
- }
15
-
16
- const defaultLogger = new NodeFunnelLogger()
17
-
18
- export class FunnelDiscordListener extends FunnelConnectorListener {
19
- private readonly config: DiscordConnectorConfig
20
- private readonly logger: FunnelLogger
21
-
22
- constructor(deps: Deps) {
23
- super()
24
- this.config = deps.config
25
- this.logger = deps.logger ?? defaultLogger
26
- Object.freeze(this)
27
- }
28
-
29
- async start(notify: NotifyFn): Promise<void> {
30
- const client = new Client({
31
- intents: [
32
- GatewayIntentBits.Guilds,
33
- GatewayIntentBits.GuildMessages,
34
- GatewayIntentBits.MessageContent,
35
- GatewayIntentBits.DirectMessages,
36
- ],
37
- partials: [Partials.Channel],
38
- })
39
-
40
- client.on("messageCreate", async (message) => {
41
- const processor = new FunnelDiscordEventProcessor({ ownUserId: client.user?.id ?? "" })
42
-
43
- const result = processor.process({
44
- authorId: message.author.id,
45
- authorIsBot: message.author.bot,
46
- channelId: message.channelId,
47
- guildId: message.guildId,
48
- mentionedUserIds: [...message.mentions.users.keys()],
49
- raw: message.toJSON(),
50
- })
51
-
52
- if (result.skip) return
53
-
54
- try {
55
- await notify(result.content, result.meta)
56
- } catch (error) {
57
- this.logger.error("discord notify error", {
58
- error: error instanceof Error ? error.message : String(error),
59
- })
60
- }
61
- })
62
-
63
- client.on("error", (error) => {
64
- this.logger.error("discord client error", {
65
- error: error instanceof Error ? error.message : String(error),
66
- })
67
- })
68
-
69
- await client.login(this.config.botToken)
70
- }
71
- }
@@ -1,88 +0,0 @@
1
- import { FunnelCallableConnectorStore } from "@/modules/connectors/funnel-callable-connector-store"
2
- import type { FunnelConnectorAdapter } from "@/modules/connectors/funnel-connector-adapter"
3
- import type { FunnelConnectorListener } from "@/modules/connectors/funnel-connector-listener"
4
- import {
5
- DEFAULT_FUNNEL_DIR,
6
- FunnelJsonConnectorStore,
7
- } from "@/modules/connectors/funnel-json-connector-store"
8
- import { FunnelDiscordAdapter } from "@/modules/connectors/funnel-discord-adapter"
9
- import { FunnelDiscordListener } from "@/modules/connectors/funnel-discord-listener"
10
- import {
11
- type DiscordConnectorConfig,
12
- discordConnectorSchema,
13
- } from "@/modules/connectors/discord-connector-schema"
14
- import { FunnelFileSystem } from "@/modules/fs/funnel-file-system"
15
- import type { FunnelLogger } from "@/modules/logger/funnel-logger"
16
-
17
- type Deps = {
18
- fs?: FunnelFileSystem
19
- dir?: string
20
- logger?: FunnelLogger
21
- }
22
-
23
- export type DiscordUpdateFields = {
24
- botToken?: string
25
- }
26
-
27
- export class FunnelDiscordStore extends FunnelCallableConnectorStore<DiscordConnectorConfig> {
28
- readonly type = "discord" as const
29
- private readonly store: FunnelJsonConnectorStore<DiscordConnectorConfig>
30
- private readonly logger?: FunnelLogger
31
-
32
- constructor(deps: Deps = {}) {
33
- super()
34
- this.store = new FunnelJsonConnectorStore<DiscordConnectorConfig>({
35
- type: "discord",
36
- schema: discordConnectorSchema,
37
- fs: deps.fs,
38
- dir: deps.dir ?? DEFAULT_FUNNEL_DIR,
39
- })
40
- this.logger = deps.logger
41
- Object.freeze(this)
42
- }
43
-
44
- list(): DiscordConnectorConfig[] {
45
- return this.store.list()
46
- }
47
-
48
- get(name: string): DiscordConnectorConfig | null {
49
- return this.store.get(name)
50
- }
51
-
52
- has(name: string): boolean {
53
- return this.store.has(name)
54
- }
55
-
56
- add(config: DiscordConnectorConfig): void {
57
- if (this.has(config.name)) throw new Error(`connector "${config.name}" already exists`)
58
-
59
- this.store.write(config)
60
- }
61
-
62
- update(name: string, fields: DiscordUpdateFields): void {
63
- const current = this.store.get(name)
64
-
65
- if (!current) throw new Error(`connector "${name}" not found`)
66
-
67
- this.store.write({
68
- ...current,
69
- botToken: fields.botToken ?? current.botToken,
70
- })
71
- }
72
-
73
- remove(name: string): void {
74
- this.store.remove(name)
75
- }
76
-
77
- rename(oldName: string, newName: string): void {
78
- this.store.rename(oldName, newName)
79
- }
80
-
81
- createListener(config: DiscordConnectorConfig): FunnelConnectorListener {
82
- return new FunnelDiscordListener({ config, logger: this.logger })
83
- }
84
-
85
- createAdapter(config: DiscordConnectorConfig): FunnelConnectorAdapter {
86
- return new FunnelDiscordAdapter({ config })
87
- }
88
- }
@@ -1,101 +0,0 @@
1
- import { FunnelCallableConnectorStore } from "@/modules/connectors/funnel-callable-connector-store"
2
- import type { FunnelConnectorAdapter } from "@/modules/connectors/funnel-connector-adapter"
3
- import type { FunnelConnectorListener } from "@/modules/connectors/funnel-connector-listener"
4
- import {
5
- DEFAULT_FUNNEL_DIR,
6
- FunnelJsonConnectorStore,
7
- } from "@/modules/connectors/funnel-json-connector-store"
8
- import { FunnelGhAdapter } from "@/modules/connectors/funnel-gh-adapter"
9
- import { FunnelGhListener } from "@/modules/connectors/funnel-gh-listener"
10
- import {
11
- type GhConnectorConfig,
12
- ghConnectorSchema,
13
- } from "@/modules/connectors/gh-connector-schema"
14
- import { FunnelFileSystem } from "@/modules/fs/funnel-file-system"
15
- import type { FunnelLogger } from "@/modules/logger/funnel-logger"
16
- import type { FunnelProcessRunner } from "@/modules/process/funnel-process-runner"
17
- import type { FunnelClock } from "@/modules/time/funnel-clock"
18
-
19
- type Deps = {
20
- fs?: FunnelFileSystem
21
- dir?: string
22
- process?: FunnelProcessRunner
23
- logger?: FunnelLogger
24
- clock?: FunnelClock
25
- }
26
-
27
- export type GhUpdateFields = {
28
- pollInterval?: number
29
- }
30
-
31
- export class FunnelGhStore extends FunnelCallableConnectorStore<GhConnectorConfig> {
32
- readonly type = "gh" as const
33
- private readonly store: FunnelJsonConnectorStore<GhConnectorConfig>
34
- private readonly process?: FunnelProcessRunner
35
- private readonly logger?: FunnelLogger
36
- private readonly clock?: FunnelClock
37
-
38
- constructor(deps: Deps = {}) {
39
- super()
40
- this.store = new FunnelJsonConnectorStore<GhConnectorConfig>({
41
- type: "gh",
42
- schema: ghConnectorSchema,
43
- fs: deps.fs,
44
- dir: deps.dir ?? DEFAULT_FUNNEL_DIR,
45
- })
46
- this.process = deps.process
47
- this.logger = deps.logger
48
- this.clock = deps.clock
49
- Object.freeze(this)
50
- }
51
-
52
- list(): GhConnectorConfig[] {
53
- return this.store.list()
54
- }
55
-
56
- get(name: string): GhConnectorConfig | null {
57
- return this.store.get(name)
58
- }
59
-
60
- has(name: string): boolean {
61
- return this.store.has(name)
62
- }
63
-
64
- add(config: GhConnectorConfig): void {
65
- if (this.has(config.name)) throw new Error(`connector "${config.name}" already exists`)
66
-
67
- this.store.write(config)
68
- }
69
-
70
- update(name: string, fields: GhUpdateFields): void {
71
- const current = this.store.get(name)
72
-
73
- if (!current) throw new Error(`connector "${name}" not found`)
74
-
75
- this.store.write({
76
- ...current,
77
- pollInterval: fields.pollInterval ?? current.pollInterval,
78
- })
79
- }
80
-
81
- remove(name: string): void {
82
- this.store.remove(name)
83
- }
84
-
85
- rename(oldName: string, newName: string): void {
86
- this.store.rename(oldName, newName)
87
- }
88
-
89
- createListener(config: GhConnectorConfig): FunnelConnectorListener {
90
- return new FunnelGhListener({
91
- config,
92
- process: this.process,
93
- logger: this.logger,
94
- now: this.clock ? () => this.clock!.now() : undefined,
95
- })
96
- }
97
-
98
- createAdapter(_config: GhConnectorConfig): FunnelConnectorAdapter {
99
- return new FunnelGhAdapter()
100
- }
101
- }
@@ -1,100 +0,0 @@
1
- import { homedir } from "node:os"
2
- import { join } from "node:path"
3
- import type { ZodType } from "zod"
4
- import { FunnelFileSystem } from "@/modules/fs/funnel-file-system"
5
- import { NodeFunnelFileSystem } from "@/modules/fs/node-funnel-file-system"
6
-
7
- const defaultFs = new NodeFunnelFileSystem()
8
-
9
- export const DEFAULT_FUNNEL_DIR = join(homedir(), ".funnel")
10
-
11
- type Props<TConfig> = {
12
- type: string
13
- schema: ZodType<TConfig>
14
- fs?: FunnelFileSystem
15
- dir?: string
16
- }
17
-
18
- export class FunnelJsonConnectorStore<TConfig extends { type: string; name: string }> {
19
- private readonly type: string
20
- private readonly schema: ZodType<TConfig>
21
- private readonly fs: FunnelFileSystem
22
- private readonly dir: string
23
-
24
- constructor(props: Props<TConfig>) {
25
- this.type = props.type
26
- this.schema = props.schema
27
- this.fs = props.fs ?? defaultFs
28
- const base = props.dir ?? DEFAULT_FUNNEL_DIR
29
- this.dir = join(base, "connectors", props.type)
30
- Object.freeze(this)
31
- }
32
-
33
- list(): TConfig[] {
34
- if (!this.fs.existsSync(this.dir)) return []
35
-
36
- const files = this.fs.readdirSync(this.dir).filter((f) => f.endsWith(".json"))
37
- const configs: TConfig[] = []
38
-
39
- for (const file of files) {
40
- const name = file.slice(0, -5)
41
- const config = this.get(name)
42
-
43
- if (config) configs.push(config)
44
- }
45
-
46
- return configs
47
- }
48
-
49
- get(name: string): TConfig | null {
50
- const path = this.pathFor(name)
51
-
52
- if (!this.fs.existsSync(path)) return null
53
-
54
- const content = this.fs.readFileSync(path)
55
- const parsed = JSON.parse(content)
56
- const result = this.schema.safeParse(parsed)
57
-
58
- if (!result.success) {
59
- throw new Error(
60
- `invalid ${this.type} connector "${name}": ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ")}`,
61
- )
62
- }
63
-
64
- return result.data
65
- }
66
-
67
- has(name: string): boolean {
68
- return this.fs.existsSync(this.pathFor(name))
69
- }
70
-
71
- write(config: TConfig): void {
72
- this.fs.mkdirSync(this.dir, { recursive: true })
73
- this.fs.writeFileSync(this.pathFor(config.name), `${JSON.stringify(config, null, 2)}\n`)
74
- }
75
-
76
- remove(name: string): void {
77
- if (!this.has(name)) throw new Error(`connector "${name}" not found`)
78
-
79
- this.fs.unlink(this.pathFor(name))
80
- }
81
-
82
- rename(oldName: string, newName: string): void {
83
- const config = this.get(oldName)
84
-
85
- if (!config) throw new Error(`connector "${oldName}" not found`)
86
-
87
- if (this.has(newName)) {
88
- throw new Error(`connector "${newName}" already exists`)
89
- }
90
-
91
- const renamed = { ...config, name: newName } as TConfig
92
-
93
- this.write(renamed)
94
- this.fs.unlink(this.pathFor(oldName))
95
- }
96
-
97
- pathFor(name: string): string {
98
- return join(this.dir, `${name}.json`)
99
- }
100
- }
@@ -1,130 +0,0 @@
1
- import {
2
- FunnelConnectorListener,
3
- type NotifyFn,
4
- } from "@/modules/connectors/funnel-connector-listener"
5
- import { FunnelScheduleStore } from "@/modules/connectors/funnel-schedule-store"
6
- import { matchCron } from "@/modules/connectors/match-cron"
7
- import { ScheduleLastFiredStore } from "@/modules/connectors/schedule-last-fired-store"
8
- import { FunnelLogger } from "@/modules/logger/funnel-logger"
9
- import { NodeFunnelLogger } from "@/modules/logger/node-funnel-logger"
10
- import type { ScheduleConnectorConfig } from "@/modules/connectors/schedule-connector-schema"
11
-
12
- type Deps = {
13
- config: ScheduleConnectorConfig
14
- store: FunnelScheduleStore
15
- lastFiredStore: ScheduleLastFiredStore
16
- logger?: FunnelLogger
17
- now?: () => Date
18
- }
19
-
20
- const defaultLogger = new NodeFunnelLogger()
21
-
22
- const MAX_CATCHUP_MINUTES = 60 * 24
23
-
24
- export class FunnelScheduleListener extends FunnelConnectorListener {
25
- private readonly config: ScheduleConnectorConfig
26
- private readonly store: FunnelScheduleStore
27
- private readonly lastFiredStore: ScheduleLastFiredStore
28
- private readonly logger: FunnelLogger
29
- private readonly now: () => Date
30
-
31
- constructor(deps: Deps) {
32
- super()
33
- this.config = deps.config
34
- this.store = deps.store
35
- this.lastFiredStore = deps.lastFiredStore
36
- this.logger = deps.logger ?? defaultLogger
37
- this.now = deps.now ?? (() => new Date())
38
- Object.freeze(this)
39
- }
40
-
41
- async start(notify: NotifyFn): Promise<void> {
42
- const scheduleNext = () => {
43
- const date = this.now()
44
- const msUntilNextMinute = 60_000 - (date.getSeconds() * 1000 + date.getMilliseconds())
45
- const timer = setTimeout(async () => {
46
- await this.tick(notify)
47
- scheduleNext()
48
- }, msUntilNextMinute)
49
-
50
- timer.unref()
51
- }
52
-
53
- await this.tick(notify)
54
- scheduleNext()
55
- }
56
-
57
- async tick(notify: NotifyFn): Promise<void> {
58
- const config = this.store.get(this.config.name)
59
-
60
- if (!config) return
61
-
62
- const now = this.truncateToMinute(this.now())
63
- const state = this.lastFiredStore.load()
64
- let changed = false
65
-
66
- for (const entry of config.entries) {
67
- if (!entry.enabled) continue
68
-
69
- const lastFired = state.get(entry.id)
70
- const searchFrom = lastFired ? new Date(lastFired.getTime() + 60_000) : now
71
-
72
- if (searchFrom.getTime() > now.getTime()) continue
73
-
74
- const match = this.findMostRecentMatch(entry.cron, searchFrom, now, entry.id)
75
-
76
- if (!match) continue
77
-
78
- const meta: Record<string, string> = {
79
- event_type: "schedule",
80
- schedule_id: entry.id,
81
- cron: entry.cron,
82
- fired_at: match.toISOString(),
83
- }
84
-
85
- if (match.getTime() !== now.getTime()) meta.catchup = "true"
86
-
87
- await notify(entry.prompt, meta)
88
- state.set(entry.id, match)
89
- changed = true
90
- }
91
-
92
- if (changed) this.lastFiredStore.save(state)
93
- }
94
-
95
- private findMostRecentMatch(
96
- cron: string,
97
- from: Date,
98
- until: Date,
99
- entryId: string,
100
- ): Date | null {
101
- const maxIterations = Math.min(
102
- MAX_CATCHUP_MINUTES,
103
- Math.floor((until.getTime() - from.getTime()) / 60_000) + 1,
104
- )
105
-
106
- for (let i = 0; i < maxIterations; i++) {
107
- const candidate = new Date(until.getTime() - i * 60_000)
108
-
109
- try {
110
- if (matchCron(cron, candidate)) return candidate
111
- } catch (error) {
112
- this.logger.error("invalid cron expression in schedule", {
113
- connector: this.config.name,
114
- id: entryId,
115
- cron,
116
- error: error instanceof Error ? error.message : String(error),
117
- })
118
- return null
119
- }
120
- }
121
-
122
- return null
123
- }
124
-
125
- private truncateToMinute(date: Date): Date {
126
- const copy = new Date(date.getTime())
127
- copy.setSeconds(0, 0)
128
- return copy
129
- }
130
- }
@@ -1,195 +0,0 @@
1
- import { join } from "node:path"
2
- import type { FunnelConnectorListener } from "@/modules/connectors/funnel-connector-listener"
3
- import { FunnelConnectorTypeStore } from "@/modules/connectors/funnel-connector-type-store"
4
- import { DEFAULT_FUNNEL_DIR } from "@/modules/connectors/funnel-json-connector-store"
5
- import { FunnelScheduleListener } from "@/modules/connectors/funnel-schedule-listener"
6
- import { ScheduleLastFiredStore } from "@/modules/connectors/schedule-last-fired-store"
7
- import {
8
- type ScheduleConnectorConfig,
9
- type ScheduleEntry,
10
- scheduleEntrySchema,
11
- } from "@/modules/connectors/schedule-connector-schema"
12
- import { FunnelFileSystem } from "@/modules/fs/funnel-file-system"
13
- import { NodeFunnelFileSystem } from "@/modules/fs/node-funnel-file-system"
14
- import { FunnelIdGenerator } from "@/modules/id/funnel-id-generator"
15
- import { NodeFunnelIdGenerator } from "@/modules/id/node-funnel-id-generator"
16
- import { FunnelLogger } from "@/modules/logger/funnel-logger"
17
- import { NodeFunnelLogger } from "@/modules/logger/node-funnel-logger"
18
- import type { FunnelClock } from "@/modules/time/funnel-clock"
19
-
20
- type Deps = {
21
- fs?: FunnelFileSystem
22
- dir?: string
23
- logger?: FunnelLogger
24
- idGenerator?: FunnelIdGenerator
25
- clock?: FunnelClock
26
- }
27
-
28
- const defaultFs = new NodeFunnelFileSystem()
29
- const defaultLogger = new NodeFunnelLogger()
30
- const defaultIdGenerator = new NodeFunnelIdGenerator()
31
-
32
- export class FunnelScheduleStore extends FunnelConnectorTypeStore<ScheduleConnectorConfig> {
33
- readonly type = "schedule" as const
34
- private readonly fs: FunnelFileSystem
35
- private readonly baseDir: string
36
- private readonly dir: string
37
- private readonly logger: FunnelLogger
38
- private readonly idGenerator: FunnelIdGenerator
39
- private readonly clock?: FunnelClock
40
-
41
- constructor(deps: Deps = {}) {
42
- super()
43
- this.fs = deps.fs ?? defaultFs
44
- this.baseDir = deps.dir ?? DEFAULT_FUNNEL_DIR
45
- this.dir = join(this.baseDir, "connectors", "schedule")
46
- this.logger = deps.logger ?? defaultLogger
47
- this.idGenerator = deps.idGenerator ?? defaultIdGenerator
48
- this.clock = deps.clock
49
- Object.freeze(this)
50
- }
51
-
52
- list(): ScheduleConnectorConfig[] {
53
- if (!this.fs.existsSync(this.dir)) return []
54
-
55
- const files = this.fs.readdirSync(this.dir).filter((f) => f.endsWith(".jsonl"))
56
- const configs: ScheduleConnectorConfig[] = []
57
-
58
- for (const file of files) {
59
- const name = file.slice(0, -6)
60
- const config = this.get(name)
61
-
62
- if (config) configs.push(config)
63
- }
64
-
65
- return configs
66
- }
67
-
68
- get(name: string): ScheduleConnectorConfig | null {
69
- const path = this.pathFor(name)
70
-
71
- if (!this.fs.existsSync(path)) return null
72
-
73
- return { type: "schedule", name, entries: this.readEntries(name) }
74
- }
75
-
76
- has(name: string): boolean {
77
- return this.fs.existsSync(this.pathFor(name))
78
- }
79
-
80
- add(config: ScheduleConnectorConfig): void {
81
- if (this.has(config.name)) throw new Error(`connector "${config.name}" already exists`)
82
-
83
- this.fs.mkdirSync(this.dir, { recursive: true })
84
- const lines = config.entries.map((e) => JSON.stringify(e)).join("\n")
85
- this.fs.writeFileSync(this.pathFor(config.name), lines ? `${lines}\n` : "")
86
- }
87
-
88
- remove(name: string): void {
89
- if (!this.has(name)) throw new Error(`connector "${name}" not found`)
90
-
91
- this.fs.unlink(this.pathFor(name))
92
- this.fs.unlink(this.statePathFor(name))
93
- }
94
-
95
- rename(oldName: string, newName: string): void {
96
- if (!this.has(oldName)) throw new Error(`connector "${oldName}" not found`)
97
- if (this.has(newName)) throw new Error(`connector "${newName}" already exists`)
98
-
99
- const content = this.fs.readFileSync(this.pathFor(oldName))
100
- this.fs.writeFileSync(this.pathFor(newName), content)
101
- this.fs.unlink(this.pathFor(oldName))
102
-
103
- if (this.fs.existsSync(this.statePathFor(oldName))) {
104
- const state = this.fs.readFileSync(this.statePathFor(oldName))
105
- this.fs.writeFileSync(this.statePathFor(newName), state)
106
- this.fs.unlink(this.statePathFor(oldName))
107
- }
108
- }
109
-
110
- addEntry(name: string, entry: Omit<ScheduleEntry, "id"> & { id?: string }): ScheduleEntry {
111
- if (!this.has(name)) throw new Error(`connector "${name}" not found`)
112
-
113
- const full: ScheduleEntry = {
114
- id: entry.id ?? this.idGenerator.generate(),
115
- cron: entry.cron,
116
- prompt: entry.prompt,
117
- enabled: entry.enabled ?? true,
118
- }
119
-
120
- this.fs.appendFileSync(this.pathFor(name), `${JSON.stringify(full)}\n`)
121
-
122
- return full
123
- }
124
-
125
- removeEntry(name: string, id: string): void {
126
- const entries = this.readEntries(name)
127
- const next = entries.filter((e) => e.id !== id)
128
-
129
- if (next.length === entries.length) throw new Error(`schedule entry "${id}" not found`)
130
-
131
- const content = next.map((e) => JSON.stringify(e)).join("\n")
132
- this.fs.writeFileSync(this.pathFor(name), content ? `${content}\n` : "")
133
- }
134
-
135
- createListener(config: ScheduleConnectorConfig): FunnelConnectorListener {
136
- return new FunnelScheduleListener({
137
- config,
138
- store: this,
139
- lastFiredStore: this.createLastFiredStore(config.name),
140
- logger: this.logger,
141
- now: this.clock ? () => this.clock!.now() : undefined,
142
- })
143
- }
144
-
145
- private createLastFiredStore(name: string): ScheduleLastFiredStore {
146
- return new ScheduleLastFiredStore({ connector: name, fs: this.fs, dir: this.baseDir })
147
- }
148
-
149
- private pathFor(name: string): string {
150
- return join(this.dir, `${name}.jsonl`)
151
- }
152
-
153
- private statePathFor(name: string): string {
154
- return join(this.dir, `${name}.state.json`)
155
- }
156
-
157
- private readEntries(name: string): ScheduleEntry[] {
158
- const path = this.pathFor(name)
159
-
160
- if (!this.fs.existsSync(path)) return []
161
-
162
- const content = this.fs.readFileSync(path)
163
- const lines = content.split("\n").filter((l) => l.trim().length > 0)
164
- const entries: ScheduleEntry[] = []
165
-
166
- for (let i = 0; i < lines.length; i++) {
167
- const line = lines[i]!
168
- const lineNumber = i + 1
169
-
170
- try {
171
- const parsed = JSON.parse(line)
172
- const result = scheduleEntrySchema.safeParse(parsed)
173
-
174
- if (!result.success) {
175
- this.logger.warn("skipping invalid schedule entry", {
176
- connector: name,
177
- line: lineNumber,
178
- issues: result.error.issues.map((iss) => `${iss.path.join(".")}: ${iss.message}`),
179
- })
180
- continue
181
- }
182
-
183
- entries.push(result.data)
184
- } catch (error) {
185
- this.logger.warn("skipping unparseable schedule entry", {
186
- connector: name,
187
- line: lineNumber,
188
- error: error instanceof Error ? error.message : String(error),
189
- })
190
- }
191
- }
192
-
193
- return entries
194
- }
195
- }