@interactive-inc/claude-funnel 0.37.0 → 0.39.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 +7 -9
- package/dist/bin.js +300 -290
- package/dist/gateway/daemon.js +192 -192
- package/dist/index.d.ts +1855 -3399
- package/dist/index.js +393 -379
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { a as FunnelProcessRunner, i as NodeFunnelProcessRunner, n as FunnelGhLi
|
|
|
4
4
|
import { a as ScheduleStateStore, i as FunnelScheduleListener, n as scheduleConnectorSchema, o as NodeFunnelFileSystem, r as scheduleEntrySchema, s as FunnelFileSystem, t as scheduleCatchupPolicySchema } from "./schedule-connector-schema-CM-sRkac.js";
|
|
5
5
|
import { i as FunnelSlackAdapter, n as FunnelSlackListener, r as FunnelSlackEventProcessor, t as slackConnectorSchema } from "./slack-connector-schema-DDbSGPZn.js";
|
|
6
6
|
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { hc } from "hono/client";
|
|
7
8
|
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
8
9
|
import { z } from "zod";
|
|
9
10
|
import { homedir, tmpdir } from "node:os";
|
|
@@ -1739,7 +1740,14 @@ var MemoryFunnelClock = class extends FunnelClock {
|
|
|
1739
1740
|
const publishRequestSchema = z.object({
|
|
1740
1741
|
content: z.string().min(1),
|
|
1741
1742
|
meta: z.record(z.string(), z.string()).optional(),
|
|
1742
|
-
connector: z.string().min(1).optional()
|
|
1743
|
+
connector: z.string().min(1).optional(),
|
|
1744
|
+
/**
|
|
1745
|
+
* Address the event to a single subscriber. When set, only the WS client that
|
|
1746
|
+
* declared this id at upgrade time (`?id=<subscriberId>`) receives it among the
|
|
1747
|
+
* channel's regular subscribers; tap=all observers still see it. Omit for the
|
|
1748
|
+
* default fanout. The route surfaces it to subscribers as `meta.target`.
|
|
1749
|
+
*/
|
|
1750
|
+
target: z.string().min(1).optional()
|
|
1743
1751
|
});
|
|
1744
1752
|
const publishResponseSchema = z.object({
|
|
1745
1753
|
ok: z.literal(true),
|
|
@@ -2100,6 +2108,8 @@ var FunnelBroadcaster = class {
|
|
|
2100
2108
|
}
|
|
2101
2109
|
matchesClient(event, data) {
|
|
2102
2110
|
if (data.tapAll) return true;
|
|
2111
|
+
const target = event.meta?.target;
|
|
2112
|
+
if (target && target !== data.subscriberId) return false;
|
|
2103
2113
|
const channelId = event.meta?.channelId;
|
|
2104
2114
|
if (channelId && channelId !== data.channel) return false;
|
|
2105
2115
|
const connector = event.meta?.connector;
|
|
@@ -2111,6 +2121,11 @@ var FunnelBroadcaster = class {
|
|
|
2111
2121
|
* receive (passive observation). For each per-channel group:
|
|
2112
2122
|
* - fanout → every matching client receives
|
|
2113
2123
|
* - exclusive → exactly one client receives, picked round-robin per channel
|
|
2124
|
+
*
|
|
2125
|
+
* `meta.target` narrows the regular (non-tap) recipient set first via
|
|
2126
|
+
* `matchesClient`: only the subscriber whose `subscriberId` equals `target`
|
|
2127
|
+
* stays in the running, so a targeted event reaches one named instance while
|
|
2128
|
+
* still being observable by tap=all clients.
|
|
2114
2129
|
*/
|
|
2115
2130
|
pickRecipients(event) {
|
|
2116
2131
|
const exclusiveByChannel = /* @__PURE__ */ new Map();
|
|
@@ -2982,13 +2997,17 @@ const channelsPublishHandler$1 = factory$1.createHandlers(zParam(z.object({ chan
|
|
|
2982
2997
|
}), (c) => {
|
|
2983
2998
|
const param = c.req.valid("param");
|
|
2984
2999
|
const body = c.req.valid("json");
|
|
3000
|
+
const meta = body.target ? {
|
|
3001
|
+
...body.meta,
|
|
3002
|
+
target: body.target
|
|
3003
|
+
} : body.meta;
|
|
2985
3004
|
const response = {
|
|
2986
3005
|
ok: true,
|
|
2987
3006
|
offset: c.var.deps.emit({
|
|
2988
3007
|
channel: param.channel,
|
|
2989
3008
|
connector: body.connector,
|
|
2990
3009
|
content: body.content,
|
|
2991
|
-
meta
|
|
3010
|
+
meta
|
|
2992
3011
|
}).offset
|
|
2993
3012
|
};
|
|
2994
3013
|
return c.json(response);
|
|
@@ -3076,7 +3095,7 @@ const connectionViewSql = `CREATE TEMP VIEW connection AS SELECT
|
|
|
3076
3095
|
FROM connectiondb.leuco_log`;
|
|
3077
3096
|
//#endregion
|
|
3078
3097
|
//#region lib/gateway/routes/debug.ts
|
|
3079
|
-
const extractPreview
|
|
3098
|
+
const extractPreview = (payload) => {
|
|
3080
3099
|
if (typeof payload !== "string" || payload.length === 0) return null;
|
|
3081
3100
|
try {
|
|
3082
3101
|
const parsed = JSON.parse(payload);
|
|
@@ -3182,7 +3201,7 @@ const debugHandler$1 = factory$1.createHandlers(async (c) => {
|
|
|
3182
3201
|
outcome: typeof row.outcome === "string" ? row.outcome : "?",
|
|
3183
3202
|
payload: rawPayload,
|
|
3184
3203
|
payloadParsed,
|
|
3185
|
-
preview: extractPreview
|
|
3204
|
+
preview: extractPreview(row.payload)
|
|
3186
3205
|
});
|
|
3187
3206
|
}
|
|
3188
3207
|
if (listener && (!listener.alive || listener.errors > 0) || !listener) {
|
|
@@ -3294,13 +3313,10 @@ const statusHandler$1 = factory$1.createHandlers((c) => {
|
|
|
3294
3313
|
});
|
|
3295
3314
|
//#endregion
|
|
3296
3315
|
//#region lib/gateway/routes/index.ts
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
* shape as CLI's `c.var.funnel`.
|
|
3302
|
-
*/
|
|
3303
|
-
const gatewayRoutes = factory$1.createApp().get("/health", ...healthHandler).get("/status", ...statusHandler$1).get("/debug", ...debugHandler$1).get("/listeners", ...listenersListHandler).post("/listeners/:channel/:connector/start", ...listenersStartHandler).delete("/listeners/:channel/:connector", ...listenersStopHandler).post("/listeners/:channel/:connector/restart", ...listenersRestartHandler).post("/channels/:channel/connectors/:connector/call", ...channelsConnectorsCallHandler).post("/channels/:channel/publish", ...channelsPublishHandler$1);
|
|
3316
|
+
function buildGatewayRoutes() {
|
|
3317
|
+
return factory$1.createApp().get("/health", ...healthHandler).get("/status", ...statusHandler$1).get("/debug", ...debugHandler$1).get("/listeners", ...listenersListHandler).post("/listeners/:channel/:connector/start", ...listenersStartHandler).delete("/listeners/:channel/:connector", ...listenersStopHandler).post("/listeners/:channel/:connector/restart", ...listenersRestartHandler).post("/channels/:channel/connectors/:connector/call", ...channelsConnectorsCallHandler).post("/channels/:channel/publish", ...channelsPublishHandler$1);
|
|
3318
|
+
}
|
|
3319
|
+
const gatewayRoutes = buildGatewayRoutes();
|
|
3304
3320
|
//#endregion
|
|
3305
3321
|
//#region lib/gateway/gateway-server.ts
|
|
3306
3322
|
const DEFAULT_HOST = "127.0.0.1";
|
|
@@ -3455,12 +3471,14 @@ var FunnelGatewayServer = class {
|
|
|
3455
3471
|
const sinceRaw = url.searchParams.get("since");
|
|
3456
3472
|
const sinceParsed = sinceRaw === null ? NaN : Number.parseInt(sinceRaw, 10);
|
|
3457
3473
|
const since = Number.isFinite(sinceParsed) && sinceParsed >= 0 ? sinceParsed : void 0;
|
|
3474
|
+
const subscriberId = url.searchParams.get("id") ?? void 0;
|
|
3458
3475
|
if (server.upgrade(request, { data: {
|
|
3459
3476
|
channel: channelId,
|
|
3460
3477
|
channelName,
|
|
3461
3478
|
connectors,
|
|
3462
3479
|
tapAll,
|
|
3463
3480
|
delivery,
|
|
3481
|
+
subscriberId,
|
|
3464
3482
|
since
|
|
3465
3483
|
} })) return void 0;
|
|
3466
3484
|
return new Response("WebSocket upgrade failed", { status: 400 });
|
|
@@ -4100,6 +4118,10 @@ var Funnel = class Funnel {
|
|
|
4100
4118
|
tmpDir: this.paths.tmpDir
|
|
4101
4119
|
}, channelName ?? null);
|
|
4102
4120
|
}
|
|
4121
|
+
gatewayClient() {
|
|
4122
|
+
const { port } = this.gateway.getStatus();
|
|
4123
|
+
return hc(`http://127.0.0.1:${port}`);
|
|
4124
|
+
}
|
|
4103
4125
|
};
|
|
4104
4126
|
//#endregion
|
|
4105
4127
|
//#region lib/engine/mcp/channel-subscriber.ts
|
|
@@ -5072,6 +5094,17 @@ const queryToCliArgs = (url, reservedKeys = []) => {
|
|
|
5072
5094
|
return args;
|
|
5073
5095
|
};
|
|
5074
5096
|
//#endregion
|
|
5097
|
+
//#region lib/cli/routes/channels.add.ts
|
|
5098
|
+
const help$17 = `funnel channels add — add a channel
|
|
5099
|
+
|
|
5100
|
+
usage: funnel channels add <name> [--delivery fanout|exclusive]
|
|
5101
|
+
|
|
5102
|
+
options:
|
|
5103
|
+
--delivery routing mode (default fanout):
|
|
5104
|
+
fanout every connected client receives every event
|
|
5105
|
+
exclusive each event delivered to exactly one client (round-robin)`;
|
|
5106
|
+
const channelsAddHelpHandler = factory.createHandlers((c) => c.text(help$17));
|
|
5107
|
+
//#endregion
|
|
5075
5108
|
//#region lib/cli/router/validator.ts
|
|
5076
5109
|
const zValidator$1 = (target, schema, helpText) => zValidator(target, schema, (result, c) => {
|
|
5077
5110
|
if (helpText && c.req.query("help")) return c.text(helpText);
|
|
@@ -5083,18 +5116,10 @@ const zValidator$1 = (target, schema, helpText) => zValidator(target, schema, (r
|
|
|
5083
5116
|
});
|
|
5084
5117
|
//#endregion
|
|
5085
5118
|
//#region lib/cli/routes/channels.add.$channel.ts
|
|
5086
|
-
const
|
|
5087
|
-
|
|
5088
|
-
usage: funnel channels add <name> [--delivery fanout|exclusive]
|
|
5089
|
-
|
|
5090
|
-
options:
|
|
5091
|
-
--delivery routing mode (default fanout):
|
|
5092
|
-
fanout every connected client receives every event
|
|
5093
|
-
exclusive each event delivered to exactly one client (round-robin)`;
|
|
5094
|
-
const channelsAddHandler = factory.createHandlers(zValidator$1("param", z.object({ channel: z.string() })), zValidator$1("query", z.object({ delivery: channelDeliveryModeSchema.optional() }), addHelp$3), (c) => {
|
|
5119
|
+
const channelsAddHandler = factory.createHandlers(zValidator$1("param", z.object({ channel: z.string() })), zValidator$1("query", z.object({ delivery: channelDeliveryModeSchema.optional() })), (c) => {
|
|
5095
5120
|
const param = c.req.valid("param");
|
|
5096
5121
|
const query = c.req.valid("query");
|
|
5097
|
-
const created = c.
|
|
5122
|
+
const created = c.env.funnel.channels.add({
|
|
5098
5123
|
name: param.channel,
|
|
5099
5124
|
delivery: query.delivery
|
|
5100
5125
|
});
|
|
@@ -5104,14 +5129,14 @@ const channelsConnectorsGroupHandler = factory.createHandlers(zValidator$1("para
|
|
|
5104
5129
|
|
|
5105
5130
|
usage: funnel channels <channel> connectors`), (c) => {
|
|
5106
5131
|
const param = c.req.valid("param");
|
|
5107
|
-
const channel = c.
|
|
5132
|
+
const channel = c.env.funnel.channels.get(param.channel);
|
|
5108
5133
|
if (!channel) throw new HTTPException(404, { message: `channel "${param.channel}" not found` });
|
|
5109
5134
|
if (channel.connectors.length === 0) return c.text(`no connectors in channel "${channel.name}"`);
|
|
5110
5135
|
return c.text(channel.connectors.map((c) => `${c.name} (${c.type}, id: ${c.id})`).join("\n"));
|
|
5111
5136
|
});
|
|
5112
5137
|
//#endregion
|
|
5113
|
-
//#region lib/cli/routes/channels.$channel.connectors.add
|
|
5114
|
-
const
|
|
5138
|
+
//#region lib/cli/routes/channels.$channel.connectors.add.ts
|
|
5139
|
+
const help$16 = `funnel channels <channel> connectors add <connector> — add a connector to a channel
|
|
5115
5140
|
|
|
5116
5141
|
usage:
|
|
5117
5142
|
funnel channels <channel> connectors add <connector> --type=slack --bot-token=xoxb-... --app-token=xapp-...
|
|
@@ -5120,6 +5145,9 @@ usage:
|
|
|
5120
5145
|
funnel channels <channel> connectors add <connector> --type=schedule
|
|
5121
5146
|
|
|
5122
5147
|
Token uniqueness is enforced across all channels.`;
|
|
5148
|
+
const channelsConnectorsAddHelpHandler = factory.createHandlers((c) => c.text(help$16));
|
|
5149
|
+
//#endregion
|
|
5150
|
+
//#region lib/cli/routes/channels.$channel.connectors.add.$connector.ts
|
|
5123
5151
|
const slackBody = z.object({
|
|
5124
5152
|
type: z.literal("slack"),
|
|
5125
5153
|
"bot-token": z.string().startsWith("xoxb-"),
|
|
@@ -5143,10 +5171,10 @@ const addBody = z.discriminatedUnion("type", [
|
|
|
5143
5171
|
const channelsConnectorsAddHandler = factory.createHandlers(zValidator$1("param", z.object({
|
|
5144
5172
|
channel: z.string(),
|
|
5145
5173
|
connector: z.string()
|
|
5146
|
-
})), zValidator$1("query", addBody
|
|
5174
|
+
})), zValidator$1("query", addBody), async (c) => {
|
|
5147
5175
|
const param = c.req.valid("param");
|
|
5148
5176
|
const query = c.req.valid("query");
|
|
5149
|
-
const funnel = c.
|
|
5177
|
+
const funnel = c.env.funnel;
|
|
5150
5178
|
if (query.type === "slack") {
|
|
5151
5179
|
const created = funnel.channels.addConnector(param.channel, {
|
|
5152
5180
|
type: "slack",
|
|
@@ -5184,28 +5212,34 @@ const channelsConnectorsAddHandler = factory.createHandlers(zValidator$1("param"
|
|
|
5184
5212
|
return c.text(`added schedule connector "${created.name}" to channel "${param.channel}"`);
|
|
5185
5213
|
});
|
|
5186
5214
|
//#endregion
|
|
5187
|
-
//#region lib/cli/routes/channels.$channel.connectors.remove
|
|
5188
|
-
const
|
|
5215
|
+
//#region lib/cli/routes/channels.$channel.connectors.remove.ts
|
|
5216
|
+
const help$15 = `funnel channels <channel> connectors remove <connector> — remove a connector
|
|
5189
5217
|
|
|
5190
5218
|
usage: funnel channels <channel> connectors remove <connector>`;
|
|
5219
|
+
const channelsConnectorsRemoveHelpHandler = factory.createHandlers((c) => c.text(help$15));
|
|
5220
|
+
//#endregion
|
|
5221
|
+
//#region lib/cli/routes/channels.$channel.connectors.remove.$connector.ts
|
|
5191
5222
|
const channelsConnectorsRemoveHandler = factory.createHandlers(zValidator$1("param", z.object({
|
|
5192
5223
|
channel: z.string(),
|
|
5193
5224
|
connector: z.string()
|
|
5194
|
-
})), zValidator$1("query", z.object({})
|
|
5225
|
+
})), zValidator$1("query", z.object({})), async (c) => {
|
|
5195
5226
|
const param = c.req.valid("param");
|
|
5196
|
-
const funnel = c.
|
|
5227
|
+
const funnel = c.env.funnel;
|
|
5197
5228
|
await funnel.listeners.stop(param.channel, param.connector);
|
|
5198
5229
|
funnel.channels.removeConnector(param.channel, param.connector);
|
|
5199
5230
|
return c.text(`removed connector "${param.connector}" from channel "${param.channel}"`);
|
|
5200
5231
|
});
|
|
5201
5232
|
//#endregion
|
|
5202
|
-
//#region lib/cli/routes/channels.$channel.connectors.set
|
|
5203
|
-
const
|
|
5233
|
+
//#region lib/cli/routes/channels.$channel.connectors.set.ts
|
|
5234
|
+
const help$14 = `funnel channels <channel> connectors set <connector> — update connector fields
|
|
5204
5235
|
|
|
5205
5236
|
usage:
|
|
5206
5237
|
funnel channels <ch> connectors set <conn> [--bot-token=...] [--app-token=...] # slack
|
|
5207
5238
|
funnel channels <ch> connectors set <conn> [--bot-token=...] # discord
|
|
5208
5239
|
funnel channels <ch> connectors set <conn> [--poll-interval=N] # gh`;
|
|
5240
|
+
const channelsConnectorsSetHelpHandler = factory.createHandlers((c) => c.text(help$14));
|
|
5241
|
+
//#endregion
|
|
5242
|
+
//#region lib/cli/routes/channels.$channel.connectors.set.$connector.ts
|
|
5209
5243
|
const channelsConnectorsSetHandler = factory.createHandlers(zValidator$1("param", z.object({
|
|
5210
5244
|
channel: z.string(),
|
|
5211
5245
|
connector: z.string()
|
|
@@ -5213,10 +5247,10 @@ const channelsConnectorsSetHandler = factory.createHandlers(zValidator$1("param"
|
|
|
5213
5247
|
"bot-token": z.string().optional(),
|
|
5214
5248
|
"app-token": z.string().optional(),
|
|
5215
5249
|
"poll-interval": z.coerce.number().int().positive().optional()
|
|
5216
|
-
}).passthrough()
|
|
5250
|
+
}).passthrough()), async (c) => {
|
|
5217
5251
|
const param = c.req.valid("param");
|
|
5218
5252
|
const query = c.req.valid("query");
|
|
5219
|
-
const funnel = c.
|
|
5253
|
+
const funnel = c.env.funnel;
|
|
5220
5254
|
const existing = funnel.channels.getConnector(param.channel, param.connector);
|
|
5221
5255
|
if (!existing) throw new HTTPException(404, { message: `connector "${param.connector}" not found in channel "${param.channel}"` });
|
|
5222
5256
|
if (existing.type === "slack") funnel.channels.updateSlackConnector(param.channel, param.connector, {
|
|
@@ -5236,27 +5270,36 @@ const channelsConnectorsShowHandler = factory.createHandlers(zValidator$1("param
|
|
|
5236
5270
|
|
|
5237
5271
|
usage: funnel channels <channel> connectors show <connector>`), (c) => {
|
|
5238
5272
|
const param = c.req.valid("param");
|
|
5239
|
-
const connector = c.
|
|
5273
|
+
const connector = c.env.funnel.channels.getConnector(param.channel, param.connector);
|
|
5240
5274
|
if (!connector) throw new HTTPException(404, { message: `connector "${param.connector}" not found in channel "${param.channel}"` });
|
|
5241
5275
|
return c.text(JSON.stringify(connector, null, 2));
|
|
5242
5276
|
});
|
|
5243
5277
|
//#endregion
|
|
5244
|
-
//#region lib/cli/routes/channels.$channel.connectors
|
|
5245
|
-
const
|
|
5278
|
+
//#region lib/cli/routes/channels.$channel.connectors.rename.ts
|
|
5279
|
+
const help$13 = `funnel channels <channel> connectors rename <connector> <new-name>
|
|
5246
5280
|
|
|
5247
5281
|
usage: funnel channels <channel> connectors rename <connector> <new-name>`;
|
|
5282
|
+
const channelsConnectorsRenameHelpHandler = factory.createHandlers((c) => c.text(help$13));
|
|
5283
|
+
//#endregion
|
|
5284
|
+
//#region lib/cli/routes/channels.$channel.connectors.$connector.rename.$newName.ts
|
|
5248
5285
|
const channelsConnectorsRenameHandler = factory.createHandlers(zValidator$1("param", z.object({
|
|
5249
5286
|
channel: z.string(),
|
|
5250
5287
|
connector: z.string(),
|
|
5251
5288
|
newName: z.string()
|
|
5252
|
-
})), zValidator$1("query", z.object({})
|
|
5289
|
+
})), zValidator$1("query", z.object({})), async (c) => {
|
|
5253
5290
|
const param = c.req.valid("param");
|
|
5254
|
-
const funnel = c.
|
|
5291
|
+
const funnel = c.env.funnel;
|
|
5255
5292
|
await funnel.listeners.stop(param.channel, param.connector);
|
|
5256
5293
|
funnel.channels.renameConnector(param.channel, param.connector, param.newName);
|
|
5257
5294
|
await funnel.listeners.start(param.channel, param.newName);
|
|
5258
5295
|
return c.text(`renamed connector "${param.connector}" to "${param.newName}"`);
|
|
5259
5296
|
});
|
|
5297
|
+
//#endregion
|
|
5298
|
+
//#region lib/cli/routes/channels.$channel.connectors.$connector.rename.ts
|
|
5299
|
+
const help$12 = `funnel channels <channel> connectors rename <connector> <new-name>
|
|
5300
|
+
|
|
5301
|
+
usage: funnel channels <channel> connectors rename <connector> <new-name>`;
|
|
5302
|
+
const channelsConnectorRenameHelpHandler = factory.createHandlers((c) => c.text(help$12));
|
|
5260
5303
|
const channelsConnectorsRequestHandler = factory.createHandlers(zValidator$1("param", z.object({
|
|
5261
5304
|
channel: z.string(),
|
|
5262
5305
|
connector: z.string()
|
|
@@ -5265,7 +5308,7 @@ const channelsConnectorsRequestHandler = factory.createHandlers(zValidator$1("pa
|
|
|
5265
5308
|
usage: funnel channels <channel> connectors <connector> request --method=<api.method> [--key=value ...]`), async (c) => {
|
|
5266
5309
|
const param = c.req.valid("param");
|
|
5267
5310
|
const query = c.req.valid("query");
|
|
5268
|
-
const funnel = c.
|
|
5311
|
+
const funnel = c.env.funnel;
|
|
5269
5312
|
const passthrough = {};
|
|
5270
5313
|
for (const [k, v] of new URL(c.req.url).searchParams) {
|
|
5271
5314
|
if (k === "method") continue;
|
|
@@ -5285,15 +5328,18 @@ const channelsConnectorsSchedulesGroupHandler = factory.createHandlers(zValidato
|
|
|
5285
5328
|
|
|
5286
5329
|
usage: funnel channels <ch> connectors <conn> schedules`), (c) => {
|
|
5287
5330
|
const param = c.req.valid("param");
|
|
5288
|
-
const entries = c.
|
|
5331
|
+
const entries = c.env.funnel.channels.listScheduleEntries(param.channel, param.connector);
|
|
5289
5332
|
if (entries.length === 0) return c.text("no schedule entries");
|
|
5290
5333
|
return c.text(entries.map((e) => `${e.id}\t${e.cron}\t${e.enabled ? "on" : "off"}\t${e.prompt}`).join("\n"));
|
|
5291
5334
|
});
|
|
5292
5335
|
//#endregion
|
|
5293
|
-
//#region lib/cli/routes/channels.$channel.connectors.$connector.schedules.add
|
|
5294
|
-
const
|
|
5336
|
+
//#region lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.ts
|
|
5337
|
+
const help$11 = `funnel channels <ch> connectors <conn> schedules add <id> — add a schedule entry
|
|
5295
5338
|
|
|
5296
5339
|
usage: funnel channels <ch> connectors <conn> schedules add <id> --cron="*/5 * * * *" --prompt="..." [--enabled=true] [--catchup-policy=latest|all|skip]`;
|
|
5340
|
+
const channelsConnectorSchedulesAddHelpHandler = factory.createHandlers((c) => c.text(help$11));
|
|
5341
|
+
//#endregion
|
|
5342
|
+
//#region lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.ts
|
|
5297
5343
|
const channelsConnectorsSchedulesAddHandler = factory.createHandlers(zValidator$1("param", z.object({
|
|
5298
5344
|
channel: z.string(),
|
|
5299
5345
|
connector: z.string(),
|
|
@@ -5303,10 +5349,10 @@ const channelsConnectorsSchedulesAddHandler = factory.createHandlers(zValidator$
|
|
|
5303
5349
|
prompt: z.string(),
|
|
5304
5350
|
enabled: z.coerce.boolean().optional(),
|
|
5305
5351
|
"catchup-policy": scheduleCatchupPolicySchema.optional()
|
|
5306
|
-
})
|
|
5352
|
+
})), async (c) => {
|
|
5307
5353
|
const param = c.req.valid("param");
|
|
5308
5354
|
const query = c.req.valid("query");
|
|
5309
|
-
const funnel = c.
|
|
5355
|
+
const funnel = c.env.funnel;
|
|
5310
5356
|
const entry = funnel.channels.addScheduleEntry(param.channel, param.connector, {
|
|
5311
5357
|
id: param.id,
|
|
5312
5358
|
cron: query.cron,
|
|
@@ -5318,24 +5364,27 @@ const channelsConnectorsSchedulesAddHandler = factory.createHandlers(zValidator$
|
|
|
5318
5364
|
return c.text(`added schedule entry "${entry.id}"`);
|
|
5319
5365
|
});
|
|
5320
5366
|
//#endregion
|
|
5321
|
-
//#region lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove
|
|
5322
|
-
const
|
|
5367
|
+
//#region lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.ts
|
|
5368
|
+
const help$10 = `funnel channels <ch> connectors <conn> schedules remove <id>
|
|
5323
5369
|
|
|
5324
5370
|
usage: funnel channels <ch> connectors <conn> schedules remove <id>`;
|
|
5371
|
+
const channelsConnectorSchedulesRemoveHelpHandler = factory.createHandlers((c) => c.text(help$10));
|
|
5372
|
+
//#endregion
|
|
5373
|
+
//#region lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.ts
|
|
5325
5374
|
const channelsConnectorsSchedulesRemoveHandler = factory.createHandlers(zValidator$1("param", z.object({
|
|
5326
5375
|
channel: z.string(),
|
|
5327
5376
|
connector: z.string(),
|
|
5328
5377
|
id: z.string()
|
|
5329
|
-
})), zValidator$1("query", z.object({})
|
|
5378
|
+
})), zValidator$1("query", z.object({})), async (c) => {
|
|
5330
5379
|
const param = c.req.valid("param");
|
|
5331
|
-
const funnel = c.
|
|
5380
|
+
const funnel = c.env.funnel;
|
|
5332
5381
|
funnel.channels.removeScheduleEntry(param.channel, param.connector, param.id);
|
|
5333
5382
|
await funnel.listeners.restart(param.channel, param.connector);
|
|
5334
5383
|
return c.text(`removed schedule entry "${param.id}"`);
|
|
5335
5384
|
});
|
|
5336
5385
|
//#endregion
|
|
5337
|
-
//#region lib/cli/routes/channels
|
|
5338
|
-
const
|
|
5386
|
+
//#region lib/cli/routes/channels.publish.ts
|
|
5387
|
+
const help$9 = `funnel channels <channel> publish — push arbitrary content into a channel
|
|
5339
5388
|
|
|
5340
5389
|
usage: funnel channels <channel> publish --content="<text>" [--connector=<name>] [--meta-<key>=<value> ...]
|
|
5341
5390
|
|
|
@@ -5343,14 +5392,17 @@ options:
|
|
|
5343
5392
|
--content Required. The event body delivered to subscribers.
|
|
5344
5393
|
--connector Optional. Stamp the event with a connector name (resolved to id when found).
|
|
5345
5394
|
--meta-<key> Optional. Repeatable. Added to meta. Example: --meta-source=cron`;
|
|
5395
|
+
const channelsPublishHelpHandler = factory.createHandlers((c) => c.text(help$9));
|
|
5396
|
+
//#endregion
|
|
5397
|
+
//#region lib/cli/routes/channels.$channel.publish.ts
|
|
5346
5398
|
const querySchema = z.object({
|
|
5347
5399
|
content: z.string().min(1, { message: "--content is required" }),
|
|
5348
5400
|
connector: z.string().min(1).optional()
|
|
5349
5401
|
}).passthrough();
|
|
5350
|
-
const channelsPublishHandler = factory.createHandlers(zValidator$1("param", z.object({ channel: z.string() })), zValidator$1("query", querySchema
|
|
5402
|
+
const channelsPublishHandler = factory.createHandlers(zValidator$1("param", z.object({ channel: z.string() })), zValidator$1("query", querySchema), async (c) => {
|
|
5351
5403
|
const param = c.req.valid("param");
|
|
5352
5404
|
const query = c.req.valid("query");
|
|
5353
|
-
const funnel = c.
|
|
5405
|
+
const funnel = c.env.funnel;
|
|
5354
5406
|
const meta = {};
|
|
5355
5407
|
for (const [k, v] of new URL(c.req.url).searchParams) if (k.startsWith("meta-")) meta[k.slice(5)] = v;
|
|
5356
5408
|
const result = await funnel.publisher.publish(param.channel, {
|
|
@@ -5363,28 +5415,42 @@ const channelsPublishHandler = factory.createHandlers(zValidator$1("param", z.ob
|
|
|
5363
5415
|
return c.text(`published (offset=${result.offset})`);
|
|
5364
5416
|
});
|
|
5365
5417
|
//#endregion
|
|
5366
|
-
//#region lib/cli/routes/channels.remove
|
|
5367
|
-
const
|
|
5418
|
+
//#region lib/cli/routes/channels.remove.ts
|
|
5419
|
+
const help$8 = `funnel channels remove — remove a channel
|
|
5368
5420
|
|
|
5369
5421
|
usage: funnel channels remove <name>`;
|
|
5370
|
-
const
|
|
5422
|
+
const channelsRemoveHelpHandler = factory.createHandlers((c) => c.text(help$8));
|
|
5423
|
+
//#endregion
|
|
5424
|
+
//#region lib/cli/routes/channels.remove.$channel.ts
|
|
5425
|
+
const channelsRemoveHandler = factory.createHandlers(zValidator$1("param", z.object({ channel: z.string() })), zValidator$1("query", z.object({})), (c) => {
|
|
5371
5426
|
const param = c.req.valid("param");
|
|
5372
|
-
c.
|
|
5427
|
+
c.env.funnel.channels.remove(param.channel);
|
|
5373
5428
|
return c.text(`removed channel "${param.channel}"`);
|
|
5374
5429
|
});
|
|
5375
5430
|
//#endregion
|
|
5376
|
-
//#region lib/cli/routes/channels
|
|
5377
|
-
const
|
|
5431
|
+
//#region lib/cli/routes/channels.rename.ts
|
|
5432
|
+
const help$7 = `funnel channels rename — rename a channel
|
|
5433
|
+
|
|
5434
|
+
usage:
|
|
5435
|
+
funnel channels rename <old> <new>
|
|
5436
|
+
funnel channels <old> rename <new>`;
|
|
5437
|
+
const channelsRenameHelpHandler = factory.createHandlers((c) => c.text(help$7));
|
|
5438
|
+
//#endregion
|
|
5439
|
+
//#region lib/cli/routes/channels.$channel.rename.ts
|
|
5440
|
+
const help$6 = `funnel channels rename — rename a channel
|
|
5378
5441
|
|
|
5379
5442
|
usage:
|
|
5380
5443
|
funnel channels rename <old> <new>
|
|
5381
5444
|
funnel channels <old> rename <new>`;
|
|
5445
|
+
const channelsChannelRenameHelpHandler = factory.createHandlers((c) => c.text(help$6));
|
|
5446
|
+
//#endregion
|
|
5447
|
+
//#region lib/cli/routes/channels.$channel.rename.$newName.ts
|
|
5382
5448
|
const channelsRenameHandler = factory.createHandlers(zValidator$1("param", z.object({
|
|
5383
5449
|
channel: z.string(),
|
|
5384
5450
|
newName: z.string()
|
|
5385
|
-
})), zValidator$1("query", z.object({})
|
|
5451
|
+
})), zValidator$1("query", z.object({})), (c) => {
|
|
5386
5452
|
const param = c.req.valid("param");
|
|
5387
|
-
c.
|
|
5453
|
+
c.env.funnel.channels.rename(param.channel, param.newName);
|
|
5388
5454
|
return c.text(`renamed channel "${param.channel}" to "${param.newName}"`);
|
|
5389
5455
|
});
|
|
5390
5456
|
const channelsSetDeliveryHandler = factory.createHandlers(zValidator$1("param", z.object({
|
|
@@ -5401,12 +5467,12 @@ modes:
|
|
|
5401
5467
|
tap=all clients (TUI dashboard, debugging) always receive regardless of mode.
|
|
5402
5468
|
`), (c) => {
|
|
5403
5469
|
const param = c.req.valid("param");
|
|
5404
|
-
c.
|
|
5470
|
+
c.env.funnel.channels.setDelivery(param.channel, param.mode);
|
|
5405
5471
|
return c.text(`channel "${param.channel}" delivery set to ${param.mode}`);
|
|
5406
5472
|
});
|
|
5407
5473
|
const channelsShowHandler = factory.createHandlers(zValidator$1("param", z.object({ channel: z.string() })), zValidator$1("query", z.object({}), `funnel channels <name> — show channel details`), (c) => {
|
|
5408
5474
|
const param = c.req.valid("param");
|
|
5409
|
-
const channel = c.
|
|
5475
|
+
const channel = c.env.funnel.channels.get(param.channel);
|
|
5410
5476
|
if (!channel) throw new HTTPException(404, { message: `channel "${param.channel}" not found` });
|
|
5411
5477
|
const connectorLines = channel.connectors.length ? channel.connectors.map((c) => ` - ${c.name} (${c.type}, id: ${c.id})`) : [" (none)"];
|
|
5412
5478
|
const lines = [
|
|
@@ -5444,7 +5510,7 @@ examples:
|
|
|
5444
5510
|
funnel channels prod-inbox connectors add prod-slack --type=slack --bot-token=xoxb-... --app-token=xapp-...
|
|
5445
5511
|
funnel channels prod-inbox`), (c) => {
|
|
5446
5512
|
const query = c.req.valid("query");
|
|
5447
|
-
const channels = c.
|
|
5513
|
+
const channels = c.env.funnel.channels.list();
|
|
5448
5514
|
if (query.json === "true" || query.json === "") return c.json(channels.map((ch) => ({
|
|
5449
5515
|
id: ch.id,
|
|
5450
5516
|
name: ch.name,
|
|
@@ -5464,6 +5530,106 @@ examples:
|
|
|
5464
5530
|
return c.text(lines.join("\n"));
|
|
5465
5531
|
});
|
|
5466
5532
|
//#endregion
|
|
5533
|
+
//#region lib/cli/routes/channels.validate.ts
|
|
5534
|
+
const help$5 = `funnel channels <channel> validate — check connector configuration
|
|
5535
|
+
|
|
5536
|
+
usage: funnel channels <channel> validate [--json]
|
|
5537
|
+
|
|
5538
|
+
options:
|
|
5539
|
+
--json output as JSON
|
|
5540
|
+
|
|
5541
|
+
Checks that each connector has the required tokens and fields set.
|
|
5542
|
+
Does not make any network calls — static config check only.
|
|
5543
|
+
|
|
5544
|
+
examples:
|
|
5545
|
+
funnel channels open-karte validate
|
|
5546
|
+
funnel channels open-karte validate --json`;
|
|
5547
|
+
const channelsValidateHelpHandler = factory.createHandlers((c) => c.text(help$5));
|
|
5548
|
+
//#endregion
|
|
5549
|
+
//#region lib/cli/routes/channels.$channel.validate.ts
|
|
5550
|
+
const validateConnector = (connector) => {
|
|
5551
|
+
const issues = [];
|
|
5552
|
+
if (connector.type === "slack") {
|
|
5553
|
+
if (!(connector.botToken || connector.botTokenEnv)) issues.push({
|
|
5554
|
+
connector: connector.name,
|
|
5555
|
+
field: "botToken",
|
|
5556
|
+
message: "missing botToken (xoxb-...) or botTokenEnv"
|
|
5557
|
+
});
|
|
5558
|
+
if (!(connector.appToken || connector.appTokenEnv)) issues.push({
|
|
5559
|
+
connector: connector.name,
|
|
5560
|
+
field: "appToken",
|
|
5561
|
+
message: "missing appToken (xapp-...) or appTokenEnv"
|
|
5562
|
+
});
|
|
5563
|
+
if (connector.botToken && typeof connector.botToken === "string" && !connector.botToken.startsWith("xoxb-")) issues.push({
|
|
5564
|
+
connector: connector.name,
|
|
5565
|
+
field: "botToken",
|
|
5566
|
+
message: `botToken must start with xoxb- (got: ${connector.botToken.slice(0, 8)}...)`
|
|
5567
|
+
});
|
|
5568
|
+
if (connector.appToken && typeof connector.appToken === "string" && !connector.appToken.startsWith("xapp-")) issues.push({
|
|
5569
|
+
connector: connector.name,
|
|
5570
|
+
field: "appToken",
|
|
5571
|
+
message: `appToken must start with xapp- (got: ${connector.appToken.slice(0, 8)}...)`
|
|
5572
|
+
});
|
|
5573
|
+
}
|
|
5574
|
+
if (connector.type === "gh") {
|
|
5575
|
+
if (!(connector.token || connector.tokenEnv)) issues.push({
|
|
5576
|
+
connector: connector.name,
|
|
5577
|
+
field: "token",
|
|
5578
|
+
message: "missing token or tokenEnv for GitHub connector"
|
|
5579
|
+
});
|
|
5580
|
+
if (!connector.repo) issues.push({
|
|
5581
|
+
connector: connector.name,
|
|
5582
|
+
field: "repo",
|
|
5583
|
+
message: "missing repo (expected owner/repo format)"
|
|
5584
|
+
});
|
|
5585
|
+
}
|
|
5586
|
+
if (connector.type === "discord") {
|
|
5587
|
+
if (!(connector.botToken || connector.botTokenEnv)) issues.push({
|
|
5588
|
+
connector: connector.name,
|
|
5589
|
+
field: "botToken",
|
|
5590
|
+
message: "missing botToken or botTokenEnv for Discord connector"
|
|
5591
|
+
});
|
|
5592
|
+
}
|
|
5593
|
+
return issues;
|
|
5594
|
+
};
|
|
5595
|
+
const channelsValidateHandler = factory.createHandlers(zValidator$1("param", z.object({ channel: z.string() })), zValidator$1("query", z.object({ json: z.enum([
|
|
5596
|
+
"true",
|
|
5597
|
+
"false",
|
|
5598
|
+
""
|
|
5599
|
+
]).optional() })), (c) => {
|
|
5600
|
+
const param = c.req.valid("param");
|
|
5601
|
+
const query = c.req.valid("query");
|
|
5602
|
+
const funnel = c.env.funnel;
|
|
5603
|
+
const isJson = query.json === "true" || query.json === "";
|
|
5604
|
+
const channel = funnel.channels.get(param.channel);
|
|
5605
|
+
if (!channel) throw new HTTPException(404, { message: `channel "${param.channel}" not found` });
|
|
5606
|
+
if (channel.connectors.length === 0) {
|
|
5607
|
+
if (isJson) return c.json({
|
|
5608
|
+
channel: channel.name,
|
|
5609
|
+
valid: false,
|
|
5610
|
+
issues: [{
|
|
5611
|
+
connector: "(none)",
|
|
5612
|
+
field: "connectors",
|
|
5613
|
+
message: "no connectors configured"
|
|
5614
|
+
}]
|
|
5615
|
+
});
|
|
5616
|
+
return c.text(`⚠ ${channel.name}: no connectors configured`);
|
|
5617
|
+
}
|
|
5618
|
+
const allIssues = [];
|
|
5619
|
+
for (const connector of channel.connectors) {
|
|
5620
|
+
const issues = validateConnector(connector);
|
|
5621
|
+
allIssues.push(...issues);
|
|
5622
|
+
}
|
|
5623
|
+
if (isJson) return c.json({
|
|
5624
|
+
channel: channel.name,
|
|
5625
|
+
valid: allIssues.length === 0,
|
|
5626
|
+
issues: allIssues
|
|
5627
|
+
});
|
|
5628
|
+
if (allIssues.length === 0) return c.text(`✓ ${channel.name}: all connectors valid`);
|
|
5629
|
+
const lines = allIssues.map((issue) => `✗ ${channel.name}/${issue.connector}: ${issue.message}`);
|
|
5630
|
+
return c.text(lines.join("\n"));
|
|
5631
|
+
});
|
|
5632
|
+
//#endregion
|
|
5467
5633
|
//#region lib/cli/routes/claude.ts
|
|
5468
5634
|
const claudeHelp = `funnel claude — launch Claude Code
|
|
5469
5635
|
|
|
@@ -5495,7 +5661,7 @@ const claudeHandler = factory.createHandlers(zValidator$1("query", z.object({
|
|
|
5495
5661
|
channel: z.string().optional()
|
|
5496
5662
|
}).passthrough(), claudeHelp), async (c) => {
|
|
5497
5663
|
const query = c.req.valid("query");
|
|
5498
|
-
const funnel = c.
|
|
5664
|
+
const funnel = c.env.funnel;
|
|
5499
5665
|
const userArgs = queryToCliArgs(c.req.url, RESERVED_KEYS$1);
|
|
5500
5666
|
if (query.channel && !query.profile) {
|
|
5501
5667
|
const exitCode = await funnel.claude.launch({
|
|
@@ -5548,6 +5714,71 @@ const claudeHandler = factory.createHandlers(zValidator$1("query", z.object({
|
|
|
5548
5714
|
process.exit(exitCode);
|
|
5549
5715
|
});
|
|
5550
5716
|
//#endregion
|
|
5717
|
+
//#region lib/cli/routes/debug-row.ts
|
|
5718
|
+
const stringOrNull = (value) => typeof value === "string" && value.length > 0 ? value : null;
|
|
5719
|
+
const numberOrNull = (value) => typeof value === "number" ? value : null;
|
|
5720
|
+
const stringOr = (value, fallback) => typeof value === "string" ? value : fallback;
|
|
5721
|
+
/**
|
|
5722
|
+
* Parse a payload string as a JSON object. Returns null for non-strings,
|
|
5723
|
+
* malformed JSON, or any non-object JSON (arrays, primitives) — the callers
|
|
5724
|
+
* only ever want the object form.
|
|
5725
|
+
*/
|
|
5726
|
+
const parsePayloadObject = (payload) => {
|
|
5727
|
+
if (payload === null) return null;
|
|
5728
|
+
try {
|
|
5729
|
+
const parsed = JSON.parse(payload);
|
|
5730
|
+
if (isStringKeyedObject(parsed)) return parsed;
|
|
5731
|
+
} catch {
|
|
5732
|
+
return null;
|
|
5733
|
+
}
|
|
5734
|
+
return null;
|
|
5735
|
+
};
|
|
5736
|
+
const isStringKeyedObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5737
|
+
const truncate = (text, max) => text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
5738
|
+
/**
|
|
5739
|
+
* A short human preview of a payload: the `text` field when the payload is a
|
|
5740
|
+
* JSON object that has one, otherwise the raw payload, both truncated.
|
|
5741
|
+
*/
|
|
5742
|
+
const previewOf = (payload) => {
|
|
5743
|
+
if (typeof payload !== "string" || payload.length === 0) return null;
|
|
5744
|
+
const parsed = parsePayloadObject(payload);
|
|
5745
|
+
if (parsed !== null && "text" in parsed) return truncate(String(parsed.text), 60);
|
|
5746
|
+
return truncate(payload, 60);
|
|
5747
|
+
};
|
|
5748
|
+
/** Narrow one processed-table row into a `DebugEvent`. */
|
|
5749
|
+
const toDebugEvent = (row) => {
|
|
5750
|
+
const payload = stringOrNull(row.payload);
|
|
5751
|
+
return {
|
|
5752
|
+
seq: numberOrNull(row.seq),
|
|
5753
|
+
ts: numberOrNull(row.ts),
|
|
5754
|
+
type: stringOr(row.type, "?"),
|
|
5755
|
+
outcome: stringOr(row.outcome, "?"),
|
|
5756
|
+
eventId: stringOrNull(row.event_id),
|
|
5757
|
+
payload,
|
|
5758
|
+
payloadParsed: parsePayloadObject(payload),
|
|
5759
|
+
preview: previewOf(row.payload)
|
|
5760
|
+
};
|
|
5761
|
+
};
|
|
5762
|
+
/** Narrow one connection-table row into a `DebugConnectionError`. */
|
|
5763
|
+
const toDebugConnectionError = (row) => ({
|
|
5764
|
+
seq: numberOrNull(row.seq),
|
|
5765
|
+
ts: numberOrNull(row.ts),
|
|
5766
|
+
type: stringOr(row.type, "?"),
|
|
5767
|
+
status: stringOr(row.status, "?"),
|
|
5768
|
+
detail: stringOrNull(row.detail)
|
|
5769
|
+
});
|
|
5770
|
+
/**
|
|
5771
|
+
* Open a reader, run one query, and always close — the shared shape behind
|
|
5772
|
+
* every diagnostic lookup. Returns the rows or the reader's `Error`.
|
|
5773
|
+
*/
|
|
5774
|
+
const queryRows = (reader, sql, params) => {
|
|
5775
|
+
try {
|
|
5776
|
+
return reader.query(sql, params);
|
|
5777
|
+
} finally {
|
|
5778
|
+
reader.close();
|
|
5779
|
+
}
|
|
5780
|
+
};
|
|
5781
|
+
//#endregion
|
|
5551
5782
|
//#region lib/cli/routes/debug.ts
|
|
5552
5783
|
const debugHelp = `funnel debug — diagnose why Claude is not receiving events
|
|
5553
5784
|
|
|
@@ -5639,17 +5870,6 @@ const formatTs = (epochMs) => {
|
|
|
5639
5870
|
if (typeof epochMs !== "number") return "?";
|
|
5640
5871
|
return new Date(epochMs).toISOString().slice(11, 19);
|
|
5641
5872
|
};
|
|
5642
|
-
const truncate = (text, max) => text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
5643
|
-
const extractPreview = (payload) => {
|
|
5644
|
-
if (typeof payload !== "string" || payload.length === 0) return null;
|
|
5645
|
-
try {
|
|
5646
|
-
const parsed = JSON.parse(payload);
|
|
5647
|
-
if (parsed !== null && typeof parsed === "object" && "text" in parsed) return truncate(String(parsed.text), 60);
|
|
5648
|
-
} catch {
|
|
5649
|
-
return truncate(payload, 60);
|
|
5650
|
-
}
|
|
5651
|
-
return truncate(payload, 60);
|
|
5652
|
-
};
|
|
5653
5873
|
const buildDiagnosis = (report) => {
|
|
5654
5874
|
const rootCause = (report.connectionErrors[report.connectionErrors.length - 1] ?? null)?.detail ?? null;
|
|
5655
5875
|
if (!report.gateway.running) return {
|
|
@@ -5762,6 +5982,16 @@ const resolveStoreOrNull = () => {
|
|
|
5762
5982
|
connectionPath
|
|
5763
5983
|
};
|
|
5764
5984
|
};
|
|
5985
|
+
/**
|
|
5986
|
+
* Resolve the connector name for a connector id on a channel, used to attribute
|
|
5987
|
+
* a replayed event back to its source connector. Returns undefined when the id
|
|
5988
|
+
* is null or no longer present (connectors can be removed after an event was
|
|
5989
|
+
* logged).
|
|
5990
|
+
*/
|
|
5991
|
+
const connectorOf = (channel, connectorId) => {
|
|
5992
|
+
if (connectorId === null) return void 0;
|
|
5993
|
+
return channel.connectors?.find((connector) => connector.id === connectorId)?.name;
|
|
5994
|
+
};
|
|
5765
5995
|
const resolveChannelId = (channels, channelName) => {
|
|
5766
5996
|
if (channelName) {
|
|
5767
5997
|
const match = channels.find((ch) => ch.name === channelName);
|
|
@@ -5799,7 +6029,7 @@ const debugEventsHandler = factory.createHandlers(zValidator$1("query", z.object
|
|
|
5799
6029
|
]).optional()
|
|
5800
6030
|
}), debugEventsHelp), async (c) => {
|
|
5801
6031
|
const query = c.req.valid("query");
|
|
5802
|
-
const channels = c.
|
|
6032
|
+
const channels = c.env.funnel.channels.list();
|
|
5803
6033
|
const isJson = query.json === "true" || query.json === "";
|
|
5804
6034
|
const limit = query.limit ? Math.max(1, Number(query.limit)) : 20;
|
|
5805
6035
|
const store = resolveStoreOrNull();
|
|
@@ -5828,34 +6058,9 @@ const debugEventsHandler = factory.createHandlers(zValidator$1("query", z.object
|
|
|
5828
6058
|
}
|
|
5829
6059
|
const channel = resolved.channel;
|
|
5830
6060
|
const reader = new ConnectorDiagnosticSqlReader(store);
|
|
5831
|
-
const rows = (
|
|
5832
|
-
try {
|
|
5833
|
-
if (channel) return reader.query("SELECT seq, ts, type, outcome, payload FROM processed WHERE channel_id = ? ORDER BY seq DESC LIMIT ?", [channel.id, limit]);
|
|
5834
|
-
return reader.query("SELECT seq, ts, type, outcome, payload FROM processed ORDER BY seq DESC LIMIT ?", [limit]);
|
|
5835
|
-
} finally {
|
|
5836
|
-
reader.close();
|
|
5837
|
-
}
|
|
5838
|
-
})();
|
|
6061
|
+
const rows = channel ? queryRows(reader, "SELECT seq, ts, type, outcome, payload FROM processed WHERE channel_id = ? ORDER BY seq DESC LIMIT ?", [channel.id, limit]) : queryRows(reader, "SELECT seq, ts, type, outcome, payload FROM processed ORDER BY seq DESC LIMIT ?", [limit]);
|
|
5839
6062
|
if (rows instanceof Error) return c.text(`error: ${rows.message}`);
|
|
5840
|
-
const events =
|
|
5841
|
-
const rawPayload = typeof row.payload === "string" ? row.payload : null;
|
|
5842
|
-
let payloadParsed = null;
|
|
5843
|
-
if (rawPayload) try {
|
|
5844
|
-
const parsed = JSON.parse(rawPayload);
|
|
5845
|
-
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) payloadParsed = parsed;
|
|
5846
|
-
} catch {
|
|
5847
|
-
payloadParsed = null;
|
|
5848
|
-
}
|
|
5849
|
-
return {
|
|
5850
|
-
seq: typeof row.seq === "number" ? row.seq : null,
|
|
5851
|
-
ts: typeof row.ts === "number" ? row.ts : null,
|
|
5852
|
-
type: typeof row.type === "string" ? row.type : "?",
|
|
5853
|
-
outcome: typeof row.outcome === "string" ? row.outcome : "?",
|
|
5854
|
-
payload: rawPayload,
|
|
5855
|
-
payloadParsed,
|
|
5856
|
-
preview: extractPreview(row.payload)
|
|
5857
|
-
};
|
|
5858
|
-
});
|
|
6063
|
+
const events = rows.reverse().map(toDebugEvent);
|
|
5859
6064
|
if (isJson) return c.json(events);
|
|
5860
6065
|
if (events.length === 0) return c.text("no events recorded");
|
|
5861
6066
|
const lines = events.map((ev) => {
|
|
@@ -5877,7 +6082,7 @@ const debugDroppedHandler = factory.createHandlers(zValidator$1("query", z.objec
|
|
|
5877
6082
|
]).optional()
|
|
5878
6083
|
}), debugDroppedHelp), async (c) => {
|
|
5879
6084
|
const query = c.req.valid("query");
|
|
5880
|
-
const channels = c.
|
|
6085
|
+
const channels = c.env.funnel.channels.list();
|
|
5881
6086
|
const isJson = query.json === "true" || query.json === "";
|
|
5882
6087
|
const limit = query.limit ? Math.max(1, Number(query.limit)) : 20;
|
|
5883
6088
|
const store = resolveStoreOrNull();
|
|
@@ -5906,39 +6111,13 @@ const debugDroppedHandler = factory.createHandlers(zValidator$1("query", z.objec
|
|
|
5906
6111
|
}
|
|
5907
6112
|
const channel = resolvedDropped.channel;
|
|
5908
6113
|
const reader = new ConnectorDiagnosticSqlReader(store);
|
|
5909
|
-
const rows = (
|
|
5910
|
-
try {
|
|
5911
|
-
if (channel) return reader.query("SELECT p.seq, p.ts, p.type, p.outcome, p.payload, p.event_id FROM processed p WHERE p.channel_id = ? AND p.outcome LIKE 'skip:%' ORDER BY p.seq DESC LIMIT ?", [channel.id, limit]);
|
|
5912
|
-
return reader.query("SELECT seq, ts, type, outcome, payload, event_id FROM processed WHERE outcome LIKE 'skip:%' ORDER BY seq DESC LIMIT ?", [limit]);
|
|
5913
|
-
} finally {
|
|
5914
|
-
reader.close();
|
|
5915
|
-
}
|
|
5916
|
-
})();
|
|
6114
|
+
const rows = channel ? queryRows(reader, "SELECT p.seq, p.ts, p.type, p.outcome, p.payload, p.event_id FROM processed p WHERE p.channel_id = ? AND p.outcome LIKE 'skip:%' ORDER BY p.seq DESC LIMIT ?", [channel.id, limit]) : queryRows(reader, "SELECT seq, ts, type, outcome, payload, event_id FROM processed WHERE outcome LIKE 'skip:%' ORDER BY seq DESC LIMIT ?", [limit]);
|
|
5917
6115
|
if (rows instanceof Error) return c.text(`error: ${rows.message}`);
|
|
5918
|
-
const events =
|
|
5919
|
-
const rawPayload = typeof row.payload === "string" ? row.payload : null;
|
|
5920
|
-
let payloadParsed = null;
|
|
5921
|
-
if (rawPayload) try {
|
|
5922
|
-
const parsed = JSON.parse(rawPayload);
|
|
5923
|
-
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) payloadParsed = parsed;
|
|
5924
|
-
} catch {
|
|
5925
|
-
payloadParsed = null;
|
|
5926
|
-
}
|
|
5927
|
-
return {
|
|
5928
|
-
seq: typeof row.seq === "number" ? row.seq : null,
|
|
5929
|
-
ts: typeof row.ts === "number" ? row.ts : null,
|
|
5930
|
-
type: typeof row.type === "string" ? row.type : "?",
|
|
5931
|
-
outcome: typeof row.outcome === "string" ? row.outcome : "?",
|
|
5932
|
-
event_id: typeof row.event_id === "string" ? row.event_id : null,
|
|
5933
|
-
payload: rawPayload,
|
|
5934
|
-
payloadParsed,
|
|
5935
|
-
preview: extractPreview(row.payload)
|
|
5936
|
-
};
|
|
5937
|
-
});
|
|
6116
|
+
const events = rows.reverse().map(toDebugEvent);
|
|
5938
6117
|
if (isJson) return c.json(events);
|
|
5939
6118
|
if (events.length === 0) return c.text("no dropped events recorded");
|
|
5940
6119
|
const lines = events.map((ev) => {
|
|
5941
|
-
return `${formatTs(ev.ts)} ${ev.type.padEnd(8)} ${ev.outcome.padEnd(20)}${ev.seq !== null ? ` seq=${ev.seq}` : ""}${ev.
|
|
6120
|
+
return `${formatTs(ev.ts)} ${ev.type.padEnd(8)} ${ev.outcome.padEnd(20)}${ev.seq !== null ? ` seq=${ev.seq}` : ""}${ev.eventId ? ` event_id=${ev.eventId.slice(0, 8)}` : ""}${ev.preview ? ` "${ev.preview}"` : ""}`;
|
|
5942
6121
|
});
|
|
5943
6122
|
return c.text(lines.join("\n"));
|
|
5944
6123
|
});
|
|
@@ -5952,7 +6131,7 @@ const debugErrorsHandler = factory.createHandlers(zValidator$1("query", z.object
|
|
|
5952
6131
|
]).optional()
|
|
5953
6132
|
}), debugErrorsHelp), async (c) => {
|
|
5954
6133
|
const query = c.req.valid("query");
|
|
5955
|
-
const channels = c.
|
|
6134
|
+
const channels = c.env.funnel.channels.list();
|
|
5956
6135
|
const isJson = query.json === "true" || query.json === "";
|
|
5957
6136
|
const limit = query.limit ? Math.max(1, Number(query.limit)) : 20;
|
|
5958
6137
|
const store = resolveStoreOrNull();
|
|
@@ -5981,22 +6160,9 @@ const debugErrorsHandler = factory.createHandlers(zValidator$1("query", z.object
|
|
|
5981
6160
|
}
|
|
5982
6161
|
const channel = resolvedErrors.channel;
|
|
5983
6162
|
const reader = new ConnectorDiagnosticSqlReader(store);
|
|
5984
|
-
const rows = (()
|
|
5985
|
-
try {
|
|
5986
|
-
if (channel) return reader.query("SELECT seq, ts, type, status, detail FROM connection WHERE channel_id = ? AND status IN ('auth-failed','error') ORDER BY seq DESC LIMIT ?", [channel.id, limit]);
|
|
5987
|
-
return reader.query("SELECT seq, ts, type, status, detail FROM connection WHERE status IN ('auth-failed','error') ORDER BY seq DESC LIMIT ?", [limit]);
|
|
5988
|
-
} finally {
|
|
5989
|
-
reader.close();
|
|
5990
|
-
}
|
|
5991
|
-
})();
|
|
6163
|
+
const rows = channel ? queryRows(reader, "SELECT seq, ts, type, status, detail FROM connection WHERE channel_id = ? AND status IN ('auth-failed','error') ORDER BY seq DESC LIMIT ?", [channel.id, limit]) : queryRows(reader, "SELECT seq, ts, type, status, detail FROM connection WHERE status IN ('auth-failed','error') ORDER BY seq DESC LIMIT ?", [limit]);
|
|
5992
6164
|
if (rows instanceof Error) return c.text(`error: ${rows.message}`);
|
|
5993
|
-
const errors =
|
|
5994
|
-
seq: typeof row.seq === "number" ? row.seq : null,
|
|
5995
|
-
ts: typeof row.ts === "number" ? row.ts : null,
|
|
5996
|
-
type: typeof row.type === "string" ? row.type : "?",
|
|
5997
|
-
status: typeof row.status === "string" ? row.status : "?",
|
|
5998
|
-
detail: typeof row.detail === "string" && row.detail.length > 0 ? row.detail : null
|
|
5999
|
-
}));
|
|
6165
|
+
const errors = rows.reverse().map(toDebugConnectionError);
|
|
6000
6166
|
if (isJson) return c.json(errors);
|
|
6001
6167
|
if (errors.length === 0) return c.text("no connection errors recorded");
|
|
6002
6168
|
const lines = errors.map((ev) => {
|
|
@@ -6032,50 +6198,13 @@ const buildChannelReport = async (targetChannel, gatewayStatus, gatewayBodyOrNul
|
|
|
6032
6198
|
baseReport.claudeClients = gatewayBodyOrNull.clients.filter((cl) => !cl.tapAll && cl.channelName === targetChannelName).length;
|
|
6033
6199
|
}
|
|
6034
6200
|
if (store) {
|
|
6035
|
-
const
|
|
6036
|
-
|
|
6037
|
-
try {
|
|
6038
|
-
return reader.query("SELECT seq, ts, type, outcome, payload FROM processed WHERE channel_id = ? ORDER BY seq DESC LIMIT ?", [targetChannel.id, limit]);
|
|
6039
|
-
} finally {
|
|
6040
|
-
reader.close();
|
|
6041
|
-
}
|
|
6042
|
-
})();
|
|
6043
|
-
if (!(evRows instanceof Error)) baseReport.recentEvents = evRows.reverse().map((row) => {
|
|
6044
|
-
const rawPayload = typeof row.payload === "string" ? row.payload : null;
|
|
6045
|
-
let payloadParsed = null;
|
|
6046
|
-
if (rawPayload) try {
|
|
6047
|
-
const parsed = JSON.parse(rawPayload);
|
|
6048
|
-
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) payloadParsed = parsed;
|
|
6049
|
-
} catch {
|
|
6050
|
-
payloadParsed = null;
|
|
6051
|
-
}
|
|
6052
|
-
return {
|
|
6053
|
-
seq: typeof row.seq === "number" ? row.seq : null,
|
|
6054
|
-
ts: typeof row.ts === "number" ? row.ts : null,
|
|
6055
|
-
type: typeof row.type === "string" ? row.type : "?",
|
|
6056
|
-
outcome: typeof row.outcome === "string" ? row.outcome : "?",
|
|
6057
|
-
payload: rawPayload,
|
|
6058
|
-
payloadParsed,
|
|
6059
|
-
preview: extractPreview(row.payload)
|
|
6060
|
-
};
|
|
6061
|
-
});
|
|
6201
|
+
const evRows = queryRows(new ConnectorDiagnosticSqlReader(store), "SELECT seq, ts, type, outcome, payload FROM processed WHERE channel_id = ? ORDER BY seq DESC LIMIT ?", [targetChannel.id, limit]);
|
|
6202
|
+
if (!(evRows instanceof Error)) baseReport.recentEvents = evRows.reverse().map(toDebugEvent);
|
|
6062
6203
|
const hasDeadListeners = baseReport.listeners.some((l) => !l.alive);
|
|
6063
6204
|
const hasListenerErrors = baseReport.listeners.some((l) => l.errors > 0);
|
|
6064
6205
|
if (hasDeadListeners || hasListenerErrors) {
|
|
6065
|
-
const
|
|
6066
|
-
|
|
6067
|
-
try {
|
|
6068
|
-
return errReader.query("SELECT ts, type, status, detail FROM connection WHERE channel_id = ? AND status IN ('auth-failed','error') ORDER BY seq DESC LIMIT 3", [targetChannel.id]);
|
|
6069
|
-
} finally {
|
|
6070
|
-
errReader.close();
|
|
6071
|
-
}
|
|
6072
|
-
})();
|
|
6073
|
-
if (!(errRows instanceof Error)) baseReport.connectionErrors = errRows.reverse().map((row) => ({
|
|
6074
|
-
ts: typeof row.ts === "number" ? row.ts : null,
|
|
6075
|
-
type: typeof row.type === "string" ? row.type : "?",
|
|
6076
|
-
status: typeof row.status === "string" ? row.status : "?",
|
|
6077
|
-
detail: typeof row.detail === "string" && row.detail.length > 0 ? row.detail : null
|
|
6078
|
-
}));
|
|
6206
|
+
const errRows = queryRows(new ConnectorDiagnosticSqlReader(store), "SELECT ts, type, status, detail FROM connection WHERE channel_id = ? AND status IN ('auth-failed','error') ORDER BY seq DESC LIMIT 3", [targetChannel.id]);
|
|
6207
|
+
if (!(errRows instanceof Error)) baseReport.connectionErrors = errRows.reverse().map(toDebugConnectionError);
|
|
6079
6208
|
}
|
|
6080
6209
|
}
|
|
6081
6210
|
return {
|
|
@@ -6098,7 +6227,7 @@ const debugHandler = factory.createHandlers(zValidator$1("query", z.object({
|
|
|
6098
6227
|
limit: z.string().optional()
|
|
6099
6228
|
}), debugHelp), async (c) => {
|
|
6100
6229
|
const query = c.req.valid("query");
|
|
6101
|
-
const funnel = c.
|
|
6230
|
+
const funnel = c.env.funnel;
|
|
6102
6231
|
const channels = funnel.channels.list();
|
|
6103
6232
|
const gatewayStatus = funnel.gateway.getStatus();
|
|
6104
6233
|
const isJson = query.json === "true" || query.json === "";
|
|
@@ -6193,7 +6322,7 @@ examples:
|
|
|
6193
6322
|
fnl debug replay --channel open-karte --seq 412
|
|
6194
6323
|
fnl debug replay --channel open-karte --json`), async (c) => {
|
|
6195
6324
|
const query = c.req.valid("query");
|
|
6196
|
-
const funnel = c.
|
|
6325
|
+
const funnel = c.env.funnel;
|
|
6197
6326
|
const channels = funnel.channels.list();
|
|
6198
6327
|
const isJson = query.json === "true" || query.json === "";
|
|
6199
6328
|
const resolved = resolveChannelId(channels, query.channel);
|
|
@@ -6221,15 +6350,7 @@ examples:
|
|
|
6221
6350
|
if (isJson) return c.json({ error: "no diagnostic store yet (start the gateway first)" });
|
|
6222
6351
|
return c.text("no diagnostic store yet (start the gateway first)");
|
|
6223
6352
|
}
|
|
6224
|
-
const
|
|
6225
|
-
const rows = (() => {
|
|
6226
|
-
try {
|
|
6227
|
-
if (query.seq) return reader.query("SELECT seq, event_id, type, payload, connector_id, channel_id FROM processed WHERE channel_id = ? AND seq = ? LIMIT 1", [targetChannel.id, Number(query.seq)]);
|
|
6228
|
-
return reader.query("SELECT seq, event_id, type, payload, connector_id, channel_id FROM processed WHERE channel_id = ? AND outcome LIKE 'emitted%' ORDER BY seq DESC LIMIT 1", [targetChannel.id]);
|
|
6229
|
-
} finally {
|
|
6230
|
-
reader.close();
|
|
6231
|
-
}
|
|
6232
|
-
})();
|
|
6353
|
+
const rows = query.seq ? queryRows(new ConnectorDiagnosticSqlReader(store), "SELECT seq, event_id, type, payload, connector_id, channel_id FROM processed WHERE channel_id = ? AND seq = ? LIMIT 1", [targetChannel.id, Number(query.seq)]) : queryRows(new ConnectorDiagnosticSqlReader(store), "SELECT seq, event_id, type, payload, connector_id, channel_id FROM processed WHERE channel_id = ? AND outcome LIKE 'emitted%' ORDER BY seq DESC LIMIT 1", [targetChannel.id]);
|
|
6233
6354
|
if (rows instanceof Error) {
|
|
6234
6355
|
if (isJson) return c.json({ error: rows.message });
|
|
6235
6356
|
return c.text(`error: ${rows.message}`);
|
|
@@ -6244,24 +6365,15 @@ examples:
|
|
|
6244
6365
|
const connectorId = typeof firstRow.connector_id === "string" ? firstRow.connector_id : null;
|
|
6245
6366
|
let content = typeof firstRow.payload === "string" ? firstRow.payload : null;
|
|
6246
6367
|
if ((!content || content.length === 0) && eventId) {
|
|
6247
|
-
const
|
|
6248
|
-
const
|
|
6249
|
-
|
|
6250
|
-
return rawReader.query("SELECT payload FROM raw WHERE event_id = ? LIMIT 1", [eventId]);
|
|
6251
|
-
} finally {
|
|
6252
|
-
rawReader.close();
|
|
6253
|
-
}
|
|
6254
|
-
})();
|
|
6255
|
-
if (!(rawRows instanceof Error) && rawRows[0]) {
|
|
6256
|
-
const rawRow = rawRows[0];
|
|
6257
|
-
content = typeof rawRow.payload === "string" ? rawRow.payload : null;
|
|
6258
|
-
}
|
|
6368
|
+
const rawRows = queryRows(new ConnectorDiagnosticSqlReader(store), "SELECT payload FROM raw WHERE event_id = ? LIMIT 1", [eventId]);
|
|
6369
|
+
const rawRow = rawRows instanceof Error ? null : rawRows[0];
|
|
6370
|
+
if (rawRow) content = typeof rawRow.payload === "string" ? rawRow.payload : null;
|
|
6259
6371
|
}
|
|
6260
6372
|
if (!content) {
|
|
6261
6373
|
if (isJson) return c.json({ error: "event has no payload to replay" });
|
|
6262
6374
|
return c.text("event has no payload to replay");
|
|
6263
6375
|
}
|
|
6264
|
-
const connectorName =
|
|
6376
|
+
const connectorName = connectorOf(targetChannel, connectorId);
|
|
6265
6377
|
const result = await funnel.publisher.publish(targetChannel.name, {
|
|
6266
6378
|
content,
|
|
6267
6379
|
connector: connectorName
|
|
@@ -6277,7 +6389,7 @@ examples:
|
|
|
6277
6389
|
if (isJson) return c.json({ error: result.reason });
|
|
6278
6390
|
return c.text(`error: ${result.reason}`);
|
|
6279
6391
|
}
|
|
6280
|
-
const preview =
|
|
6392
|
+
const preview = previewOf(content);
|
|
6281
6393
|
if (isJson) return c.json({
|
|
6282
6394
|
replayed: true,
|
|
6283
6395
|
seq,
|
|
@@ -6287,103 +6399,6 @@ examples:
|
|
|
6287
6399
|
return c.text(`replayed seq=${seq ?? "?"} → offset=${result.offset}${preview ? ` "${preview}"` : ""}`);
|
|
6288
6400
|
});
|
|
6289
6401
|
//#endregion
|
|
6290
|
-
//#region lib/cli/routes/channels.$channel.validate.ts
|
|
6291
|
-
const validateHelp = `funnel channels <channel> validate — check connector configuration
|
|
6292
|
-
|
|
6293
|
-
usage: funnel channels <channel> validate [--json]
|
|
6294
|
-
|
|
6295
|
-
options:
|
|
6296
|
-
--json output as JSON
|
|
6297
|
-
|
|
6298
|
-
Checks that each connector has the required tokens and fields set.
|
|
6299
|
-
Does not make any network calls — static config check only.
|
|
6300
|
-
|
|
6301
|
-
examples:
|
|
6302
|
-
funnel channels open-karte validate
|
|
6303
|
-
funnel channels open-karte validate --json`;
|
|
6304
|
-
const validateConnector = (connector) => {
|
|
6305
|
-
const issues = [];
|
|
6306
|
-
if (connector.type === "slack") {
|
|
6307
|
-
if (!(connector.botToken || connector.botTokenEnv)) issues.push({
|
|
6308
|
-
connector: connector.name,
|
|
6309
|
-
field: "botToken",
|
|
6310
|
-
message: "missing botToken (xoxb-...) or botTokenEnv"
|
|
6311
|
-
});
|
|
6312
|
-
if (!(connector.appToken || connector.appTokenEnv)) issues.push({
|
|
6313
|
-
connector: connector.name,
|
|
6314
|
-
field: "appToken",
|
|
6315
|
-
message: "missing appToken (xapp-...) or appTokenEnv"
|
|
6316
|
-
});
|
|
6317
|
-
if (connector.botToken && typeof connector.botToken === "string" && !connector.botToken.startsWith("xoxb-")) issues.push({
|
|
6318
|
-
connector: connector.name,
|
|
6319
|
-
field: "botToken",
|
|
6320
|
-
message: `botToken must start with xoxb- (got: ${connector.botToken.slice(0, 8)}...)`
|
|
6321
|
-
});
|
|
6322
|
-
if (connector.appToken && typeof connector.appToken === "string" && !connector.appToken.startsWith("xapp-")) issues.push({
|
|
6323
|
-
connector: connector.name,
|
|
6324
|
-
field: "appToken",
|
|
6325
|
-
message: `appToken must start with xapp- (got: ${connector.appToken.slice(0, 8)}...)`
|
|
6326
|
-
});
|
|
6327
|
-
}
|
|
6328
|
-
if (connector.type === "gh") {
|
|
6329
|
-
if (!(connector.token || connector.tokenEnv)) issues.push({
|
|
6330
|
-
connector: connector.name,
|
|
6331
|
-
field: "token",
|
|
6332
|
-
message: "missing token or tokenEnv for GitHub connector"
|
|
6333
|
-
});
|
|
6334
|
-
if (!connector.repo) issues.push({
|
|
6335
|
-
connector: connector.name,
|
|
6336
|
-
field: "repo",
|
|
6337
|
-
message: "missing repo (expected owner/repo format)"
|
|
6338
|
-
});
|
|
6339
|
-
}
|
|
6340
|
-
if (connector.type === "discord") {
|
|
6341
|
-
if (!(connector.botToken || connector.botTokenEnv)) issues.push({
|
|
6342
|
-
connector: connector.name,
|
|
6343
|
-
field: "botToken",
|
|
6344
|
-
message: "missing botToken or botTokenEnv for Discord connector"
|
|
6345
|
-
});
|
|
6346
|
-
}
|
|
6347
|
-
return issues;
|
|
6348
|
-
};
|
|
6349
|
-
const channelsValidateHandler = factory.createHandlers(zValidator$1("param", z.object({ channel: z.string() })), zValidator$1("query", z.object({ json: z.enum([
|
|
6350
|
-
"true",
|
|
6351
|
-
"false",
|
|
6352
|
-
""
|
|
6353
|
-
]).optional() }), validateHelp), (c) => {
|
|
6354
|
-
const param = c.req.valid("param");
|
|
6355
|
-
const query = c.req.valid("query");
|
|
6356
|
-
const funnel = c.var.funnel;
|
|
6357
|
-
const isJson = query.json === "true" || query.json === "";
|
|
6358
|
-
const channel = funnel.channels.get(param.channel);
|
|
6359
|
-
if (!channel) throw new HTTPException(404, { message: `channel "${param.channel}" not found` });
|
|
6360
|
-
if (channel.connectors.length === 0) {
|
|
6361
|
-
if (isJson) return c.json({
|
|
6362
|
-
channel: channel.name,
|
|
6363
|
-
valid: false,
|
|
6364
|
-
issues: [{
|
|
6365
|
-
connector: "(none)",
|
|
6366
|
-
field: "connectors",
|
|
6367
|
-
message: "no connectors configured"
|
|
6368
|
-
}]
|
|
6369
|
-
});
|
|
6370
|
-
return c.text(`⚠ ${channel.name}: no connectors configured`);
|
|
6371
|
-
}
|
|
6372
|
-
const allIssues = [];
|
|
6373
|
-
for (const connector of channel.connectors) {
|
|
6374
|
-
const issues = validateConnector(connector);
|
|
6375
|
-
allIssues.push(...issues);
|
|
6376
|
-
}
|
|
6377
|
-
if (isJson) return c.json({
|
|
6378
|
-
channel: channel.name,
|
|
6379
|
-
valid: allIssues.length === 0,
|
|
6380
|
-
issues: allIssues
|
|
6381
|
-
});
|
|
6382
|
-
if (allIssues.length === 0) return c.text(`✓ ${channel.name}: all connectors valid`);
|
|
6383
|
-
const lines = allIssues.map((issue) => `✗ ${channel.name}/${issue.connector}: ${issue.message}`);
|
|
6384
|
-
return c.text(lines.join("\n"));
|
|
6385
|
-
});
|
|
6386
|
-
//#endregion
|
|
6387
6402
|
//#region lib/cli/routes/gateway.ts
|
|
6388
6403
|
const groupHelp$1 = `funnel gateway — manage the funnel daemon
|
|
6389
6404
|
|
|
@@ -6411,7 +6426,7 @@ examples:
|
|
|
6411
6426
|
|
|
6412
6427
|
see also: fnl debug --channel <name> (higher-level diagnosis with next-action hints)`;
|
|
6413
6428
|
const renderGatewayStatus = async (c) => {
|
|
6414
|
-
const status = c.
|
|
6429
|
+
const status = c.env.funnel.gateway.getStatus();
|
|
6415
6430
|
if (!status.running) throw new HTTPException(503, { message: "funnel gateway: not running" });
|
|
6416
6431
|
const res = await fetch(`http://127.0.0.1:${status.port}/status`).catch(() => null);
|
|
6417
6432
|
if (!res) return c.text(`funnel gateway: running (pid ${status.pid}) — health check failed`);
|
|
@@ -6453,7 +6468,7 @@ Reads /listeners from the running gateway daemon and prints the live registry.
|
|
|
6453
6468
|
|
|
6454
6469
|
examples:
|
|
6455
6470
|
funnel gateway listeners`), async (c) => {
|
|
6456
|
-
const result = await c.
|
|
6471
|
+
const result = await c.env.funnel.listeners.list();
|
|
6457
6472
|
if (result.state === "offline") throw new HTTPException(503, { message: "funnel gateway: not running" });
|
|
6458
6473
|
if (result.state === "error") throw new HTTPException(503, { message: `funnel gateway: ${result.reason}` });
|
|
6459
6474
|
if (result.listeners.length === 0) return c.text("funnel gateway: no running listeners");
|
|
@@ -6619,7 +6634,7 @@ const gatewaySqlHandler = factory.createHandlers(zValidator$1("query", z.object(
|
|
|
6619
6634
|
limit: z.string().optional()
|
|
6620
6635
|
}), sqlHelp), async (c) => {
|
|
6621
6636
|
const query = c.req.valid("query");
|
|
6622
|
-
const funnel = c.
|
|
6637
|
+
const funnel = c.env.funnel;
|
|
6623
6638
|
let sql = null;
|
|
6624
6639
|
let params = [];
|
|
6625
6640
|
let resolvedChannelId = null;
|
|
@@ -6669,7 +6684,7 @@ examples:
|
|
|
6669
6684
|
funnel gateway restart
|
|
6670
6685
|
funnel gateway restart --no-caffeine`), async (c) => {
|
|
6671
6686
|
const query = c.req.valid("query");
|
|
6672
|
-
const result = await c.
|
|
6687
|
+
const result = await c.env.funnel.gateway.restart({ caffeinate: query["no-caffeine"] !== "true" });
|
|
6673
6688
|
const lines = [];
|
|
6674
6689
|
if (result.wasRunning) lines.push(result.stopped ? "funnel gateway: stopped" : "funnel gateway: failed to stop");
|
|
6675
6690
|
if (result.stopped) lines.push(result.started ? "funnel gateway: started" : "funnel gateway: failed to start");
|
|
@@ -6690,7 +6705,7 @@ examples:
|
|
|
6690
6705
|
funnel gateway run
|
|
6691
6706
|
funnel gateway run --no-caffeine`), async (c) => {
|
|
6692
6707
|
const query = c.req.valid("query");
|
|
6693
|
-
const funnel = c.
|
|
6708
|
+
const funnel = c.env.funnel;
|
|
6694
6709
|
const gatewayScript = resolveDaemonScript();
|
|
6695
6710
|
const command = query["no-caffeine"] !== "true" && process.platform === "darwin" ? [
|
|
6696
6711
|
"caffeinate",
|
|
@@ -6720,7 +6735,7 @@ examples:
|
|
|
6720
6735
|
funnel gateway start --no-caffeine`;
|
|
6721
6736
|
const gatewayStartHandler = factory.createHandlers(zValidator$1("query", z.object({ "no-caffeine": z.string().optional() }), startHelp), async (c) => {
|
|
6722
6737
|
const query = c.req.valid("query");
|
|
6723
|
-
const funnel = c.
|
|
6738
|
+
const funnel = c.env.funnel;
|
|
6724
6739
|
if (funnel.gateway.isRunning()) {
|
|
6725
6740
|
const status = funnel.gateway.getStatus();
|
|
6726
6741
|
return c.text(`funnel gateway: already running (pid ${status.pid})`);
|
|
@@ -6747,7 +6762,7 @@ examples:
|
|
|
6747
6762
|
funnel gateway status --json`), async (c) => {
|
|
6748
6763
|
const query = c.req.valid("query");
|
|
6749
6764
|
if (!(query.json === "true" || query.json === "")) return renderGatewayStatus(c);
|
|
6750
|
-
const status = c.
|
|
6765
|
+
const status = c.env.funnel.gateway.getStatus();
|
|
6751
6766
|
if (!status.running) throw new HTTPException(503, { message: "funnel gateway: not running" });
|
|
6752
6767
|
const res = await fetch(`http://127.0.0.1:${status.port}/status`).catch(() => null);
|
|
6753
6768
|
if (!res) return c.json({
|
|
@@ -6771,12 +6786,29 @@ Terminates the process whose PID is stored in ~/.funnel/gateway.pid.
|
|
|
6771
6786
|
|
|
6772
6787
|
examples:
|
|
6773
6788
|
funnel gateway stop`), async (c) => {
|
|
6774
|
-
const funnel = c.
|
|
6789
|
+
const funnel = c.env.funnel;
|
|
6775
6790
|
if (!funnel.gateway.isRunning()) return c.text("funnel gateway: no running process");
|
|
6776
6791
|
if (!await funnel.gateway.stop()) throw new HTTPException(500, { message: "funnel gateway: failed to stop" });
|
|
6777
6792
|
return c.text("funnel gateway: stopped");
|
|
6778
6793
|
});
|
|
6779
6794
|
//#endregion
|
|
6795
|
+
//#region lib/cli/routes/profiles.add.ts
|
|
6796
|
+
const help$4 = `funnel profiles add — add a profile
|
|
6797
|
+
|
|
6798
|
+
usage: funnel profiles add <name> --path <path> --channel <channel-name> [recipe]
|
|
6799
|
+
|
|
6800
|
+
options:
|
|
6801
|
+
--path working directory passed to claude as cwd
|
|
6802
|
+
--channel channel name (resolved to channel id internally)
|
|
6803
|
+
--agent sub-agent name, prepended to the launch argv as --agent <name>
|
|
6804
|
+
--options extra launch argv as one whitespace-split string (e.g. "--brief")
|
|
6805
|
+
--env env vars layered under the process, as "KEY=VAL,KEY2=VAL2"
|
|
6806
|
+
--no-resume start a fresh claude session every launch (default resumes)
|
|
6807
|
+
|
|
6808
|
+
The launch recipe (--agent / --options / --env / --resume) lives on the
|
|
6809
|
+
profile; the channel only declares transport (connectors / delivery).`;
|
|
6810
|
+
const profilesAddHelpHandler = factory.createHandlers((c) => c.text(help$4));
|
|
6811
|
+
//#endregion
|
|
6780
6812
|
//#region lib/cli/routes/parse-profile-recipe.ts
|
|
6781
6813
|
/**
|
|
6782
6814
|
* Turns the single-string CLI flags (`--agent`, `--options "<argv>"`,
|
|
@@ -6813,20 +6845,6 @@ const parseProfileRecipe = (query) => {
|
|
|
6813
6845
|
};
|
|
6814
6846
|
//#endregion
|
|
6815
6847
|
//#region lib/cli/routes/profiles.add.$profile.ts
|
|
6816
|
-
const addHelp = `funnel profiles add — add a profile
|
|
6817
|
-
|
|
6818
|
-
usage: funnel profiles add <name> --path <path> --channel <channel-name> [recipe]
|
|
6819
|
-
|
|
6820
|
-
options:
|
|
6821
|
-
--path working directory passed to claude as cwd
|
|
6822
|
-
--channel channel name (resolved to channel id internally)
|
|
6823
|
-
--agent sub-agent name, prepended to the launch argv as --agent <name>
|
|
6824
|
-
--options extra launch argv as one whitespace-split string (e.g. "--brief")
|
|
6825
|
-
--env env vars layered under the process, as "KEY=VAL,KEY2=VAL2"
|
|
6826
|
-
--no-resume start a fresh claude session every launch (default resumes)
|
|
6827
|
-
|
|
6828
|
-
The launch recipe (--agent / --options / --env / --resume) lives on the
|
|
6829
|
-
profile; the channel only declares transport (connectors / delivery).`;
|
|
6830
6848
|
const profilesAddHandler = factory.createHandlers(zValidator$1("param", z.object({ profile: z.string() })), zValidator$1("query", z.object({
|
|
6831
6849
|
path: z.string(),
|
|
6832
6850
|
channel: z.string(),
|
|
@@ -6835,10 +6853,10 @@ const profilesAddHandler = factory.createHandlers(zValidator$1("param", z.object
|
|
|
6835
6853
|
env: z.string().optional(),
|
|
6836
6854
|
resume: z.string().optional(),
|
|
6837
6855
|
"no-resume": z.string().optional()
|
|
6838
|
-
})
|
|
6856
|
+
})), (c) => {
|
|
6839
6857
|
const param = c.req.valid("param");
|
|
6840
6858
|
const query = c.req.valid("query");
|
|
6841
|
-
const funnel = c.
|
|
6859
|
+
const funnel = c.env.funnel;
|
|
6842
6860
|
const channel = funnel.channels.get(query.channel);
|
|
6843
6861
|
if (!channel) throw new HTTPException(400, { message: `channel "${query.channel}" not found` });
|
|
6844
6862
|
const recipe = parseProfileRecipe(query);
|
|
@@ -6858,22 +6876,33 @@ usage: funnel profiles <name> as-default
|
|
|
6858
6876
|
|
|
6859
6877
|
the first profile in the list is treated as the default for fnl claude.`), (c) => {
|
|
6860
6878
|
const param = c.req.valid("param");
|
|
6861
|
-
c.
|
|
6879
|
+
c.env.funnel.profiles.asDefault(param.profile);
|
|
6862
6880
|
return c.text(`profile "${param.profile}" is now the default`);
|
|
6863
6881
|
});
|
|
6864
6882
|
//#endregion
|
|
6865
|
-
//#region lib/cli/routes/profiles
|
|
6866
|
-
const
|
|
6883
|
+
//#region lib/cli/routes/profiles.rename.ts
|
|
6884
|
+
const help$3 = `funnel profiles rename — rename a profile
|
|
6885
|
+
|
|
6886
|
+
usage:
|
|
6887
|
+
funnel profiles rename <old> <new>
|
|
6888
|
+
funnel profiles <old> rename <new>`;
|
|
6889
|
+
const profilesRenameHelpHandler = factory.createHandlers((c) => c.text(help$3));
|
|
6890
|
+
//#endregion
|
|
6891
|
+
//#region lib/cli/routes/profiles.$profile.rename.ts
|
|
6892
|
+
const help$2 = `funnel profiles rename — rename a profile
|
|
6867
6893
|
|
|
6868
6894
|
usage:
|
|
6869
6895
|
funnel profiles rename <old> <new>
|
|
6870
6896
|
funnel profiles <old> rename <new>`;
|
|
6897
|
+
const profilesProfileRenameHelpHandler = factory.createHandlers((c) => c.text(help$2));
|
|
6898
|
+
//#endregion
|
|
6899
|
+
//#region lib/cli/routes/profiles.$profile.rename.$newName.ts
|
|
6871
6900
|
const profilesRenameHandler = factory.createHandlers(zValidator$1("param", z.object({
|
|
6872
6901
|
profile: z.string(),
|
|
6873
6902
|
newName: z.string()
|
|
6874
|
-
})), zValidator$1("query", z.object({})
|
|
6903
|
+
})), zValidator$1("query", z.object({})), (c) => {
|
|
6875
6904
|
const param = c.req.valid("param");
|
|
6876
|
-
c.
|
|
6905
|
+
c.env.funnel.profiles.rename(param.profile, param.newName);
|
|
6877
6906
|
return c.text(`renamed profile "${param.profile}" to "${param.newName}"`);
|
|
6878
6907
|
});
|
|
6879
6908
|
//#endregion
|
|
@@ -6885,7 +6914,7 @@ usage: funnel profiles <name> run [additional claude args...]
|
|
|
6885
6914
|
const RESERVED_KEYS = [];
|
|
6886
6915
|
const profilesLaunchHandler = factory.createHandlers(zValidator$1("param", z.object({ profile: z.string() })), zValidator$1("query", z.object({}).passthrough(), launchHelp), async (c) => {
|
|
6887
6916
|
const param = c.req.valid("param");
|
|
6888
|
-
const funnel = c.
|
|
6917
|
+
const funnel = c.env.funnel;
|
|
6889
6918
|
const profile = funnel.profiles.get(param.profile);
|
|
6890
6919
|
if (!profile) throw new HTTPException(404, { message: `profile "${param.profile}" not found` });
|
|
6891
6920
|
const exitCode = await funnel.claude.launch({
|
|
@@ -6900,18 +6929,21 @@ const profilesLaunchHandler = factory.createHandlers(zValidator$1("param", z.obj
|
|
|
6900
6929
|
process.exit(exitCode);
|
|
6901
6930
|
});
|
|
6902
6931
|
//#endregion
|
|
6903
|
-
//#region lib/cli/routes/profiles.remove
|
|
6904
|
-
const
|
|
6932
|
+
//#region lib/cli/routes/profiles.remove.ts
|
|
6933
|
+
const help$1 = `funnel profiles remove — remove a profile
|
|
6905
6934
|
|
|
6906
6935
|
usage: funnel profiles remove <name>`;
|
|
6907
|
-
const
|
|
6936
|
+
const profilesRemoveHelpHandler = factory.createHandlers((c) => c.text(help$1));
|
|
6937
|
+
//#endregion
|
|
6938
|
+
//#region lib/cli/routes/profiles.remove.$profile.ts
|
|
6939
|
+
const profilesRemoveHandler = factory.createHandlers(zValidator$1("param", z.object({ profile: z.string() })), zValidator$1("query", z.object({})), (c) => {
|
|
6908
6940
|
const param = c.req.valid("param");
|
|
6909
|
-
c.
|
|
6941
|
+
c.env.funnel.profiles.remove(param.profile);
|
|
6910
6942
|
return c.text(`removed profile "${param.profile}"`);
|
|
6911
6943
|
});
|
|
6912
6944
|
//#endregion
|
|
6913
|
-
//#region lib/cli/routes/profiles.set
|
|
6914
|
-
const
|
|
6945
|
+
//#region lib/cli/routes/profiles.set.ts
|
|
6946
|
+
const help = `funnel profiles <name> set — update a profile
|
|
6915
6947
|
|
|
6916
6948
|
usage: funnel profiles <name> set [--path <path>] [--channel <channel-name>] [recipe]
|
|
6917
6949
|
|
|
@@ -6925,6 +6957,9 @@ options:
|
|
|
6925
6957
|
|
|
6926
6958
|
Only the flags you pass are changed; --agent and --options together replace
|
|
6927
6959
|
the profile's whole options list.`;
|
|
6960
|
+
const profilesSetHelpHandler = factory.createHandlers((c) => c.text(help));
|
|
6961
|
+
//#endregion
|
|
6962
|
+
//#region lib/cli/routes/profiles.set.$profile.ts
|
|
6928
6963
|
const profilesSetHandler = factory.createHandlers(zValidator$1("param", z.object({ profile: z.string() })), zValidator$1("query", z.object({
|
|
6929
6964
|
path: z.string().optional(),
|
|
6930
6965
|
channel: z.string().optional(),
|
|
@@ -6933,10 +6968,10 @@ const profilesSetHandler = factory.createHandlers(zValidator$1("param", z.object
|
|
|
6933
6968
|
env: z.string().optional(),
|
|
6934
6969
|
resume: z.string().optional(),
|
|
6935
6970
|
"no-resume": z.string().optional()
|
|
6936
|
-
})
|
|
6971
|
+
})), (c) => {
|
|
6937
6972
|
const param = c.req.valid("param");
|
|
6938
6973
|
const query = c.req.valid("query");
|
|
6939
|
-
const funnel = c.
|
|
6974
|
+
const funnel = c.env.funnel;
|
|
6940
6975
|
const channel = query.channel !== void 0 ? funnel.channels.get(query.channel) : null;
|
|
6941
6976
|
if (query.channel !== void 0 && !channel) throw new HTTPException(400, { message: `channel "${query.channel}" not found` });
|
|
6942
6977
|
const recipe = parseProfileRecipe(query);
|
|
@@ -6971,7 +7006,7 @@ examples:
|
|
|
6971
7006
|
funnel profiles add cto --path /repo/myapp --channel prod-inbox --agent pm --options "--brief"
|
|
6972
7007
|
funnel profiles cto as-default
|
|
6973
7008
|
funnel profiles cto run`), (c) => {
|
|
6974
|
-
const profiles = c.
|
|
7009
|
+
const profiles = c.env.funnel.profiles.list();
|
|
6975
7010
|
if (profiles.length === 0) return c.text("no profiles");
|
|
6976
7011
|
const lines = profiles.map((profile, index) => {
|
|
6977
7012
|
const tag = index === 0 ? " (default)" : "";
|
|
@@ -7092,7 +7127,7 @@ const statusHandler = factory.createHandlers(zValidator$1("query", z.object({
|
|
|
7092
7127
|
interval: z.string().optional()
|
|
7093
7128
|
}), statusHelp), async (c) => {
|
|
7094
7129
|
const query = c.req.valid("query");
|
|
7095
|
-
const funnel = c.
|
|
7130
|
+
const funnel = c.env.funnel;
|
|
7096
7131
|
const isWatch = query.watch === "true" || query.watch === "";
|
|
7097
7132
|
const intervalSec = Math.min(60, Math.max(1, query.interval ? Number(query.interval) : 3));
|
|
7098
7133
|
if (!isWatch) {
|
|
@@ -7137,30 +7172,9 @@ const updateHandler = factory.createHandlers(zValidator$1("query", z.object({}),
|
|
|
7137
7172
|
});
|
|
7138
7173
|
//#endregion
|
|
7139
7174
|
//#region lib/cli/routes/index.ts
|
|
7140
|
-
const
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
* uses against a custom Funnel (e.g. one with sandboxed boundaries).
|
|
7145
|
-
*
|
|
7146
|
-
* All CLI verbs (`add` / `remove` / `set` / `rename` / `as-default` / `request`) map to POST in
|
|
7147
|
-
* to-request.ts and stay in the URL as a literal segment. Read paths (list / show / launch) keep GET.
|
|
7148
|
-
* Help shortcuts at parameterless URLs return the help text directly so `funnel <verb>` (no args) is
|
|
7149
|
-
* informative instead of 404.
|
|
7150
|
-
*/
|
|
7151
|
-
const createCliApp = (funnel) => {
|
|
7152
|
-
const base = factory.createApp();
|
|
7153
|
-
base.use((c, next) => {
|
|
7154
|
-
c.set("funnel", funnel);
|
|
7155
|
-
return next();
|
|
7156
|
-
});
|
|
7157
|
-
base.onError((error, c) => {
|
|
7158
|
-
if (error instanceof HTTPException) return c.text(`error: ${error.message}`, error.status);
|
|
7159
|
-
return c.text(`error: ${error instanceof Error ? error.message : String(error)}`, 400);
|
|
7160
|
-
});
|
|
7161
|
-
return base.get("/claude", ...claudeHandler).get("/channels", ...channelsGroupHandler).post("/channels/add", ...helpRoute(addHelp$3)).post("/channels/add/:channel", ...channelsAddHandler).post("/channels/remove", ...helpRoute(removeHelp$1)).post("/channels/remove/:channel", ...channelsRemoveHandler).post("/channels/rename/:channel/:newName", ...channelsRenameHandler).post("/channels/:channel/rename/:newName", ...channelsRenameHandler).post("/channels/rename", ...helpRoute(renameHelp$1)).post("/channels/:channel/rename", ...helpRoute(renameHelp$1)).post("/channels/:channel/set/delivery/:mode", ...channelsSetDeliveryHandler).post("/channels/publish", ...helpRoute(publishHelp)).post("/channels/:channel/publish", ...channelsPublishHandler).get("/channels/:channel/validate", ...channelsValidateHandler).get("/channels/validate", ...helpRoute(validateHelp)).get("/channels/:channel", ...channelsShowHandler).get("/channels/:channel/connectors", ...channelsConnectorsGroupHandler).post("/channels/:channel/connectors/add", ...helpRoute(addHelp$2)).post("/channels/:channel/connectors/add/:connector", ...channelsConnectorsAddHandler).post("/channels/:channel/connectors/remove", ...helpRoute(removeHelp$3)).post("/channels/:channel/connectors/remove/:connector", ...channelsConnectorsRemoveHandler).post("/channels/:channel/connectors/set", ...helpRoute(setHelp$1)).post("/channels/:channel/connectors/set/:connector", ...channelsConnectorsSetHandler).post("/channels/:channel/connectors/rename/:connector/:newName", ...channelsConnectorsRenameHandler).post("/channels/:channel/connectors/:connector/rename/:newName", ...channelsConnectorsRenameHandler).post("/channels/:channel/connectors/rename", ...helpRoute(renameHelp$2)).post("/channels/:channel/connectors/:connector/rename", ...helpRoute(renameHelp$2)).post("/channels/:channel/connectors/:connector/request", ...channelsConnectorsRequestHandler).get("/channels/:channel/connectors/:connector", ...channelsConnectorsShowHandler).get("/channels/:channel/connectors/:connector/schedules", ...channelsConnectorsSchedulesGroupHandler).post("/channels/:channel/connectors/:connector/schedules/add", ...helpRoute(addHelp$1)).post("/channels/:channel/connectors/:connector/schedules/add/:id", ...channelsConnectorsSchedulesAddHandler).post("/channels/:channel/connectors/:connector/schedules/remove", ...helpRoute(removeHelp$2)).post("/channels/:channel/connectors/:connector/schedules/remove/:id", ...channelsConnectorsSchedulesRemoveHandler).get("/profiles", ...profilesGroupHandler).post("/profiles/add", ...helpRoute(addHelp)).post("/profiles/add/:profile", ...profilesAddHandler).post("/profiles/set", ...helpRoute(setHelp)).post("/profiles/set/:profile", ...profilesSetHandler).post("/profiles/remove", ...helpRoute(removeHelp)).post("/profiles/remove/:profile", ...profilesRemoveHandler).post("/profiles/rename/:profile/:newName", ...profilesRenameHandler).post("/profiles/:profile/rename/:newName", ...profilesRenameHandler).post("/profiles/rename", ...helpRoute(renameHelp)).post("/profiles/:profile/rename", ...helpRoute(renameHelp)).post("/profiles/:profile/as-default", ...profilesAsDefaultHandler).get("/profiles/:profile/run", ...profilesLaunchHandler).get("/profiles/:profile", ...profilesLaunchHandler).get("/gateway", ...gatewayGroupHandler).get("/gateway/status", ...gatewayStatusHandler).get("/gateway/start", ...gatewayStartHandler).get("/gateway/stop", ...gatewayStopHandler).get("/gateway/restart", ...gatewayRestartHandler).get("/gateway/run", ...gatewayRunHandler).get("/gateway/logs", ...gatewayLogsHandler).get("/gateway/sql", ...gatewaySqlHandler).get("/gateway/listeners", ...gatewayListenersHandler).get("/debug", ...debugHandler).get("/debug/events", ...debugEventsHandler).get("/debug/dropped", ...debugDroppedHandler).get("/debug/errors", ...debugErrorsHandler).get("/debug/replay", ...debugReplayHandler).get("/schema", ...schemaHandler).get("/status", ...statusHandler).get("/update", ...updateHandler);
|
|
7162
|
-
};
|
|
7163
|
-
/** CLI Hono app wired to a default `new Funnel()`. For embedding with a custom Funnel use `createCliApp`. */
|
|
7164
|
-
const app = createCliApp(new Funnel());
|
|
7175
|
+
const routes = factory.createApp().onError((error, c) => {
|
|
7176
|
+
if (error instanceof HTTPException) return c.text(`error: ${error.message}`, error.status);
|
|
7177
|
+
return c.text(`error: ${error instanceof Error ? error.message : String(error)}`, 400);
|
|
7178
|
+
}).get("/claude", ...claudeHandler).get("/channels", ...channelsGroupHandler).post("/channels/add", ...channelsAddHelpHandler).post("/channels/add/:channel", ...channelsAddHandler).post("/channels/remove", ...channelsRemoveHelpHandler).post("/channels/remove/:channel", ...channelsRemoveHandler).post("/channels/rename/:channel/:newName", ...channelsRenameHandler).post("/channels/:channel/rename/:newName", ...channelsRenameHandler).post("/channels/rename", ...channelsRenameHelpHandler).post("/channels/:channel/rename", ...channelsChannelRenameHelpHandler).post("/channels/:channel/set/delivery/:mode", ...channelsSetDeliveryHandler).post("/channels/publish", ...channelsPublishHelpHandler).post("/channels/:channel/publish", ...channelsPublishHandler).get("/channels/:channel/validate", ...channelsValidateHandler).get("/channels/validate", ...channelsValidateHelpHandler).get("/channels/:channel", ...channelsShowHandler).get("/channels/:channel/connectors", ...channelsConnectorsGroupHandler).post("/channels/:channel/connectors/add", ...channelsConnectorsAddHelpHandler).post("/channels/:channel/connectors/add/:connector", ...channelsConnectorsAddHandler).post("/channels/:channel/connectors/remove", ...channelsConnectorsRemoveHelpHandler).post("/channels/:channel/connectors/remove/:connector", ...channelsConnectorsRemoveHandler).post("/channels/:channel/connectors/set", ...channelsConnectorsSetHelpHandler).post("/channels/:channel/connectors/set/:connector", ...channelsConnectorsSetHandler).post("/channels/:channel/connectors/rename/:connector/:newName", ...channelsConnectorsRenameHandler).post("/channels/:channel/connectors/:connector/rename/:newName", ...channelsConnectorsRenameHandler).post("/channels/:channel/connectors/rename", ...channelsConnectorsRenameHelpHandler).post("/channels/:channel/connectors/:connector/rename", ...channelsConnectorRenameHelpHandler).post("/channels/:channel/connectors/:connector/request", ...channelsConnectorsRequestHandler).get("/channels/:channel/connectors/:connector", ...channelsConnectorsShowHandler).get("/channels/:channel/connectors/:connector/schedules", ...channelsConnectorsSchedulesGroupHandler).post("/channels/:channel/connectors/:connector/schedules/add", ...channelsConnectorSchedulesAddHelpHandler).post("/channels/:channel/connectors/:connector/schedules/add/:id", ...channelsConnectorsSchedulesAddHandler).post("/channels/:channel/connectors/:connector/schedules/remove", ...channelsConnectorSchedulesRemoveHelpHandler).post("/channels/:channel/connectors/:connector/schedules/remove/:id", ...channelsConnectorsSchedulesRemoveHandler).get("/profiles", ...profilesGroupHandler).post("/profiles/add", ...profilesAddHelpHandler).post("/profiles/add/:profile", ...profilesAddHandler).post("/profiles/set", ...profilesSetHelpHandler).post("/profiles/set/:profile", ...profilesSetHandler).post("/profiles/remove", ...profilesRemoveHelpHandler).post("/profiles/remove/:profile", ...profilesRemoveHandler).post("/profiles/rename/:profile/:newName", ...profilesRenameHandler).post("/profiles/:profile/rename/:newName", ...profilesRenameHandler).post("/profiles/rename", ...profilesRenameHelpHandler).post("/profiles/:profile/rename", ...profilesProfileRenameHelpHandler).post("/profiles/:profile/as-default", ...profilesAsDefaultHandler).get("/profiles/:profile/run", ...profilesLaunchHandler).get("/profiles/:profile", ...profilesLaunchHandler).get("/gateway", ...gatewayGroupHandler).get("/gateway/status", ...gatewayStatusHandler).get("/gateway/start", ...gatewayStartHandler).get("/gateway/stop", ...gatewayStopHandler).get("/gateway/restart", ...gatewayRestartHandler).get("/gateway/run", ...gatewayRunHandler).get("/gateway/logs", ...gatewayLogsHandler).get("/gateway/sql", ...gatewaySqlHandler).get("/gateway/listeners", ...gatewayListenersHandler).get("/debug", ...debugHandler).get("/debug/events", ...debugEventsHandler).get("/debug/dropped", ...debugDroppedHandler).get("/debug/errors", ...debugErrorsHandler).get("/debug/replay", ...debugReplayHandler).get("/schema", ...schemaHandler).get("/status", ...statusHandler).get("/update", ...updateHandler);
|
|
7165
7179
|
//#endregion
|
|
7166
|
-
export { CONNECTOR_CONNECTION_STATUSES, ConnectorDiagnosticLog, ConnectorDiagnosticSqlReader, DEFAULT_GATEWAY_PORT, DEFAULT_GATEWAY_TOKEN_PATH, FUNNEL_DIR, FUNNEL_MCP_ARGS, FUNNEL_MCP_COMMAND, FUNNEL_MCP_NAME, Funnel, FunnelBroadcaster, FunnelChannelPublisher, FunnelChannels, FunnelClaude, FunnelClock, FunnelConnectorFactory, FunnelConnectorListener, FunnelEventLog, FunnelFileSystem, FunnelGateway, FunnelGatewayServer, FunnelGatewayToken, FunnelIdGenerator, FunnelListenerSupervisor, FunnelListenersClient, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLocalConfigWriter, FunnelLogger, FunnelMcp, FunnelProcessRunner, FunnelProfiles, FunnelSettingsReader, FunnelSettingsStore, FunnelSlackEventProcessor, FunnelTokenPrompter, LOCAL_CONFIG_FILENAME, MemoryConnectorDiagnosticLog, MemoryFunnelClock, MemoryFunnelEventLog, MemoryFunnelFileSystem, MemoryFunnelIdGenerator, MemoryFunnelLogger, MemoryFunnelProcessRunner, MemoryFunnelTokenPrompter, MockFunnelSettingsReader, NodeFunnelClock, NodeFunnelFileSystem, NodeFunnelIdGenerator, NodeFunnelLogger, NodeFunnelProcessRunner, NodeFunnelTokenPrompter, NoopFunnelLogger, SETTINGS_PATH, SETTINGS_VERSION, SqliteConnectorDiagnosticLog, SqliteFunnelEventLog, channelConfigSchema, channelDeliveryModeSchema, channelSpecSchema,
|
|
7180
|
+
export { CONNECTOR_CONNECTION_STATUSES, ConnectorDiagnosticLog, ConnectorDiagnosticSqlReader, DEFAULT_GATEWAY_PORT, DEFAULT_GATEWAY_TOKEN_PATH, FUNNEL_DIR, FUNNEL_MCP_ARGS, FUNNEL_MCP_COMMAND, FUNNEL_MCP_NAME, Funnel, FunnelBroadcaster, FunnelChannelPublisher, FunnelChannels, FunnelClaude, FunnelClock, FunnelConnectorFactory, FunnelConnectorListener, FunnelEventLog, FunnelFileSystem, FunnelGateway, FunnelGatewayServer, FunnelGatewayToken, FunnelIdGenerator, FunnelListenerSupervisor, FunnelListenersClient, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLocalConfigWriter, FunnelLogger, FunnelMcp, FunnelProcessRunner, FunnelProfiles, FunnelSettingsReader, FunnelSettingsStore, FunnelSlackEventProcessor, FunnelTokenPrompter, LOCAL_CONFIG_FILENAME, MemoryConnectorDiagnosticLog, MemoryFunnelClock, MemoryFunnelEventLog, MemoryFunnelFileSystem, MemoryFunnelIdGenerator, MemoryFunnelLogger, MemoryFunnelProcessRunner, MemoryFunnelTokenPrompter, MockFunnelSettingsReader, NodeFunnelClock, NodeFunnelFileSystem, NodeFunnelIdGenerator, NodeFunnelLogger, NodeFunnelProcessRunner, NodeFunnelTokenPrompter, NoopFunnelLogger, SETTINGS_PATH, SETTINGS_VERSION, SqliteConnectorDiagnosticLog, SqliteFunnelEventLog, channelConfigSchema, channelDeliveryModeSchema, channelSpecSchema, routes as cliRoutes, connectorConfigSchema, connectorConnectionEventSchema, connectorProcessedEventSchema, connectorRawEventSchema, connectorSpecSchema, createSettings, discordConnectorSchema, factory, funnelEventSchema, funnelJsonSchema, ghConnectorSchema, localConfigSchema, profileConfigSchema, profileSpecSchema, publishRequestSchema, publishResponseSchema, queryToCliArgs, resolveFunnelDir, resolveFunnelPort, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema, settingsSchema, slackConnectorSchema, startChannelServer, toRequest };
|