@interactive-inc/claude-funnel 0.4.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (349) hide show
  1. package/README.md +233 -111
  2. package/dist/bin.js +1417 -0
  3. package/dist/gateway/daemon.js +513 -0
  4. package/dist/highlights-eq9cgrbb.scm +604 -0
  5. package/dist/highlights-ghv9g403.scm +205 -0
  6. package/dist/highlights-hk7bwhj4.scm +284 -0
  7. package/dist/highlights-r812a2qc.scm +150 -0
  8. package/dist/highlights-x6tmsnaa.scm +115 -0
  9. package/dist/injections-73j83es3.scm +27 -0
  10. package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  11. package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
  12. package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  13. package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  14. package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
  15. package/lib/bin.ts +78 -0
  16. package/lib/{modules → cli}/router/to-request.ts +13 -20
  17. package/lib/cli/routes/channels.$channel.connectors.$connector.rename.$newName.ts +27 -0
  18. package/lib/cli/routes/channels.$channel.connectors.$connector.request.ts +40 -0
  19. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.ts +41 -0
  20. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.ts +22 -0
  21. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.ts +23 -0
  22. package/lib/cli/routes/channels.$channel.connectors.$connector.ts +26 -0
  23. package/lib/cli/routes/channels.$channel.connectors.add.$connector.ts +92 -0
  24. package/lib/cli/routes/channels.$channel.connectors.remove.$connector.ts +22 -0
  25. package/lib/cli/routes/channels.$channel.connectors.set.$connector.ts +63 -0
  26. package/lib/cli/routes/channels.$channel.connectors.ts +26 -0
  27. package/lib/cli/routes/channels.$channel.rename.$newName.ts +22 -0
  28. package/lib/cli/routes/channels.$channel.set.delivery.$mode.ts +34 -0
  29. package/lib/cli/routes/channels.$channel.ts +34 -0
  30. package/lib/cli/routes/channels.add.$channel.ts +33 -0
  31. package/lib/cli/routes/channels.remove.$channel.ts +20 -0
  32. package/lib/cli/routes/channels.ts +39 -0
  33. package/lib/cli/routes/claude.ts +69 -0
  34. package/lib/cli/routes/gateway.listeners.ts +41 -0
  35. package/lib/cli/routes/gateway.logs.ts +123 -0
  36. package/lib/{routes/gateway/restart.ts → cli/routes/gateway.restart.ts} +20 -5
  37. package/lib/cli/routes/gateway.run.ts +41 -0
  38. package/lib/cli/routes/gateway.start.ts +50 -0
  39. package/lib/cli/routes/gateway.status.ts +19 -0
  40. package/lib/cli/routes/gateway.stop.ts +32 -0
  41. package/lib/cli/routes/gateway.ts +55 -0
  42. package/lib/cli/routes/index.ts +202 -0
  43. package/lib/cli/routes/profiles.$profile.as-default.ts +22 -0
  44. package/lib/cli/routes/profiles.$profile.rename.$newName.ts +22 -0
  45. package/lib/cli/routes/profiles.$profile.run.ts +36 -0
  46. package/lib/cli/routes/profiles.add.$profile.ts +46 -0
  47. package/lib/cli/routes/profiles.remove.$profile.ts +20 -0
  48. package/lib/cli/routes/profiles.set.$profile.ts +46 -0
  49. package/lib/cli/routes/profiles.ts +40 -0
  50. package/lib/cli/routes/status.ts +93 -0
  51. package/lib/cli/routes/update.ts +27 -0
  52. package/lib/connectors/connector-config-schema.ts +16 -0
  53. package/lib/connectors/connector-factory.ts +94 -0
  54. package/lib/connectors/connector-listener.ts +20 -0
  55. package/lib/{modules/connectors/funnel-discord-adapter.ts → connectors/discord-adapter.ts} +6 -11
  56. package/lib/{modules/connectors → connectors}/discord-connector-schema.ts +4 -1
  57. package/lib/connectors/discord-listener.ts +111 -0
  58. package/lib/{modules/connectors/funnel-gh-adapter.ts → connectors/gh-adapter.ts} +3 -6
  59. package/lib/{modules/connectors → connectors}/gh-connector-schema.ts +4 -1
  60. package/lib/{modules/connectors/funnel-gh-listener.ts → connectors/gh-listener.ts} +57 -22
  61. package/lib/{modules/connectors → connectors}/match-cron.ts +10 -4
  62. package/lib/connectors/schedule-connector-schema.ts +33 -0
  63. package/lib/connectors/schedule-listener.ts +207 -0
  64. package/lib/connectors/schedule-state-store.ts +54 -0
  65. package/lib/connectors/slack-adapter.ts +36 -0
  66. package/lib/{modules/connectors → connectors}/slack-connector-schema.ts +4 -1
  67. package/lib/{modules/connectors/funnel-slack-event-processor.ts → connectors/slack-event-processor.ts} +15 -9
  68. package/lib/{modules/connectors/funnel-slack-listener.ts → connectors/slack-listener.ts} +39 -14
  69. package/lib/engine/channels/channels.ts +520 -0
  70. package/lib/{modules/claude/funnel-claude.ts → engine/claude/claude.ts} +47 -62
  71. package/lib/engine/claude/gateway-controller.ts +4 -0
  72. package/lib/{modules/fs/funnel-file-system.ts → engine/fs/file-system.ts} +9 -0
  73. package/lib/{modules/fs/memory-funnel-file-system.ts → engine/fs/memory-file-system.ts} +20 -3
  74. package/lib/{modules/fs/node-funnel-file-system.ts → engine/fs/node-file-system.ts} +14 -2
  75. package/lib/{modules/http/memory-funnel-http-client.ts → engine/http/memory-http-client.ts} +1 -5
  76. package/lib/{modules/http/node-funnel-http-client.ts → engine/http/node-http-client.ts} +1 -5
  77. package/lib/engine/id/id-generator.ts +7 -0
  78. package/lib/engine/id/memory-id-generator.ts +20 -0
  79. package/lib/engine/id/node-id-generator.ts +7 -0
  80. package/lib/engine/logger/logger.ts +11 -0
  81. package/lib/engine/logger/memory-logger.ts +28 -0
  82. package/lib/engine/logger/node-logger.ts +49 -0
  83. package/lib/engine/logger/noop-logger.ts +9 -0
  84. package/lib/engine/mcp/channel-server.ts +204 -0
  85. package/lib/{modules/mcp/funnel-mcp.ts → engine/mcp/mcp.ts} +29 -10
  86. package/lib/{modules/process/memory-funnel-process-runner.ts → engine/process/memory-process-runner.ts} +1 -1
  87. package/lib/{modules/process/node-funnel-process-runner.ts → engine/process/node-process-runner.ts} +12 -21
  88. package/lib/{modules/process/funnel-process-runner.ts → engine/process/process-runner.ts} +5 -0
  89. package/lib/engine/profiles/profile-channel-checker.ts +7 -0
  90. package/lib/engine/profiles/profiles.ts +126 -0
  91. package/lib/{modules/settings/mock-funnel-settings-reader.ts → engine/settings/mock-settings-reader.ts} +4 -3
  92. package/lib/{modules/settings/funnel-settings-reader.ts → engine/settings/settings-reader.ts} +1 -1
  93. package/lib/engine/settings/settings-schema.ts +46 -0
  94. package/lib/engine/settings/settings-store.ts +110 -0
  95. package/lib/engine/time/clock.ts +15 -0
  96. package/lib/engine/time/memory-clock.ts +26 -0
  97. package/lib/engine/time/node-clock.ts +7 -0
  98. package/lib/funnel.ts +148 -56
  99. package/lib/gateway/auth-middleware.ts +44 -0
  100. package/lib/gateway/broadcaster.ts +319 -0
  101. package/lib/gateway/daemon.ts +47 -0
  102. package/lib/gateway/factory.ts +10 -0
  103. package/lib/gateway/funnel-event-store.ts +155 -0
  104. package/lib/gateway/gateway-server.ts +414 -0
  105. package/lib/gateway/gateway-token.ts +79 -0
  106. package/lib/{modules/gateway/funnel-gateway.ts → gateway/gateway.ts} +70 -27
  107. package/lib/{modules/gateway → gateway}/kill-competing-slack-gateways.ts +7 -3
  108. package/lib/gateway/listener-supervisor.ts +339 -0
  109. package/lib/gateway/listeners-client.ts +128 -0
  110. package/lib/gateway/resolve-daemon-script.ts +26 -0
  111. package/lib/gateway/routes/channels.connectors.call.ts +39 -0
  112. package/lib/gateway/routes/health.ts +13 -0
  113. package/lib/gateway/routes/index.ts +24 -0
  114. package/lib/gateway/routes/listeners.list.ts +6 -0
  115. package/lib/gateway/routes/listeners.restart.ts +15 -0
  116. package/lib/gateway/routes/listeners.start.ts +15 -0
  117. package/lib/gateway/routes/listeners.stop.ts +15 -0
  118. package/lib/gateway/routes/route-deps.ts +11 -0
  119. package/lib/gateway/routes/status.ts +15 -0
  120. package/lib/gateway/routes/validator.ts +17 -0
  121. package/lib/index.ts +50 -92
  122. package/lib/logger/leuco-human-file-writer.ts +65 -0
  123. package/lib/logger/leuco-human-logger.ts +98 -0
  124. package/lib/logger/leuco-human-record.ts +16 -0
  125. package/lib/logger/leuco-human-stdout-writer.ts +26 -0
  126. package/lib/logger/leuco-human-writer.ts +14 -0
  127. package/lib/logger/leuco-logger-memory-sink.ts +67 -0
  128. package/lib/logger/leuco-logger-record.ts +13 -0
  129. package/lib/logger/leuco-logger-sink.ts +33 -0
  130. package/lib/logger/leuco-logger-sqlite-sink.ts +355 -0
  131. package/lib/logger/leuco-logger.ts +135 -0
  132. package/lib/tui/app.tsx +357 -0
  133. package/lib/tui/components/add-row.tsx +18 -0
  134. package/lib/tui/components/brand.tsx +27 -0
  135. package/lib/tui/components/card.tsx +44 -0
  136. package/lib/tui/components/detail-bar.tsx +46 -0
  137. package/lib/tui/components/editable-field.tsx +33 -0
  138. package/lib/tui/components/empty-state.tsx +11 -0
  139. package/lib/tui/components/gateway-status.tsx +66 -0
  140. package/lib/tui/components/keymap.tsx +29 -0
  141. package/lib/tui/components/menu-item.tsx +73 -0
  142. package/lib/tui/components/menu.tsx +26 -0
  143. package/lib/tui/components/panel-header.tsx +22 -0
  144. package/lib/tui/components/readonly-field.tsx +18 -0
  145. package/lib/tui/components/section-header.tsx +25 -0
  146. package/lib/tui/components/selection-accent.tsx +32 -0
  147. package/lib/tui/components/session-item.tsx +33 -0
  148. package/lib/tui/components/session-list.tsx +33 -0
  149. package/lib/tui/components/ui/hascii/accordion-item.tsx +88 -0
  150. package/lib/tui/components/ui/hascii/accordion.tsx +96 -0
  151. package/lib/tui/components/ui/hascii/alert-dialog.tsx +43 -0
  152. package/lib/tui/components/ui/hascii/badge.tsx +51 -0
  153. package/lib/tui/components/ui/hascii/breadcrumb.tsx +58 -0
  154. package/lib/tui/components/ui/hascii/button.tsx +194 -0
  155. package/lib/tui/components/ui/hascii/card-content.tsx +14 -0
  156. package/lib/tui/components/ui/hascii/card-description.tsx +13 -0
  157. package/lib/tui/components/ui/hascii/card-footer.tsx +14 -0
  158. package/lib/tui/components/ui/hascii/card-header.tsx +14 -0
  159. package/lib/tui/components/ui/hascii/card-title.tsx +13 -0
  160. package/lib/tui/components/ui/hascii/card.tsx +27 -0
  161. package/lib/tui/components/ui/hascii/checkbox.tsx +65 -0
  162. package/lib/tui/components/ui/hascii/command.tsx +159 -0
  163. package/lib/tui/components/ui/hascii/dialog-content.tsx +14 -0
  164. package/lib/tui/components/ui/hascii/dialog-description.tsx +13 -0
  165. package/lib/tui/components/ui/hascii/dialog-footer.tsx +14 -0
  166. package/lib/tui/components/ui/hascii/dialog-header.tsx +14 -0
  167. package/lib/tui/components/ui/hascii/dialog-title.tsx +13 -0
  168. package/lib/tui/components/ui/hascii/dialog.tsx +27 -0
  169. package/lib/tui/components/ui/hascii/file-tree.tsx +142 -0
  170. package/lib/tui/components/ui/hascii/focus-group.tsx +62 -0
  171. package/lib/tui/components/ui/hascii/form-item.tsx +43 -0
  172. package/lib/tui/components/ui/hascii/input-otp.tsx +86 -0
  173. package/lib/tui/components/ui/hascii/input.tsx +130 -0
  174. package/lib/tui/components/ui/hascii/pagination.tsx +105 -0
  175. package/lib/tui/components/ui/hascii/progress.tsx +28 -0
  176. package/lib/tui/components/ui/hascii/select.tsx +131 -0
  177. package/lib/tui/components/ui/hascii/separator.tsx +35 -0
  178. package/lib/tui/components/ui/hascii/sidebar-content.tsx +23 -0
  179. package/lib/tui/components/ui/hascii/sidebar-header.tsx +14 -0
  180. package/lib/tui/components/ui/hascii/sidebar-menu-item.tsx +67 -0
  181. package/lib/tui/components/ui/hascii/sidebar.tsx +24 -0
  182. package/lib/tui/components/ui/hascii/skeleton.tsx +60 -0
  183. package/lib/tui/components/ui/hascii/slider.tsx +91 -0
  184. package/lib/tui/components/ui/hascii/snackbar.tsx +75 -0
  185. package/lib/tui/components/ui/hascii/sparkline.tsx +53 -0
  186. package/lib/tui/components/ui/hascii/spinner.tsx +47 -0
  187. package/lib/tui/components/ui/hascii/stepper.tsx +54 -0
  188. package/lib/tui/components/ui/hascii/switch.tsx +66 -0
  189. package/lib/tui/components/ui/hascii/table.tsx +95 -0
  190. package/lib/tui/components/ui/hascii/tabs.tsx +59 -0
  191. package/lib/tui/components/ui/hascii/toggle-group-item.tsx +45 -0
  192. package/lib/tui/components/ui/hascii/toggle-group.tsx +99 -0
  193. package/lib/tui/components/ui/hascii/tree.tsx +104 -0
  194. package/lib/tui/components/view-shell.tsx +44 -0
  195. package/lib/tui/filter-input.tsx +33 -0
  196. package/lib/tui/hooks/hascii/use-pressable.ts +54 -0
  197. package/lib/tui/parse-comma-list.ts +14 -0
  198. package/lib/tui/profile-launcher.tsx +61 -0
  199. package/lib/tui/scrollbar-options.ts +19 -0
  200. package/lib/tui/sidebar.tsx +50 -0
  201. package/lib/tui/theme.ts +40 -0
  202. package/lib/tui/tui.tsx +20 -0
  203. package/lib/tui/types.ts +38 -0
  204. package/lib/tui/unique-name.ts +18 -0
  205. package/lib/tui/use-event-stream.ts +133 -0
  206. package/lib/tui/use-snapshot.ts +99 -0
  207. package/lib/tui/utils/hascii/form-item-context.tsx +23 -0
  208. package/lib/tui/utils/hascii/input-focus-context.tsx +31 -0
  209. package/lib/tui/utils/hascii/theme-context.tsx +26 -0
  210. package/lib/tui/utils/hascii/theme.ts +176 -0
  211. package/lib/tui/views/channels-view.tsx +108 -0
  212. package/lib/tui/views/connectors-view.tsx +164 -0
  213. package/lib/tui/views/events-view.tsx +160 -0
  214. package/lib/tui/views/listeners-view.tsx +80 -0
  215. package/lib/tui/views/profiles-view.tsx +152 -0
  216. package/package.json +51 -34
  217. package/lib/modules/channels/channel-connector-ref-updater.ts +0 -4
  218. package/lib/modules/channels/funnel-channels.ts +0 -155
  219. package/lib/modules/connectors/connector-config-schema.ts +0 -16
  220. package/lib/modules/connectors/connector-existence-checker.ts +0 -3
  221. package/lib/modules/connectors/funnel-callable-connector-store.ts +0 -9
  222. package/lib/modules/connectors/funnel-connector-listener.ts +0 -5
  223. package/lib/modules/connectors/funnel-connector-stores.ts +0 -24
  224. package/lib/modules/connectors/funnel-connector-type-store.ts +0 -24
  225. package/lib/modules/connectors/funnel-connectors.ts +0 -145
  226. package/lib/modules/connectors/funnel-discord-listener.ts +0 -65
  227. package/lib/modules/connectors/funnel-discord-store.ts +0 -84
  228. package/lib/modules/connectors/funnel-gh-store.ts +0 -84
  229. package/lib/modules/connectors/funnel-json-connector-store.ts +0 -100
  230. package/lib/modules/connectors/funnel-schedule-listener.ts +0 -124
  231. package/lib/modules/connectors/funnel-schedule-store.ts +0 -178
  232. package/lib/modules/connectors/funnel-slack-adapter.ts +0 -31
  233. package/lib/modules/connectors/funnel-slack-store.ts +0 -86
  234. package/lib/modules/connectors/migrate-legacy-connectors.ts +0 -77
  235. package/lib/modules/connectors/schedule-connector-schema.ts +0 -18
  236. package/lib/modules/connectors/schedule-last-fired-store.ts +0 -48
  237. package/lib/modules/gateway/daemon.ts +0 -207
  238. package/lib/modules/gateway/funnel-broadcaster.ts +0 -37
  239. package/lib/modules/gateway/funnel-event-logger.ts +0 -59
  240. package/lib/modules/logger.ts +0 -26
  241. package/lib/modules/mcp/channel-server.ts +0 -76
  242. package/lib/modules/profiles/funnel-profiles.ts +0 -123
  243. package/lib/modules/profiles/profile-channel-checker.ts +0 -3
  244. package/lib/modules/profiles/profile-channel-ref-updater.ts +0 -3
  245. package/lib/modules/repos/funnel-repositories.ts +0 -107
  246. package/lib/modules/schedule/funnel-schedule.ts +0 -34
  247. package/lib/modules/settings/funnel-settings-store.ts +0 -56
  248. package/lib/modules/settings/settings-schema.ts +0 -33
  249. package/lib/modules/tui/app.tsx +0 -44
  250. package/lib/modules/tui/tui.tsx +0 -13
  251. package/lib/routes/channels/add.help.ts +0 -3
  252. package/lib/routes/channels/add.ts +0 -21
  253. package/lib/routes/channels/connectors-attach.help.ts +0 -3
  254. package/lib/routes/channels/connectors-attach.ts +0 -17
  255. package/lib/routes/channels/connectors-detach.help.ts +0 -3
  256. package/lib/routes/channels/connectors-detach.ts +0 -17
  257. package/lib/routes/channels/group.help.ts +0 -16
  258. package/lib/routes/channels/group.ts +0 -22
  259. package/lib/routes/channels/remove.help.ts +0 -3
  260. package/lib/routes/channels/remove.ts +0 -17
  261. package/lib/routes/channels/rename.help.ts +0 -5
  262. package/lib/routes/channels/rename.ts +0 -17
  263. package/lib/routes/channels/routes.ts +0 -19
  264. package/lib/routes/channels/show.help.ts +0 -1
  265. package/lib/routes/channels/show.ts +0 -26
  266. package/lib/routes/claude/claude.help.ts +0 -16
  267. package/lib/routes/claude/claude.ts +0 -76
  268. package/lib/routes/claude/routes.ts +0 -4
  269. package/lib/routes/connectors/add.help.ts +0 -28
  270. package/lib/routes/connectors/add.ts +0 -64
  271. package/lib/routes/connectors/group.help.ts +0 -14
  272. package/lib/routes/connectors/group.ts +0 -18
  273. package/lib/routes/connectors/remove.help.ts +0 -3
  274. package/lib/routes/connectors/remove.ts +0 -17
  275. package/lib/routes/connectors/rename.help.ts +0 -5
  276. package/lib/routes/connectors/rename.ts +0 -17
  277. package/lib/routes/connectors/routes.ts +0 -23
  278. package/lib/routes/connectors/schedules-add.help.ts +0 -11
  279. package/lib/routes/connectors/schedules-add.ts +0 -33
  280. package/lib/routes/connectors/schedules-group.help.ts +0 -1
  281. package/lib/routes/connectors/schedules-group.ts +0 -38
  282. package/lib/routes/connectors/schedules-remove.help.ts +0 -3
  283. package/lib/routes/connectors/schedules-remove.ts +0 -17
  284. package/lib/routes/connectors/set.help.ts +0 -8
  285. package/lib/routes/connectors/set.ts +0 -72
  286. package/lib/routes/connectors/show.help.ts +0 -1
  287. package/lib/routes/connectors/show.ts +0 -41
  288. package/lib/routes/gateway/group.help.ts +0 -15
  289. package/lib/routes/gateway/group.ts +0 -28
  290. package/lib/routes/gateway/logs.help.ts +0 -13
  291. package/lib/routes/gateway/logs.ts +0 -100
  292. package/lib/routes/gateway/restart.help.ts +0 -10
  293. package/lib/routes/gateway/routes.ts +0 -18
  294. package/lib/routes/gateway/run.help.ts +0 -12
  295. package/lib/routes/gateway/run.ts +0 -35
  296. package/lib/routes/gateway/start.help.ts +0 -15
  297. package/lib/routes/gateway/start.ts +0 -32
  298. package/lib/routes/gateway/status.help.ts +0 -9
  299. package/lib/routes/gateway/status.ts +0 -28
  300. package/lib/routes/gateway/stop.help.ts +0 -8
  301. package/lib/routes/gateway/stop.ts +0 -21
  302. package/lib/routes/profiles/add.help.ts +0 -3
  303. package/lib/routes/profiles/add.ts +0 -33
  304. package/lib/routes/profiles/group.help.ts +0 -16
  305. package/lib/routes/profiles/group.ts +0 -25
  306. package/lib/routes/profiles/launch.help.ts +0 -4
  307. package/lib/routes/profiles/launch.ts +0 -36
  308. package/lib/routes/profiles/remove.help.ts +0 -3
  309. package/lib/routes/profiles/remove.ts +0 -17
  310. package/lib/routes/profiles/rename.help.ts +0 -5
  311. package/lib/routes/profiles/rename.ts +0 -17
  312. package/lib/routes/profiles/routes.ts +0 -18
  313. package/lib/routes/profiles/set.help.ts +0 -5
  314. package/lib/routes/profiles/set.ts +0 -32
  315. package/lib/routes/repos/add.help.ts +0 -6
  316. package/lib/routes/repos/add.ts +0 -20
  317. package/lib/routes/repos/group.help.ts +0 -11
  318. package/lib/routes/repos/group.ts +0 -18
  319. package/lib/routes/repos/remove.help.ts +0 -3
  320. package/lib/routes/repos/remove.ts +0 -17
  321. package/lib/routes/repos/rename.help.ts +0 -5
  322. package/lib/routes/repos/rename.ts +0 -17
  323. package/lib/routes/repos/routes.ts +0 -17
  324. package/lib/routes/repos/set.help.ts +0 -5
  325. package/lib/routes/repos/set.ts +0 -21
  326. package/lib/routes/repos/show.help.ts +0 -1
  327. package/lib/routes/repos/show.ts +0 -19
  328. package/lib/routes/request/discord-help.ts +0 -9
  329. package/lib/routes/request/discord.help.ts +0 -19
  330. package/lib/routes/request/discord.ts +0 -65
  331. package/lib/routes/request/group.help.ts +0 -15
  332. package/lib/routes/request/group.ts +0 -9
  333. package/lib/routes/request/routes.ts +0 -14
  334. package/lib/routes/request/slack-help.ts +0 -9
  335. package/lib/routes/request/slack.help.ts +0 -19
  336. package/lib/routes/request/slack.ts +0 -61
  337. package/lib/routes/status/routes.ts +0 -4
  338. package/lib/routes/status/status.help.ts +0 -6
  339. package/lib/routes/status/status.ts +0 -77
  340. package/lib/routes/update/routes.ts +0 -4
  341. package/lib/routes/update/update.help.ts +0 -5
  342. package/lib/routes/update/update.ts +0 -21
  343. package/lib/routes.ts +0 -40
  344. /package/lib/{factory.ts → cli/factory.ts} +0 -0
  345. /package/lib/{modules → cli}/router/query-to-cli-args.ts +0 -0
  346. /package/lib/{modules → cli}/router/validator.ts +0 -0
  347. /package/lib/{modules/connectors/funnel-connector-adapter.ts → connectors/connector-adapter.ts} +0 -0
  348. /package/lib/{modules/connectors/funnel-discord-event-processor.ts → connectors/discord-event-processor.ts} +0 -0
  349. /package/lib/{modules/http/funnel-http-client.ts → engine/http/http-client.ts} +0 -0
