@interactive-inc/claude-funnel 0.53.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +3 -3
  2. package/dist/bin.js +1276 -520
  3. package/dist/claude.d.ts +22 -5
  4. package/dist/claude.js +455 -168
  5. package/dist/{connector-adapter-CePYBTgW.d.ts → connector-adapter-1PxjN-Uk.d.ts} +1 -1
  6. package/dist/{connector-adapter-D5Utumgz.js → connector-adapter-qwXLjQId.js} +1 -1
  7. package/dist/{connector-listener-DU54DN-f.js → connector-listener-CpHBecCj.js} +1 -1
  8. package/dist/connectors/discord.d.ts +6 -6
  9. package/dist/connectors/discord.js +2 -2
  10. package/dist/connectors/gh.d.ts +6 -6
  11. package/dist/connectors/gh.js +2 -2
  12. package/dist/connectors/schedule.d.ts +12 -2
  13. package/dist/connectors/schedule.js +2 -2
  14. package/dist/connectors/slack.d.ts +3 -3
  15. package/dist/connectors/slack.js +2 -2
  16. package/dist/{connector-diagnostic-log-yTOojKUR.d.ts → diagnostic-log-Bxe7Bbvw.d.ts} +2 -2
  17. package/dist/diagnostic-sql-reader-CzYgZpq2.js +83 -0
  18. package/dist/diagnostics.d.ts +2 -0
  19. package/dist/diagnostics.js +2 -0
  20. package/dist/{discord-connector-schema-CBDyGdOI.js → discord-connector-schema-B_N6IXLz.js} +1 -1
  21. package/dist/{discord-connector-schema-R0Uu-3ns.d.ts → discord-connector-schema-CPgcZkXh.d.ts} +1 -1
  22. package/dist/{discord-listener-_jSE3HsQ.js → discord-listener-C0MoKdQO.js} +6 -6
  23. package/dist/docs.d.ts +2 -0
  24. package/dist/docs.js +2 -0
  25. package/dist/doctor.d.ts +2 -0
  26. package/dist/doctor.js +2 -0
  27. package/dist/{file-process-guard-DMeLB6Zd.d.ts → file-process-guard-DI1742H5.d.ts} +5 -4
  28. package/dist/funnel-diagnostics-BpKYrMSu.js +300 -0
  29. package/dist/funnel-diagnostics-qWy5tPSq.d.ts +176 -0
  30. package/dist/funnel-docs-dXPokzr5.d.ts +18 -0
  31. package/dist/funnel-docs-ng5K8w4j.js +653 -0
  32. package/dist/funnel-doctor-BF3Rdgk0.d.ts +34 -0
  33. package/dist/funnel-doctor-CApCezTq.js +82 -0
  34. package/dist/funnel-recovery-BUBsu7WX.d.ts +101 -0
  35. package/dist/funnel-recovery-D9CxD5Zs.js +134 -0
  36. package/dist/gateway/daemon.js +810 -211
  37. package/dist/{gateway-base-url-ssk_He5G.js → gateway-base-url-6foMXfFf.js} +5 -5
  38. package/dist/gateway.d.ts +2 -2
  39. package/dist/gateway.js +2 -2
  40. package/dist/{gh-connector-schema-eoTtHbY6.d.ts → gh-connector-schema-CU1ojfIF.d.ts} +1 -1
  41. package/dist/{gh-connector-schema-o3Q1-ojL.js → gh-connector-schema-DUcZgN2Q.js} +1 -1
  42. package/dist/{gh-listener-DH-fClQm.js → gh-listener-Dsx6AmhH.js} +5 -5
  43. package/dist/{index-DF5VmCPJ.d.ts → index-CrngHrne.d.ts} +104 -607
  44. package/dist/index.d.ts +16 -11
  45. package/dist/index.js +508 -972
  46. package/dist/{local-config-json-schema-D8i-BogY.js → local-config-json-schema-DE1zkMcb.js} +12 -8
  47. package/dist/{local-config-sync-Cq39mT6p.d.ts → local-config-sync-B8b04LrZ.d.ts} +21 -16
  48. package/dist/local-config.d.ts +2 -2
  49. package/dist/local-config.js +2 -2
  50. package/dist/{memory-connector-diagnostic-log-COUWCsT_.js → memory-diagnostic-log-BZ1VD80X.js} +26 -95
  51. package/dist/{memory-token-prompter-CKV7VBM5.d.ts → memory-token-prompter-Lo3YRDzq.d.ts} +4 -4
  52. package/dist/{memory-token-prompter-Q7Snwsv2.js → memory-token-prompter-vBXxY20-.js} +2 -2
  53. package/dist/{profiles-f0mNmEyP.d.ts → profiles-EHTeCOqB.d.ts} +3 -2
  54. package/dist/profiles.d.ts +1 -1
  55. package/dist/profiles.js +1 -1
  56. package/dist/recovery.d.ts +2 -0
  57. package/dist/recovery.js +2 -0
  58. package/dist/{resolve-connector-token-BHmZLRrV.js → resolve-connector-token-CczqG_Ig.js} +1 -1
  59. package/dist/{schedule-connector-schema-iCI61gzU.js → schedule-connector-schema-B_xO5z5B.js} +1 -1
  60. package/dist/{schedule-listener-CUyUFFR1.d.ts → schedule-listener-DKh0hnkK.d.ts} +5 -5
  61. package/dist/{schedule-listener-ePAjians.js → schedule-listener-DP9Jhc6U.js} +14 -4
  62. package/dist/settings-reader-CBrgz01o.d.ts +18 -0
  63. package/dist/{settings-reader-BSU6JyvM.d.ts → settings-schema-zhnMIa8I.d.ts} +1 -16
  64. package/dist/{slack-connector-schema-BCNWluHM.js → slack-connector-schema-C1zEf4TG.js} +1 -1
  65. package/dist/{slack-listener-Bv5xI9gC.d.ts → slack-listener-COQA8wAZ.d.ts} +4 -4
  66. package/dist/{slack-listener-ClQuHhEF.js → slack-listener-DUKPcpJH.js} +7 -7
  67. package/dist/{mcp-QeNCBhOD.js → yaml-render-OhUN-qkS.js} +52 -34
  68. package/package.json +21 -1
  69. /package/dist/{file-system-BeOKXjlV.d.ts → file-system-Wub9Nto4.d.ts} +0 -0
  70. /package/dist/{process-runner-DfniuWVU.d.ts → process-runner-D5I_jhYQ.d.ts} +0 -0
  71. /package/dist/{profiles-wMRnjSid.js → profiles-MnXvYfZF.js} +0 -0
