@interactive-inc/claude-funnel 0.10.0 → 0.15.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 (236) hide show
  1. package/README.md +106 -56
  2. package/dist/bin.js +557 -530
  3. package/dist/connectors/schedule.d.ts +2 -49
  4. package/dist/connectors/schedule.js +1 -1
  5. package/dist/connectors/slack.d.ts +4 -48
  6. package/dist/connectors/slack.js +1 -1
  7. package/dist/gateway/daemon.js +213 -211
  8. package/dist/index.d.ts +465 -173
  9. package/dist/index.js +692 -154
  10. package/dist/{schedule-connector-schema-CkuIQ0JQ.js → schedule-connector-schema-FxP7LPlx.js} +11 -0
  11. package/dist/{file-system-Co60LrmR.d.ts → schedule-listener-BPodvbld.d.ts} +56 -1
  12. package/dist/{slack-connector-schema-Cd22WiHB.js → slack-connector-schema-B4hsf3AY.js} +10 -1
  13. package/dist/slack-listener-CHj6uMY-.d.ts +74 -0
  14. package/package.json +2 -6
  15. package/schemas/funnel.schema.json +144 -0
  16. package/dist/slack-connector-schema-D7zAHN8k.d.ts +0 -15
  17. package/lib/bin.ts +0 -3
  18. package/lib/cli/factory.ts +0 -10
  19. package/lib/cli/index.ts +0 -85
  20. package/lib/cli/router/query-to-cli-args.ts +0 -20
  21. package/lib/cli/router/to-request.ts +0 -113
  22. package/lib/cli/router/validator.ts +0 -27
  23. package/lib/cli/routes/channels.$channel.connectors.$connector.rename.$newName.ts +0 -27
  24. package/lib/cli/routes/channels.$channel.connectors.$connector.request.ts +0 -40
  25. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.ts +0 -41
  26. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.ts +0 -22
  27. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.ts +0 -23
  28. package/lib/cli/routes/channels.$channel.connectors.$connector.ts +0 -26
  29. package/lib/cli/routes/channels.$channel.connectors.add.$connector.ts +0 -92
  30. package/lib/cli/routes/channels.$channel.connectors.remove.$connector.ts +0 -22
  31. package/lib/cli/routes/channels.$channel.connectors.set.$connector.ts +0 -63
  32. package/lib/cli/routes/channels.$channel.connectors.ts +0 -26
  33. package/lib/cli/routes/channels.$channel.publish.ts +0 -52
  34. package/lib/cli/routes/channels.$channel.rename.$newName.ts +0 -22
  35. package/lib/cli/routes/channels.$channel.set.delivery.$mode.ts +0 -34
  36. package/lib/cli/routes/channels.$channel.ts +0 -34
  37. package/lib/cli/routes/channels.add.$channel.ts +0 -33
  38. package/lib/cli/routes/channels.remove.$channel.ts +0 -20
  39. package/lib/cli/routes/channels.ts +0 -39
  40. package/lib/cli/routes/claude.ts +0 -70
  41. package/lib/cli/routes/gateway.listeners.ts +0 -41
  42. package/lib/cli/routes/gateway.logs.ts +0 -123
  43. package/lib/cli/routes/gateway.restart.ts +0 -50
  44. package/lib/cli/routes/gateway.run.ts +0 -41
  45. package/lib/cli/routes/gateway.start.ts +0 -50
  46. package/lib/cli/routes/gateway.status.ts +0 -19
  47. package/lib/cli/routes/gateway.stop.ts +0 -32
  48. package/lib/cli/routes/gateway.ts +0 -55
  49. package/lib/cli/routes/index.ts +0 -219
  50. package/lib/cli/routes/profiles.$profile.as-default.ts +0 -22
  51. package/lib/cli/routes/profiles.$profile.rename.$newName.ts +0 -22
  52. package/lib/cli/routes/profiles.$profile.run.ts +0 -36
  53. package/lib/cli/routes/profiles.add.$profile.ts +0 -49
  54. package/lib/cli/routes/profiles.remove.$profile.ts +0 -20
  55. package/lib/cli/routes/profiles.set.$profile.ts +0 -45
  56. package/lib/cli/routes/profiles.ts +0 -40
  57. package/lib/cli/routes/status.ts +0 -93
  58. package/lib/cli/routes/update.ts +0 -27
  59. package/lib/connectors/connector-adapter.ts +0 -9
  60. package/lib/connectors/connector-config-schema.ts +0 -16
  61. package/lib/connectors/connector-factory.ts +0 -94
  62. package/lib/connectors/connector-listener.ts +0 -20
  63. package/lib/connectors/discord-adapter.ts +0 -51
  64. package/lib/connectors/discord-connector-schema.ts +0 -12
  65. package/lib/connectors/discord-event-processor.ts +0 -48
  66. package/lib/connectors/discord-listener.ts +0 -111
  67. package/lib/connectors/discord.ts +0 -4
  68. package/lib/connectors/gh-adapter.ts +0 -48
  69. package/lib/connectors/gh-connector-schema.ts +0 -12
  70. package/lib/connectors/gh-listener.ts +0 -137
  71. package/lib/connectors/gh.ts +0 -3
  72. package/lib/connectors/match-cron.ts +0 -78
  73. package/lib/connectors/schedule-connector-schema.ts +0 -33
  74. package/lib/connectors/schedule-listener.ts +0 -207
  75. package/lib/connectors/schedule-state-store.ts +0 -54
  76. package/lib/connectors/schedule.ts +0 -4
  77. package/lib/connectors/slack-adapter.ts +0 -36
  78. package/lib/connectors/slack-connector-schema.ts +0 -13
  79. package/lib/connectors/slack-event-processor.ts +0 -97
  80. package/lib/connectors/slack-listener.ts +0 -97
  81. package/lib/connectors/slack.ts +0 -4
  82. package/lib/engine/channels/channels.ts +0 -520
  83. package/lib/engine/claude/claude.ts +0 -205
  84. package/lib/engine/claude/gateway-controller.ts +0 -4
  85. package/lib/engine/fs/file-system.ts +0 -23
  86. package/lib/engine/fs/memory-file-system.ts +0 -102
  87. package/lib/engine/fs/node-file-system.ts +0 -68
  88. package/lib/engine/http/http-client.ts +0 -17
  89. package/lib/engine/http/memory-http-client.ts +0 -36
  90. package/lib/engine/http/node-http-client.ts +0 -23
  91. package/lib/engine/id/id-generator.ts +0 -7
  92. package/lib/engine/id/memory-id-generator.ts +0 -20
  93. package/lib/engine/id/node-id-generator.ts +0 -7
  94. package/lib/engine/logger/logger.ts +0 -11
  95. package/lib/engine/logger/memory-logger.ts +0 -28
  96. package/lib/engine/logger/node-logger.ts +0 -49
  97. package/lib/engine/logger/noop-logger.ts +0 -9
  98. package/lib/engine/mcp/channel-server.ts +0 -123
  99. package/lib/engine/mcp/channel-subscriber.ts +0 -82
  100. package/lib/engine/mcp/mcp.ts +0 -126
  101. package/lib/engine/mcp/read-channel-connectors.ts +0 -34
  102. package/lib/engine/mcp/read-gateway-token.ts +0 -16
  103. package/lib/engine/mcp/usage-hint-for-type.ts +0 -15
  104. package/lib/engine/process/memory-process-runner.ts +0 -88
  105. package/lib/engine/process/node-process-runner.ts +0 -91
  106. package/lib/engine/process/process-runner.ts +0 -33
  107. package/lib/engine/profiles/profile-channel-checker.ts +0 -7
  108. package/lib/engine/profiles/profiles.ts +0 -126
  109. package/lib/engine/settings/mock-settings-reader.ts +0 -27
  110. package/lib/engine/settings/settings-reader.ts +0 -6
  111. package/lib/engine/settings/settings-schema.ts +0 -48
  112. package/lib/engine/settings/settings-store.ts +0 -110
  113. package/lib/engine/time/clock.ts +0 -15
  114. package/lib/engine/time/memory-clock.ts +0 -26
  115. package/lib/engine/time/node-clock.ts +0 -7
  116. package/lib/funnel.ts +0 -294
  117. package/lib/gateway/auth-middleware.ts +0 -44
  118. package/lib/gateway/broadcaster.ts +0 -319
  119. package/lib/gateway/channel-publisher.ts +0 -67
  120. package/lib/gateway/daemon.ts +0 -47
  121. package/lib/gateway/factory.ts +0 -10
  122. package/lib/gateway/funnel-event-store.ts +0 -155
  123. package/lib/gateway/gateway-server.ts +0 -426
  124. package/lib/gateway/gateway-token.ts +0 -79
  125. package/lib/gateway/gateway.ts +0 -209
  126. package/lib/gateway/kill-competing-slack-gateways.ts +0 -56
  127. package/lib/gateway/listener-supervisor.ts +0 -339
  128. package/lib/gateway/listeners-client.ts +0 -128
  129. package/lib/gateway/publish-schema.ts +0 -27
  130. package/lib/gateway/resolve-daemon-script.ts +0 -26
  131. package/lib/gateway/routes/channels.connectors.call.ts +0 -39
  132. package/lib/gateway/routes/channels.publish.ts +0 -44
  133. package/lib/gateway/routes/health.ts +0 -13
  134. package/lib/gateway/routes/index.ts +0 -26
  135. package/lib/gateway/routes/listeners.list.ts +0 -6
  136. package/lib/gateway/routes/listeners.restart.ts +0 -15
  137. package/lib/gateway/routes/listeners.start.ts +0 -15
  138. package/lib/gateway/routes/listeners.stop.ts +0 -15
  139. package/lib/gateway/routes/route-deps.ts +0 -19
  140. package/lib/gateway/routes/status.ts +0 -15
  141. package/lib/gateway/routes/validator.ts +0 -17
  142. package/lib/index.ts +0 -67
  143. package/lib/logger/leuco-human-file-writer.ts +0 -65
  144. package/lib/logger/leuco-human-logger.ts +0 -98
  145. package/lib/logger/leuco-human-record.ts +0 -16
  146. package/lib/logger/leuco-human-stdout-writer.ts +0 -26
  147. package/lib/logger/leuco-human-writer.ts +0 -14
  148. package/lib/logger/leuco-logger-memory-sink.ts +0 -67
  149. package/lib/logger/leuco-logger-record.ts +0 -13
  150. package/lib/logger/leuco-logger-sink.ts +0 -33
  151. package/lib/logger/leuco-logger-sqlite-sink.ts +0 -355
  152. package/lib/logger/leuco-logger.ts +0 -135
  153. package/lib/tui/app.tsx +0 -357
  154. package/lib/tui/components/add-row.tsx +0 -18
  155. package/lib/tui/components/brand.tsx +0 -27
  156. package/lib/tui/components/card.tsx +0 -44
  157. package/lib/tui/components/detail-bar.tsx +0 -46
  158. package/lib/tui/components/editable-field.tsx +0 -33
  159. package/lib/tui/components/empty-state.tsx +0 -11
  160. package/lib/tui/components/gateway-status.tsx +0 -66
  161. package/lib/tui/components/keymap.tsx +0 -29
  162. package/lib/tui/components/menu-item.tsx +0 -73
  163. package/lib/tui/components/menu.tsx +0 -26
  164. package/lib/tui/components/panel-header.tsx +0 -22
  165. package/lib/tui/components/readonly-field.tsx +0 -18
  166. package/lib/tui/components/section-header.tsx +0 -25
  167. package/lib/tui/components/selection-accent.tsx +0 -32
  168. package/lib/tui/components/session-item.tsx +0 -33
  169. package/lib/tui/components/session-list.tsx +0 -33
  170. package/lib/tui/components/ui/hascii/accordion-item.tsx +0 -88
  171. package/lib/tui/components/ui/hascii/accordion.tsx +0 -96
  172. package/lib/tui/components/ui/hascii/alert-dialog.tsx +0 -43
  173. package/lib/tui/components/ui/hascii/badge.tsx +0 -51
  174. package/lib/tui/components/ui/hascii/breadcrumb.tsx +0 -58
  175. package/lib/tui/components/ui/hascii/button.tsx +0 -194
  176. package/lib/tui/components/ui/hascii/card-content.tsx +0 -14
  177. package/lib/tui/components/ui/hascii/card-description.tsx +0 -13
  178. package/lib/tui/components/ui/hascii/card-footer.tsx +0 -14
  179. package/lib/tui/components/ui/hascii/card-header.tsx +0 -14
  180. package/lib/tui/components/ui/hascii/card-title.tsx +0 -13
  181. package/lib/tui/components/ui/hascii/card.tsx +0 -27
  182. package/lib/tui/components/ui/hascii/checkbox.tsx +0 -65
  183. package/lib/tui/components/ui/hascii/command.tsx +0 -159
  184. package/lib/tui/components/ui/hascii/dialog-content.tsx +0 -14
  185. package/lib/tui/components/ui/hascii/dialog-description.tsx +0 -13
  186. package/lib/tui/components/ui/hascii/dialog-footer.tsx +0 -14
  187. package/lib/tui/components/ui/hascii/dialog-header.tsx +0 -14
  188. package/lib/tui/components/ui/hascii/dialog-title.tsx +0 -13
  189. package/lib/tui/components/ui/hascii/dialog.tsx +0 -27
  190. package/lib/tui/components/ui/hascii/file-tree.tsx +0 -142
  191. package/lib/tui/components/ui/hascii/focus-group.tsx +0 -62
  192. package/lib/tui/components/ui/hascii/form-item.tsx +0 -43
  193. package/lib/tui/components/ui/hascii/input-otp.tsx +0 -86
  194. package/lib/tui/components/ui/hascii/input.tsx +0 -130
  195. package/lib/tui/components/ui/hascii/pagination.tsx +0 -105
  196. package/lib/tui/components/ui/hascii/progress.tsx +0 -28
  197. package/lib/tui/components/ui/hascii/select.tsx +0 -131
  198. package/lib/tui/components/ui/hascii/separator.tsx +0 -35
  199. package/lib/tui/components/ui/hascii/sidebar-content.tsx +0 -23
  200. package/lib/tui/components/ui/hascii/sidebar-header.tsx +0 -14
  201. package/lib/tui/components/ui/hascii/sidebar-menu-item.tsx +0 -67
  202. package/lib/tui/components/ui/hascii/sidebar.tsx +0 -24
  203. package/lib/tui/components/ui/hascii/skeleton.tsx +0 -60
  204. package/lib/tui/components/ui/hascii/slider.tsx +0 -91
  205. package/lib/tui/components/ui/hascii/snackbar.tsx +0 -75
  206. package/lib/tui/components/ui/hascii/sparkline.tsx +0 -53
  207. package/lib/tui/components/ui/hascii/spinner.tsx +0 -47
  208. package/lib/tui/components/ui/hascii/stepper.tsx +0 -54
  209. package/lib/tui/components/ui/hascii/switch.tsx +0 -66
  210. package/lib/tui/components/ui/hascii/table.tsx +0 -95
  211. package/lib/tui/components/ui/hascii/tabs.tsx +0 -59
  212. package/lib/tui/components/ui/hascii/toggle-group-item.tsx +0 -45
  213. package/lib/tui/components/ui/hascii/toggle-group.tsx +0 -99
  214. package/lib/tui/components/ui/hascii/tree.tsx +0 -104
  215. package/lib/tui/components/view-shell.tsx +0 -44
  216. package/lib/tui/filter-input.tsx +0 -33
  217. package/lib/tui/hooks/hascii/use-pressable.ts +0 -54
  218. package/lib/tui/parse-comma-list.ts +0 -14
  219. package/lib/tui/profile-launcher.tsx +0 -61
  220. package/lib/tui/scrollbar-options.ts +0 -19
  221. package/lib/tui/sidebar.tsx +0 -50
  222. package/lib/tui/theme.ts +0 -40
  223. package/lib/tui/tui.tsx +0 -20
  224. package/lib/tui/types.ts +0 -38
  225. package/lib/tui/unique-name.ts +0 -18
  226. package/lib/tui/use-event-stream.ts +0 -133
  227. package/lib/tui/use-snapshot.ts +0 -99
  228. package/lib/tui/utils/hascii/form-item-context.tsx +0 -23
  229. package/lib/tui/utils/hascii/input-focus-context.tsx +0 -31
  230. package/lib/tui/utils/hascii/theme-context.tsx +0 -26
  231. package/lib/tui/utils/hascii/theme.ts +0 -176
  232. package/lib/tui/views/channels-view.tsx +0 -108
  233. package/lib/tui/views/connectors-view.tsx +0 -164
  234. package/lib/tui/views/events-view.tsx +0 -160
  235. package/lib/tui/views/listeners-view.tsx +0 -80
  236. package/lib/tui/views/profiles-view.tsx +0 -152
