@interactive-inc/claude-funnel 0.60.0 → 0.63.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 (88) hide show
  1. package/README.md +2 -2
  2. package/dist/bin.js +428 -761
  3. package/dist/{channels-2g_BU1N0.d.ts → channels-B8RQPrVq.d.ts} +17 -16
  4. package/dist/claude.d.ts +5 -7
  5. package/dist/claude.js +143 -36
  6. package/dist/{connector-descriptor-6SXJoszo.d.ts → connector-descriptor-ClEEbuW3.d.ts} +50 -11
  7. package/dist/connector-diagnostics-recorder-COtNEmUp.js +42 -0
  8. package/dist/connectors/discord.d.ts +31 -37
  9. package/dist/connectors/discord.js +3 -3
  10. package/dist/connectors/gh.d.ts +37 -33
  11. package/dist/connectors/gh.js +3 -3
  12. package/dist/connectors/schedule.d.ts +9 -57
  13. package/dist/connectors/schedule.js +3 -3
  14. package/dist/connectors/slack.d.ts +71 -131
  15. package/dist/connectors/slack.js +4 -3
  16. package/dist/diagnostics.d.ts +1 -1
  17. package/dist/diagnostics.js +1 -1
  18. package/dist/discord-connector-DIFkYBbi.js +250 -0
  19. package/dist/discord-connector-schema-D-bOVAKt.d.ts +22 -0
  20. package/dist/docs.js +1 -1
  21. package/dist/doctor.d.ts +1 -1
  22. package/dist/doctor.js +1 -1
  23. package/dist/{file-process-guard-DOlCr4GF.d.ts → file-process-guard-DGHxALfI.d.ts} +8 -6
  24. package/dist/{file-system-o51IsM0W.d.ts → file-system-VhwwXZbm.d.ts} +8 -0
  25. package/dist/flume-source-listener-Dim5szHG.d.ts +133 -0
  26. package/dist/{funnel-diagnostics-CSiJmPlZ.js → funnel-diagnostics-Cvk6Sk4x.js} +193 -43
  27. package/dist/{funnel-diagnostics-DpXOsCty.d.ts → funnel-diagnostics-b9ar0Ing.d.ts} +67 -5
  28. package/dist/{funnel-docs-BxXZ9Ksx.js → funnel-docs-C-ge0MuB.js} +42 -6
  29. package/dist/{funnel-doctor-CZf_0Luq.d.ts → funnel-doctor-CnRQi4kM.d.ts} +2 -2
  30. package/dist/{funnel-doctor-DiJCjHsg.js → funnel-doctor-XrI2GBH8.js} +1 -1
  31. package/dist/funnel-error-0t1MK1R6.js +75 -0
  32. package/dist/{funnel-recovery-DnLrdWO9.d.ts → funnel-recovery-CMhY8Jfk.d.ts} +1 -1
  33. package/dist/gateway/daemon.js +167 -527
  34. package/dist/gateway.d.ts +3 -3
  35. package/dist/gateway.js +3 -3
  36. package/dist/gh-connector-BUGCOEWS.js +187 -0
  37. package/dist/{gh-connector-schema-Rzwc1c1N.js → gh-connector-schema-CAqIhzGr.js} +7 -0
  38. package/dist/gh-connector-schema-DWQaB6gX.d.ts +16 -0
  39. package/dist/{index-CgY8NdMz.d.ts → index-DxRikYmu.d.ts} +37 -19
  40. package/dist/index.d.ts +182 -22
  41. package/dist/index.js +365 -174
  42. package/dist/{local-config-json-schema-JyLqOQNX.js → local-config-json-schema-DexV8vX3.js} +24 -4
  43. package/dist/local-config.d.ts +39 -2
  44. package/dist/local-config.js +53 -2
  45. package/dist/logger.js +1 -1
  46. package/dist/loopback-fetch-CVNuN3YZ.js +40 -0
  47. package/dist/{local-config-sync-Dh1Croqe.d.ts → memory-token-prompter-DP_YV9xX.d.ts} +30 -3
  48. package/dist/node-file-system-BOXIHW_Q.js +174 -0
  49. package/dist/{profiles-DSzTeKQw.js → profiles-ZHLONml4.js} +49 -49
  50. package/dist/{profiles-Cy5wXQ0L.d.ts → profiles-cVZQkM69.d.ts} +3 -3
  51. package/dist/profiles.d.ts +1 -1
  52. package/dist/profiles.js +1 -1
  53. package/dist/recovery.d.ts +1 -1
  54. package/dist/recovery.js +1 -1
  55. package/dist/resolve-connector-token-DxDG9mhf.js +22 -0
  56. package/dist/{schedule-connector-L4uzg5M8.js → schedule-connector-9k3gOIgl.js} +54 -55
  57. package/dist/schedule-connector-schema-Z0RXLgPI.d.ts +49 -0
  58. package/dist/settings-reader-BNxjsxCB.d.ts +27 -0
  59. package/dist/{settings-store-CUKSeTXC.js → settings-store-C2QdOH-t.js} +23 -4
  60. package/dist/slack-connector-BU86fIge.js +359 -0
  61. package/dist/slack-event-processor-BhCf5Wiy.d.ts +95 -0
  62. package/dist/slack-event-processor-xFDG3US0.js +176 -0
  63. package/dist/slot-fields-D-pvMgTK.js +249 -0
  64. package/dist/{memory-diagnostic-log-CI60kNfB.js → sqlite-diagnostic-log-DOTPW-tG.js} +373 -249
  65. package/dist/{yaml-render-qW34NlYz.js → yaml-render--J1_3BSA.js} +28 -21
  66. package/package.json +2 -4
  67. package/dist/discord-connector-BL36yvbL.js +0 -250
  68. package/dist/gateway-base-url-Dy4Ykuoh.js +0 -14
  69. package/dist/gh-connector-DpiixfQZ.js +0 -226
  70. package/dist/http-client-oICicjuO.d.ts +0 -18
  71. package/dist/memory-token-prompter-B4sjyaAq.d.ts +0 -57
  72. package/dist/memory-token-prompter-CZde7e6y.js +0 -61
  73. package/dist/node-file-system-Blr8pAir.js +0 -48
  74. package/dist/settings-reader-BIFB_j2f.d.ts +0 -18
  75. package/dist/slack-connector-DQIFPdBF.js +0 -484
  76. package/dist/slot-fields-CMoRpwuy.js +0 -45
  77. /package/dist/{connector-adapter-DU9Rvyec.js → connector-adapter-Dvs8N7ew.js} +0 -0
  78. /package/dist/{connector-listener-DR3aKOuK.js → connector-listener-mPGZYa8e.js} +0 -0
  79. /package/dist/{diagnostic-sql-reader-C9zR-Csp.js → diagnostic-sql-reader-oXZnWFf_.js} +0 -0
  80. /package/dist/{discord-connector-schema-B_N6IXLz.js → discord-connector-schema-B4YpWpR3.js} +0 -0
  81. /package/dist/{error-message-of-Byi4y0Uf.js → error-message-of-ColuYmAk.js} +0 -0
  82. /package/dist/{funnel-log-sqlite-sink-kqJbx2H7.js → funnel-log-sqlite-sink-DLYkY0pZ.js} +0 -0
  83. /package/dist/{funnel-recovery-BFdPjL6Z.js → funnel-recovery-DKnEutUS.js} +0 -0
  84. /package/dist/{node-http-client-lowp60Oa.js → node-http-client-u00atiKx.js} +0 -0
  85. /package/dist/{schedule-connector-schema-CfyuMCMh.js → schedule-connector-schema-DKEPZnVv.js} +0 -0
  86. /package/dist/{settings-reader-CtQ-Ix8_.js → settings-reader-9FcX3qS1.js} +0 -0
  87. /package/dist/{settings-schema-D1xcOqRu.d.ts → settings-schema-BL_c2Udm.d.ts} +0 -0
  88. /package/dist/{slack-connector-schema-C1zEf4TG.js → slack-connector-schema-Dem8to4P.js} +0 -0