@@ -1,48 +0,0 @@
1
- import { dirname, join } from "node:path"
2
- import { DEFAULT_FUNNEL_DIR } from "@/modules/connectors/funnel-json-connector-store"
3
- import { FunnelFileSystem } from "@/modules/fs/funnel-file-system"
4
- import { NodeFunnelFileSystem } from "@/modules/fs/node-funnel-file-system"
5
-
6
- type Deps = {
7
- connector: string
8
- fs?: FunnelFileSystem
9
- dir?: string
10
- }
11
-
12
- const defaultFs = new NodeFunnelFileSystem()
13
-
14
- export class ScheduleLastFiredStore {
15
- private readonly path: string
16
- private readonly fs: FunnelFileSystem
17
-
18
- constructor(deps: Deps) {
19
- this.fs = deps.fs ?? defaultFs
20
- const base = deps.dir ?? DEFAULT_FUNNEL_DIR
21
- this.path = join(base, "connectors", "schedule", `${deps.connector}.state.json`)
22
- Object.freeze(this)
23
- }
24
-
25
- load(): Map<string, Date> {
26
- if (!this.fs.existsSync(this.path)) return new Map()
27
-
28
- const raw = JSON.parse(this.fs.readFileSync(this.path)) as Record<string, string>
29
- const map = new Map<string, Date>()
30
-
31
- for (const [id, iso] of Object.entries(raw)) {
32
- map.set(id, new Date(iso))
33
- }
34
-
35
- return map
36
- }
37
-
38
- save(state: Map<string, Date>): void {
39
- const obj: Record<string, string> = {}
40
-
41
- for (const [id, date] of state) {
42
- obj[id] = date.toISOString()
43
- }
44
-
45
- this.fs.mkdirSync(dirname(this.path), { recursive: true })
46
- this.fs.writeFileSync(this.path, `${JSON.stringify(obj, null, 2)}\n`)
47
- }
48
- }
@@ -1,207 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs"
3
- import { join } from "node:path"
4
- import type { ServerWebSocket } from "bun"
5
- import { Hono } from "hono"
6
- import { logger } from "@/modules/logger"
7
- import { FunnelChannels } from "@/modules/channels/funnel-channels"
8
- import { FunnelConnectors } from "@/modules/connectors/funnel-connectors"
9
- import { createConnectorStores } from "@/modules/connectors/funnel-connector-stores"
10
- import { migrateLegacyConnectors } from "@/modules/connectors/migrate-legacy-connectors"
11
- import { FunnelBroadcaster } from "@/modules/gateway/funnel-broadcaster"
12
- import { FunnelEventLogger } from "@/modules/gateway/funnel-event-logger"
13
- import { killCompetingSlackGateways } from "@/modules/gateway/kill-competing-slack-gateways"
14
- import { FunnelProfiles } from "@/modules/profiles/funnel-profiles"
15
- import { FUNNEL_DIR, FunnelSettingsStore } from "@/modules/settings/funnel-settings-store"
16
-
17
- const PORT = Number(process.env.FUNNEL_PORT) || 9742
18
- const PID_FILE = join(FUNNEL_DIR, "gateway.pid")
19
- const LOG_DIR = "/tmp/funnel/events"
20
-
21
- mkdirSync(FUNNEL_DIR, { recursive: true })
22
-
23
- if (existsSync(PID_FILE)) {
24
- const existing = Number(readFileSync(PID_FILE, "utf-8").trim())
25
-
26
- if (existing > 0) {
27
- const check = Bun.spawnSync(["ps", "-p", String(existing), "-o", "state="], {
28
- stdout: "pipe",
29
- stderr: "pipe",
30
- })
31
-
32
- if (check.exitCode === 0 && check.stdout.toString().trim()) {
33
- logger.error(`funnel gateway already running`, { pid: existing })
34
- process.exit(1)
35
- }
36
- }
37
- }
38
-
39
- writeFileSync(PID_FILE, String(process.pid))
40
-
41
- process.on("exit", () => {
42
- try {
43
- unlinkSync(PID_FILE)
44
- } catch {
45
- // ignore
46
- }
47
- })
48
- process.on("SIGINT", () => process.exit(130))
49
- process.on("SIGTERM", () => process.exit(143))
50
-
51
- const store = new FunnelSettingsStore()
52
- const connectorStores = createConnectorStores()
53
-
54
- migrateLegacyConnectors({ stores: connectorStores })
55
-
56
- const profiles = new FunnelProfiles({ store })
57
- const channels: FunnelChannels = new FunnelChannels({
58
- store,
59
- connectorChecker: { has: (name: string) => connectors.has(name) },
60
- profileChecker: profiles,
61
- profileRefUpdater: profiles,
62
- })
63
- const connectors: FunnelConnectors = new FunnelConnectors({
64
- ...connectorStores,
65
- refUpdater: channels,
66
- })
67
-
68
- const eventLogger = new FunnelEventLogger({ logDir: LOG_DIR })
69
- const broadcaster = new FunnelBroadcaster()
70
- const app = new Hono()
71
-
72
- app.get("/health", (c) =>
73
- c.json({
74
- ok: true,
75
- pid: process.pid,
76
- clients: broadcaster.getClientCount(),
77
- }),
78
- )
79
-
80
- app.get("/status", (c) =>
81
- c.json({
82
- ok: true,
83
- clients: broadcaster.listChannels(),
84
- }),
85
- )
86
-
87
- const resolveConnectors = (channelName: string): string[] => {
88
- const settings = store.read()
89
- const channel = settings?.channels.find((c) => c.name === channelName)
90
-
91
- return channel?.connectors ?? []
92
- }
93
-
94
- type WsData = { channel: string; connectors: string[] }
95
-
96
- Bun.serve<WsData>({
97
- port: PORT,
98
- fetch(request, server) {
99
- const url = new URL(request.url)
100
-
101
- if (url.pathname === "/ws" && request.headers.get("upgrade") === "websocket") {
102
- const channelName = url.searchParams.get("channel") ?? ""
103
- const connectors = channelName ? resolveConnectors(channelName) : []
104
- const data: WsData = { channel: channelName, connectors }
105
-
106
- const upgraded = server.upgrade(request, { data })
107
-
108
- if (upgraded) return undefined
109
-
110
- return new Response("WebSocket upgrade failed", { status: 400 })
111
- }
112
-
113
- return app.fetch(request)
114
- },
115
- websocket: {
116
- open(ws: ServerWebSocket<WsData>) {
117
- const data = ws.data
118
-
119
- broadcaster.addClient(ws, data)
120
-
121
- eventLogger.log("channel connected", {
122
- event_type: "system",
123
- action: "channel_connect",
124
- channel: data.channel,
125
- connectors: data.connectors.join(","),
126
- total: String(broadcaster.getClientCount()),
127
- })
128
- },
129
- close(ws: ServerWebSocket<WsData>) {
130
- broadcaster.removeClient(ws)
131
-
132
- eventLogger.log("channel disconnected", {
133
- event_type: "system",
134
- action: "channel_disconnect",
135
- total: String(broadcaster.getClientCount()),
136
- })
137
- },
138
- message(_ws: ServerWebSocket<WsData>, _message: string | Buffer) {
139
- // Future: channel → gateway messages
140
- },
141
- },
142
- })
143
-
144
- eventLogger.log("gateway started", {
145
- event_type: "system",
146
- action: "gateway_start",
147
- port: String(PORT),
148
- pid: String(process.pid),
149
- })
150
-
151
- logger.info(`funnel gateway listening`, {
152
- url: `http://localhost:${PORT}`,
153
- websocket: `ws://localhost:${PORT}/ws`,
154
- health: `http://localhost:${PORT}/health`,
155
- })
156
-
157
- const notify = async (
158
- connectorName: string,
159
- content: string,
160
- meta?: Record<string, string>,
161
- ): Promise<void> => {
162
- const withConnector: Record<string, string> = { ...meta, connector: connectorName }
163
-
164
- eventLogger.log(content, withConnector)
165
- broadcaster.broadcast(content, withConnector)
166
- }
167
-
168
- const allConnectors = connectors.list()
169
-
170
- // Multiple Slack Socket Mode connections sharing one App Token steal DMs/mentions
171
- // from each other. Terminate other bun + gateway/bolt/slack processes first.
172
- if (allConnectors.some((c) => c.type === "slack")) {
173
- const killed = await killCompetingSlackGateways({ selfPid: process.pid })
174
-
175
- if (killed.length > 0) {
176
- eventLogger.log("killed competing Slack gateway processes", {
177
- event_type: "system",
178
- action: "kill_competing",
179
- pids: killed.join(","),
180
- })
181
- }
182
- }
183
-
184
- for (const { config, listener } of connectors.createListeners()) {
185
- const bind = (content: string, meta?: Record<string, string>) =>
186
- notify(config.name, content, meta)
187
-
188
- try {
189
- await listener.start(bind)
190
-
191
- eventLogger.log(`${config.type} listener started: ${config.name}`, {
192
- event_type: "system",
193
- action: `${config.type}_connect`,
194
- connector: config.name,
195
- })
196
-
197
- logger.info(`${config.type} listener started`, { connector: config.name })
198
- } catch (error) {
199
- logger.error(`${config.type} listener failed`, {
200
- connector: config.name,
201
- error: error instanceof Error ? error.message : String(error),
202
- })
203
- }
204
- }
205
-
206
- logger.info(`event logs: ${LOG_DIR}`)
207
- logger.info("funnel gateway running")
@@ -1,37 +0,0 @@
1
- import type { ServerWebSocket } from "bun"
2
-
3
- type ClientData = {
4
- channel: string
5
- connectors: string[]
6
- }
7
-
8
- export class FunnelBroadcaster {
9
- private readonly clients: Map<ServerWebSocket<unknown>, ClientData> = new Map()
10
-
11
- addClient(ws: ServerWebSocket<unknown>, data: ClientData): void {
12
- this.clients.set(ws, data)
13
- }
14
-
15
- removeClient(ws: ServerWebSocket<unknown>): void {
16
- this.clients.delete(ws)
17
- }
18
-
19
- getClientCount(): number {
20
- return this.clients.size
21
- }
22
-
23
- listChannels(): { channel: string; connectors: string[] }[] {
24
- return [...this.clients.values()].map((d) => ({ ...d }))
25
- }
26
-
27
- broadcast(content: string, meta?: Record<string, string>): void {
28
- const payload = JSON.stringify({ content, meta })
29
- const connector = meta?.connector
30
-
31
- for (const [ws, data] of this.clients) {
32
- if (connector && !data.connectors.includes(connector)) continue
33
-
34
- ws.send(payload)
35
- }
36
- }
37
- }
@@ -1,59 +0,0 @@
1
- import { join } from "node:path"
2
- import { FunnelFileSystem } from "@/modules/fs/funnel-file-system"
3
- import { NodeFunnelFileSystem } from "@/modules/fs/node-funnel-file-system"
4
-
5
- const MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000 // 30 days
6
-
7
- type Deps = {
8
- logDir: string
9
- fs?: FunnelFileSystem
10
- now?: () => number
11
- }
12
-
13
- const defaultFs = new NodeFunnelFileSystem()
14
-
15
- export class FunnelEventLogger {
16
- private readonly logDir: string
17
- private readonly fs: FunnelFileSystem
18
- private readonly now: () => number
19
-
20
- constructor(deps: Deps) {
21
- this.logDir = deps.logDir
22
- this.fs = deps.fs ?? defaultFs
23
- this.now = deps.now ?? (() => Date.now())
24
- this.fs.mkdirSync(this.logDir, { recursive: true })
25
- this.rotate()
26
- Object.freeze(this)
27
- }
28
-
29
- log(content: string, meta?: Record<string, string>): void {
30
- const entry = {
31
- timestamp: new Date(this.now()).toISOString(),
32
- eventType: meta?.event_type ?? "unknown",
33
- content: content.length > 2000 ? `${content.slice(0, 2000)}...` : content,
34
- meta,
35
- }
36
- const dateStr = new Date(this.now()).toISOString().slice(0, 10)
37
- const logFile = join(this.logDir, `${dateStr}.jsonl`)
38
-
39
- this.fs.appendFileSync(logFile, `${JSON.stringify(entry)}\n`)
40
- }
41
-
42
- private rotate(): void {
43
- const now = this.now()
44
-
45
- for (const name of this.fs.readdirSync(this.logDir)) {
46
- if (!name.endsWith(".jsonl")) continue
47
-
48
- const path = join(this.logDir, name)
49
-
50
- try {
51
- const stat = this.fs.statSync(path)
52
-
53
- if (now - stat.mtimeMs > MAX_AGE_MS) this.fs.unlink(path)
54
- } catch {
55
- // ignore
56
- }
57
- }
58
- }
59
- }
@@ -1,26 +0,0 @@
1
- import { appendFileSync, mkdirSync } from "node:fs"
2
- import { dirname, join } from "node:path"
3
-
4
- const LOG_FILE = join("/tmp/funnel", "funnel.log")
5
-
6
- type Level = "info" | "warn" | "error"
7
-
8
- const write = (level: Level, message: string, meta?: Record<string, unknown>) => {
9
- mkdirSync(dirname(LOG_FILE), { recursive: true })
10
-
11
- const entry = {
12
- time: new Date().toISOString(),
13
- level,
14
- message,
15
- ...(meta ? { meta } : {}),
16
- }
17
-
18
- appendFileSync(LOG_FILE, `${JSON.stringify(entry)}\n`)
19
- }
20
-
21
- export const logger = {
22
- info: (message: string, meta?: Record<string, unknown>) => write("info", message, meta),
23
- warn: (message: string, meta?: Record<string, unknown>) => write("warn", message, meta),
24
- error: (message: string, meta?: Record<string, unknown>) => write("error", message, meta),
25
- file: LOG_FILE,
26
- }
@@ -1,76 +0,0 @@
1
- import { Server } from "@modelcontextprotocol/sdk/server/index.js"
2
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
3
- import { FUNNEL_MCP_NAME } from "@/modules/mcp/funnel-mcp"
4
-
5
- const GATEWAY_WS_URL = process.env.FUNNEL_GATEWAY_URL ?? "ws://localhost:9742/ws"
6
- const RECONNECT_DELAY = 1000
7
- const MAX_RECONNECT_DELAY = 10000
8
-
9
- export const startChannelServer = async (): Promise<void> => {
10
- const server = new Server(
11
- { name: FUNNEL_MCP_NAME, version: "1.0.0" },
12
- {
13
- capabilities: {
14
- experimental: { "claude/channel": {} },
15
- },
16
- instructions: [
17
- `Events arrive inside <channel source="${FUNNEL_MCP_NAME}"> tags. Use meta.event_type to discriminate.`,
18
- "",
19
- "To reply or act on an event, run `funnel request <platform> --help` via the Bash tool (e.g. `funnel request slack --help`). For general CLI usage, run `funnel --help`.",
20
- ].join("\n"),
21
- },
22
- )
23
-
24
- const transport = new StdioServerTransport()
25
-
26
- await server.connect(transport)
27
-
28
- const channelId = process.env.FUNNEL_CHANNEL_ID
29
-
30
- if (!channelId) return
31
-
32
- const wsUrl = `${GATEWAY_WS_URL}?channel=${encodeURIComponent(channelId)}`
33
- let reconnectDelay = RECONNECT_DELAY
34
-
35
- const connect = () => {
36
- const ws = new WebSocket(wsUrl)
37
-
38
- ws.addEventListener("open", () => {
39
- reconnectDelay = RECONNECT_DELAY
40
- process.stderr.write(`funnel: connected (${wsUrl})\n`)
41
- })
42
-
43
- ws.addEventListener("message", async (event) => {
44
- try {
45
- const payload = JSON.parse(String(event.data))
46
- const eventType = payload.meta?.event_type ?? "unknown"
47
-
48
- process.stderr.write(`funnel: received event (${eventType})\n`)
49
-
50
- await server.notification({
51
- method: "notifications/claude/channel",
52
- params: {
53
- content: payload.content,
54
- meta: payload.meta,
55
- },
56
- })
57
- } catch (error) {
58
- process.stderr.write(
59
- `funnel: error: ${error instanceof Error ? error.message : String(error)}\n`,
60
- )
61
- }
62
- })
63
-
64
- ws.addEventListener("close", () => {
65
- process.stderr.write(`funnel: disconnected, reconnecting in ${reconnectDelay}ms\n`)
66
- setTimeout(connect, reconnectDelay)
67
- reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY)
68
- })
69
-
70
- ws.addEventListener("error", () => {
71
- // close handler will reconnect
72
- })
73
- }
74
-
75
- connect()
76
- }
@@ -1,123 +0,0 @@
1
- import { FunnelSettingsReader } from "@/modules/settings/funnel-settings-reader"
2
- import type { ProfileConfig } from "@/modules/settings/settings-schema"
3
-
4
- type Deps = {
5
- store: FunnelSettingsReader
6
- }
7
-
8
- export class FunnelProfiles {
9
- private readonly store: FunnelSettingsReader
10
-
11
- constructor(deps: Deps) {
12
- this.store = deps.store
13
- Object.freeze(this)
14
- }
15
-
16
- list(): ProfileConfig[] {
17
- return this.store.read().profiles
18
- }
19
-
20
- get(name: string): ProfileConfig | null {
21
- return this.list().find((p) => p.name === name) ?? null
22
- }
23
-
24
- add(config: ProfileConfig): void {
25
- const settings = this.store.read()
26
-
27
- if (settings.profiles.some((p) => p.name === config.name)) {
28
- throw new Error(`profile "${config.name}" already exists`)
29
- }
30
-
31
- if (!settings.channels.some((c) => c.name === config.channel)) {
32
- throw new Error(`channel "${config.channel}" not found`)
33
- }
34
-
35
- if (config.repo && !settings.repositories.some((r) => r.name === config.repo)) {
36
- throw new Error(`repo "${config.repo}" not found`)
37
- }
38
-
39
- settings.profiles.push(config)
40
-
41
- this.store.write(settings)
42
- }
43
-
44
- remove(name: string): void {
45
- const settings = this.store.read()
46
-
47
- const index = settings.profiles.findIndex((p) => p.name === name)
48
-
49
- if (index < 0) throw new Error(`profile "${name}" not found`)
50
-
51
- settings.profiles.splice(index, 1)
52
-
53
- this.store.write(settings)
54
- }
55
-
56
- rename(oldName: string, newName: string): void {
57
- const settings = this.store.read()
58
-
59
- const profile = settings.profiles.find((p) => p.name === oldName)
60
-
61
- if (!profile) throw new Error(`profile "${oldName}" not found`)
62
-
63
- if (settings.profiles.some((p) => p.name === newName)) {
64
- throw new Error(`profile "${newName}" already exists`)
65
- }
66
-
67
- profile.name = newName
68
-
69
- this.store.write(settings)
70
- }
71
-
72
- hasChannelRef(channelName: string): boolean {
73
- return this.store.read().profiles.some((p) => p.channel === channelName)
74
- }
75
-
76
- renameChannelRef(oldName: string, newName: string): void {
77
- const settings = this.store.read()
78
- let changed = false
79
-
80
- for (const profile of settings.profiles) {
81
- if (profile.channel === oldName) {
82
- profile.channel = newName
83
- changed = true
84
- }
85
- }
86
-
87
- if (changed) this.store.write(settings)
88
- }
89
-
90
- update(name: string, fields: Partial<Omit<ProfileConfig, "name">>): void {
91
- const settings = this.store.read()
92
-
93
- const profile = settings.profiles.find((p) => p.name === name)
94
-
95
- if (!profile) throw new Error(`profile "${name}" not found`)
96
-
97
- if (fields.channel !== undefined) {
98
- if (!settings.channels.some((c) => c.name === fields.channel)) {
99
- throw new Error(`channel "${fields.channel}" not found`)
100
- }
101
-
102
- profile.channel = fields.channel
103
- }
104
-
105
- if (fields.repo !== undefined) {
106
- if (fields.repo && !settings.repositories.some((r) => r.name === fields.repo)) {
107
- throw new Error(`repo "${fields.repo}" not found`)
108
- }
109
-
110
- profile.repo = fields.repo || undefined
111
- }
112
-
113
- if (fields.subAgent !== undefined) {
114
- profile.subAgent = fields.subAgent || undefined
115
- }
116
-
117
- if (fields.envFiles !== undefined) {
118
- profile.envFiles = fields.envFiles
119
- }
120
-
121
- this.store.write(settings)
122
- }
123
- }
@@ -1,3 +0,0 @@
1
- export type ProfileChannelChecker = {
2
- hasChannelRef(channelName: string): boolean
3
- }
@@ -1,3 +0,0 @@
1
- export type ProfileChannelRefUpdater = {
2
- renameChannelRef(oldName: string, newName: string): void
3
- }
@@ -1,107 +0,0 @@
1
- import type { FunnelMcp } from "@/modules/mcp/funnel-mcp"
2
- import { FunnelSettingsReader } from "@/modules/settings/funnel-settings-reader"
3
- import type { RepositoryConfig } from "@/modules/settings/settings-schema"
4
-
5
- type Deps = {
6
- store: FunnelSettingsReader
7
- mcp: FunnelMcp
8
- }
9
-
10
- export class FunnelRepositories {
11
- private readonly store: FunnelSettingsReader
12
- private readonly mcp: FunnelMcp
13
-
14
- constructor(deps: Deps) {
15
- this.store = deps.store
16
- this.mcp = deps.mcp
17
- Object.freeze(this)
18
- }
19
-
20
- list(): RepositoryConfig[] {
21
- return this.store.read().repositories
22
- }
23
-
24
- get(name: string): RepositoryConfig | null {
25
- return this.list().find((r) => r.name === name) ?? null
26
- }
27
-
28
- add(config: RepositoryConfig): void {
29
- const settings = this.store.read()
30
-
31
- if (settings.repositories.some((r) => r.name === config.name)) {
32
- throw new Error(`repo "${config.name}" already exists`)
33
- }
34
-
35
- this.mcp.install(config.path)
36
-
37
- settings.repositories.push(config)
38
-
39
- this.store.write(settings)
40
- }
41
-
42
- remove(name: string): void {
43
- const settings = this.store.read()
44
-
45
- const index = settings.repositories.findIndex((r) => r.name === name)
46
-
47
- if (index < 0) throw new Error(`repo "${name}" not found`)
48
-
49
- if (settings.profiles.some((p) => p.repo === name)) {
50
- throw new Error(`repo "${name}" is referenced by a profile`)
51
- }
52
-
53
- const repo = settings.repositories[index]!
54
-
55
- this.mcp.uninstall(repo.path)
56
-
57
- settings.repositories.splice(index, 1)
58
-
59
- this.store.write(settings)
60
- }
61
-
62
- rename(oldName: string, newName: string): void {
63
- const settings = this.store.read()
64
-
65
- const repo = settings.repositories.find((r) => r.name === oldName)
66
-
67
- if (!repo) throw new Error(`repo "${oldName}" not found`)
68
-
69
- if (settings.repositories.some((r) => r.name === newName)) {
70
- throw new Error(`repo "${newName}" already exists`)
71
- }
72
-
73
- repo.name = newName
74
-
75
- for (const profile of settings.profiles) {
76
- if (profile.repo === oldName) profile.repo = newName
77
- }
78
-
79
- this.store.write(settings)
80
- }
81
-
82
- update(name: string, fields: Partial<Pick<RepositoryConfig, "path">>): void {
83
- const settings = this.store.read()
84
-
85
- const repo = settings.repositories.find((r) => r.name === name)
86
-
87
- if (!repo) throw new Error(`repo "${name}" not found`)
88
-
89
- if (fields.path !== undefined && fields.path !== repo.path) {
90
- this.mcp.uninstall(repo.path)
91
-
92
- this.mcp.install(fields.path)
93
-
94
- repo.path = fields.path
95
- }
96
-
97
- this.store.write(settings)
98
- }
99
-
100
- resolvePath(name: string): string {
101
- const repo = this.get(name)
102
-
103
- if (!repo) throw new Error(`repo "${name}" not found`)
104
-
105
- return repo.path
106
- }
107
- }