@@ -160,6 +160,7 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
160
160
  lastFiredStore;
161
161
  logger;
162
162
  now;
163
+ onFired;
163
164
  timer = null;
164
165
  stopped = false;
165
166
  constructor(deps) {
@@ -168,6 +169,7 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
168
169
  this.lastFiredStore = deps.lastFiredStore;
169
170
  this.logger = deps.logger ?? defaultLogger;
170
171
  this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
172
+ this.onFired = deps.onFired ?? null;
171
173
  }
172
174
  async start(notify) {
173
175
  this.stopped = false;
@@ -243,6 +245,15 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
243
245
  };
244
246
  if (catchup) meta.catchup = "true";
245
247
  await notify(entry.prompt, meta);
248
+ if (this.onFired) try {
249
+ await this.onFired(entry, firedAt);
250
+ } catch (error) {
251
+ this.logger.error("schedule onFired callback failed", {
252
+ connector: this.config.name,
253
+ id: entry.id,
254
+ error: error instanceof Error ? error.message : String(error)
255
+ });
256
+ }
246
257
  }
247
258
  findMostRecentMatch(cron, from, until, entryId) {
248
259
  const maxIterations = Math.min(MAX_CATCHUP_MINUTES, Math.floor((until.getTime() - from.getTime()) / 6e4) + 1);
@@ -1,3 +1,4 @@
1
+ import { n as FunnelConnectorListener, r as NotifyFn, t as FunnelLogger } from "./logger-CTlXs7z4.js";
1
2
  import { z } from "zod";
2
3
 
3
4
  //#region lib/connectors/schedule-connector-schema.d.ts
@@ -71,4 +72,58 @@ declare abstract class FunnelFileSystem {
71
72
  abstract statSync(path: string): FileStat;
72
73
  }
73
74
  //#endregion
74
- export { ScheduleEntry as a, scheduleEntrySchema as c, ScheduleConnectorConfig as i, FunnelFileSystem as n, scheduleCatchupPolicySchema as o, ScheduleCatchupPolicy as r, scheduleConnectorSchema as s, FileStat as t };
75
+ //#region lib/connectors/schedule-state-store.d.ts
76
+ type Deps$1 = {
77
+ path: string;
78
+ fs?: FunnelFileSystem;
79
+ };
80
+ /**
81
+ * Per-connector lastFiredAt persistence for the schedule listener. The path is
82
+ * passed in by FunnelConnectorFactory so this store does not know about the
83
+ * funnel directory layout (`channels/<id>/connectors/<id>/state.json` lives
84
+ * outside this class).
85
+ */
86
+ declare class ScheduleStateStore {
87
+ private readonly path;
88
+ private readonly fs;
89
+ constructor(deps: Deps$1);
90
+ load(): Map<string, Date>;
91
+ save(state: Map<string, Date>): void;
92
+ }
93
+ //#endregion
94
+ //#region lib/connectors/schedule-listener.d.ts
95
+ type ScheduleOnFired = (entry: ScheduleEntry, firedAt: Date) => void | Promise<void>;
96
+ type Deps = {
97
+ config: ScheduleConnectorConfig;
98
+ lastFiredStore: ScheduleStateStore;
99
+ logger?: FunnelLogger;
100
+ now?: () => Date;
101
+ /**
102
+ * Invoked after a schedule entry fires successfully. Use to remove one-shot
103
+ * entries from the connector config, or to log per-fire side effects.
104
+ * Errors from this callback are caught and logged; they do not abort the tick.
105
+ */
106
+ onFired?: ScheduleOnFired;
107
+ };
108
+ declare class FunnelScheduleListener extends FunnelConnectorListener {
109
+ private readonly config;
110
+ private readonly lastFiredStore;
111
+ private readonly logger;
112
+ private readonly now;
113
+ private readonly onFired;
114
+ private timer;
115
+ private stopped;
116
+ constructor(deps: Deps);
117
+ start(notify: NotifyFn): Promise<void>;
118
+ stop(): Promise<void>;
119
+ isAlive(): boolean;
120
+ tick(notify: NotifyFn): Promise<void>;
121
+ private fireEntry;
122
+ private notifyOne;
123
+ private findMostRecentMatch;
124
+ private findAllMatches;
125
+ private logInvalidCron;
126
+ private truncateToMinute;
127
+ }
128
+ //#endregion
129
+ export { FunnelFileSystem as a, ScheduleEntry as c, scheduleEntrySchema as d, FileStat as i, scheduleCatchupPolicySchema as l, ScheduleOnFired as n, ScheduleCatchupPolicy as o, ScheduleStateStore as r, ScheduleConnectorConfig as s, FunnelScheduleListener as t, scheduleConnectorSchema as u };
@@ -85,11 +85,15 @@ const defaultLogger = new NodeFunnelLogger();
85
85
  var FunnelSlackListener = class extends FunnelConnectorListener {
86
86
  config;
87
87
  logger;
88
+ onAppCreated;
89
+ preprocessEvent;
88
90
  app = null;
89
91
  constructor(deps) {
90
92
  super();
91
93
  this.config = deps.config;
92
94
  this.logger = deps.logger ?? defaultLogger;
95
+ this.onAppCreated = deps.onAppCreated ?? null;
96
+ this.preprocessEvent = deps.preprocessEvent ?? null;
93
97
  }
94
98
  async start(notify) {
95
99
  const app = new App({
@@ -103,10 +107,14 @@ var FunnelSlackListener = class extends FunnelConnectorListener {
103
107
  ownBotUserId: authResult.user_id ?? "",
104
108
  ownBotId: authResult.bot_id ?? ""
105
109
  });
110
+ const preprocess = this.preprocessEvent;
106
111
  app.use(async (args) => {
107
112
  const parsed = middlewareArgsSchema.safeParse(args);
108
113
  if (!parsed.success || !parsed.data.event) return;
109
- const result = processor.process(parsed.data.event);
114
+ const rawEvent = parsed.data.event;
115
+ const event = preprocess ? preprocess(rawEvent) : rawEvent;
116
+ if (event === null) return;
117
+ const result = processor.process(event);
110
118
  if (result.skip) return;
111
119
  if (result.shouldReact) try {
112
120
  await app.client.reactions.add({
@@ -121,6 +129,7 @@ var FunnelSlackListener = class extends FunnelConnectorListener {
121
129
  app.error(async (error) => {
122
130
  this.logger.error("Slack error", { error: error instanceof Error ? error.message : String(error) });
123
131
  });
132
+ if (this.onAppCreated) await this.onAppCreated(app);
124
133
  await app.start();
125
134
  this.app = app;
126
135
  }
@@ -0,0 +1,74 @@
1
+ import { n as FunnelConnectorListener, r as NotifyFn, t as FunnelLogger } from "./logger-CTlXs7z4.js";
2
+ import { z } from "zod";
3
+ import { App } from "@slack/bolt";
4
+
5
+ //#region lib/connectors/slack-connector-schema.d.ts
6
+ declare const slackConnectorSchema: z.ZodObject<{
7
+ id: z.ZodString;
8
+ name: z.ZodString;
9
+ type: z.ZodLiteral<"slack">;
10
+ botToken: z.ZodString;
11
+ appToken: z.ZodString;
12
+ createdAt: z.ZodOptional<z.ZodString>;
13
+ updatedAt: z.ZodOptional<z.ZodString>;
14
+ }, z.core.$strip>;
15
+ type SlackConnectorConfig = z.infer<typeof slackConnectorSchema>;
16
+ //#endregion
17
+ //#region lib/connectors/slack-event-processor.d.ts
18
+ type SlackRawEvent = Record<string, unknown>;
19
+ type SlackProcessedSkip = {
20
+ skip: true;
21
+ };
22
+ type SlackProcessedEmit = {
23
+ skip: false;
24
+ content: string;
25
+ meta: Record<string, string>;
26
+ shouldReact: boolean;
27
+ channel: string;
28
+ timestamp: string;
29
+ };
30
+ type SlackProcessed = SlackProcessedSkip | SlackProcessedEmit;
31
+ type Props = {
32
+ ownBotUserId: string;
33
+ ownBotId: string;
34
+ now?: () => number;
35
+ };
36
+ declare class FunnelSlackEventProcessor {
37
+ private readonly ownBotUserId;
38
+ private readonly ownBotId;
39
+ private readonly now;
40
+ private readonly dedup;
41
+ constructor(props: Props);
42
+ process(event: SlackRawEvent): SlackProcessed;
43
+ }
44
+ //#endregion
45
+ //#region lib/connectors/slack-listener.d.ts
46
+ type SlackOnAppCreated = (app: App) => void | Promise<void>;
47
+ type SlackPreprocessEvent = (event: SlackRawEvent) => SlackRawEvent | null;
48
+ type Deps = {
49
+ config: SlackConnectorConfig;
50
+ logger?: FunnelLogger;
51
+ /**
52
+ * Invoked after the Bolt App is constructed, before it starts.
53
+ * Use to attach app.action handlers, custom middleware, etc.
54
+ */
55
+ onAppCreated?: SlackOnAppCreated;
56
+ /**
57
+ * Transform or drop the raw Slack event before the built-in processor sees it.
58
+ * Return null to drop the event entirely.
59
+ */
60
+ preprocessEvent?: SlackPreprocessEvent;
61
+ };
62
+ declare class FunnelSlackListener extends FunnelConnectorListener {
63
+ private readonly config;
64
+ private readonly logger;
65
+ private readonly onAppCreated;
66
+ private readonly preprocessEvent;
67
+ private app;
68
+ constructor(deps: Deps);
69
+ start(notify: NotifyFn): Promise<void>;
70
+ stop(): Promise<void>;
71
+ isAlive(): boolean;
72
+ }
73
+ //#endregion
74
+ export { SlackProcessed as a, SlackRawEvent as c, FunnelSlackEventProcessor as i, SlackConnectorConfig as l, SlackOnAppCreated as n, SlackProcessedEmit as o, SlackPreprocessEvent as r, SlackProcessedSkip as s, FunnelSlackListener as t, slackConnectorSchema as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interactive-inc/claude-funnel",
3
- "version": "0.10.0",
3
+ "version": "0.15.1",
4
4
  "description": "Hub CLI that routes external events (Slack / GitHub / Discord) to Claude Code agents through subscription channels over MCP.",
5
5
  "keywords": [
6
6
  "bun",
@@ -27,12 +27,8 @@
27
27
  "funnel": "./dist/bin.js"
28
28
  },
29
29
  "files": [
30
- "lib/**/*.ts",
31
- "lib/**/*.tsx",
32
- "!lib/**/*.test.ts",
33
- "!lib/**/*.test.tsx",
34
- "!lib/**/*.bun-test.ts",
35
30
  "dist/**/*",
31
+ "schemas/**/*",
36
32
  "README.md",
37
33
  "LICENSE"
38
34
  ],
@@ -0,0 +1,144 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "$schema": {
6
+ "type": "string"
7
+ },
8
+ "channel": {
9
+ "type": "string"
10
+ },
11
+ "options": {
12
+ "type": "array",
13
+ "items": {
14
+ "type": "string"
15
+ }
16
+ },
17
+ "env": {
18
+ "type": "object",
19
+ "propertyNames": {
20
+ "type": "string"
21
+ },
22
+ "additionalProperties": {
23
+ "type": "string"
24
+ }
25
+ },
26
+ "connectors": {
27
+ "type": "array",
28
+ "items": {
29
+ "oneOf": [
30
+ {
31
+ "type": "object",
32
+ "properties": {
33
+ "type": {
34
+ "type": "string",
35
+ "const": "slack"
36
+ },
37
+ "name": {
38
+ "type": "string"
39
+ },
40
+ "botToken": {
41
+ "type": "string"
42
+ },
43
+ "appToken": {
44
+ "type": "string"
45
+ },
46
+ "env": {
47
+ "type": "object",
48
+ "properties": {
49
+ "botToken": {
50
+ "type": "string"
51
+ },
52
+ "appToken": {
53
+ "type": "string"
54
+ }
55
+ },
56
+ "additionalProperties": false
57
+ }
58
+ },
59
+ "required": [
60
+ "type",
61
+ "name"
62
+ ],
63
+ "additionalProperties": false
64
+ },
65
+ {
66
+ "type": "object",
67
+ "properties": {
68
+ "type": {
69
+ "type": "string",
70
+ "const": "discord"
71
+ },
72
+ "name": {
73
+ "type": "string"
74
+ },
75
+ "botToken": {
76
+ "type": "string"
77
+ },
78
+ "env": {
79
+ "type": "object",
80
+ "properties": {
81
+ "botToken": {
82
+ "type": "string"
83
+ }
84
+ },
85
+ "additionalProperties": false
86
+ }
87
+ },
88
+ "required": [
89
+ "type",
90
+ "name"
91
+ ],
92
+ "additionalProperties": false
93
+ },
94
+ {
95
+ "type": "object",
96
+ "properties": {
97
+ "type": {
98
+ "type": "string",
99
+ "const": "gh"
100
+ },
101
+ "name": {
102
+ "type": "string"
103
+ },
104
+ "pollInterval": {
105
+ "type": "integer",
106
+ "exclusiveMinimum": 0,
107
+ "maximum": 9007199254740991
108
+ }
109
+ },
110
+ "required": [
111
+ "type",
112
+ "name"
113
+ ],
114
+ "additionalProperties": false
115
+ },
116
+ {
117
+ "type": "object",
118
+ "properties": {
119
+ "type": {
120
+ "type": "string",
121
+ "const": "schedule"
122
+ },
123
+ "name": {
124
+ "type": "string"
125
+ }
126
+ },
127
+ "required": [
128
+ "type",
129
+ "name"
130
+ ],
131
+ "additionalProperties": false
132
+ }
133
+ ]
134
+ }
135
+ }
136
+ },
137
+ "required": [
138
+ "channel"
139
+ ],
140
+ "additionalProperties": false,
141
+ "title": "Funnel per-repo launch config",
142
+ "description": "Used by `fnl claude` when no --profile / --channel is given. Declares the channel to subscribe to, optional sub-agent and brief flag, environment variables to layer under process.env, and optional connectors to materialize into ~/.funnel/settings.json on launch."
143
+ }
144
+
@@ -1,15 +0,0 @@
1
- import { z } from "zod";
2
-
3
- //#region lib/connectors/slack-connector-schema.d.ts
4
- declare const slackConnectorSchema: z.ZodObject<{
5
- id: z.ZodString;
6
- name: z.ZodString;
7
- type: z.ZodLiteral<"slack">;
8
- botToken: z.ZodString;
9
- appToken: z.ZodString;
10
- createdAt: z.ZodOptional<z.ZodString>;
11
- updatedAt: z.ZodOptional<z.ZodString>;
12
- }, z.core.$strip>;
13
- type SlackConnectorConfig = z.infer<typeof slackConnectorSchema>;
14
- //#endregion
15
- export { slackConnectorSchema as n, SlackConnectorConfig as t };
package/lib/bin.ts DELETED
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import "@/cli"
@@ -1,10 +0,0 @@
1
- import { createFactory } from "hono/factory"
2
- import type { Funnel } from "@/funnel"
3
-
4
- export type Env = {
5
- Variables: {
6
- funnel: Funnel
7
- }
8
- }
9
-
10
- export const factory = createFactory<Env>()
package/lib/cli/index.ts DELETED
@@ -1,85 +0,0 @@
1
- import pkg from "@/../package.json" with { type: "json" }
2
- import { startChannelServer } from "@/engine/mcp/channel-server"
3
- import { toRequest } from "@/cli/router/to-request"
4
- import { launchTui } from "@/tui/tui"
5
- import { createCliApp } from "@/cli/routes"
6
- import { Funnel } from "@/funnel"
7
-
8
- process.title = "funnel"
9
-
10
- const funnel = new Funnel()
11
-
12
- const app = createCliApp(funnel)
13
-
14
- const HELP = `funnel — Open Claude Funnel
15
-
16
- usage: funnel [command]
17
-
18
- commands:
19
- (none) launch TUI
20
- claude launch Claude Code (default profile or --profile)
21
- channels manage subscription boxes (and their nested connectors)
22
- profiles manage launch profiles
23
- gateway manage the gateway daemon (HTTP + WS)
24
- status show overall connection status
25
- update update funnel to the latest version
26
- mcp run as an MCP server (invoked from .mcp.json)
27
-
28
- options:
29
- --help, -h show help
30
- --version, -v show version
31
-
32
- more: funnel <command> --help`
33
-
34
- const args = process.argv.slice(2)
35
-
36
- if (args.length === 0) {
37
- await launchTui(funnel)
38
- process.exit(0)
39
- }
40
-
41
- if (args[0] === "--version" || args[0] === "-v") {
42
- process.stdout.write(`${pkg.version}\n`)
43
- process.exit(0)
44
- }
45
-
46
- if (args[0] === "mcp") {
47
- await startChannelServer({ dir: funnel.paths.dir })
48
- }
49
-
50
- if (args[0] !== "mcp") {
51
- const { method, url } = toRequest(args)
52
-
53
- const parsed = new URL(url)
54
-
55
- const wantsHelp = parsed.searchParams.has("help")
56
-
57
- if (wantsHelp && parsed.pathname === "/") {
58
- process.stdout.write(`${HELP}\n`)
59
- process.exit(0)
60
- }
61
-
62
- const res = await app.request(url, { method })
63
-
64
- if (res.ok) {
65
- const body = await res.text()
66
- if (body) process.stdout.write(`${body}\n`)
67
- process.exit(0)
68
- }
69
-
70
- if (wantsHelp) {
71
- const segments = parsed.pathname.split("/").filter(Boolean)
72
- const group = segments[0]
73
- const fallback = group
74
- ? await app.request(`http://localhost/${group}?help=true`, { method: "GET" })
75
- : null
76
-
77
- const text = fallback?.ok ? await fallback.text() : HELP
78
- process.stdout.write(`${text}\n`)
79
- process.exit(0)
80
- }
81
-
82
- const text = await res.text()
83
- if (text) process.stderr.write(`${text}\n`)
84
- process.exit(1)
85
- }
@@ -1,20 +0,0 @@
1
- const BUILTIN_SKIP = new Set(["help"])
2
-
3
- export const queryToCliArgs = (url: string, reservedKeys: string[] = []): string[] => {
4
- const skipped = new Set([...BUILTIN_SKIP, ...reservedKeys])
5
- const args: string[] = []
6
- const searchParams = new URL(url).searchParams
7
-
8
- for (const entry of searchParams.entries()) {
9
- const key = entry[0]
10
- const value = entry[1]
11
-
12
- if (skipped.has(key)) continue
13
-
14
- args.push(`--${key}`)
15
-
16
- if (value !== "true") args.push(value)
17
- }
18
-
19
- return args
20
- }
@@ -1,113 +0,0 @@
1
- const SHORT_FLAGS: Record<string, string> = {
2
- h: "help",
3
- n: "name",
4
- p: "profile",
5
- }
6
-
7
- // All CLI verbs map to POST and stay in the URL (no method-stripping).
8
- // Hono routes disambiguate by URL segment (e.g. /channels/add/:channel vs /channels/remove/:channel).
9
- const METHOD_KEYWORDS = new Set([
10
- "add",
11
- "set",
12
- "remove",
13
- "rename",
14
- "as-default",
15
- "request",
16
- "publish",
17
- ])
18
-
19
- const API_CALL_METHODS = new Set(["get", "post", "put", "patch", "delete", "head", "options"])
20
-
21
- const isValue = (arg: string | undefined): arg is string => {
22
- return typeof arg === "string" && !arg.startsWith("-")
23
- }
24
-
25
- const consumeApiCall = (args: string[], i: number, params: URLSearchParams): number => {
26
- const nextPath = args[i + 1]
27
-
28
- if (!isValue(nextPath)) return 1
29
-
30
- params.set("path", nextPath)
31
-
32
- const nextBody = args[i + 2]
33
-
34
- if (!isValue(nextBody)) return 2
35
-
36
- params.set("body", nextBody)
37
-
38
- return 3
39
- }
40
-
41
- export const toRequest = (args: string[]) => {
42
- const segments: string[] = []
43
- const params = new URLSearchParams()
44
- let method = "GET"
45
-
46
- let i = 0
47
- while (i < args.length) {
48
- const arg = args[i]!
49
-
50
- if (arg.startsWith("--")) {
51
- const key = arg.slice(2)
52
- const next = args[i + 1]
53
-
54
- if (isValue(next)) {
55
- params.set(key, next)
56
- i += 2
57
- } else {
58
- params.set(key, "true")
59
- i++
60
- }
61
-
62
- continue
63
- }
64
-
65
- if (arg.startsWith("-") && arg.length === 2) {
66
- const long = SHORT_FLAGS[arg[1]!]
67
-
68
- if (!long) {
69
- i++
70
- continue
71
- }
72
-
73
- const next = args[i + 1]
74
-
75
- if (isValue(next)) {
76
- params.set(long, next)
77
- i += 2
78
- } else {
79
- params.set(long, "true")
80
- i++
81
- }
82
-
83
- continue
84
- }
85
-
86
- if (METHOD_KEYWORDS.has(arg)) {
87
- method = "POST"
88
- segments.push(arg)
89
- i++
90
- continue
91
- }
92
-
93
- if (API_CALL_METHODS.has(arg) && !params.has("path")) {
94
- segments.push(arg)
95
- i += consumeApiCall(args, i, params)
96
- continue
97
- }
98
-
99
- if (arg.includes("/") && !params.has("path")) {
100
- params.set("path", arg)
101
- i++
102
- continue
103
- }
104
-
105
- segments.push(arg)
106
- i++
107
- }
108
-
109
- const path = segments.length > 0 ? `/${segments.join("/")}` : "/"
110
- const query = params.size > 0 ? `?${params}` : ""
111
-
112
- return { method, path, url: `http://localhost${path}${query}` }
113
- }
@@ -1,27 +0,0 @@
1
- import { zValidator as zv } from "@hono/zod-validator"
2
- import { HTTPException } from "hono/http-exception"
3
- import type { ZodType } from "zod"
4
-
5
- export const zValidator = <Target extends "param" | "query" | "json", T extends ZodType>(
6
- target: Target,
7
- schema: T,
8
- helpText?: string,
9
- ) =>
10
- zv(target, schema, (result, c) => {
11
- if (helpText && c.req.query("help")) {
12
- return c.text(helpText)
13
- }
14
-
15
- if (result.success) return
16
-
17
- const issue = result.error.issues[0]
18
-
19
- if (!issue) {
20
- throw new HTTPException(400, { message: "invalid request" })
21
- }
22
-
23
- const path = issue.path.join(".")
24
- const message = path ? `${path}: ${issue.message}` : issue.message
25
-
26
- throw new HTTPException(400, { message })
27
- })
@@ -1,27 +0,0 @@
1
- import { z } from "zod"
2
- import { factory } from "@/cli/factory"
3
- import { zValidator } from "@/cli/router/validator"
4
-
5
- export const renameHelp = `funnel channels <channel> connectors rename <connector> <new-name>
6
-
7
- usage: funnel channels <channel> connectors rename <connector> <new-name>`
8
-
9
- export const channelsConnectorsRenameHandler = factory.createHandlers(
10
- zValidator(
11
- "param",
12
- z.object({ channel: z.string(), connector: z.string(), newName: z.string() }),
13
- ),
14
- zValidator("query", z.object({}), renameHelp),
15
- async (c) => {
16
- const param = c.req.valid("param")
17
- const funnel = c.var.funnel
18
-
19
- await funnel.listeners.stop(param.channel, param.connector)
20
-
21
- funnel.channels.renameConnector(param.channel, param.connector, param.newName)
22
-
23
- await funnel.listeners.start(param.channel, param.newName)
24
-
25
- return c.text(`renamed connector "${param.connector}" to "${param.newName}"`)
26
- },
27
- )