@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.
- package/README.md +2 -2
- package/dist/bin.js +428 -761
- package/dist/{channels-2g_BU1N0.d.ts → channels-B8RQPrVq.d.ts} +17 -16
- package/dist/claude.d.ts +5 -7
- package/dist/claude.js +143 -36
- package/dist/{connector-descriptor-6SXJoszo.d.ts → connector-descriptor-ClEEbuW3.d.ts} +50 -11
- package/dist/connector-diagnostics-recorder-COtNEmUp.js +42 -0
- package/dist/connectors/discord.d.ts +31 -37
- package/dist/connectors/discord.js +3 -3
- package/dist/connectors/gh.d.ts +37 -33
- package/dist/connectors/gh.js +3 -3
- package/dist/connectors/schedule.d.ts +9 -57
- package/dist/connectors/schedule.js +3 -3
- package/dist/connectors/slack.d.ts +71 -131
- package/dist/connectors/slack.js +4 -3
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +1 -1
- package/dist/discord-connector-DIFkYBbi.js +250 -0
- package/dist/discord-connector-schema-D-bOVAKt.d.ts +22 -0
- package/dist/docs.js +1 -1
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +1 -1
- package/dist/{file-process-guard-DOlCr4GF.d.ts → file-process-guard-DGHxALfI.d.ts} +8 -6
- package/dist/{file-system-o51IsM0W.d.ts → file-system-VhwwXZbm.d.ts} +8 -0
- package/dist/flume-source-listener-Dim5szHG.d.ts +133 -0
- package/dist/{funnel-diagnostics-CSiJmPlZ.js → funnel-diagnostics-Cvk6Sk4x.js} +193 -43
- package/dist/{funnel-diagnostics-DpXOsCty.d.ts → funnel-diagnostics-b9ar0Ing.d.ts} +67 -5
- package/dist/{funnel-docs-BxXZ9Ksx.js → funnel-docs-C-ge0MuB.js} +42 -6
- package/dist/{funnel-doctor-CZf_0Luq.d.ts → funnel-doctor-CnRQi4kM.d.ts} +2 -2
- package/dist/{funnel-doctor-DiJCjHsg.js → funnel-doctor-XrI2GBH8.js} +1 -1
- package/dist/funnel-error-0t1MK1R6.js +75 -0
- package/dist/{funnel-recovery-DnLrdWO9.d.ts → funnel-recovery-CMhY8Jfk.d.ts} +1 -1
- package/dist/gateway/daemon.js +167 -527
- package/dist/gateway.d.ts +3 -3
- package/dist/gateway.js +3 -3
- package/dist/gh-connector-BUGCOEWS.js +187 -0
- package/dist/{gh-connector-schema-Rzwc1c1N.js → gh-connector-schema-CAqIhzGr.js} +7 -0
- package/dist/gh-connector-schema-DWQaB6gX.d.ts +16 -0
- package/dist/{index-CgY8NdMz.d.ts → index-DxRikYmu.d.ts} +37 -19
- package/dist/index.d.ts +182 -22
- package/dist/index.js +365 -174
- package/dist/{local-config-json-schema-JyLqOQNX.js → local-config-json-schema-DexV8vX3.js} +24 -4
- package/dist/local-config.d.ts +39 -2
- package/dist/local-config.js +53 -2
- package/dist/logger.js +1 -1
- package/dist/loopback-fetch-CVNuN3YZ.js +40 -0
- package/dist/{local-config-sync-Dh1Croqe.d.ts → memory-token-prompter-DP_YV9xX.d.ts} +30 -3
- package/dist/node-file-system-BOXIHW_Q.js +174 -0
- package/dist/{profiles-DSzTeKQw.js → profiles-ZHLONml4.js} +49 -49
- package/dist/{profiles-Cy5wXQ0L.d.ts → profiles-cVZQkM69.d.ts} +3 -3
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.js +1 -1
- package/dist/recovery.d.ts +1 -1
- package/dist/recovery.js +1 -1
- package/dist/resolve-connector-token-DxDG9mhf.js +22 -0
- package/dist/{schedule-connector-L4uzg5M8.js → schedule-connector-9k3gOIgl.js} +54 -55
- package/dist/schedule-connector-schema-Z0RXLgPI.d.ts +49 -0
- package/dist/settings-reader-BNxjsxCB.d.ts +27 -0
- package/dist/{settings-store-CUKSeTXC.js → settings-store-C2QdOH-t.js} +23 -4
- package/dist/slack-connector-BU86fIge.js +359 -0
- package/dist/slack-event-processor-BhCf5Wiy.d.ts +95 -0
- package/dist/slack-event-processor-xFDG3US0.js +176 -0
- package/dist/slot-fields-D-pvMgTK.js +249 -0
- package/dist/{memory-diagnostic-log-CI60kNfB.js → sqlite-diagnostic-log-DOTPW-tG.js} +373 -249
- package/dist/{yaml-render-qW34NlYz.js → yaml-render--J1_3BSA.js} +28 -21
- package/package.json +2 -4
- package/dist/discord-connector-BL36yvbL.js +0 -250
- package/dist/gateway-base-url-Dy4Ykuoh.js +0 -14
- package/dist/gh-connector-DpiixfQZ.js +0 -226
- package/dist/http-client-oICicjuO.d.ts +0 -18
- package/dist/memory-token-prompter-B4sjyaAq.d.ts +0 -57
- package/dist/memory-token-prompter-CZde7e6y.js +0 -61
- package/dist/node-file-system-Blr8pAir.js +0 -48
- package/dist/settings-reader-BIFB_j2f.d.ts +0 -18
- package/dist/slack-connector-DQIFPdBF.js +0 -484
- package/dist/slot-fields-CMoRpwuy.js +0 -45
- /package/dist/{connector-adapter-DU9Rvyec.js → connector-adapter-Dvs8N7ew.js} +0 -0
- /package/dist/{connector-listener-DR3aKOuK.js → connector-listener-mPGZYa8e.js} +0 -0
- /package/dist/{diagnostic-sql-reader-C9zR-Csp.js → diagnostic-sql-reader-oXZnWFf_.js} +0 -0
- /package/dist/{discord-connector-schema-B_N6IXLz.js → discord-connector-schema-B4YpWpR3.js} +0 -0
- /package/dist/{error-message-of-Byi4y0Uf.js → error-message-of-ColuYmAk.js} +0 -0
- /package/dist/{funnel-log-sqlite-sink-kqJbx2H7.js → funnel-log-sqlite-sink-DLYkY0pZ.js} +0 -0
- /package/dist/{funnel-recovery-BFdPjL6Z.js → funnel-recovery-DKnEutUS.js} +0 -0
- /package/dist/{node-http-client-lowp60Oa.js → node-http-client-u00atiKx.js} +0 -0
- /package/dist/{schedule-connector-schema-CfyuMCMh.js → schedule-connector-schema-DKEPZnVv.js} +0 -0
- /package/dist/{settings-reader-CtQ-Ix8_.js → settings-reader-9FcX3qS1.js} +0 -0
- /package/dist/{settings-schema-D1xcOqRu.d.ts → settings-schema-BL_c2Udm.d.ts} +0 -0
- /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-
|
|
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-
|
|
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
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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,
|
|
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.
|
|
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 };
|