@@ -1,7 +1,7 @@
1
1
  import { join } from "node:path";
2
2
  import { z } from "zod";
3
3
  import { stderr, stdin } from "node:process";
4
- //#region lib/engine/local-config/local-config-schema.ts
4
+ //#region lib/services/local-config/local-config-schema.ts
5
5
  /**
6
6
  * Per-repo launch config (`funnel.json`).
7
7
  *
@@ -79,7 +79,7 @@ const localConfigSchema = z.object({
79
79
  });
80
80
  const LOCAL_CONFIG_FILENAME = "funnel.json";
81
81
  //#endregion
82
- //#region lib/engine/local-config/local-config.ts
82
+ //#region lib/services/local-config/local-config.ts
83
83
  /**
84
84
  * Reads `funnel.json` from a directory. Returns `null` when the file is
85
85
  * absent so callers can fall through to other resolution paths (default
@@ -122,7 +122,7 @@ var FunnelLocalConfig = class {
122
122
  }
123
123
  };
124
124
  //#endregion
125
- //#region lib/connectors/either-token.ts
125
+ //#region lib/engine/connectors/either-token.ts
126
126
  function botTokenSlot(slot) {
127
127
  if (slot.env !== void 0) return { botTokenEnv: slot.env };
128
128
  if (slot.literal !== void 0) return { botToken: slot.literal };
@@ -143,18 +143,22 @@ function appTokenSlot(slot) {
143
143
  */
144
144
  var FunnelTokenPrompter = class {};
145
145
  //#endregion
146
- //#region lib/engine/local-config/local-config-sync.ts
146
+ //#region lib/services/local-config/local-config-sync.ts
147
147
  /**
148
148
  * Reconciles a single funnel.json channel spec with `~/.funnel/settings.json`.
149
149
  * The spec is the source of truth for the channel it declares:
150
150
  *
151
151
  * - missing channel → created
152
152
  * - declared connector matched by name → tokens reconciled
153
- * - declared connector matched by token in the same channel under a
154
- * different name → renamed in place (then tokens reconciled)
155
- * - declared connector with no match → added
153
+ * - declared connector with no name match added (prompting for its tokens)
156
154
  * - any connector left in the channel that the spec did not touch → removed
157
155
  *
156
+ * Connectors are matched by NAME only — there is no rename-by-token path. A spec
157
+ * that renames a connector (same token, new name) is reconciled as "add the new
158
+ * name, remove the old one". Because the collision check runs at add time while
159
+ * the old connector is still present, re-using its token at the new name throws
160
+ * a token-collision error; remove the old connector via the CLI first.
161
+ *
158
162
  * Removal only fires when the channel spec has a `connectors` field. An
159
163
  * absent field means "do not manage connectors from here" and leaves
160
164
  * everything in `~/.funnel` alone. Other channels in funnel.json (not
@@ -443,7 +447,7 @@ var NodeFunnelTokenPrompter = class extends FunnelTokenPrompter {
443
447
  }
444
448
  };
445
449
  //#endregion
446
- //#region lib/engine/local-config/local-config-json-schema.ts
450
+ //#region lib/services/local-config/local-config-json-schema.ts
447
451
  /**
448
452
  * Generates the JSON Schema (draft 2020-12) for `funnel.json`. Useful for
449
453
  * `$schema` references in committed `funnel.json` files so editors can give
@@ -1,13 +1,14 @@
1
- import { i as ChannelDeliveryMode, n as FunnelIdGenerator, r as ChannelConfig, t as FunnelSettingsReader } from "./settings-reader-BSU6JyvM.js";
2
- import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog } from "./connector-diagnostic-log-yTOojKUR.js";
3
- import { r as FunnelProcessRunner } from "./process-runner-DfniuWVU.js";
4
- import { n as FunnelFileSystem } from "./file-system-BeOKXjlV.js";
5
- import { n as FunnelConnectorAdapter, t as CallInput } from "./connector-adapter-CePYBTgW.js";
6
- import { a as ScheduleEntry, n as ScheduleOnFired } from "./schedule-listener-CUyUFFR1.js";
7
- import { n as SlackOnAppCreated, r as SlackPreprocessEvent } from "./slack-listener-Bv5xI9gC.js";
1
+ import { n as ChannelDeliveryMode, t as ChannelConfig } from "./settings-schema-zhnMIa8I.js";
2
+ import { n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-CBrgz01o.js";
3
+ import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog } from "./diagnostic-log-Bxe7Bbvw.js";
4
+ import { r as FunnelProcessRunner } from "./process-runner-D5I_jhYQ.js";
5
+ import { n as FunnelFileSystem } from "./file-system-Wub9Nto4.js";
6
+ import { n as FunnelConnectorAdapter, t as CallInput } from "./connector-adapter-1PxjN-Uk.js";
7
+ import { a as ScheduleEntry, n as ScheduleOnFired } from "./schedule-listener-DKh0hnkK.js";
8
+ import { n as SlackOnAppCreated, r as SlackPreprocessEvent } from "./slack-listener-COQA8wAZ.js";
8
9
  import { z } from "zod";
9
10
 
10
- //#region lib/engine/local-config/local-config-schema.d.ts
11
+ //#region lib/services/local-config/local-config-schema.d.ts
11
12
  declare const connectorSpecSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
12
13
  type: z.ZodLiteral<"slack">;
13
14
  name: z.ZodString;
@@ -83,7 +84,7 @@ declare const localConfigSchema: z.ZodObject<{
83
84
  type LocalConfig = z.infer<typeof localConfigSchema>;
84
85
  declare const LOCAL_CONFIG_FILENAME = "funnel.json";
85
86
  //#endregion
86
- //#region lib/engine/local-config/local-config.d.ts
87
+ //#region lib/services/local-config/local-config.d.ts
87
88
  type Deps$3 = {
88
89
  fs: FunnelFileSystem;
89
90
  };
@@ -100,7 +101,7 @@ declare class FunnelLocalConfig {
100
101
  private assertProfilesValid;
101
102
  }
102
103
  //#endregion
103
- //#region lib/connectors/connector-config-schema.d.ts
104
+ //#region lib/engine/connectors/connector-config-schema.d.ts
104
105
  declare const connectorConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
105
106
  id: z.ZodString;
106
107
  name: z.ZodString;
@@ -148,7 +149,7 @@ declare const connectorConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
148
149
  type ConnectorConfig = z.infer<typeof connectorConfigSchema>;
149
150
  type ConnectorType = ConnectorConfig["type"];
150
151
  //#endregion
151
- //#region lib/connectors/either-token.d.ts
152
+ //#region lib/engine/connectors/either-token.d.ts
152
153
  /**
153
154
  * A single connector token slot is supplied one of two non-empty ways, which
154
155
  * are mutually exclusive, or left empty:
@@ -169,7 +170,7 @@ type ConnectorType = ConnectorConfig["type"];
169
170
  */
