@interactive-inc/claude-funnel 0.37.0 → 0.38.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/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";
@@ -3076,7 +3077,7 @@ const connectionViewSql = `CREATE TEMP VIEW connection AS SELECT
3076
3077
  FROM connectiondb.leuco_log`;
3077
3078
  //#endregion
3078
3079
  //#region lib/gateway/routes/debug.ts
3079
- const extractPreview$1 = (payload) => {
3080
+ const extractPreview = (payload) => {
3080
3081
  if (typeof payload !== "string" || payload.length === 0) return null;
3081
3082
  try {
3082
3083
  const parsed = JSON.parse(payload);
@@ -3182,7 +3183,7 @@ const debugHandler$1 = factory$1.createHandlers(async (c) => {
3182
3183
  outcome: typeof row.outcome === "string" ? row.outcome : "?",
3183
3184
  payload: rawPayload,
3184
3185
  payloadParsed,
3185
- preview: extractPreview$1(row.payload)
3186
+ preview: extractPreview(row.payload)
3186
3187
  });
3187
3188
  }
3188
3189
  if (listener && (!listener.alive || listener.errors > 0) || !listener) {
@@ -3294,13 +3295,10 @@ const statusHandler$1 = factory$1.createHandlers((c) => {
3294
3295
  });
3295
3296
  //#endregion
3296
3297
  //#region lib/gateway/routes/index.ts
3297
- /**
3298
- * Top-level Hono app for the gateway daemon. Mounts every HTTP endpoint flat
3299
- * (the WebSocket /ws upgrade is handled directly by `Bun.serve`). Deps come
3300
- * from the `deps` variable set by `FunnelGatewayServer`'s middleware — same
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);
3298
+ function buildGatewayRoutes() {
3299
+ 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);
3300
+ }
3301
+ const gatewayRoutes = buildGatewayRoutes();
3304
3302
  //#endregion
3305
3303
  //#region lib/gateway/gateway-server.ts
3306
3304
  const DEFAULT_HOST = "127.0.0.1";
@@ -4100,6 +4098,10 @@ var Funnel = class Funnel {
4100
4098
  tmpDir: this.paths.tmpDir
4101
4099
  }, channelName ?? null);
4102
4100
  }
4101
+ gatewayClient() {
4102
+ const { port } = this.gateway.getStatus();
4103
+ return hc(`http://127.0.0.1:${port}`);
4104
+ }
4103
4105
  };
4104
4106
  //#endregion
4105
4107
  //#region lib/engine/mcp/channel-subscriber.ts
@@ -5072,6 +5074,17 @@ const queryToCliArgs = (url, reservedKeys = []) => {
5072
5074
  return args;
5073
5075
  };
5074
5076
  //#endregion
5077
+ //#region lib/cli/routes/channels.add.ts
5078
+ const help$17 = `funnel channels add — add a channel
5079
+
5080
+ usage: funnel channels add <name> [--delivery fanout|exclusive]
5081
+
5082
+ options:
5083
+ --delivery routing mode (default fanout):
5084
+ fanout every connected client receives every event
5085
+ exclusive each event delivered to exactly one client (round-robin)`;
5086
+ const channelsAddHelpHandler = factory.createHandlers((c) => c.text(help$17));
5087
+ //#endregion
5075
5088
  //#region lib/cli/router/validator.ts
5076
5089
  const zValidator$1 = (target, schema, helpText) => zValidator(target, schema, (result, c) => {
5077
5090
  if (helpText && c.req.query("help")) return c.text(helpText);
@@ -5083,18 +5096,10 @@ const zValidator$1 = (target, schema, helpText) => zValidator(target, schema, (r
5083
5096
  });
5084
5097
  //#endregion
5085
5098
  //#region lib/cli/routes/channels.add.$channel.ts
5086
- const addHelp$3 = `funnel channels add add a channel
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) => {
5099
+ const channelsAddHandler = factory.createHandlers(zValidator$1("param", z.object({ channel: z.string() })), zValidator$1("query", z.object({ delivery: channelDeliveryModeSchema.optional() })), (c) => {
5095
5100
  const param = c.req.valid("param");
5096
5101
  const query = c.req.valid("query");
5097
- const created = c.var.funnel.channels.add({
5102
+ const created = c.env.funnel.channels.add({
5098
5103
  name: param.channel,
5099
5104
  delivery: query.delivery
5100
5105
  });
@@ -5104,14 +5109,14 @@ const channelsConnectorsGroupHandler = factory.createHandlers(zValidator$1("para
5104
5109
 
5105
5110
  usage: funnel channels <channel> connectors`), (c) => {
5106
5111
  const param = c.req.valid("param");
5107
- const channel = c.var.funnel.channels.get(param.channel);
5112
+ const channel = c.env.funnel.channels.get(param.channel);
5108
5113
  if (!channel) throw new HTTPException(404, { message: `channel "${param.channel}" not found` });
5109
5114
  if (channel.connectors.length === 0) return c.text(`no connectors in channel "${channel.name}"`);
5110
5115
  return c.text(channel.connectors.map((c) => `${c.name} (${c.type}, id: ${c.id})`).join("\n"));
5111
5116
  });
5112
5117
  //#endregion
5113
- //#region lib/cli/routes/channels.$channel.connectors.add.$connector.ts
5114
- const addHelp$2 = `funnel channels <channel> connectors add <connector> — add a connector to a channel
5118
+ //#region lib/cli/routes/channels.$channel.connectors.add.ts
5119
+ const help$16 = `funnel channels <channel> connectors add <connector> — add a connector to a channel
5115
5120
 
5116
5121
  usage:
5117
5122
  funnel channels <channel> connectors add <connector> --type=slack --bot-token=xoxb-... --app-token=xapp-...
@@ -5120,6 +5125,9 @@ usage:
5120
5125
  funnel channels <channel> connectors add <connector> --type=schedule
5121
5126
 
5122
5127
  Token uniqueness is enforced across all channels.`;
5128
+ const channelsConnectorsAddHelpHandler = factory.createHandlers((c) => c.text(help$16));
5129
+ //#endregion
5130
+ //#region lib/cli/routes/channels.$channel.connectors.add.$connector.ts
5123
5131
  const slackBody = z.object({
5124
5132
  type: z.literal("slack"),
5125
5133
  "bot-token": z.string().startsWith("xoxb-"),
@@ -5143,10 +5151,10 @@ const addBody = z.discriminatedUnion("type", [
5143
5151
  const channelsConnectorsAddHandler = factory.createHandlers(zValidator$1("param", z.object({
5144
5152
  channel: z.string(),
5145
5153
  connector: z.string()
5146
- })), zValidator$1("query", addBody, addHelp$2), async (c) => {
5154
+ })), zValidator$1("query", addBody), async (c) => {
5147
5155
  const param = c.req.valid("param");
5148
5156
  const query = c.req.valid("query");
5149
- const funnel = c.var.funnel;
5157
+ const funnel = c.env.funnel;
5150
5158
  if (query.type === "slack") {
5151
5159
  const created = funnel.channels.addConnector(param.channel, {
5152
5160
  type: "slack",
@@ -5184,28 +5192,34 @@ const channelsConnectorsAddHandler = factory.createHandlers(zValidator$1("param"
5184
5192
  return c.text(`added schedule connector "${created.name}" to channel "${param.channel}"`);
5185
5193
  });
5186
5194
  //#endregion
5187
- //#region lib/cli/routes/channels.$channel.connectors.remove.$connector.ts
5188
- const removeHelp$3 = `funnel channels <channel> connectors remove <connector> — remove a connector
5195
+ //#region lib/cli/routes/channels.$channel.connectors.remove.ts
5196
+ const help$15 = `funnel channels <channel> connectors remove <connector> — remove a connector
5189
5197
 
5190
5198
  usage: funnel channels <channel> connectors remove <connector>`;
5199
+ const channelsConnectorsRemoveHelpHandler = factory.createHandlers((c) => c.text(help$15));
5200
+ //#endregion
5201
+ //#region lib/cli/routes/channels.$channel.connectors.remove.$connector.ts
5191
5202
  const channelsConnectorsRemoveHandler = factory.createHandlers(zValidator$1("param", z.object({
5192
5203
  channel: z.string(),
5193
5204
  connector: z.string()
5194
- })), zValidator$1("query", z.object({}), removeHelp$3), async (c) => {
5205
+ })), zValidator$1("query", z.object({})), async (c) => {
5195
5206
  const param = c.req.valid("param");
5196
- const funnel = c.var.funnel;
5207
+ const funnel = c.env.funnel;
5197
5208
  await funnel.listeners.stop(param.channel, param.connector);
5198
5209
  funnel.channels.removeConnector(param.channel, param.connector);
5199
5210
  return c.text(`removed connector "${param.connector}" from channel "${param.channel}"`);
5200
5211
  });
5201
5212
  //#endregion
5202
- //#region lib/cli/routes/channels.$channel.connectors.set.$connector.ts
5203
- const setHelp$1 = `funnel channels <channel> connectors set <connector> — update connector fields
5213
+ //#region lib/cli/routes/channels.$channel.connectors.set.ts
5214
+ const help$14 = `funnel channels <channel> connectors set <connector> — update connector fields
5204
5215
 
5205
5216
  usage:
5206
5217
  funnel channels <ch> connectors set <conn> [--bot-token=...] [--app-token=...] # slack
5207
5218
  funnel channels <ch> connectors set <conn> [--bot-token=...] # discord
5208
5219
  funnel channels <ch> connectors set <conn> [--poll-interval=N] # gh`;
5220
+ const channelsConnectorsSetHelpHandler = factory.createHandlers((c) => c.text(help$14));
5221
+ //#endregion
5222
+ //#region lib/cli/routes/channels.$channel.connectors.set.$connector.ts
5209
5223
  const channelsConnectorsSetHandler = factory.createHandlers(zValidator$1("param", z.object({
5210
5224
  channel: z.string(),
5211
5225
  connector: z.string()
@@ -5213,10 +5227,10 @@ const channelsConnectorsSetHandler = factory.createHandlers(zValidator$1("param"
5213
5227
  "bot-token": z.string().optional(),
5214
5228
  "app-token": z.string().optional(),
5215
5229
  "poll-interval": z.coerce.number().int().positive().optional()
5216
- }).passthrough(), setHelp$1), async (c) => {
5230
+ }).passthrough()), async (c) => {
5217
5231
  const param = c.req.valid("param");
5218
5232
  const query = c.req.valid("query");
5219
- const funnel = c.var.funnel;
5233
+ const funnel = c.env.funnel;
5220
5234
  const existing = funnel.channels.getConnector(param.channel, param.connector);
5221
5235
  if (!existing) throw new HTTPException(404, { message: `connector "${param.connector}" not found in channel "${param.channel}"` });
5222
5236
  if (existing.type === "slack") funnel.channels.updateSlackConnector(param.channel, param.connector, {
@@ -5236,27 +5250,36 @@ const channelsConnectorsShowHandler = factory.createHandlers(zValidator$1("param
5236
5250
 
5237
5251
  usage: funnel channels <channel> connectors show <connector>`), (c) => {
5238
5252
  const param = c.req.valid("param");
5239
- const connector = c.var.funnel.channels.getConnector(param.channel, param.connector);
5253
+ const connector = c.env.funnel.channels.getConnector(param.channel, param.connector);
5240
5254
  if (!connector) throw new HTTPException(404, { message: `connector "${param.connector}" not found in channel "${param.channel}"` });
5241
5255
  return c.text(JSON.stringify(connector, null, 2));
5242
5256
  });
5243
5257
  //#endregion
5244
- //#region lib/cli/routes/channels.$channel.connectors.$connector.rename.$newName.ts
5245
- const renameHelp$2 = `funnel channels <channel> connectors rename <connector> <new-name>
5258
+ //#region lib/cli/routes/channels.$channel.connectors.rename.ts
5259
+ const help$13 = `funnel channels <channel> connectors rename <connector> <new-name>
5246
5260
 
5247
5261
  usage: funnel channels <channel> connectors rename <connector> <new-name>`;
5262
+ const channelsConnectorsRenameHelpHandler = factory.createHandlers((c) => c.text(help$13));
5263
+ //#endregion
5264
+ //#region lib/cli/routes/channels.$channel.connectors.$connector.rename.$newName.ts
5248
5265
  const channelsConnectorsRenameHandler = factory.createHandlers(zValidator$1("param", z.object({
5249
5266
  channel: z.string(),
5250
5267
  connector: z.string(),
5251
5268
  newName: z.string()
5252
- })), zValidator$1("query", z.object({}), renameHelp$2), async (c) => {
5269
+ })), zValidator$1("query", z.object({})), async (c) => {
5253
5270
  const param = c.req.valid("param");
5254
- const funnel = c.var.funnel;
5271
+ const funnel = c.env.funnel;
5255
5272
  await funnel.listeners.stop(param.channel, param.connector);
5256
5273
  funnel.channels.renameConnector(param.channel, param.connector, param.newName);
5257
5274
  await funnel.listeners.start(param.channel, param.newName);
5258
5275
  return c.text(`renamed connector "${param.connector}" to "${param.newName}"`);
5259
5276
  });
5277
+ //#endregion
5278
+ //#region lib/cli/routes/channels.$channel.connectors.$connector.rename.ts
5279
+ const help$12 = `funnel channels <channel> connectors rename <connector> <new-name>
5280
+
5281
+ usage: funnel channels <channel> connectors rename <connector> <new-name>`;
5282
+ const channelsConnectorRenameHelpHandler = factory.createHandlers((c) => c.text(help$12));
5260
5283
  const channelsConnectorsRequestHandler = factory.createHandlers(zValidator$1("param", z.object({
5261
5284
  channel: z.string(),
5262
5285
  connector: z.string()
@@ -5265,7 +5288,7 @@ const channelsConnectorsRequestHandler = factory.createHandlers(zValidator$1("pa
5265
5288
  usage: funnel channels <channel> connectors <connector> request --method=<api.method> [--key=value ...]`), async (c) => {
5266
5289
  const param = c.req.valid("param");
5267
5290
  const query = c.req.valid("query");
5268
- const funnel = c.var.funnel;
5291
+ const funnel = c.env.funnel;
5269
5292
  const passthrough = {};
5270
5293
  for (const [k, v] of new URL(c.req.url).searchParams) {
5271
5294
  if (k === "method") continue;
@@ -5285,15 +5308,18 @@ const channelsConnectorsSchedulesGroupHandler = factory.createHandlers(zValidato
5285
5308
 
5286
5309
  usage: funnel channels <ch> connectors <conn> schedules`), (c) => {
5287
5310
  const param = c.req.valid("param");
5288
- const entries = c.var.funnel.channels.listScheduleEntries(param.channel, param.connector);
5311
+ const entries = c.env.funnel.channels.listScheduleEntries(param.channel, param.connector);
5289
5312
  if (entries.length === 0) return c.text("no schedule entries");
5290
5313
  return c.text(entries.map((e) => `${e.id}\t${e.cron}\t${e.enabled ? "on" : "off"}\t${e.prompt}`).join("\n"));
5291
5314
  });
5292
5315
  //#endregion
5293
- //#region lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.ts
5294
- const addHelp$1 = `funnel channels <ch> connectors <conn> schedules add <id> — add a schedule entry
5316
+ //#region lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.ts
5317
+ const help$11 = `funnel channels <ch> connectors <conn> schedules add <id> — add a schedule entry
5295
5318
 
5296
5319
  usage: funnel channels <ch> connectors <conn> schedules add <id> --cron="*/5 * * * *" --prompt="..." [--enabled=true] [--catchup-policy=latest|all|skip]`;
5320
+ const channelsConnectorSchedulesAddHelpHandler = factory.createHandlers((c) => c.text(help$11));
5321
+ //#endregion
5322
+ //#region lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.ts
5297
5323
  const channelsConnectorsSchedulesAddHandler = factory.createHandlers(zValidator$1("param", z.object({
5298
5324
  channel: z.string(),
5299
5325
  connector: z.string(),
@@ -5303,10 +5329,10 @@ const channelsConnectorsSchedulesAddHandler = factory.createHandlers(zValidator$
5303
5329
  prompt: z.string(),
5304
5330
  enabled: z.coerce.boolean().optional(),
5305
5331
  "catchup-policy": scheduleCatchupPolicySchema.optional()
5306
- }), addHelp$1), async (c) => {
5332
+ })), async (c) => {
5307
5333
  const param = c.req.valid("param");
5308
5334
  const query = c.req.valid("query");
5309
- const funnel = c.var.funnel;
5335
+ const funnel = c.env.funnel;
5310
5336
  const entry = funnel.channels.addScheduleEntry(param.channel, param.connector, {
5311
5337
  id: param.id,
5312
5338
  cron: query.cron,
@@ -5318,24 +5344,27 @@ const channelsConnectorsSchedulesAddHandler = factory.createHandlers(zValidator$
5318
5344
  return c.text(`added schedule entry "${entry.id}"`);
5319
5345
  });
5320
5346
  //#endregion
5321
- //#region lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.ts
5322
- const removeHelp$2 = `funnel channels <ch> connectors <conn> schedules remove <id>
5347
+ //#region lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.ts
5348
+ const help$10 = `funnel channels <ch> connectors <conn> schedules remove <id>
5323
5349
 
5324
5350
  usage: funnel channels <ch> connectors <conn> schedules remove <id>`;
5351
+ const channelsConnectorSchedulesRemoveHelpHandler = factory.createHandlers((c) => c.text(help$10));
5352
+ //#endregion
5353
+ //#region lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.ts
5325
5354
  const channelsConnectorsSchedulesRemoveHandler = factory.createHandlers(zValidator$1("param", z.object({
5326
5355
  channel: z.string(),
5327
5356
  connector: z.string(),
5328
5357
  id: z.string()
5329
- })), zValidator$1("query", z.object({}), removeHelp$2), async (c) => {
5358
+ })), zValidator$1("query", z.object({})), async (c) => {
5330
5359
  const param = c.req.valid("param");
5331
- const funnel = c.var.funnel;
5360
+ const funnel = c.env.funnel;
5332
5361
  funnel.channels.removeScheduleEntry(param.channel, param.connector, param.id);
5333
5362
  await funnel.listeners.restart(param.channel, param.connector);
5334
5363
  return c.text(`removed schedule entry "${param.id}"`);
5335
5364
  });
5336
5365
  //#endregion
5337
- //#region lib/cli/routes/channels.$channel.publish.ts
5338
- const publishHelp = `funnel channels <channel> publish — push arbitrary content into a channel
5366
+ //#region lib/cli/routes/channels.publish.ts
5367
+ const help$9 = `funnel channels <channel> publish — push arbitrary content into a channel
5339
5368
 
5340
5369
  usage: funnel channels <channel> publish --content="<text>" [--connector=<name>] [--meta-<key>=<value> ...]
5341
5370
 
@@ -5343,14 +5372,17 @@ options:
5343
5372
  --content Required. The event body delivered to subscribers.
5344
5373
  --connector Optional. Stamp the event with a connector name (resolved to id when found).
5345
5374
  --meta-<key> Optional. Repeatable. Added to meta. Example: --meta-source=cron`;
5375
+ const channelsPublishHelpHandler = factory.createHandlers((c) => c.text(help$9));
5376
+ //#endregion
5377
+ //#region lib/cli/routes/channels.$channel.publish.ts
5346
5378
  const querySchema = z.object({
5347
5379
  content: z.string().min(1, { message: "--content is required" }),
5348
5380
  connector: z.string().min(1).optional()
5349
5381
  }).passthrough();
5350
- const channelsPublishHandler = factory.createHandlers(zValidator$1("param", z.object({ channel: z.string() })), zValidator$1("query", querySchema, publishHelp), async (c) => {
5382
+ const channelsPublishHandler = factory.createHandlers(zValidator$1("param", z.object({ channel: z.string() })), zValidator$1("query", querySchema), async (c) => {
5351
5383
  const param = c.req.valid("param");
5352
5384
  const query = c.req.valid("query");
5353
- const funnel = c.var.funnel;
5385
+ const funnel = c.env.funnel;
5354
5386
  const meta = {};
5355
5387
  for (const [k, v] of new URL(c.req.url).searchParams) if (k.startsWith("meta-")) meta[k.slice(5)] = v;
5356
5388
  const result = await funnel.publisher.publish(param.channel, {
@@ -5363,28 +5395,42 @@ const channelsPublishHandler = factory.createHandlers(zValidator$1("param", z.ob
5363
5395
  return c.text(`published (offset=${result.offset})`);
5364
5396
  });
5365
5397
  //#endregion
5366
- //#region lib/cli/routes/channels.remove.$channel.ts
5367
- const removeHelp$1 = `funnel channels remove — remove a channel
5398
+ //#region lib/cli/routes/channels.remove.ts
5399
+ const help$8 = `funnel channels remove — remove a channel
5368
5400
 
5369
5401
  usage: funnel channels remove <name>`;
5370
- const channelsRemoveHandler = factory.createHandlers(zValidator$1("param", z.object({ channel: z.string() })), zValidator$1("query", z.object({}), removeHelp$1), (c) => {
5402
+ const channelsRemoveHelpHandler = factory.createHandlers((c) => c.text(help$8));
5403
+ //#endregion
5404
+ //#region lib/cli/routes/channels.remove.$channel.ts
5405
+ const channelsRemoveHandler = factory.createHandlers(zValidator$1("param", z.object({ channel: z.string() })), zValidator$1("query", z.object({})), (c) => {
5371
5406
  const param = c.req.valid("param");
5372
- c.var.funnel.channels.remove(param.channel);
5407
+ c.env.funnel.channels.remove(param.channel);
5373
5408
  return c.text(`removed channel "${param.channel}"`);
5374
5409
  });
5375
5410
  //#endregion
5376
- //#region lib/cli/routes/channels.$channel.rename.$newName.ts
5377
- const renameHelp$1 = `funnel channels rename — rename a channel
5411
+ //#region lib/cli/routes/channels.rename.ts
5412
+ const help$7 = `funnel channels rename — rename a channel
5413
+
5414
+ usage:
5415
+ funnel channels rename <old> <new>
5416
+ funnel channels <old> rename <new>`;
5417
+ const channelsRenameHelpHandler = factory.createHandlers((c) => c.text(help$7));
5418
+ //#endregion
5419
+ //#region lib/cli/routes/channels.$channel.rename.ts
5420
+ const help$6 = `funnel channels rename — rename a channel
5378
5421
 
5379
5422
  usage:
5380
5423
  funnel channels rename <old> <new>
5381
5424
  funnel channels <old> rename <new>`;
5425
+ const channelsChannelRenameHelpHandler = factory.createHandlers((c) => c.text(help$6));
5426
+ //#endregion
5427
+ //#region lib/cli/routes/channels.$channel.rename.$newName.ts
5382
5428
  const channelsRenameHandler = factory.createHandlers(zValidator$1("param", z.object({
5383
5429
  channel: z.string(),
5384
5430
  newName: z.string()
5385
- })), zValidator$1("query", z.object({}), renameHelp$1), (c) => {
5431
+ })), zValidator$1("query", z.object({})), (c) => {
5386
5432
  const param = c.req.valid("param");
5387
- c.var.funnel.channels.rename(param.channel, param.newName);
5433
+ c.env.funnel.channels.rename(param.channel, param.newName);
5388
5434
  return c.text(`renamed channel "${param.channel}" to "${param.newName}"`);
5389
5435
  });
5390
5436
  const channelsSetDeliveryHandler = factory.createHandlers(zValidator$1("param", z.object({
@@ -5401,12 +5447,12 @@ modes:
5401
5447
  tap=all clients (TUI dashboard, debugging) always receive regardless of mode.
5402
5448
  `), (c) => {
5403
5449
  const param = c.req.valid("param");
5404
- c.var.funnel.channels.setDelivery(param.channel, param.mode);
5450
+ c.env.funnel.channels.setDelivery(param.channel, param.mode);
5405
5451
  return c.text(`channel "${param.channel}" delivery set to ${param.mode}`);
5406
5452
  });
5407
5453
  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
5454
  const param = c.req.valid("param");
5409
- const channel = c.var.funnel.channels.get(param.channel);
5455
+ const channel = c.env.funnel.channels.get(param.channel);
5410
5456
  if (!channel) throw new HTTPException(404, { message: `channel "${param.channel}" not found` });
5411
5457
  const connectorLines = channel.connectors.length ? channel.connectors.map((c) => ` - ${c.name} (${c.type}, id: ${c.id})`) : [" (none)"];
5412
5458
  const lines = [
@@ -5444,7 +5490,7 @@ examples:
5444
5490
  funnel channels prod-inbox connectors add prod-slack --type=slack --bot-token=xoxb-... --app-token=xapp-...
5445
5491
  funnel channels prod-inbox`), (c) => {
5446
5492
  const query = c.req.valid("query");
5447
- const channels = c.var.funnel.channels.list();
5493
+ const channels = c.env.funnel.channels.list();
5448
5494
  if (query.json === "true" || query.json === "") return c.json(channels.map((ch) => ({
5449
5495
  id: ch.id,
5450
5496
  name: ch.name,
@@ -5464,6 +5510,106 @@ examples:
5464
5510
  return c.text(lines.join("\n"));
5465
5511
  });
5466
5512
  //#endregion
5513
+ //#region lib/cli/routes/channels.validate.ts
5514
+ const help$5 = `funnel channels <channel> validate — check connector configuration
5515
+
5516
+ usage: funnel channels <channel> validate [--json]
5517
+
5518
+ options:
5519
+ --json output as JSON
5520
+
5521
+ Checks that each connector has the required tokens and fields set.
5522
+ Does not make any network calls — static config check only.
5523
+
5524
+ examples:
5525
+ funnel channels open-karte validate
5526
+ funnel channels open-karte validate --json`;
5527
+ const channelsValidateHelpHandler = factory.createHandlers((c) => c.text(help$5));
5528
+ //#endregion
5529
+ //#region lib/cli/routes/channels.$channel.validate.ts
5530
+ const validateConnector = (connector) => {
5531
+ const issues = [];
5532
+ if (connector.type === "slack") {
5533
+ if (!(connector.botToken || connector.botTokenEnv)) issues.push({
5534
+ connector: connector.name,
5535
+ field: "botToken",
5536
+ message: "missing botToken (xoxb-...) or botTokenEnv"
5537
+ });
5538
+ if (!(connector.appToken || connector.appTokenEnv)) issues.push({
5539
+ connector: connector.name,
5540
+ field: "appToken",
5541
+ message: "missing appToken (xapp-...) or appTokenEnv"
5542
+ });
5543
+ if (connector.botToken && typeof connector.botToken === "string" && !connector.botToken.startsWith("xoxb-")) issues.push({
5544
+ connector: connector.name,
5545
+ field: "botToken",
5546
+ message: `botToken must start with xoxb- (got: ${connector.botToken.slice(0, 8)}...)`
5547
+ });
5548
+ if (connector.appToken && typeof connector.appToken === "string" && !connector.appToken.startsWith("xapp-")) issues.push({
5549
+ connector: connector.name,
5550
+ field: "appToken",
5551
+ message: `appToken must start with xapp- (got: ${connector.appToken.slice(0, 8)}...)`
5552
+ });
5553
+ }
5554
+ if (connector.type === "gh") {
5555
+ if (!(connector.token || connector.tokenEnv)) issues.push({
5556
+ connector: connector.name,
5557
+ field: "token",
5558
+ message: "missing token or tokenEnv for GitHub connector"
5559
+ });
5560
+ if (!connector.repo) issues.push({
5561
+ connector: connector.name,
5562
+ field: "repo",
5563
+ message: "missing repo (expected owner/repo format)"
5564
+ });
5565
+ }
5566
+ if (connector.type === "discord") {
5567
+ if (!(connector.botToken || connector.botTokenEnv)) issues.push({
5568
+ connector: connector.name,
5569
+ field: "botToken",
5570
+ message: "missing botToken or botTokenEnv for Discord connector"
5571
+ });
5572
+ }
5573
+ return issues;
5574
+ };
5575
+ const channelsValidateHandler = factory.createHandlers(zValidator$1("param", z.object({ channel: z.string() })), zValidator$1("query", z.object({ json: z.enum([
5576
+ "true",
5577
+ "false",
5578
+ ""
5579
+ ]).optional() })), (c) => {
5580
+ const param = c.req.valid("param");
5581
+ const query = c.req.valid("query");
5582
+ const funnel = c.env.funnel;
5583
+ const isJson = query.json === "true" || query.json === "";
5584
+ const channel = funnel.channels.get(param.channel);
5585
+ if (!channel) throw new HTTPException(404, { message: `channel "${param.channel}" not found` });
5586
+ if (channel.connectors.length === 0) {
5587
+ if (isJson) return c.json({
5588
+ channel: channel.name,
5589
+ valid: false,
5590
+ issues: [{
5591
+ connector: "(none)",
5592
+ field: "connectors",
5593
+ message: "no connectors configured"
5594
+ }]
5595
+ });
5596
+ return c.text(`⚠ ${channel.name}: no connectors configured`);
5597
+ }
5598
+ const allIssues = [];
5599
+ for (const connector of channel.connectors) {
5600
+ const issues = validateConnector(connector);
5601
+ allIssues.push(...issues);
5602
+ }
5603
+ if (isJson) return c.json({
5604
+ channel: channel.name,
5605
+ valid: allIssues.length === 0,
5606
+ issues: allIssues
5607
+ });
5608
+ if (allIssues.length === 0) return c.text(`✓ ${channel.name}: all connectors valid`);
5609
+ const lines = allIssues.map((issue) => `✗ ${channel.name}/${issue.connector}: ${issue.message}`);
5610
+ return c.text(lines.join("\n"));
5611
+ });
5612
+ //#endregion
5467
5613
  //#region lib/cli/routes/claude.ts
5468
5614
  const claudeHelp = `funnel claude — launch Claude Code
5469
5615
 
@@ -5495,7 +5641,7 @@ const claudeHandler = factory.createHandlers(zValidator$1("query", z.object({
5495
5641
  channel: z.string().optional()
5496
5642
  }).passthrough(), claudeHelp), async (c) => {
5497
5643
  const query = c.req.valid("query");
5498
- const funnel = c.var.funnel;
5644
+ const funnel = c.env.funnel;
5499
5645
  const userArgs = queryToCliArgs(c.req.url, RESERVED_KEYS$1);
5500
5646
  if (query.channel && !query.profile) {
5501
5647
  const exitCode = await funnel.claude.launch({
@@ -5548,6 +5694,71 @@ const claudeHandler = factory.createHandlers(zValidator$1("query", z.object({
5548
5694
  process.exit(exitCode);
5549
5695
  });
5550
5696
  //#endregion
5697
+ //#region lib/cli/routes/debug-row.ts
5698
+ const stringOrNull = (value) => typeof value === "string" && value.length > 0 ? value : null;
5699
+ const numberOrNull = (value) => typeof value === "number" ? value : null;
5700
+ const stringOr = (value, fallback) => typeof value === "string" ? value : fallback;
5701
+ /**
5702
+ * Parse a payload string as a JSON object. Returns null for non-strings,
5703
+ * malformed JSON, or any non-object JSON (arrays, primitives) — the callers
5704
+ * only ever want the object form.
5705
+ */
5706
+ const parsePayloadObject = (payload) => {
5707
+ if (payload === null) return null;
5708
+ try {
5709
+ const parsed = JSON.parse(payload);
5710
+ if (isStringKeyedObject(parsed)) return parsed;
5711
+ } catch {
5712
+ return null;
5713
+ }
5714
+ return null;
5715
+ };
5716
+ const isStringKeyedObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
5717
+ const truncate = (text, max) => text.length <= max ? text : `${text.slice(0, max)}…`;
5718
+ /**
5719
+ * A short human preview of a payload: the `text` field when the payload is a
5720
+ * JSON object that has one, otherwise the raw payload, both truncated.
5721
+ */
5722
+ const previewOf = (payload) => {
5723
+ if (typeof payload !== "string" || payload.length === 0) return null;
5724
+ const parsed = parsePayloadObject(payload);
5725
+ if (parsed !== null && "text" in parsed) return truncate(String(parsed.text), 60);
5726
+ return truncate(payload, 60);
5727
+ };
5728
+ /** Narrow one processed-table row into a `DebugEvent`. */
5729
+ const toDebugEvent = (row) => {
5730
+ const payload = stringOrNull(row.payload);
5731
+ return {
5732
+ seq: numberOrNull(row.seq),
5733
+ ts: numberOrNull(row.ts),
5734
+ type: stringOr(row.type, "?"),
5735
+ outcome: stringOr(row.outcome, "?"),
5736
+ eventId: stringOrNull(row.event_id),
5737
+ payload,
5738
+ payloadParsed: parsePayloadObject(payload),
5739
+ preview: previewOf(row.payload)
5740
+ };
5741
+ };
5742
+ /** Narrow one connection-table row into a `DebugConnectionError`. */
5743
+ const toDebugConnectionError = (row) => ({
5744
+ seq: numberOrNull(row.seq),
5745
+ ts: numberOrNull(row.ts),
5746
+ type: stringOr(row.type, "?"),
5747
+ status: stringOr(row.status, "?"),
5748
+ detail: stringOrNull(row.detail)
5749
+ });
5750
+ /**
5751
+ * Open a reader, run one query, and always close — the shared shape behind
5752
+ * every diagnostic lookup. Returns the rows or the reader's `Error`.
5753
+ */
5754
+ const queryRows = (reader, sql, params) => {
5755
+ try {
5756
+ return reader.query(sql, params);
5757
+ } finally {
5758
+ reader.close();
5759
+ }
5760
+ };
5761
+ //#endregion
5551
5762
  //#region lib/cli/routes/debug.ts
5552
5763
  const debugHelp = `funnel debug — diagnose why Claude is not receiving events
5553
5764
 
@@ -5639,17 +5850,6 @@ const formatTs = (epochMs) => {
5639
5850
  if (typeof epochMs !== "number") return "?";
5640
5851
  return new Date(epochMs).toISOString().slice(11, 19);
5641
5852
  };
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
5853
  const buildDiagnosis = (report) => {
5654
5854
  const rootCause = (report.connectionErrors[report.connectionErrors.length - 1] ?? null)?.detail ?? null;
5655
5855
  if (!report.gateway.running) return {
@@ -5762,6 +5962,16 @@ const resolveStoreOrNull = () => {
5762
5962
  connectionPath
5763
5963
  };
5764
5964
  };
5965
+ /**
5966
+ * Resolve the connector name for a connector id on a channel, used to attribute
5967
+ * a replayed event back to its source connector. Returns undefined when the id
5968
+ * is null or no longer present (connectors can be removed after an event was
5969
+ * logged).
5970
+ */
5971
+ const connectorOf = (channel, connectorId) => {
5972
+ if (connectorId === null) return void 0;
5973
+ return channel.connectors?.find((connector) => connector.id === connectorId)?.name;
5974
+ };
5765
5975
  const resolveChannelId = (channels, channelName) => {
5766
5976
  if (channelName) {
5767
5977
  const match = channels.find((ch) => ch.name === channelName);
@@ -5799,7 +6009,7 @@ const debugEventsHandler = factory.createHandlers(zValidator$1("query", z.object
5799
6009
  ]).optional()
5800
6010
  }), debugEventsHelp), async (c) => {
5801
6011
  const query = c.req.valid("query");
5802
- const channels = c.var.funnel.channels.list();
6012
+ const channels = c.env.funnel.channels.list();
5803
6013
  const isJson = query.json === "true" || query.json === "";
5804
6014
  const limit = query.limit ? Math.max(1, Number(query.limit)) : 20;
5805
6015
  const store = resolveStoreOrNull();
@@ -5828,34 +6038,9 @@ const debugEventsHandler = factory.createHandlers(zValidator$1("query", z.object
5828
6038
  }
5829
6039
  const channel = resolved.channel;
5830
6040
  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
- })();
6041
+ 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
6042
  if (rows instanceof Error) return c.text(`error: ${rows.message}`);
5840
- const events = [...rows].reverse().map((row) => {
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
- });
6043
+ const events = rows.reverse().map(toDebugEvent);
5859
6044
  if (isJson) return c.json(events);
5860
6045
  if (events.length === 0) return c.text("no events recorded");
5861
6046
  const lines = events.map((ev) => {
@@ -5877,7 +6062,7 @@ const debugDroppedHandler = factory.createHandlers(zValidator$1("query", z.objec
5877
6062
  ]).optional()
5878
6063
  }), debugDroppedHelp), async (c) => {
5879
6064
  const query = c.req.valid("query");
5880
- const channels = c.var.funnel.channels.list();
6065
+ const channels = c.env.funnel.channels.list();
5881
6066
  const isJson = query.json === "true" || query.json === "";
5882
6067
  const limit = query.limit ? Math.max(1, Number(query.limit)) : 20;
5883
6068
  const store = resolveStoreOrNull();
@@ -5906,39 +6091,13 @@ const debugDroppedHandler = factory.createHandlers(zValidator$1("query", z.objec
5906
6091
  }
5907
6092
  const channel = resolvedDropped.channel;
5908
6093
  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
- })();
6094
+ 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
6095
  if (rows instanceof Error) return c.text(`error: ${rows.message}`);
5918
- const events = [...rows].reverse().map((row) => {
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
- });
6096
+ const events = rows.reverse().map(toDebugEvent);
5938
6097
  if (isJson) return c.json(events);
5939
6098
  if (events.length === 0) return c.text("no dropped events recorded");
5940
6099
  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.event_id ? ` event_id=${ev.event_id.slice(0, 8)}` : ""}${ev.preview ? ` "${ev.preview}"` : ""}`;
6100
+ 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
6101
  });
5943
6102
  return c.text(lines.join("\n"));
5944
6103
  });
@@ -5952,7 +6111,7 @@ const debugErrorsHandler = factory.createHandlers(zValidator$1("query", z.object
5952
6111
  ]).optional()
5953
6112
  }), debugErrorsHelp), async (c) => {
5954
6113
  const query = c.req.valid("query");
5955
- const channels = c.var.funnel.channels.list();
6114
+ const channels = c.env.funnel.channels.list();
5956
6115
  const isJson = query.json === "true" || query.json === "";
5957
6116
  const limit = query.limit ? Math.max(1, Number(query.limit)) : 20;
5958
6117
  const store = resolveStoreOrNull();
@@ -5981,22 +6140,9 @@ const debugErrorsHandler = factory.createHandlers(zValidator$1("query", z.object
5981
6140
  }
5982
6141
  const channel = resolvedErrors.channel;
5983
6142
  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
- })();
6143
+ 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
6144
  if (rows instanceof Error) return c.text(`error: ${rows.message}`);
5993
- const errors = [...rows].reverse().map((row) => ({
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
- }));
6145
+ const errors = rows.reverse().map(toDebugConnectionError);
6000
6146
  if (isJson) return c.json(errors);
6001
6147
  if (errors.length === 0) return c.text("no connection errors recorded");
6002
6148
  const lines = errors.map((ev) => {
@@ -6032,50 +6178,13 @@ const buildChannelReport = async (targetChannel, gatewayStatus, gatewayBodyOrNul
6032
6178
  baseReport.claudeClients = gatewayBodyOrNull.clients.filter((cl) => !cl.tapAll && cl.channelName === targetChannelName).length;
6033
6179
  }
6034
6180
  if (store) {
6035
- const reader = new ConnectorDiagnosticSqlReader(store);
6036
- const evRows = (() => {
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
- });
6181
+ 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]);
6182
+ if (!(evRows instanceof Error)) baseReport.recentEvents = evRows.reverse().map(toDebugEvent);
6062
6183
  const hasDeadListeners = baseReport.listeners.some((l) => !l.alive);
6063
6184
  const hasListenerErrors = baseReport.listeners.some((l) => l.errors > 0);
6064
6185
  if (hasDeadListeners || hasListenerErrors) {
6065
- const errReader = new ConnectorDiagnosticSqlReader(store);
6066
- const errRows = (() => {
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
- }));
6186
+ 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]);
6187
+ if (!(errRows instanceof Error)) baseReport.connectionErrors = errRows.reverse().map(toDebugConnectionError);
6079
6188
  }
6080
6189
  }
6081
6190
  return {
@@ -6098,7 +6207,7 @@ const debugHandler = factory.createHandlers(zValidator$1("query", z.object({
6098
6207
  limit: z.string().optional()
6099
6208
  }), debugHelp), async (c) => {
6100
6209
  const query = c.req.valid("query");
6101
- const funnel = c.var.funnel;
6210
+ const funnel = c.env.funnel;
6102
6211
  const channels = funnel.channels.list();
6103
6212
  const gatewayStatus = funnel.gateway.getStatus();
6104
6213
  const isJson = query.json === "true" || query.json === "";
@@ -6193,7 +6302,7 @@ examples:
6193
6302
  fnl debug replay --channel open-karte --seq 412
6194
6303
  fnl debug replay --channel open-karte --json`), async (c) => {
6195
6304
  const query = c.req.valid("query");
6196
- const funnel = c.var.funnel;
6305
+ const funnel = c.env.funnel;
6197
6306
  const channels = funnel.channels.list();
6198
6307
  const isJson = query.json === "true" || query.json === "";
6199
6308
  const resolved = resolveChannelId(channels, query.channel);
@@ -6221,15 +6330,7 @@ examples:
6221
6330
  if (isJson) return c.json({ error: "no diagnostic store yet (start the gateway first)" });
6222
6331
  return c.text("no diagnostic store yet (start the gateway first)");
6223
6332
  }
6224
- const reader = new ConnectorDiagnosticSqlReader(store);
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
- })();
6333
+ 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
6334
  if (rows instanceof Error) {
6234
6335
  if (isJson) return c.json({ error: rows.message });
6235
6336
  return c.text(`error: ${rows.message}`);
@@ -6244,24 +6345,15 @@ examples:
6244
6345
  const connectorId = typeof firstRow.connector_id === "string" ? firstRow.connector_id : null;
6245
6346
  let content = typeof firstRow.payload === "string" ? firstRow.payload : null;
6246
6347
  if ((!content || content.length === 0) && eventId) {
6247
- const rawReader = new ConnectorDiagnosticSqlReader(store);
6248
- const rawRows = (() => {
6249
- try {
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
- }
6348
+ const rawRows = queryRows(new ConnectorDiagnosticSqlReader(store), "SELECT payload FROM raw WHERE event_id = ? LIMIT 1", [eventId]);
6349
+ const rawRow = rawRows instanceof Error ? null : rawRows[0];
6350
+ if (rawRow) content = typeof rawRow.payload === "string" ? rawRow.payload : null;
6259
6351
  }
6260
6352
  if (!content) {
6261
6353
  if (isJson) return c.json({ error: "event has no payload to replay" });
6262
6354
  return c.text("event has no payload to replay");
6263
6355
  }
6264
- const connectorName = connectorId ? targetChannel.connectors?.find((c) => c.id === connectorId)?.name : void 0;
6356
+ const connectorName = connectorOf(targetChannel, connectorId);
6265
6357
  const result = await funnel.publisher.publish(targetChannel.name, {
6266
6358
  content,
6267
6359
  connector: connectorName
@@ -6277,7 +6369,7 @@ examples:
6277
6369
  if (isJson) return c.json({ error: result.reason });
6278
6370
  return c.text(`error: ${result.reason}`);
6279
6371
  }
6280
- const preview = extractPreview(content);
6372
+ const preview = previewOf(content);
6281
6373
  if (isJson) return c.json({
6282
6374
  replayed: true,
6283
6375
  seq,
@@ -6287,103 +6379,6 @@ examples:
6287
6379
  return c.text(`replayed seq=${seq ?? "?"} → offset=${result.offset}${preview ? ` "${preview}"` : ""}`);
6288
6380
  });
6289
6381
  //#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
6382
  //#region lib/cli/routes/gateway.ts
6388
6383
  const groupHelp$1 = `funnel gateway — manage the funnel daemon
6389
6384
 
@@ -6411,7 +6406,7 @@ examples:
6411
6406
 
6412
6407
  see also: fnl debug --channel <name> (higher-level diagnosis with next-action hints)`;
6413
6408
  const renderGatewayStatus = async (c) => {
6414
- const status = c.var.funnel.gateway.getStatus();
6409
+ const status = c.env.funnel.gateway.getStatus();
6415
6410
  if (!status.running) throw new HTTPException(503, { message: "funnel gateway: not running" });
6416
6411
  const res = await fetch(`http://127.0.0.1:${status.port}/status`).catch(() => null);
6417
6412
  if (!res) return c.text(`funnel gateway: running (pid ${status.pid}) — health check failed`);
@@ -6453,7 +6448,7 @@ Reads /listeners from the running gateway daemon and prints the live registry.
6453
6448
 
6454
6449
  examples:
6455
6450
  funnel gateway listeners`), async (c) => {
6456
- const result = await c.var.funnel.listeners.list();
6451
+ const result = await c.env.funnel.listeners.list();
6457
6452
  if (result.state === "offline") throw new HTTPException(503, { message: "funnel gateway: not running" });
6458
6453
  if (result.state === "error") throw new HTTPException(503, { message: `funnel gateway: ${result.reason}` });
6459
6454
  if (result.listeners.length === 0) return c.text("funnel gateway: no running listeners");
@@ -6619,7 +6614,7 @@ const gatewaySqlHandler = factory.createHandlers(zValidator$1("query", z.object(
6619
6614
  limit: z.string().optional()
6620
6615
  }), sqlHelp), async (c) => {
6621
6616
  const query = c.req.valid("query");
6622
- const funnel = c.var.funnel;
6617
+ const funnel = c.env.funnel;
6623
6618
  let sql = null;
6624
6619
  let params = [];
6625
6620
  let resolvedChannelId = null;
@@ -6669,7 +6664,7 @@ examples:
6669
6664
  funnel gateway restart
6670
6665
  funnel gateway restart --no-caffeine`), async (c) => {
6671
6666
  const query = c.req.valid("query");
6672
- const result = await c.var.funnel.gateway.restart({ caffeinate: query["no-caffeine"] !== "true" });
6667
+ const result = await c.env.funnel.gateway.restart({ caffeinate: query["no-caffeine"] !== "true" });
6673
6668
  const lines = [];
6674
6669
  if (result.wasRunning) lines.push(result.stopped ? "funnel gateway: stopped" : "funnel gateway: failed to stop");
6675
6670
  if (result.stopped) lines.push(result.started ? "funnel gateway: started" : "funnel gateway: failed to start");
@@ -6690,7 +6685,7 @@ examples:
6690
6685
  funnel gateway run
6691
6686
  funnel gateway run --no-caffeine`), async (c) => {
6692
6687
  const query = c.req.valid("query");
6693
- const funnel = c.var.funnel;
6688
+ const funnel = c.env.funnel;
6694
6689
  const gatewayScript = resolveDaemonScript();
6695
6690
  const command = query["no-caffeine"] !== "true" && process.platform === "darwin" ? [
6696
6691
  "caffeinate",
@@ -6720,7 +6715,7 @@ examples:
6720
6715
  funnel gateway start --no-caffeine`;
6721
6716
  const gatewayStartHandler = factory.createHandlers(zValidator$1("query", z.object({ "no-caffeine": z.string().optional() }), startHelp), async (c) => {
6722
6717
  const query = c.req.valid("query");
6723
- const funnel = c.var.funnel;
6718
+ const funnel = c.env.funnel;
6724
6719
  if (funnel.gateway.isRunning()) {
6725
6720
  const status = funnel.gateway.getStatus();
6726
6721
  return c.text(`funnel gateway: already running (pid ${status.pid})`);
@@ -6747,7 +6742,7 @@ examples:
6747
6742
  funnel gateway status --json`), async (c) => {
6748
6743
  const query = c.req.valid("query");
6749
6744
  if (!(query.json === "true" || query.json === "")) return renderGatewayStatus(c);
6750
- const status = c.var.funnel.gateway.getStatus();
6745
+ const status = c.env.funnel.gateway.getStatus();
6751
6746
  if (!status.running) throw new HTTPException(503, { message: "funnel gateway: not running" });
6752
6747
  const res = await fetch(`http://127.0.0.1:${status.port}/status`).catch(() => null);
6753
6748
  if (!res) return c.json({
@@ -6771,12 +6766,29 @@ Terminates the process whose PID is stored in ~/.funnel/gateway.pid.
6771
6766
 
6772
6767
  examples:
6773
6768
  funnel gateway stop`), async (c) => {
6774
- const funnel = c.var.funnel;
6769
+ const funnel = c.env.funnel;
6775
6770
  if (!funnel.gateway.isRunning()) return c.text("funnel gateway: no running process");
6776
6771
  if (!await funnel.gateway.stop()) throw new HTTPException(500, { message: "funnel gateway: failed to stop" });
6777
6772
  return c.text("funnel gateway: stopped");
6778
6773
  });
6779
6774
  //#endregion
6775
+ //#region lib/cli/routes/profiles.add.ts
6776
+ const help$4 = `funnel profiles add — add a profile
6777
+
6778
+ usage: funnel profiles add <name> --path <path> --channel <channel-name> [recipe]
6779
+
6780
+ options:
6781
+ --path working directory passed to claude as cwd
6782
+ --channel channel name (resolved to channel id internally)
6783
+ --agent sub-agent name, prepended to the launch argv as --agent <name>
6784
+ --options extra launch argv as one whitespace-split string (e.g. "--brief")
6785
+ --env env vars layered under the process, as "KEY=VAL,KEY2=VAL2"
6786
+ --no-resume start a fresh claude session every launch (default resumes)
6787
+
6788
+ The launch recipe (--agent / --options / --env / --resume) lives on the
6789
+ profile; the channel only declares transport (connectors / delivery).`;
6790
+ const profilesAddHelpHandler = factory.createHandlers((c) => c.text(help$4));
6791
+ //#endregion
6780
6792
  //#region lib/cli/routes/parse-profile-recipe.ts
6781
6793
  /**
6782
6794
  * Turns the single-string CLI flags (`--agent`, `--options "<argv>"`,
@@ -6813,20 +6825,6 @@ const parseProfileRecipe = (query) => {
6813
6825
  };
6814
6826
  //#endregion
6815
6827
  //#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
6828
  const profilesAddHandler = factory.createHandlers(zValidator$1("param", z.object({ profile: z.string() })), zValidator$1("query", z.object({
6831
6829
  path: z.string(),
6832
6830
  channel: z.string(),
@@ -6835,10 +6833,10 @@ const profilesAddHandler = factory.createHandlers(zValidator$1("param", z.object
6835
6833
  env: z.string().optional(),
6836
6834
  resume: z.string().optional(),
6837
6835
  "no-resume": z.string().optional()
6838
- }), addHelp), (c) => {
6836
+ })), (c) => {
6839
6837
  const param = c.req.valid("param");
6840
6838
  const query = c.req.valid("query");
6841
- const funnel = c.var.funnel;
6839
+ const funnel = c.env.funnel;
6842
6840
  const channel = funnel.channels.get(query.channel);
6843
6841
  if (!channel) throw new HTTPException(400, { message: `channel "${query.channel}" not found` });
6844
6842
  const recipe = parseProfileRecipe(query);
@@ -6858,22 +6856,33 @@ usage: funnel profiles <name> as-default
6858
6856
 
6859
6857
  the first profile in the list is treated as the default for fnl claude.`), (c) => {
6860
6858
  const param = c.req.valid("param");
6861
- c.var.funnel.profiles.asDefault(param.profile);
6859
+ c.env.funnel.profiles.asDefault(param.profile);
6862
6860
  return c.text(`profile "${param.profile}" is now the default`);
6863
6861
  });
6864
6862
  //#endregion
6865
- //#region lib/cli/routes/profiles.$profile.rename.$newName.ts
6866
- const renameHelp = `funnel profiles rename — rename a profile
6863
+ //#region lib/cli/routes/profiles.rename.ts
6864
+ const help$3 = `funnel profiles rename — rename a profile
6865
+
6866
+ usage:
6867
+ funnel profiles rename <old> <new>
6868
+ funnel profiles <old> rename <new>`;
6869
+ const profilesRenameHelpHandler = factory.createHandlers((c) => c.text(help$3));
6870
+ //#endregion
6871
+ //#region lib/cli/routes/profiles.$profile.rename.ts
6872
+ const help$2 = `funnel profiles rename — rename a profile
6867
6873
 
6868
6874
  usage:
6869
6875
  funnel profiles rename <old> <new>
6870
6876
  funnel profiles <old> rename <new>`;
6877
+ const profilesProfileRenameHelpHandler = factory.createHandlers((c) => c.text(help$2));
6878
+ //#endregion
6879
+ //#region lib/cli/routes/profiles.$profile.rename.$newName.ts
6871
6880
  const profilesRenameHandler = factory.createHandlers(zValidator$1("param", z.object({
6872
6881
  profile: z.string(),
6873
6882
  newName: z.string()
6874
- })), zValidator$1("query", z.object({}), renameHelp), (c) => {
6883
+ })), zValidator$1("query", z.object({})), (c) => {
6875
6884
  const param = c.req.valid("param");
6876
- c.var.funnel.profiles.rename(param.profile, param.newName);
6885
+ c.env.funnel.profiles.rename(param.profile, param.newName);
6877
6886
  return c.text(`renamed profile "${param.profile}" to "${param.newName}"`);
6878
6887
  });
6879
6888
  //#endregion
@@ -6885,7 +6894,7 @@ usage: funnel profiles <name> run [additional claude args...]
6885
6894
  const RESERVED_KEYS = [];
6886
6895
  const profilesLaunchHandler = factory.createHandlers(zValidator$1("param", z.object({ profile: z.string() })), zValidator$1("query", z.object({}).passthrough(), launchHelp), async (c) => {
6887
6896
  const param = c.req.valid("param");
6888
- const funnel = c.var.funnel;
6897
+ const funnel = c.env.funnel;
6889
6898
  const profile = funnel.profiles.get(param.profile);
6890
6899
  if (!profile) throw new HTTPException(404, { message: `profile "${param.profile}" not found` });
6891
6900
  const exitCode = await funnel.claude.launch({
@@ -6900,18 +6909,21 @@ const profilesLaunchHandler = factory.createHandlers(zValidator$1("param", z.obj
6900
6909
  process.exit(exitCode);
6901
6910
  });
6902
6911
  //#endregion
6903
- //#region lib/cli/routes/profiles.remove.$profile.ts
6904
- const removeHelp = `funnel profiles remove — remove a profile
6912
+ //#region lib/cli/routes/profiles.remove.ts
6913
+ const help$1 = `funnel profiles remove — remove a profile
6905
6914
 
6906
6915
  usage: funnel profiles remove <name>`;
6907
- const profilesRemoveHandler = factory.createHandlers(zValidator$1("param", z.object({ profile: z.string() })), zValidator$1("query", z.object({}), removeHelp), (c) => {
6916
+ const profilesRemoveHelpHandler = factory.createHandlers((c) => c.text(help$1));
6917
+ //#endregion
6918
+ //#region lib/cli/routes/profiles.remove.$profile.ts
6919
+ const profilesRemoveHandler = factory.createHandlers(zValidator$1("param", z.object({ profile: z.string() })), zValidator$1("query", z.object({})), (c) => {
6908
6920
  const param = c.req.valid("param");
6909
- c.var.funnel.profiles.remove(param.profile);
6921
+ c.env.funnel.profiles.remove(param.profile);
6910
6922
  return c.text(`removed profile "${param.profile}"`);
6911
6923
  });
6912
6924
  //#endregion
6913
- //#region lib/cli/routes/profiles.set.$profile.ts
6914
- const setHelp = `funnel profiles <name> set — update a profile
6925
+ //#region lib/cli/routes/profiles.set.ts
6926
+ const help = `funnel profiles <name> set — update a profile
6915
6927
 
6916
6928
  usage: funnel profiles <name> set [--path <path>] [--channel <channel-name>] [recipe]
6917
6929
 
@@ -6925,6 +6937,9 @@ options:
6925
6937
 
6926
6938
  Only the flags you pass are changed; --agent and --options together replace
6927
6939
  the profile's whole options list.`;
6940
+ const profilesSetHelpHandler = factory.createHandlers((c) => c.text(help));
6941
+ //#endregion
6942
+ //#region lib/cli/routes/profiles.set.$profile.ts
6928
6943
  const profilesSetHandler = factory.createHandlers(zValidator$1("param", z.object({ profile: z.string() })), zValidator$1("query", z.object({
6929
6944
  path: z.string().optional(),
6930
6945
  channel: z.string().optional(),
@@ -6933,10 +6948,10 @@ const profilesSetHandler = factory.createHandlers(zValidator$1("param", z.object
6933
6948
  env: z.string().optional(),
6934
6949
  resume: z.string().optional(),
6935
6950
  "no-resume": z.string().optional()
6936
- }), setHelp), (c) => {
6951
+ })), (c) => {
6937
6952
  const param = c.req.valid("param");
6938
6953
  const query = c.req.valid("query");
6939
- const funnel = c.var.funnel;
6954
+ const funnel = c.env.funnel;
6940
6955
  const channel = query.channel !== void 0 ? funnel.channels.get(query.channel) : null;
6941
6956
  if (query.channel !== void 0 && !channel) throw new HTTPException(400, { message: `channel "${query.channel}" not found` });
6942
6957
  const recipe = parseProfileRecipe(query);
@@ -6971,7 +6986,7 @@ examples:
6971
6986
  funnel profiles add cto --path /repo/myapp --channel prod-inbox --agent pm --options "--brief"
6972
6987
  funnel profiles cto as-default
6973
6988
  funnel profiles cto run`), (c) => {
6974
- const profiles = c.var.funnel.profiles.list();
6989
+ const profiles = c.env.funnel.profiles.list();
6975
6990
  if (profiles.length === 0) return c.text("no profiles");
6976
6991
  const lines = profiles.map((profile, index) => {
6977
6992
  const tag = index === 0 ? " (default)" : "";
@@ -7092,7 +7107,7 @@ const statusHandler = factory.createHandlers(zValidator$1("query", z.object({
7092
7107
  interval: z.string().optional()
7093
7108
  }), statusHelp), async (c) => {
7094
7109
  const query = c.req.valid("query");
7095
- const funnel = c.var.funnel;
7110
+ const funnel = c.env.funnel;
7096
7111
  const isWatch = query.watch === "true" || query.watch === "";
7097
7112
  const intervalSec = Math.min(60, Math.max(1, query.interval ? Number(query.interval) : 3));
7098
7113
  if (!isWatch) {
@@ -7137,30 +7152,9 @@ const updateHandler = factory.createHandlers(zValidator$1("query", z.object({}),
7137
7152
  });
7138
7153
  //#endregion
7139
7154
  //#region lib/cli/routes/index.ts
7140
- const helpRoute = (text) => factory.createHandlers((c) => c.text(text));
7141
- /**
7142
- * Build the CLI Hono app wired to a specific Funnel instance.
7143
- * Exposed so library consumers can mount the same routes their `fnl` CLI
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());
7155
+ const routes = factory.createApp().onError((error, c) => {
7156
+ if (error instanceof HTTPException) return c.text(`error: ${error.message}`, error.status);
7157
+ return c.text(`error: ${error instanceof Error ? error.message : String(error)}`, 400);
7158
+ }).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
7159
  //#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, app as cliApp, connectorConfigSchema, connectorConnectionEventSchema, connectorProcessedEventSchema, connectorRawEventSchema, connectorSpecSchema, createCliApp, createSettings, discordConnectorSchema, factory, funnelEventSchema, funnelJsonSchema, ghConnectorSchema, localConfigSchema, profileConfigSchema, profileSpecSchema, publishRequestSchema, publishResponseSchema, queryToCliArgs, resolveFunnelDir, resolveFunnelPort, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema, settingsSchema, slackConnectorSchema, startChannelServer, toRequest };
7160
+ 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 };