@@ -1,6 +1,6 @@
1
- import { t as NodeFunnelFileSystem } from "./node-file-system-Blr8pAir.js";
1
+ import { t as NodeFunnelFileSystem } from "./node-file-system-BOXIHW_Q.js";
2
2
  import { t as NodeFunnelProcessRunner } from "./node-process-runner-DxTvycoK.js";
3
- import { n as FUNNEL_DIR, o as resolveFunnelPort, p as NodeFunnelIdGenerator } from "./settings-store-CUKSeTXC.js";
3
+ import { n as FUNNEL_DIR, o as resolveFunnelPort, p as NodeFunnelIdGenerator } from "./settings-store-C2QdOH-t.js";
4
4
  import { join } from "node:path";
5
5
  import { stringify } from "yaml";
6
6
  //#region lib/engine/claude/claude.ts
@@ -21,6 +21,7 @@ var FunnelClaude = class {
21
21
  process;
22
22
  idGenerator;
23
23
  logger;
24
+ dir;
24
25
  constructor(deps) {
25
26
  this.channels = deps.channels;
26
27
  this.mcp = deps.mcp;
@@ -30,6 +31,7 @@ var FunnelClaude = class {
30
31
  this.process = deps.process ?? defaultProcess$1;
31
32
  this.idGenerator = deps.idGenerator ?? defaultIdGenerator;
32
33
  this.logger = deps.logger;
34
+ this.dir = deps.dir;
33
35
  Object.freeze(this);
34
36
  }
35
37
  async launch(options) {
@@ -115,6 +117,7 @@ var FunnelClaude = class {
115
117
  for (const [key, value] of Object.entries(globalThis.process.env)) if (typeof value === "string") env[key] = value;
116
118
  env.FUNNEL_CHANNEL_ID = channelId;
117
119
  env.FUNNEL_PORT = String(resolveFunnelPort());
120
+ if (this.dir !== void 0) env.FUNNEL_DIR = this.dir;
118
121
  return env;
119
122
  }
120
123
  };
@@ -122,7 +125,7 @@ var FunnelClaude = class {
122
125
  //#region lib/engine/claude/file-process-guard.ts
123
126
  const defaultFs$1 = new NodeFunnelFileSystem();
124
127
  const defaultProcess = new NodeFunnelProcessRunner();
125
- var FileProcessGuard = class {
128
+ var FunnelFileProcessGuard = class {
126
129
  fs;
127
130
  process;
128
131
  pidDir;
@@ -223,26 +226,30 @@ var FunnelMcp = class {
223
226
  }
224
227
  install(repoPath) {
225
228
  if (!this.fs.existsSync(repoPath)) throw new Error(`repository does not exist: ${repoPath}`);
226
- const config = this.readConfig(repoPath);
227
- const existing = config.mcpServers;
228
- const servers = isRecord(existing) ? existing : {};
229
- const targetName = this.findServerName(servers) ?? "funnel";
230
- servers[targetName] = {
231
- command: "bun",
232
- args: FUNNEL_MCP_ARGS
233
- };
234
- config.mcpServers = servers;
235
- this.writeConfig(repoPath, config);
229
+ this.fs.withFileLock(join(repoPath, ".mcp.json.lock"), () => {
230
+ const config = this.readConfig(repoPath);
231
+ const existing = config.mcpServers;
232
+ const servers = isRecord(existing) ? existing : {};
233
+ const targetName = this.findServerName(servers) ?? "funnel";
234
+ servers[targetName] = {
235
+ command: "bun",
236
+ args: FUNNEL_MCP_ARGS
237
+ };
238
+ config.mcpServers = servers;
239
+ this.writeConfig(repoPath, config);
240
+ });
236
241
  }
237
242
  uninstall(repoPath) {
238
243
  if (!this.fs.existsSync(repoPath)) return;
239
- const config = this.readConfig(repoPath);
240
- const servers = config.mcpServers;
241
- if (!isRecord(servers)) return;
242
- const name = this.findServerName(servers);
243
- if (!name) return;
244
- delete servers[name];
245
- this.writeConfig(repoPath, config);
244
+ this.fs.withFileLock(join(repoPath, ".mcp.json.lock"), () => {
245
+ const config = this.readConfig(repoPath);
246
+ const servers = config.mcpServers;
247
+ if (!isRecord(servers)) return;
248
+ const name = this.findServerName(servers);
249
+ if (!name) return;
250
+ delete servers[name];
251
+ this.writeConfig(repoPath, config);
252
+ });
246
253
  }
247
254
  findInstalledName(cwd) {
248
255
  const servers = this.readConfig(cwd).mcpServers;
@@ -301,4 +308,4 @@ const renderYaml = (value) => {
301
308
  });
302
309
  };
303
310
  //#endregion
304
- export { FunnelMcp as a, FUNNEL_MCP_NAME as i, FUNNEL_MCP_ARGS as n, FileProcessGuard as o, FUNNEL_MCP_COMMAND as r, FunnelClaude as s, renderYaml as t };
311
+ export { FunnelMcp as a, FUNNEL_MCP_NAME as i, FUNNEL_MCP_ARGS as n, FunnelFileProcessGuard as o, FUNNEL_MCP_COMMAND as r, FunnelClaude as s, renderYaml as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interactive-inc/claude-funnel",
3
- "version": "0.60.0",
3
+ "version": "0.63.0",
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",
@@ -118,10 +118,8 @@
118
118
  },
119
119
  "dependencies": {
120
120
  "@hono/zod-validator": "0.8.0",
121
+ "@interactive-inc/flume": "^0.9.2",
121
122
  "@modelcontextprotocol/sdk": "^1.29.0",
122
- "@slack/bolt": "^4.7.2",
123
- "@slack/web-api": "^7.16.0",
124
- "discord.js": "^14.26.4",
125
123
  "hono": "^4.12.19",
126
124
  "yaml": "^2.9.0",
127
125
  "zod": "^4.4.3"
@@ -1,250 +0,0 @@
1
- import { t as discordConnectorSchema } from "./discord-connector-schema-B_N6IXLz.js";
2
- import { t as NodeFunnelHttpClient } from "./node-http-client-lowp60Oa.js";
3
- import { t as FunnelConnectorAdapter } from "./connector-adapter-DU9Rvyec.js";
4
- import { t as FunnelConnectorListener } from "./connector-listener-DR3aKOuK.js";
5
- import { t as errorMessageOf } from "./error-message-of-Byi4y0Uf.js";
6
- import { n as resolveConnectorToken, t as slotFields } from "./slot-fields-CMoRpwuy.js";
7
- import { Client, GatewayIntentBits, Partials } from "discord.js";
8
- //#region lib/engine/connectors/discord-adapter.ts
9
- const DISCORD_API_BASE = "https://discord.com/api/v10";
10
- const defaultHttp = new NodeFunnelHttpClient();
11
- var FunnelDiscordAdapter = class extends FunnelConnectorAdapter {
12
- token;
13
- http;
14
- constructor(deps) {
15
- super();
16
- this.token = resolveConnectorToken({
17
- literal: deps.config.botToken,
18
- envVar: deps.config.botTokenEnv,
19
- env: deps.env ?? process.env,
20
- label: `${deps.config.name}.botToken`
21
- });
22
- this.http = deps.http ?? defaultHttp;
23
- Object.freeze(this);
24
- }
25
- async call(input) {
26
- const method = (input.method || "GET").toUpperCase();
27
- const path = input.path.startsWith("/") ? input.path : `/${input.path}`;
28
- const body = input.body;
29
- const hasBody = body !== null && typeof body === "object" && method !== "GET" && Object.keys(body).length > 0;
30
- const res = await this.http.fetch({
31
- method,
32
- url: `${DISCORD_API_BASE}${path}`,
33
- headers: {
34
- Authorization: `Bot ${this.token}`,
35
- "Content-Type": "application/json"
36
- },
37
- body: hasBody ? JSON.stringify(input.body) : void 0
38
- });
39
- if (!res.ok) throw new Error(`Discord API failed (${res.status}): ${await res.text()}`);
40
- if (res.status === 204) return null;
41
- return await res.json();
42
- }
43
- };
44
- //#endregion
45
- //#region lib/engine/connectors/discord-event-processor.ts
46
- var FunnelDiscordEventProcessor = class {
47
- ownUserId;
48
- constructor(props) {
49
- this.ownUserId = props.ownUserId;
50
- }
51
- process(message) {
52
- if (message.authorIsBot) return { skip: true };
53
- const mentioned = this.ownUserId ? message.mentionedUserIds.includes(this.ownUserId) : false;
54
- return {
55
- skip: false,
56
- content: JSON.stringify(message.raw),
57
- meta: {
58
- event_type: "discord",
59
- channel_id: message.channelId,
60
- user_id: message.authorId,
61
- mentioned: String(mentioned),
62
- guild_id: message.guildId ?? ""
63
- }
64
- };
65
- }
66
- };
67
- //#endregion
68
- //#region lib/engine/connectors/discord-listener.ts
69
- var FunnelDiscordListener = class extends FunnelConnectorListener {
70
- config;
71
- channelId;
72
- env;
73
- logger;
74
- diagnosticLog;
75
- client = null;
76
- constructor(deps) {
77
- super();
78
- this.config = deps.config;
79
- this.channelId = deps.channelId ?? null;
80
- this.env = deps.env ?? process.env;
81
- this.logger = deps.logger;
82
- this.diagnosticLog = deps.diagnosticLog;
83
- }
84
- async start(notify) {
85
- this.recordConnection("started", "");
86
- const client = new Client({
87
- intents: [
88
- GatewayIntentBits.Guilds,
89
- GatewayIntentBits.GuildMessages,
90
- GatewayIntentBits.MessageContent,
91
- GatewayIntentBits.DirectMessages
92
- ],
93
- partials: [Partials.Channel]
94
- });
95
- client.on("messageCreate", async (message) => {
96
- const ownUserId = client.user?.id ?? "";
97
- const mentionedUserIds = [...message.mentions.users.keys()];
98
- this.logger?.info("discord messageCreate", {
99
- author: message.author.id,
100
- authorIsBot: String(message.author.bot),
101
- channelId: message.channelId,
102
- guildId: message.guildId ?? "",
103
- mentions: mentionedUserIds.join(","),
104
- ownUserId,
105
- mentioned: String(mentionedUserIds.includes(ownUserId))
106
- });
107
- const rawEvent = message.toJSON();
108
- const eventId = crypto.randomUUID();
109
- this.recordRaw(eventId, rawEvent);
110
- const result = new FunnelDiscordEventProcessor({ ownUserId }).process({
111
- authorId: message.author.id,
112
- authorIsBot: message.author.bot,
113
- channelId: message.channelId,
114
- guildId: message.guildId,
115
- mentionedUserIds,
116
- raw: rawEvent
117
- });
118
- if (result.skip) {
119
- this.recordProcessed(eventId, rawEvent, "skip:bot", "");
120
- this.logger?.info("discord skip", { reason: "bot author" });
121
- return;
122
- }
123
- try {
124
- await notify(result.content, result.meta);
125
- } catch (error) {
126
- this.recordProcessed(eventId, rawEvent, "emitted:delivery-failed", result.content);
127
- this.logger?.error("discord notify error", { error: errorMessageOf(error) });
128
- return;
129
- }
130
- this.recordProcessed(eventId, rawEvent, "emitted", result.content);
131
- });
132
- client.on("ready", (readyClient) => {
133
- this.logger?.info("discord ready", {
134
- userId: readyClient.user.id,
135
- tag: readyClient.user.tag,
136
- guilds: String(readyClient.guilds.cache.size)
137
- });
138
- });
139
- client.on("error", (error) => {
140
- this.recordConnection("error", errorMessageOf(error));
141
- this.logger?.error("discord client error", { error: errorMessageOf(error) });
142
- });
143
- try {
144
- await client.login(resolveConnectorToken({
145
- literal: this.config.botToken,
146
- envVar: this.config.botTokenEnv,
147
- env: this.env,
148
- label: `${this.config.name}.botToken`
149
- }));
150
- } catch (error) {
151
- this.recordConnection("error", errorMessageOf(error));
152
- throw error;
153
- }
154
- this.client = client;
155
- this.recordConnection("connected", "");
156
- }
157
- async stop() {
158
- if (!this.client) return;
159
- try {
160
- await this.client.destroy();
161
- this.recordConnection("disconnected", "");
162
- } catch (error) {
163
- this.recordConnection("error", errorMessageOf(error));
164
- this.logger?.error("discord stop error", { error: errorMessageOf(error) });
165
- } finally {
166
- this.client = null;
167
- this.recordConnection("stopped", "");
168
- }
169
- }
170
- isAlive() {
171
- return this.client !== null;
172
- }
173
- recordRaw(eventId, rawEvent) {
174
- this.diagnosticLog?.recordRaw({
175
- eventId,
176
- type: "discord",
177
- connectorId: this.config.id,
178
- channelId: this.channelId,
179
- payload: JSON.stringify(rawEvent)
180
- });
181
- }
182
- recordProcessed(eventId, rawEvent, outcome, content) {
183
- this.diagnosticLog?.recordProcessed({
184
- eventId,
185
- type: "discord",
186
- connectorId: this.config.id,
187
- channelId: this.channelId,
188
- outcome,
189
- payload: content || JSON.stringify(rawEvent)
190
- });
191
- }
192
- recordConnection(status, detail) {
193
- this.diagnosticLog?.recordConnection({
194
- type: "discord",
195
- connectorId: this.config.id,
196
- channelId: this.channelId,
197
- status,
198
- detail
199
- });
200
- }
201
- };
202
- //#endregion
203
- //#region lib/engine/connectors/discord-connector.ts
204
- /**
205
- * Discord connector descriptor. Pass `discordConnector()` to
206
- * `new Funnel({ connectors: [...] })` to enable the type.
207
- */
208
- const discordConnector = () => ({
209
- type: "discord",
210
- toolExposed: true,
211
- createListener(config, deps) {
212
- return new FunnelDiscordListener({
213
- config: discordConnectorSchema.parse(config),
214
- channelId: deps.channelId,
215
- logger: deps.logger,
216
- diagnosticLog: deps.diagnosticLog
217
- });
218
- },
219
- createAdapter(config) {
220
- return new FunnelDiscordAdapter({ config: discordConnectorSchema.parse(config) });
221
- },
222
- secretTokens(config) {
223
- return [discordConnectorSchema.parse(config).botToken].filter((token) => token !== void 0);
224
- },
225
- buildConfig(input, context) {
226
- return discordConnectorSchema.parse({
227
- id: context.id,
228
- type: "discord",
229
- name: input.name,
230
- ...typeof input.botToken === "string" ? { botToken: input.botToken } : {},
231
- ...typeof input.botTokenEnv === "string" ? { botTokenEnv: input.botTokenEnv } : {},
232
- createdAt: context.now,
233
- updatedAt: context.now
234
- });
235
- },
236
- applyUpdate(config, fields, context) {
237
- const current = discordConnectorSchema.parse(config);
238
- return discordConnectorSchema.parse({
239
- id: current.id,
240
- name: current.name,
241
- type: "discord",
242
- createdAt: current.createdAt,
243
- updatedAt: context.now,
244
- ...slotFields("botToken", "botTokenEnv", fields, current)
245
- });
246
- },
247
- operations: {}
248
- });
249
- //#endregion
250
- export { FunnelDiscordAdapter as i, FunnelDiscordListener as n, FunnelDiscordEventProcessor as r, discordConnector as t };
@@ -1,14 +0,0 @@
1
- //#region lib/engine/http/gateway-base-url.ts
2
- /**
3
- * The HTTP base URL of a gateway daemon on the loopback interface. The daemon
4
- * always binds 127.0.0.1 for its management API (only the WS `/ws` endpoint is
5
- * ever exposed off-box), so every in-process HTTP client — publisher, listeners
6
- * client, MCP channel server — talks to it here. Centralizing the construction
7
- * keeps the host/port shape in one place instead of re-spelling
8
- * `http://127.0.0.1:${port}` at each call site.
9
- */
10
- function gatewayLoopbackUrl(port) {
11
- return `http://127.0.0.1:${port}`;
12
- }
13
- //#endregion
14
- export { gatewayLoopbackUrl as t };
@@ -1,226 +0,0 @@
1
- import { t as NodeFunnelProcessRunner } from "./node-process-runner-DxTvycoK.js";
2
- import { t as ghConnectorSchema } from "./gh-connector-schema-Rzwc1c1N.js";
3
- import { t as FunnelConnectorAdapter } from "./connector-adapter-DU9Rvyec.js";
4
- import { t as FunnelConnectorListener } from "./connector-listener-DR3aKOuK.js";
5
- import { t as errorMessageOf } from "./error-message-of-Byi4y0Uf.js";
6
- import { z } from "zod";
7
- //#region lib/engine/connectors/gh-adapter.ts
8
- const defaultProcess$1 = new NodeFunnelProcessRunner();
9
- var FunnelGhAdapter = class extends FunnelConnectorAdapter {
10
- process;
11
- constructor(deps = {}) {
12
- super();
13
- this.process = deps.process ?? defaultProcess$1;
14
- Object.freeze(this);
15
- }
16
- async call(input) {
17
- const args = ["api", input.path];
18
- if (input.method && input.method.toLowerCase() !== "get") args.push("-X", input.method.toUpperCase());
19
- const hasBody = input.body && typeof input.body === "object" && Object.keys(input.body).length > 0;
20
- if (hasBody) args.push("--input", "-");
21
- const result = await this.process.run(["gh", ...args], { input: hasBody ? JSON.stringify(input.body) : void 0 });
22
- if (result.exitCode !== 0) throw new Error(`gh api failed: ${result.stderr.trim() || result.stdout.trim()}`);
23
- try {
24
- return JSON.parse(result.stdout);
25
- } catch {
26
- return result.stdout;
27
- }
28
- }
29
- };
30
- //#endregion
31
- //#region lib/engine/connectors/gh-listener.ts
32
- const ghNotificationSchema = z.object({
33
- id: z.string(),
34
- reason: z.string(),
35
- subject: z.object({
36
- type: z.string(),
37
- url: z.string(),
38
- title: z.string()
39
- }),
40
- repository: z.object({ full_name: z.string() }),
41
- updated_at: z.string()
42
- });
43
- const ghNotificationsSchema = z.array(ghNotificationSchema);
44
- const defaultProcess = new NodeFunnelProcessRunner();
45
- const MAX_SEEN = 1e4;
46
- const KEEP_SEEN = 5e3;
47
- var FunnelGhListener = class extends FunnelConnectorListener {
48
- config;
49
- channelId;
50
- process;
51
- logger;
52
- diagnosticLog;
53
- now;
54
- seen = /* @__PURE__ */ new Map();
55
- bootstrapped = false;
56
- since;
57
- timer = null;
58
- constructor(deps) {
59
- super();
60
- this.config = deps.config;
61
- this.channelId = deps.channelId ?? null;
62
- this.process = deps.process ?? defaultProcess;
63
- this.logger = deps.logger;
64
- this.diagnosticLog = deps.diagnosticLog;
65
- this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
66
- this.since = this.now().toISOString();
67
- }
68
- async start(notify) {
69
- this.recordConnection("started", "");
70
- await this.pollOnce(notify);
71
- const interval = this.config.pollInterval ?? 60;
72
- this.timer = setInterval(() => void this.pollOnce(notify), interval * 1e3);
73
- this.timer.unref();
74
- }
75
- async stop() {
76
- if (!this.timer) return;
77
- clearInterval(this.timer);
78
- this.timer = null;
79
- this.recordConnection("stopped", "");
80
- }
81
- isAlive() {
82
- return this.timer !== null;
83
- }
84
- async pollOnce(notify) {
85
- const nextSince = this.now().toISOString();
86
- const params = new URLSearchParams({
87
- since: this.since,
88
- all: "false"
89
- });
90
- try {
91
- const result = await this.process.run([
92
- "gh",
93
- "api",
94
- `/notifications?${params}`
95
- ]);
96
- if (result.exitCode !== 0) {
97
- this.recordConnection("error", `gh api exited ${result.exitCode}: ${result.stderr}`);
98
- this.logger?.error("gh poll failed", { stderr: result.stderr });
99
- return;
100
- }
101
- const parsed = ghNotificationsSchema.safeParse(JSON.parse(result.stdout));
102
- if (!parsed.success) {
103
- this.recordConnection("error", `gh response schema mismatch: ${parsed.error.message}`);
104
- this.logger?.warn("gh response did not match schema", { error: parsed.error.message });
105
- return;
106
- }
107
- if (!this.bootstrapped) this.recordConnection("connected", "");
108
- const items = parsed.data;
109
- for (const item of items) {
110
- if (this.seen.get(item.id) === item.updated_at) continue;
111
- this.seen.set(item.id, item.updated_at);
112
- this.recordRaw(item.id, item);
113
- if (!this.bootstrapped) {
114
- this.recordProcessed(item.id, item, "skip:bootstrap", "");
115
- continue;
116
- }
117
- const meta = {
118
- event_type: "gh",
119
- reason: item.reason,
120
- subject_type: item.subject.type,
121
- subject_url: item.subject.url,
122
- repository: item.repository.full_name,
123
- thread_id: item.id,
124
- updated_at: item.updated_at
125
- };
126
- const content = JSON.stringify(item);
127
- try {
128
- await notify(content, meta);
129
- } catch (error) {
130
- this.recordProcessed(item.id, item, "emitted:delivery-failed", content);
131
- throw error;
132
- }
133
- this.recordProcessed(item.id, item, "emitted", content);
134
- }
135
- if (this.seen.size > MAX_SEEN) {
136
- const toDrop = this.seen.size - KEEP_SEEN;
137
- let dropped = 0;
138
- for (const key of this.seen.keys()) {
139
- if (dropped >= toDrop) break;
140
- this.seen.delete(key);
141
- dropped++;
142
- }
143
- }
144
- this.since = nextSince;
145
- this.bootstrapped = true;
146
- } catch (error) {
147
- this.recordConnection("error", errorMessageOf(error));
148
- this.logger?.error("gh poll error", { error: errorMessageOf(error) });
149
- }
150
- }
151
- recordRaw(eventId, item) {
152
- this.diagnosticLog?.recordRaw({
153
- eventId,
154
- type: "gh",
155
- connectorId: this.config.id,
156
- channelId: this.channelId,
157
- payload: JSON.stringify(item)
158
- });
159
- }
160
- recordProcessed(eventId, item, outcome, content) {
161
- this.diagnosticLog?.recordProcessed({
162
- eventId,
163
- type: "gh",
164
- connectorId: this.config.id,
165
- channelId: this.channelId,
166
- outcome,
167
- payload: content || JSON.stringify(item)
168
- });
169
- }
170
- recordConnection(status, detail) {
171
- this.diagnosticLog?.recordConnection({
172
- type: "gh",
173
- connectorId: this.config.id,
174
- channelId: this.channelId,
175
- status,
176
- detail
177
- });
178
- }
179
- };
180
- //#endregion
181
- //#region lib/engine/connectors/gh-connector.ts
182
- /**
183
- * GitHub connector descriptor. Pass `ghConnector()` to
184
- * `new Funnel({ connectors: [...] })` to enable the type.
185
- */
186
- const ghConnector = () => ({
187
- type: "gh",
188
- toolExposed: true,
189
- createListener(config, deps) {
190
- return new FunnelGhListener({
191
- config: ghConnectorSchema.parse(config),
192
- channelId: deps.channelId,
193
- process: deps.process,
194
- logger: deps.logger,
195
- diagnosticLog: deps.diagnosticLog
196
- });
197
- },
198
- createAdapter(config, deps) {
199
- ghConnectorSchema.parse(config);
200
- return new FunnelGhAdapter({ process: deps.process });
201
- },
202
- secretTokens() {
203
- return [];
204
- },
205
- buildConfig(input, context) {
206
- return ghConnectorSchema.parse({
207
- id: context.id,
208
- type: "gh",
209
- name: input.name,
210
- ...typeof input.pollInterval === "number" ? { pollInterval: input.pollInterval } : {},
211
- createdAt: context.now,
212
- updatedAt: context.now
213
- });
214
- },
215
- applyUpdate(config, fields, context) {
216
- const current = ghConnectorSchema.parse(config);
217
- return ghConnectorSchema.parse({
218
- ...current,
219
- ...typeof fields.pollInterval === "number" ? { pollInterval: fields.pollInterval } : {},
220
- updatedAt: context.now
221
- });
222
- },
223
- operations: {}
224
- });
225
- //#endregion
226
- export { FunnelGhListener as n, FunnelGhAdapter as r, ghConnector as t };
@@ -1,18 +0,0 @@
1
- //#region lib/engine/http/http-client.d.ts
2
- type HttpRequest = {
3
- method: string;
4
- url: string;
5
- headers?: Record<string, string>;
6
- body?: string;
7
- };
8
- type HttpResponse = {
9
- status: number;
10
- ok: boolean;
11
- text(): Promise<string>;
12
- json(): Promise<unknown>;
13
- };
14
- declare abstract class FunnelHttpClient {
15
- abstract fetch(request: HttpRequest): Promise<HttpResponse>;
16
- }
17
- //#endregion
18
- export { HttpRequest as n, HttpResponse as r, FunnelHttpClient as t };
@@ -1,57 +0,0 @@
1
- import { n as FunnelFileSystem } from "./file-system-o51IsM0W.js";
2
- import { i as FunnelTokenPrompter } from "./local-config-sync-Dh1Croqe.js";
3
-
4
- //#region lib/services/local-config/local-config-json-schema.d.ts
5
- /**
6
- * Generates the JSON Schema (draft 2020-12) for `funnel.json`. Useful for
7
- * `$schema` references in committed `funnel.json` files so editors can give
8
- * autocomplete and validation for channels[] (transport) and profiles[]
9
- * (launch recipe) without anyone hand-maintaining a separate schema.
10
- */
11
- declare const funnelJsonSchema: () => Record<string, unknown>;
12
- //#endregion
13
- //#region lib/services/local-config/local-config-writer.d.ts
14
- type Deps = {
15
- fs: FunnelFileSystem;
16
- };
17
- /**
18
- * The one path that mutates the repo-committed funnel.json, and it only ever
19
- * inserts `id`. On first launch a repo has no `id`; funnel generates one and
20
- * writes it back here so future launches resolve the same `~/.funnel/projects/<id>/`.
21
- * Idempotent — a no-op once `id` is present. Kept separate from the read-only
22
- * FunnelLocalConfig so reads stay side-effect free.
23
- */
24
- declare class FunnelLocalConfigWriter {
25
- private readonly fs;
26
- constructor(deps: Deps);
27
- ensureId(cwd: string, id: string): void;
28
- }
29
- //#endregion
30
- //#region lib/engine/token-prompter/node-token-prompter.d.ts
31
- /**
32
- * Reads a secret from stdin in raw mode. Echoes a `*` per byte so the user
33
- * can see progress without exposing the token. Refuses to prompt when stdin
34
- * is not a TTY — callers should surface the resulting error with a hint
35
- * pointing at the corresponding env var or CLI command.
36
- */
37
- declare class NodeFunnelTokenPrompter extends FunnelTokenPrompter {
38
- promptSecret(label: string): Promise<string>;
39
- private readSecret;
40
- }
41
- //#endregion
42
- //#region lib/engine/token-prompter/memory-token-prompter.d.ts
43
- type Props = {
44
- answers?: Record<string, string>;
45
- };
46
- /**
47
- * Pre-seeded answers keyed by prompt label. Tests configure the map up front;
48
- * unmapped labels throw so the test surfaces unexpected prompts loudly.
49
- */
50
- declare class MemoryFunnelTokenPrompter extends FunnelTokenPrompter {
51
- private readonly answers;
52
- readonly asked: string[];
53
- constructor(props?: Props);
54
- promptSecret(label: string): Promise<string>;
55
- }
56
- //#endregion
57
- export { funnelJsonSchema as i, NodeFunnelTokenPrompter as n, FunnelLocalConfigWriter as r, MemoryFunnelTokenPrompter as t };