170
171
  type EitherToken<Literal extends string, Env extends string> = (Partial<Record<Literal, string>> & Partial<Record<Env, never>>) | (Partial<Record<Literal, never>> & Partial<Record<Env, string>>);
171
172
  //#endregion
172
- //#region lib/connectors/connector-factory.d.ts
173
+ //#region lib/engine/connectors/connector-factory.d.ts
173
174
  type SlackListenerOptions = {
174
175
  onAppCreated?: SlackOnAppCreated;
175
176
  preprocessEvent?: SlackPreprocessEvent;
@@ -337,7 +338,7 @@ declare abstract class FunnelTokenPrompter {
337
338
  abstract promptSecret(label: string): Promise<string>;
338
339
  }
339
340
  //#endregion
340
- //#region lib/engine/local-config/local-config-sync.d.ts
341
+ //#region lib/services/local-config/local-config-sync.d.ts
341
342
  type Deps = {
342
343
  channels: FunnelChannels;
343
344
  prompter: FunnelTokenPrompter;
@@ -356,11 +357,15 @@ type LocalConfigSyncResult = {
356
357
  *
357
358
  * - missing channel → created
358
359
  * - declared connector matched by name → tokens reconciled
359
- * - declared connector matched by token in the same channel under a
360
- * different name → renamed in place (then tokens reconciled)
361
- * - declared connector with no match → added
360
+ * - declared connector with no name match added (prompting for its tokens)
362
361
  * - any connector left in the channel that the spec did not touch → removed
363
362
  *
363
+ * Connectors are matched by NAME only — there is no rename-by-token path. A spec
364
+ * that renames a connector (same token, new name) is reconciled as "add the new
365
+ * name, remove the old one". Because the collision check runs at add time while
366
+ * the old connector is still present, re-using its token at the new name throws
367
+ * a token-collision error; remove the old connector via the CLI first.
368
+ *
364
369
  * Removal only fires when the channel spec has a `connectors` field. An
365
370
  * absent field means "do not manage connectors from here" and leaves
366
371
  * everything in `~/.funnel` alone. Other channels in funnel.json (not
@@ -1,3 +1,3 @@
1
- import { C as profileSpecSchema, S as localConfigSchema, _ as LOCAL_CONFIG_FILENAME, b as channelSpecSchema, g as ConnectorSpec, h as ChannelSpec, i as FunnelTokenPrompter, m as FunnelLocalConfig, n as FunnelLocalConfigSync, r as LocalConfigSyncResult, t as ConnectorSyncOutcome, v as LocalConfig, x as connectorSpecSchema, y as ProfileSpec } from "./local-config-sync-Cq39mT6p.js";
2
- import { i as funnelJsonSchema, n as NodeFunnelTokenPrompter, r as FunnelLocalConfigWriter, t as MemoryFunnelTokenPrompter } from "./memory-token-prompter-CKV7VBM5.js";
1
+ import { C as profileSpecSchema, S as localConfigSchema, _ as LOCAL_CONFIG_FILENAME, b as channelSpecSchema, g as ConnectorSpec, h as ChannelSpec, i as FunnelTokenPrompter, m as FunnelLocalConfig, n as FunnelLocalConfigSync, r as LocalConfigSyncResult, t as ConnectorSyncOutcome, v as LocalConfig, x as connectorSpecSchema, y as ProfileSpec } from "./local-config-sync-B8b04LrZ.js";
2
+ import { i as funnelJsonSchema, n as NodeFunnelTokenPrompter, r as FunnelLocalConfigWriter, t as MemoryFunnelTokenPrompter } from "./memory-token-prompter-Lo3YRDzq.js";
3
3
  export { ChannelSpec, ConnectorSpec, ConnectorSyncOutcome, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLocalConfigWriter, FunnelTokenPrompter, LOCAL_CONFIG_FILENAME, LocalConfig, LocalConfigSyncResult, MemoryFunnelTokenPrompter, NodeFunnelTokenPrompter, ProfileSpec, channelSpecSchema, connectorSpecSchema, funnelJsonSchema, localConfigSchema, profileSpecSchema };
@@ -1,3 +1,3 @@
1
- import { a as FunnelLocalConfig, c as connectorSpecSchema, i as FunnelTokenPrompter, l as localConfigSchema, n as NodeFunnelTokenPrompter, o as LOCAL_CONFIG_FILENAME, r as FunnelLocalConfigSync, s as channelSpecSchema, t as funnelJsonSchema, u as profileSpecSchema } from "./local-config-json-schema-D8i-BogY.js";
2
- import { n as FunnelLocalConfigWriter, t as MemoryFunnelTokenPrompter } from "./memory-token-prompter-Q7Snwsv2.js";
1
+ import { a as FunnelLocalConfig, c as connectorSpecSchema, i as FunnelTokenPrompter, l as localConfigSchema, n as NodeFunnelTokenPrompter, o as LOCAL_CONFIG_FILENAME, r as FunnelLocalConfigSync, s as channelSpecSchema, t as funnelJsonSchema, u as profileSpecSchema } from "./local-config-json-schema-DE1zkMcb.js";
2
+ import { n as FunnelLocalConfigWriter, t as MemoryFunnelTokenPrompter } from "./memory-token-prompter-vBXxY20-.js";
3
3
  export { FunnelLocalConfig, FunnelLocalConfigSync, FunnelLocalConfigWriter, FunnelTokenPrompter, LOCAL_CONFIG_FILENAME, MemoryFunnelTokenPrompter, NodeFunnelTokenPrompter, channelSpecSchema, connectorSpecSchema, funnelJsonSchema, localConfigSchema, profileSpecSchema };
@@ -1,13 +1,14 @@
1
- import { n as NodeFunnelProcessRunner } from "./gh-connector-schema-o3Q1-ojL.js";
1
+ import { n as NodeFunnelProcessRunner } from "./gh-connector-schema-DUcZgN2Q.js";
2
2
  import { t as NodeFunnelFileSystem } from "./node-file-system-BcrmWN9I.js";
3
- import { r as FUNNEL_DIR, s as resolveFunnelPort, t as gatewayLoopbackUrl } from "./gateway-base-url-ssk_He5G.js";
3
+ import { r as FUNNEL_DIR, s as resolveFunnelPort, t as gatewayLoopbackUrl } from "./gateway-base-url-6foMXfFf.js";
4
+ import { t as ConnectorDiagnosticSqlReader } from "./diagnostic-sql-reader-CzYgZpq2.js";
4
5
  import { dirname, join } from "node:path";
5
6
  import { chmodSync, existsSync, mkdirSync } from "node:fs";
6
7
  import { z } from "zod";
7
8
  import { homedir, tmpdir } from "node:os";
9
+ import { Database } from "bun:sqlite";
8
10
  import { timingSafeEqual } from "node:crypto";
9
11
  import { createFactory } from "hono/factory";
10
- import { Database } from "bun:sqlite";
11
12
  import { HTTPException } from "hono/http-exception";
12
13
  import { zValidator } from "@hono/zod-validator";
13
14
  //#region lib/engine/settings/tmp-dir.ts
@@ -328,7 +329,7 @@ var FunnelBroadcaster = class {
328
329
  }
329
330
  };
330
331
  //#endregion
331
- //#region lib/gateway/funnel-event-log.ts
332
+ //#region lib/gateway/event-log/event-log.ts
332
333
  /**
333
334
  * Replayable event payload persisted by the gateway. Domain events the
334
335
  * broadcaster emits to WS clients land here so reconnects across daemon
@@ -647,7 +648,7 @@ function toRecord(row) {
647
648
  };
648
649
  }
649
650
  //#endregion
650
- //#region lib/gateway/sqlite-funnel-event-log.ts
651
+ //#region lib/gateway/event-log/sqlite-event-log.ts
651
652
  const MAX_CONTENT_CHARS = 2e3;
652
653
  /**
653
654
  * SQLite-backed `FunnelEventLog`. One indexed table holds every broadcaster
@@ -669,9 +670,11 @@ const MAX_CONTENT_CHARS = 2e3;
669
670
  var SqliteFunnelEventLog = class extends FunnelEventLog {
670
671
  sink;
671
672
  now;
673
+ logger;
672
674
  constructor(props) {
673
675
  super();
674
676
  this.now = props.now ?? (() => Date.now());
677
+ this.logger = props.logger;
675
678
  this.sink = new LeucoLoggerSqliteSink({
676
679
  path: props.path,
677
680
  indexes: ["channel_id", "connector_id"],
@@ -699,11 +702,15 @@ var SqliteFunnelEventLog = class extends FunnelEventLog {
699
702
  connector_id: record.connectorId,
700
703
  meta: record.meta
701
704
  };
702
- this.sink.write({
705
+ const result = this.sink.write({
703
706
  seq: record.offset,
704
707
  ts: this.now(),
705
708
  event
706
709
  });
710
+ if (result instanceof Error) this.logger?.error("event log write failed", {
711
+ offset: record.offset,
712
+ error: result.message
713
+ });
707
714
  }
708
715
  /**
709
716
  * Returns events with offset > since. Filtering by channel/connector is
@@ -1119,87 +1126,6 @@ const channelsPublishHandler = factory.createHandlers(zParam(z.object({ channel:
1119
1126
  return c.json(response);
1120
1127
  });
1121
1128
  //#endregion
1122
- //#region lib/gateway/connector-diagnostic-sql-reader.ts
1123
- /**
1124
- * Read-only SQL surface over the three diagnostic tables, for Claude to query
1125
- * the log with arbitrary `SELECT`s. It opens all files read-only and exposes
1126
- * three views — `raw`, `processed`, `connection` — that hide the storage
1127
- * details (the physical table is `leuco_log` and each row's columns live
1128
- * inside a JSON `event` blob): the views surface the columns as plain fields,
1129
- * with `payload` already pulled out of the nested JSON.
1130
- *
1131
- * The tables are separate files. `raw` and `processed` share an `event_id`,
1132
- * so a `JOIN` answers "the event arrived, but what verdict did it get?";
1133
- * `connection` answers the other half — "did the listener ever connect at
1134
- * all?". Writes are impossible: the connection is read-only and `query`
1135
- * rejects anything but a single `SELECT`.
1136
- */
1137
- var ConnectorDiagnosticSqlReader = class {
1138
- db;
1139
- constructor(props) {
1140
- const db = new Database(props.rawPath, { readonly: true });
1141
- try {
1142
- db.run("PRAGMA busy_timeout = 500");
1143
- db.prepare("ATTACH DATABASE ? AS processeddb").run(props.processedPath);
1144
- db.prepare("ATTACH DATABASE ? AS connectiondb").run(props.connectionPath);
1145
- db.run(rawViewSql);
1146
- db.run(processedViewSql);
1147
- db.run(connectionViewSql);
1148
- } catch (error) {
1149
- db.close();
1150
- throw error;
1151
- }
1152
- this.db = db;
1153
- Object.freeze(this);
1154
- }
1155
- /**
1156
- * Run one read-only `SELECT` and return the rows. Returns an `Error` (rather
1157
- * than throwing) for a non-SELECT statement or a SQL error, so the caller
1158
- * can surface the message without a stack trace.
1159
- */
1160
- query(sql, params = []) {
1161
- const trimmed = sql.trim().replace(/;$/, "").trim();
1162
- if (!/^select\b/i.test(trimmed)) return /* @__PURE__ */ new Error("only a single SELECT statement is allowed");
1163
- if (trimmed.includes(";")) return /* @__PURE__ */ new Error("only a single statement is allowed (remove the ';')");
1164
- try {
1165
- return this.db.prepare(trimmed).all(...params);
1166
- } catch (error) {
1167
- return error instanceof Error ? error : new Error(String(error));
1168
- }
1169
- }
1170
- close() {
1171
- this.db.close();
1172
- }
1173
- };
1174
- const rawViewSql = `CREATE TEMP VIEW raw AS SELECT
1175
- seq,
1176
- ts,
1177
- json_extract(event, '$.event_id') AS event_id,
1178
- json_extract(event, '$.type') AS type,
1179
- json_extract(event, '$.connector_id') AS connector_id,
1180
- json_extract(event, '$.channel_id') AS channel_id,
1181
- json_extract(event, '$.payload') AS payload
1182
- FROM main.leuco_log`;
1183
- const processedViewSql = `CREATE TEMP VIEW processed AS SELECT
1184
- seq,
1185
- ts,
1186
- json_extract(event, '$.event_id') AS event_id,
1187
- json_extract(event, '$.type') AS type,
1188
- json_extract(event, '$.connector_id') AS connector_id,
1189
- json_extract(event, '$.channel_id') AS channel_id,
1190
- json_extract(event, '$.outcome') AS outcome,
1191
- json_extract(event, '$.payload') AS payload
1192
- FROM processeddb.leuco_log`;
1193
- const connectionViewSql = `CREATE TEMP VIEW connection AS SELECT
1194
- seq,
1195
- ts,
1196
- json_extract(event, '$.type') AS type,
1197
- json_extract(event, '$.connector_id') AS connector_id,
1198
- json_extract(event, '$.channel_id') AS channel_id,
1199
- json_extract(event, '$.status') AS status,
1200
- json_extract(event, '$.detail') AS detail
1201
- FROM connectiondb.leuco_log`;
1202
- //#endregion
1203
1129
  //#region lib/gateway/routes/debug.ts
1204
1130
  const extractPreview = (payload) => {
1205
1131
  if (typeof payload !== "string" || payload.length === 0) return null;
@@ -1420,7 +1346,11 @@ const statusHandler = factory.createHandlers((c) => {
1420
1346
  //#endregion
1421
1347
  //#region lib/gateway/routes/index.ts
1422
1348
  function buildGatewayRoutes() {
1423
- return factory.createApp().get("/health", ...healthHandler).get("/status", ...statusHandler).get("/debug", ...debugHandler).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);
1349
+ return factory.createApp().onError((error, c) => {
1350
+ if (error instanceof HTTPException) return error.getResponse();
1351
+ const message = error instanceof Error ? error.message : String(error);
1352
+ return c.json({ error: message }, 500);
1353
+ }).get("/health", ...healthHandler).get("/status", ...statusHandler).get("/debug", ...debugHandler).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);
1424
1354
  }
1425
1355
  const gatewayRoutes = buildGatewayRoutes();
1426
1356
  //#endregion
@@ -1486,7 +1416,8 @@ var FunnelGatewayServer = class {
1486
1416
  if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
1487
1417
  this.eventLog = new SqliteFunnelEventLog({
1488
1418
  path: this.dbPath,
1489
- now: this.nowMs
1419
+ now: this.nowMs,
1420
+ logger: this.logger
1490
1421
  });
1491
1422
  }
1492
1423
  this.broadcaster = new FunnelBroadcaster({
@@ -1724,7 +1655,7 @@ var FunnelGatewayServer = class {
1724
1655
  return { offset: event.offset };
1725
1656
  }
1726
1657
  lookupChannelId(channelName) {
1727
- return this.channels.get(channelName)?.id ?? null;
1658
+ return this.channels.get(channelName)?.id ?? this.channels.getById(channelName)?.id ?? null;
1728
1659
  }
1729
1660
  lookupConnectorId(channelId, connectorName) {
1730
1661
  return this.channels.getById(channelId)?.connectors.find((c) => c.name === connectorName)?.id ?? null;
@@ -1806,7 +1737,7 @@ function channelWsProtocols(token) {
1806
1737
  return [`funnel.token.${token}`];
1807
1738
  }
1808
1739
  //#endregion
1809
- //#region lib/gateway/memory-funnel-event-log.ts
1740
+ //#region lib/gateway/event-log/memory-event-log.ts
1810
1741
  /**
1811
1742
  * In-process `FunnelEventLog` backed by a plain array. Used by tests and by
1812
1743
  * embedders that do not need durability — replay works within the process
@@ -1849,7 +1780,7 @@ var MemoryFunnelEventLog = class extends FunnelEventLog {
1849
1780
  close() {}
1850
1781
  };
1851
1782
  //#endregion
1852
- //#region lib/gateway/connector-diagnostic-log.ts
1783
+ //#region lib/gateway/diagnostic-log/diagnostic-log.ts
1853
1784
  /**
1854
1785
  * Points in the listener's connection lifecycle. The single source of truth
1855
1786
  * for the value set: the `status` column schema, the `ConnectorConnectionStatus`
@@ -1940,7 +1871,7 @@ const connectorConnectionEventSchema = z.object({
1940
1871
  */
1941
1872
  var ConnectorDiagnosticLog = class {};
1942
1873
  //#endregion
1943
- //#region lib/gateway/sqlite-connector-diagnostic-log.ts
1874
+ //#region lib/gateway/diagnostic-log/sqlite-diagnostic-log.ts
1944
1875
  /**
1945
1876
  * Cap on a raw payload kept verbatim. The point of the raw table is to see
1946
1877
  * what Slack/Discord actually sent, and a typical event is a few KB — so 256
@@ -2196,7 +2127,7 @@ const headFields = (payload) => {
2196
2127
  }
2197
2128
  };
2198
2129
  //#endregion
2199
- //#region lib/gateway/memory-connector-diagnostic-log.ts
2130
+ //#region lib/gateway/diagnostic-log/memory-diagnostic-log.ts
2200
2131
  /**
2201
2132
  * In-process `ConnectorDiagnosticLog` backed by one array per table. Used by tests
2202
2133
  * and embedders that do not need durability. Like the SQLite log it keeps
@@ -2273,4 +2204,4 @@ const takeRecent = (events, limit) => {
2273
2204
  return events.slice(-limit);
2274
2205
  };
2275
2206
  //#endregion
2276
- export { funnelTmpDir as C, publishResponseSchema as S, FunnelEventLog as _, connectorConnectionEventSchema as a, FunnelChannelPublisher as b, MemoryFunnelEventLog as c, DEFAULT_GATEWAY_TOKEN_PATH as d, FunnelGatewayToken as f, SqliteFunnelEventLog as g, FunnelListenerSupervisor as h, ConnectorDiagnosticLog as i, channelWsProtocols as l, ConnectorDiagnosticSqlReader as m, SqliteConnectorDiagnosticLog as n, connectorProcessedEventSchema as o, FunnelGatewayServer as p, CONNECTOR_CONNECTION_STATUSES as r, connectorRawEventSchema as s, MemoryConnectorDiagnosticLog as t, channelWsUrl as u, funnelEventSchema as v, publishRequestSchema as x, FunnelBroadcaster as y };
2207
+ export { funnelTmpDir as C, publishResponseSchema as S, funnelEventSchema as _, connectorConnectionEventSchema as a, FunnelChannelPublisher as b, MemoryFunnelEventLog as c, DEFAULT_GATEWAY_TOKEN_PATH as d, FunnelGatewayToken as f, FunnelEventLog as g, SqliteFunnelEventLog as h, ConnectorDiagnosticLog as i, channelWsProtocols as l, FunnelListenerSupervisor as m, SqliteConnectorDiagnosticLog as n, connectorProcessedEventSchema as o, FunnelGatewayServer as p, CONNECTOR_CONNECTION_STATUSES as r, connectorRawEventSchema as s, MemoryConnectorDiagnosticLog as t, channelWsUrl as u, FunnelBroadcaster as v, publishRequestSchema as x, requireBearerToken as y };
@@ -1,7 +1,7 @@
1
- import { n as FunnelFileSystem } from "./file-system-BeOKXjlV.js";
2
- import { i as FunnelTokenPrompter } from "./local-config-sync-Cq39mT6p.js";
1
+ import { n as FunnelFileSystem } from "./file-system-Wub9Nto4.js";
2
+ import { i as FunnelTokenPrompter } from "./local-config-sync-B8b04LrZ.js";
3
3
 
4
- //#region lib/engine/local-config/local-config-json-schema.d.ts
4
+ //#region lib/services/local-config/local-config-json-schema.d.ts
5
5
  /**
6
6
  * Generates the JSON Schema (draft 2020-12) for `funnel.json`. Useful for
7
7
  * `$schema` references in committed `funnel.json` files so editors can give
@@ -10,7 +10,7 @@ import { i as FunnelTokenPrompter } from "./local-config-sync-Cq39mT6p.js";
10
10
  */
11
11
  declare const funnelJsonSchema: () => Record<string, unknown>;
12
12
  //#endregion
13
- //#region lib/engine/local-config/local-config-writer.d.ts
13
+ //#region lib/services/local-config/local-config-writer.d.ts
14
14
  type Deps = {
15
15
  fs: FunnelFileSystem;
16
16
  };
@@ -1,6 +1,6 @@
1
- import { i as FunnelTokenPrompter, o as LOCAL_CONFIG_FILENAME } from "./local-config-json-schema-D8i-BogY.js";
1
+ import { i as FunnelTokenPrompter, o as LOCAL_CONFIG_FILENAME } from "./local-config-json-schema-DE1zkMcb.js";
2
2
  import { join } from "node:path";
3
- //#region lib/engine/local-config/local-config-writer.ts
3
+ //#region lib/services/local-config/local-config-writer.ts
4
4
  const isRecord = (value) => {
5
5
  return typeof value === "object" && value !== null && !Array.isArray(value);
6
6
  };
@@ -1,5 +1,6 @@
1
- import { a as ProfileConfig, n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-BSU6JyvM.js";
2
- import { n as FunnelFileSystem } from "./file-system-BeOKXjlV.js";
1
+ import { r as ProfileConfig } from "./settings-schema-zhnMIa8I.js";
2
+ import { n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-CBrgz01o.js";
3
+ import { n as FunnelFileSystem } from "./file-system-Wub9Nto4.js";
3
4
 
4
5
  //#region lib/engine/profiles/profiles.d.ts
5
6
  type Deps = {
@@ -1,2 +1,2 @@
1
- import { t as FunnelProfiles } from "./profiles-f0mNmEyP.js";
1
+ import { t as FunnelProfiles } from "./profiles-EHTeCOqB.js";
2
2
  export { FunnelProfiles };
package/dist/profiles.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as FunnelProfiles } from "./profiles-wMRnjSid.js";
1
+ import { t as FunnelProfiles } from "./profiles-MnXvYfZF.js";
2
2
  export { FunnelProfiles };
@@ -0,0 +1,2 @@
1
+ import { a as RecoveryListenerControl, i as RecoveryGatewayControl, n as RecoveryAction, o as RecoveryResult, r as RecoveryChannelSource, t as FunnelRecovery } from "./funnel-recovery-BUBsu7WX.js";
2
+ export { FunnelRecovery, RecoveryAction, RecoveryChannelSource, RecoveryGatewayControl, RecoveryListenerControl, RecoveryResult };
@@ -0,0 +1,2 @@
1
+ import { t as FunnelRecovery } from "./funnel-recovery-D9CxD5Zs.js";
2
+ export { FunnelRecovery };
@@ -1,4 +1,4 @@
1
- //#region lib/connectors/resolve-connector-token.ts
1
+ //#region lib/engine/connectors/resolve-connector-token.ts
2
2
  /**
3
3
  * Resolves a connector token from either a literal value or the name of an env
4
4
  * var. A connector config carries one or the other per slot (see
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- //#region lib/connectors/schedule-connector-schema.ts
2
+ //#region lib/engine/connectors/schedule-connector-schema.ts
3
3
  /**
4
4
  * Catch-up behavior when the daemon was down past one or more matching minutes.
5
5
  *
@@ -1,8 +1,8 @@
1
- import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "./connector-diagnostic-log-yTOojKUR.js";
2
- import { n as FunnelFileSystem } from "./file-system-BeOKXjlV.js";
1
+ import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "./diagnostic-log-Bxe7Bbvw.js";
2
+ import { n as FunnelFileSystem } from "./file-system-Wub9Nto4.js";
3
3
  import { z } from "zod";
4
4
 
5
- //#region lib/connectors/schedule-state-store.d.ts
5
+ //#region lib/engine/connectors/schedule-state-store.d.ts
6
6
  type Deps$1 = {
7
7
  path: string;
8
8
  fs?: FunnelFileSystem;
@@ -21,7 +21,7 @@ declare class ScheduleStateStore {
21
21
  save(state: Map<string, Date>): void;
22
22
  }
23
23
  //#endregion
24
- //#region lib/connectors/schedule-connector-schema.d.ts
24
+ //#region lib/engine/connectors/schedule-connector-schema.d.ts
25
25
  /**
26
26
  * Catch-up behavior when the daemon was down past one or more matching minutes.
27
27
  *
@@ -67,7 +67,7 @@ declare const scheduleConnectorSchema: z.ZodObject<{
67
67
  }, z.core.$strip>;
68
68
  type ScheduleConnectorConfig = z.infer<typeof scheduleConnectorSchema>;
69
69
  //#endregion
70
- //#region lib/connectors/schedule-listener.d.ts
70
+ //#region lib/engine/connectors/schedule-listener.d.ts
71
71
  type ScheduleOnFired = (entry: ScheduleEntry, firedAt: Date) => void | Promise<void>;
72
72
  type Deps = {
73
73
  config: ScheduleConnectorConfig;
@@ -1,7 +1,7 @@
1
- import { t as FunnelConnectorListener } from "./connector-listener-DU54DN-f.js";
1
+ import { t as FunnelConnectorListener } from "./connector-listener-CpHBecCj.js";
2
2
  import { t as NodeFunnelFileSystem } from "./node-file-system-BcrmWN9I.js";
3
3
  import { dirname } from "node:path";
4
- //#region lib/connectors/match-cron.ts
4
+ //#region lib/engine/connectors/match-cron.ts
5
5
  const parseField = (expr, min, max) => {
6
6
  const values = /* @__PURE__ */ new Set();
7
7
  for (const part of expr.split(",")) {
@@ -35,6 +35,16 @@ const parseField = (expr, min, max) => {
35
35
  values
36
36
  };
37
37
  };
38
+ /**
39
+ * Returns true when `date` (local time) satisfies a 5-field cron expression.
40
+ *
41
+ * Two deliberate deviations from Vixie cron, called out so schedules are not
42
+ * written expecting the other behavior:
43
+ * - Day-of-month and day-of-week are ANDed, not ORed. Vixie cron fires when
44
+ * EITHER matches once both are restricted; here every field must match.
45
+ * - Day-of-week is 0-6 (Sunday=0). `7` for Sunday is NOT accepted (it throws
46
+ * as out-of-range); use `0`.
47
+ */
38
48
  const matchCron = (expr, date) => {
39
49
  const parts = expr.trim().split(/\s+/);
40
50
  if (parts.length !== 5) throw new Error(`cron must have 5 fields (got ${parts.length}): "${expr}"`);
@@ -66,7 +76,7 @@ const matchCron = (expr, date) => {
66
76
  return true;
67
77
  };
68
78
  //#endregion
69
- //#region lib/connectors/schedule-state-store.ts
79
+ //#region lib/engine/connectors/schedule-state-store.ts
70
80
  const defaultFs = new NodeFunnelFileSystem();
71
81
  /**
72
82
  * Per-connector lastFiredAt persistence for the schedule listener. The path is
@@ -98,7 +108,7 @@ var ScheduleStateStore = class {
98
108
  }
99
109
  };
100
110
  //#endregion
101
- //#region lib/connectors/schedule-listener.ts
111
+ //#region lib/engine/connectors/schedule-listener.ts
102
112
  const MAX_CATCHUP_MINUTES = 1440;
103
113
  var FunnelScheduleListener = class extends FunnelConnectorListener {
104
114
  config;
@@ -0,0 +1,18 @@
1
+ import { a as Settings } from "./settings-schema-zhnMIa8I.js";
2
+
3
+ //#region lib/engine/id/id-generator.d.ts
4
+ /**
5
+ * ID generator boundary. Default NodeFunnelIdGenerator wraps `crypto.randomUUID()`;
6
+ * MemoryFunnelIdGenerator emits `<prefix>-1, <prefix>-2, ...` for deterministic tests.
7
+ */
8
+ declare abstract class FunnelIdGenerator {
9
+ abstract generate(): string;
10
+ }
11
+ //#endregion
12
+ //#region lib/engine/settings/settings-reader.d.ts
13
+ declare abstract class FunnelSettingsReader {
14
+ abstract read(): Settings;
15
+ abstract write(settings: Settings): void;
16
+ }
17
+ //#endregion
18
+ export { FunnelIdGenerator as n, FunnelSettingsReader as t };
@@ -149,19 +149,4 @@ declare const settingsSchema: z.ZodObject<{
149
149
  }, z.core.$strip>;
150
150
  type Settings = z.infer<typeof settingsSchema>;
151
151
  //#endregion
152
- //#region lib/engine/id/id-generator.d.ts
153
- /**
154
- * ID generator boundary. Default NodeFunnelIdGenerator wraps `crypto.randomUUID()`;
155
- * MemoryFunnelIdGenerator emits `<prefix>-1, <prefix>-2, ...` for deterministic tests.
156
- */
157
- declare abstract class FunnelIdGenerator {
158
- abstract generate(): string;
159
- }
160
- //#endregion
161
- //#region lib/engine/settings/settings-reader.d.ts
162
- declare abstract class FunnelSettingsReader {
163
- abstract read(): Settings;
164
- abstract write(settings: Settings): void;
165
- }
166
- //#endregion
167
- export { ProfileConfig as a, channelConfigSchema as c, settingsSchema as d, ChannelDeliveryMode as i, channelDeliveryModeSchema as l, FunnelIdGenerator as n, SETTINGS_VERSION as o, ChannelConfig as r, Settings as s, FunnelSettingsReader as t, profileConfigSchema as u };
152
+ export { Settings as a, profileConfigSchema as c, SETTINGS_VERSION as i, settingsSchema as l, ChannelDeliveryMode as n, channelConfigSchema as o, ProfileConfig as r, channelDeliveryModeSchema as s, ChannelConfig as t };
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- //#region lib/connectors/slack-connector-schema.ts
2
+ //#region lib/engine/connectors/slack-connector-schema.ts
3
3
  /**
4
4
  * A slack connector resolves its tokens one of two ways, set at sync time:
5
5
  *
@@ -1,8 +1,8 @@
1
- import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "./connector-diagnostic-log-yTOojKUR.js";
1
+ import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "./diagnostic-log-Bxe7Bbvw.js";
2
2
  import { z } from "zod";
3
3
  import { App } from "@slack/bolt";
4
4
 
5
- //#region lib/connectors/slack-event-processor.d.ts
5
+ //#region lib/engine/connectors/slack-event-processor.d.ts
6
6
  type SlackRawEvent = Record<string, unknown>;
7
7
  /**
8
8
  * Why the processor dropped an event. Mirrored verbatim into the diagnostic
@@ -41,7 +41,7 @@ declare class FunnelSlackEventProcessor {
41
41
  process(event: SlackRawEvent): SlackProcessed;
42
42
  }
43
43
  //#endregion
44
- //#region lib/connectors/slack-connector-schema.d.ts
44
+ //#region lib/engine/connectors/slack-connector-schema.d.ts
45
45
  /**
46
46
  * A slack connector resolves its tokens one of two ways, set at sync time:
47
47
  *
@@ -70,7 +70,7 @@ declare const slackConnectorSchema: z.ZodObject<{
70
70
  }, z.core.$strip>;
71
71
  type SlackConnectorConfig = z.infer<typeof slackConnectorSchema>;
72
72
  //#endregion
73
- //#region lib/connectors/slack-listener.d.ts
73
+ //#region lib/engine/connectors/slack-listener.d.ts
74
74
  type SlackOnAppCreated = (app: App) => void | Promise<void>;
75
75
  type SlackPreprocessEvent = (event: SlackRawEvent) => SlackRawEvent | null;
76
76
  type Deps = {