@interactive-inc/claude-funnel 0.53.0 → 0.56.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 +1229 -486
  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 +838 -252
  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 +509 -973
  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-BbFVqDzz.js} +30 -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
@@ -431,6 +432,10 @@ var LeucoLoggerSqliteSink = class {
431
432
  trimOldestStmt;
432
433
  insertsSinceByteCheck = 0;
433
434
  constructor(props) {
435
+ if (props.path !== ":memory:") {
436
+ const dir = dirname(props.path);
437
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
438
+ }
434
439
  this.db = new Database(props.path);
435
440
  this.db.run("PRAGMA journal_mode = WAL");
436
441
  this.migrate();
@@ -647,7 +652,7 @@ function toRecord(row) {
647
652
  };
648
653
  }
649
654
  //#endregion
650
- //#region lib/gateway/sqlite-funnel-event-log.ts
655
+ //#region lib/gateway/event-log/sqlite-event-log.ts
651
656
  const MAX_CONTENT_CHARS = 2e3;
652
657
  /**
653
658
  * SQLite-backed `FunnelEventLog`. One indexed table holds every broadcaster
@@ -669,9 +674,11 @@ const MAX_CONTENT_CHARS = 2e3;
669
674
  var SqliteFunnelEventLog = class extends FunnelEventLog {
670
675
  sink;
671
676
  now;
677
+ logger;
672
678
  constructor(props) {
673
679
  super();
674
680
  this.now = props.now ?? (() => Date.now());
681
+ this.logger = props.logger;
675
682
  this.sink = new LeucoLoggerSqliteSink({
676
683
  path: props.path,
677
684
  indexes: ["channel_id", "connector_id"],
@@ -699,11 +706,15 @@ var SqliteFunnelEventLog = class extends FunnelEventLog {
699
706
  connector_id: record.connectorId,
700
707
  meta: record.meta
701
708
  };
702
- this.sink.write({
709
+ const result = this.sink.write({
703
710
  seq: record.offset,
704
711
  ts: this.now(),
705
712
  event
706
713
  });
714
+ if (result instanceof Error) this.logger?.error("event log write failed", {
715
+ offset: record.offset,
716
+ error: result.message
717
+ });
707
718
  }
708
719
  /**
709
720
  * Returns events with offset > since. Filtering by channel/connector is
@@ -1119,87 +1130,6 @@ const channelsPublishHandler = factory.createHandlers(zParam(z.object({ channel:
1119
1130
  return c.json(response);
1120
1131
  });
1121
1132
  //#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
1133
  //#region lib/gateway/routes/debug.ts
1204
1134
  const extractPreview = (payload) => {
1205
1135
  if (typeof payload !== "string" || payload.length === 0) return null;
@@ -1420,7 +1350,11 @@ const statusHandler = factory.createHandlers((c) => {
1420
1350
  //#endregion
1421
1351
  //#region lib/gateway/routes/index.ts
1422
1352
  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);
1353
+ return factory.createApp().onError((error, c) => {
1354
+ if (error instanceof HTTPException) return error.getResponse();
1355
+ const message = error instanceof Error ? error.message : String(error);
1356
+ return c.json({ error: message }, 500);
1357
+ }).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
1358
  }
1425
1359
  const gatewayRoutes = buildGatewayRoutes();
1426
1360
  //#endregion
@@ -1486,7 +1420,8 @@ var FunnelGatewayServer = class {
1486
1420
  if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
1487
1421
  this.eventLog = new SqliteFunnelEventLog({
1488
1422
  path: this.dbPath,
1489
- now: this.nowMs
1423
+ now: this.nowMs,
1424
+ logger: this.logger
1490
1425
  });
1491
1426
  }
1492
1427
  this.broadcaster = new FunnelBroadcaster({
@@ -1724,7 +1659,7 @@ var FunnelGatewayServer = class {
1724
1659
  return { offset: event.offset };
1725
1660
  }
1726
1661
  lookupChannelId(channelName) {
1727
- return this.channels.get(channelName)?.id ?? null;
1662
+ return this.channels.get(channelName)?.id ?? this.channels.getById(channelName)?.id ?? null;
1728
1663
  }
1729
1664
  lookupConnectorId(channelId, connectorName) {
1730
1665
  return this.channels.getById(channelId)?.connectors.find((c) => c.name === connectorName)?.id ?? null;
@@ -1806,7 +1741,7 @@ function channelWsProtocols(token) {
1806
1741
  return [`funnel.token.${token}`];
1807
1742
  }
1808
1743
  //#endregion
1809
- //#region lib/gateway/memory-funnel-event-log.ts
1744
+ //#region lib/gateway/event-log/memory-event-log.ts
1810
1745
  /**
1811
1746
  * In-process `FunnelEventLog` backed by a plain array. Used by tests and by
1812
1747
  * embedders that do not need durability — replay works within the process
@@ -1849,7 +1784,7 @@ var MemoryFunnelEventLog = class extends FunnelEventLog {
1849
1784
  close() {}
1850
1785
  };
1851
1786
  //#endregion
1852
- //#region lib/gateway/connector-diagnostic-log.ts
1787
+ //#region lib/gateway/diagnostic-log/diagnostic-log.ts
1853
1788
  /**
1854
1789
  * Points in the listener's connection lifecycle. The single source of truth
1855
1790
  * for the value set: the `status` column schema, the `ConnectorConnectionStatus`
@@ -1940,7 +1875,7 @@ const connectorConnectionEventSchema = z.object({
1940
1875
  */
1941
1876
  var ConnectorDiagnosticLog = class {};
1942
1877
  //#endregion
1943
- //#region lib/gateway/sqlite-connector-diagnostic-log.ts
1878
+ //#region lib/gateway/diagnostic-log/sqlite-diagnostic-log.ts
1944
1879
  /**
1945
1880
  * Cap on a raw payload kept verbatim. The point of the raw table is to see
1946
1881
  * what Slack/Discord actually sent, and a typical event is a few KB — so 256
@@ -2196,7 +2131,7 @@ const headFields = (payload) => {
2196
2131
  }
2197
2132
  };
2198
2133
  //#endregion
2199
- //#region lib/gateway/memory-connector-diagnostic-log.ts
2134
+ //#region lib/gateway/diagnostic-log/memory-diagnostic-log.ts
2200
2135
  /**
2201
2136
  * In-process `ConnectorDiagnosticLog` backed by one array per table. Used by tests
2202
2137
  * and embedders that do not need durability. Like the SQLite log it keeps
@@ -2273,4 +2208,4 @@ const takeRecent = (events, limit) => {
2273
2208
  return events.slice(-limit);
2274
2209
  };
2275
2210
  //#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 };
2211
+ 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 = {