@interactive-inc/claude-funnel 0.59.1 → 0.60.1

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 (85) hide show
  1. package/README.md +9 -3
  2. package/dist/bin.js +549 -487
  3. package/dist/channels-2g_BU1N0.d.ts +174 -0
  4. package/dist/claude.d.ts +9 -5
  5. package/dist/claude.js +54 -17
  6. package/dist/{diagnostic-log-Cb3v8P7p.d.ts → connector-descriptor-6SXJoszo.d.ts} +158 -2
  7. package/dist/connectors/discord.d.ts +30 -4
  8. package/dist/connectors/discord.js +2 -2
  9. package/dist/connectors/gh.d.ts +21 -5
  10. package/dist/connectors/gh.js +3 -3
  11. package/dist/connectors/schedule.d.ts +124 -2
  12. package/dist/connectors/schedule.js +3 -3
  13. package/dist/connectors/slack.d.ts +149 -5
  14. package/dist/connectors/slack.js +2 -2
  15. package/dist/{diagnostic-sql-reader-CzYgZpq2.js → diagnostic-sql-reader-C9zR-Csp.js} +5 -5
  16. package/dist/diagnostics.d.ts +1 -1
  17. package/dist/diagnostics.js +1 -1
  18. package/dist/{discord-listener-CKsZGTnH.js → discord-connector-BL36yvbL.js} +60 -37
  19. package/dist/docs.d.ts +1 -1
  20. package/dist/docs.js +1 -1
  21. package/dist/doctor.d.ts +1 -1
  22. package/dist/doctor.js +1 -1
  23. package/dist/error-message-of-Byi4y0Uf.js +9 -0
  24. package/dist/{file-process-guard-JhFpmHYo.d.ts → file-process-guard-C_PLxfUX.d.ts} +6 -5
  25. package/dist/{funnel-diagnostics-BpKYrMSu.js → funnel-diagnostics-CSiJmPlZ.js} +19 -2
  26. package/dist/{funnel-diagnostics-K-wON25Y.d.ts → funnel-diagnostics-DpXOsCty.d.ts} +3 -3
  27. package/dist/{funnel-docs-ng5K8w4j.js → funnel-docs-BxXZ9Ksx.js} +76 -3
  28. package/dist/{funnel-docs-DYBs1-H_.d.ts → funnel-docs-CNklHvbt.d.ts} +1 -1
  29. package/dist/{funnel-doctor-vxO96TCA.d.ts → funnel-doctor-CZf_0Luq.d.ts} +2 -2
  30. package/dist/{funnel-recovery-COExL9MD.d.ts → funnel-recovery-DnLrdWO9.d.ts} +1 -1
  31. package/dist/gateway/daemon.js +326 -266
  32. package/dist/gateway-base-url-Dy4Ykuoh.js +14 -0
  33. package/dist/gateway.d.ts +2 -2
  34. package/dist/gateway.js +2 -2
  35. package/dist/{gh-listener-B2I4s8qh.js → gh-connector-DpiixfQZ.js} +53 -5
  36. package/dist/gh-connector-schema-Rzwc1c1N.js +12 -0
  37. package/dist/http-client-oICicjuO.d.ts +18 -0
  38. package/dist/index-CgY8NdMz.d.ts +1057 -0
  39. package/dist/index.d.ts +1558 -17
  40. package/dist/index.js +376 -343
  41. package/dist/{local-config-json-schema-DE1zkMcb.js → local-config-json-schema-JyLqOQNX.js} +9 -5
  42. package/dist/local-config-sync-Dh1Croqe.d.ts +169 -0
  43. package/dist/local-config.d.ts +2 -2
  44. package/dist/local-config.js +2 -2
  45. package/dist/logger.js +1 -1
  46. package/dist/{memory-diagnostic-log-B9Us7X05.js → memory-diagnostic-log-CI60kNfB.js} +33 -18
  47. package/dist/{memory-token-prompter-CcShtF8B.d.ts → memory-token-prompter-B4sjyaAq.d.ts} +2 -2
  48. package/dist/{memory-token-prompter-C7vREzCL.js → memory-token-prompter-CZde7e6y.js} +1 -1
  49. package/dist/{node-file-system-BcrmWN9I.js → node-file-system-Blr8pAir.js} +1 -1
  50. package/dist/node-http-client-lowp60Oa.js +25 -0
  51. package/dist/{gh-connector-schema-ClPLSYD9.js → node-process-runner-DxTvycoK.js} +1 -12
  52. package/dist/{profiles-g2qGVOWv.d.ts → profiles-Cy5wXQ0L.d.ts} +3 -3
  53. package/dist/{profiles-MnXvYfZF.js → profiles-DSzTeKQw.js} +1 -1
  54. package/dist/profiles.d.ts +1 -1
  55. package/dist/profiles.js +1 -1
  56. package/dist/recovery.d.ts +1 -1
  57. package/dist/recovery.js +1 -1
  58. package/dist/{schedule-listener-DP9Jhc6U.js → schedule-connector-L4uzg5M8.js} +109 -9
  59. package/dist/{settings-reader-DPwqOVUm.d.ts → settings-reader-BIFB_j2f.d.ts} +1 -1
  60. package/dist/settings-schema-D1xcOqRu.d.ts +78 -0
  61. package/dist/{gateway-base-url-DxVjjDoW.js → settings-store-CUKSeTXC.js} +27 -29
  62. package/dist/{slack-listener-C4wlZaOq.js → slack-connector-DQIFPdBF.js} +67 -12
  63. package/dist/slot-fields-CMoRpwuy.js +45 -0
  64. package/dist/{yaml-render-cZu6CxkE.js → yaml-render-93pX7EF7.js} +7 -4
  65. package/package.json +1 -1
  66. package/dist/connector-adapter-DGacCppE.d.ts +0 -25
  67. package/dist/discord-connector-schema-CQyfDkLD.d.ts +0 -39
  68. package/dist/gh-connector-schema-CZzwzvqY.d.ts +0 -14
  69. package/dist/index-D7mjirUL.d.ts +0 -3602
  70. package/dist/local-config-sync-BGPAS9Be.d.ts +0 -401
  71. package/dist/process-runner-DIm1cy95.d.ts +0 -52
  72. package/dist/resolve-connector-token-CczqG_Ig.js +0 -22
  73. package/dist/schedule-listener-DoMPjHZj.d.ts +0 -112
  74. package/dist/settings-schema-1hh11jnN.d.ts +0 -152
  75. package/dist/slack-listener-Dj9NFbAJ.d.ts +0 -136
  76. /package/dist/{connector-adapter-qwXLjQId.js → connector-adapter-DU9Rvyec.js} +0 -0
  77. /package/dist/{connector-listener-CpHBecCj.js → connector-listener-DR3aKOuK.js} +0 -0
  78. /package/dist/{file-system-PWKKU7lA.js → file-system-Wvzc2ePY.js} +0 -0
  79. /package/dist/{file-system-DxpnnUVb.d.ts → file-system-o51IsM0W.d.ts} +0 -0
  80. /package/dist/{funnel-doctor-CApCezTq.js → funnel-doctor-DiJCjHsg.js} +0 -0
  81. /package/dist/{funnel-log-sqlite-sink-B_5_4ybn.js → funnel-log-sqlite-sink-kqJbx2H7.js} +0 -0
  82. /package/dist/{funnel-recovery-D9CxD5Zs.js → funnel-recovery-BFdPjL6Z.js} +0 -0
  83. /package/dist/{logger-BP6SisKt.js → logger-B6iyNbxM.js} +0 -0
  84. /package/dist/{schedule-connector-schema-B_xO5z5B.js → schedule-connector-schema-CfyuMCMh.js} +0 -0
  85. /package/dist/{settings-reader-DPqrpV7s.js → settings-reader-CtQ-Ix8_.js} +0 -0
@@ -1,3 +1,6 @@
1
+ import { t as discordConnectorSchema } from "./discord-connector-schema-B_N6IXLz.js";
2
+ import { t as ghConnectorSchema } from "./gh-connector-schema-Rzwc1c1N.js";
3
+ import { t as slackConnectorSchema } from "./slack-connector-schema-C1zEf4TG.js";
1
4
  import { join } from "node:path";
2
5
  import { z } from "zod";
3
6
  import { stderr, stdin } from "node:process";
@@ -296,16 +299,17 @@ var FunnelLocalConfigSync = class {
296
299
  const existing = this.channels.getConnector(channelName, spec.name);
297
300
  if (existing && existing.type !== "gh") throw new Error(`connector "${spec.name}" exists in channel "${channelName}" with type "${existing.type}", funnel.json declares "gh"`);
298
301
  if (existing && existing.type === "gh") {
299
- if (spec.pollInterval !== void 0 && existing.pollInterval !== spec.pollInterval) {
302
+ const gh = ghConnectorSchema.parse(existing);
303
+ if (spec.pollInterval !== void 0 && gh.pollInterval !== spec.pollInterval) {
300
304
  this.channels.updateGhConnector(channelName, spec.name, { pollInterval: spec.pollInterval });
301
305
  return {
302
- id: existing.id,
306
+ id: gh.id,
303
307
  name: spec.name,
304
308
  changed: true
305
309
  };
306
310
  }
307
311
  return {
308
- id: existing.id,
312
+ id: gh.id,
309
313
  name: spec.name,
310
314
  changed: false
311
315
  };
@@ -341,13 +345,13 @@ var FunnelLocalConfigSync = class {
341
345
  const existing = this.channels.getConnector(channelName, connectorName);
342
346
  if (!existing) return null;
343
347
  if (existing.type !== "slack") throw new Error(`connector "${connectorName}" exists in channel "${channelName}" with type "${existing.type}", funnel.json declares "slack"`);
344
- return existing;
348
+ return slackConnectorSchema.parse(existing);
345
349
  }
346
350
  findExistingDiscord(channelName, connectorName) {
347
351
  const existing = this.channels.getConnector(channelName, connectorName);
348
352
  if (!existing) return null;
349
353
  if (existing.type !== "discord") throw new Error(`connector "${connectorName}" exists in channel "${channelName}" with type "${existing.type}", funnel.json declares "discord"`);
350
- return existing;
354
+ return discordConnectorSchema.parse(existing);
351
355
  }
352
356
  removeExtras(channelName, touched) {
353
357
  const channel = this.channels.get(channelName);
@@ -0,0 +1,169 @@
1
+ import { n as FunnelFileSystem } from "./file-system-o51IsM0W.js";
2
+ import { r as FunnelChannels } from "./channels-2g_BU1N0.js";
3
+ import { z } from "zod";
4
+
5
+ //#region lib/services/local-config/local-config-schema.d.ts
6
+ declare const connectorSpecSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
7
+ type: z.ZodLiteral<"slack">;
8
+ name: z.ZodString;
9
+ minify: z.ZodOptional<z.ZodBoolean>;
10
+ }, z.core.$strip>, z.ZodObject<{
11
+ type: z.ZodLiteral<"discord">;
12
+ name: z.ZodString;
13
+ }, z.core.$strip>, z.ZodObject<{
14
+ type: z.ZodLiteral<"gh">;
15
+ name: z.ZodString;
16
+ pollInterval: z.ZodOptional<z.ZodNumber>;
17
+ }, z.core.$strip>, z.ZodObject<{
18
+ type: z.ZodLiteral<"schedule">;
19
+ name: z.ZodString;
20
+ }, z.core.$strip>], "type">;
21
+ type ConnectorSpec = z.infer<typeof connectorSpecSchema>;
22
+ declare const channelSpecSchema: z.ZodObject<{
23
+ name: z.ZodString;
24
+ connectors: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
25
+ type: z.ZodLiteral<"slack">;
26
+ name: z.ZodString;
27
+ minify: z.ZodOptional<z.ZodBoolean>;
28
+ }, z.core.$strip>, z.ZodObject<{
29
+ type: z.ZodLiteral<"discord">;
30
+ name: z.ZodString;
31
+ }, z.core.$strip>, z.ZodObject<{
32
+ type: z.ZodLiteral<"gh">;
33
+ name: z.ZodString;
34
+ pollInterval: z.ZodOptional<z.ZodNumber>;
35
+ }, z.core.$strip>, z.ZodObject<{
36
+ type: z.ZodLiteral<"schedule">;
37
+ name: z.ZodString;
38
+ }, z.core.$strip>], "type">>>;
39
+ }, z.core.$strip>;
40
+ type ChannelSpec = z.infer<typeof channelSpecSchema>;
41
+ declare const profileSpecSchema: z.ZodObject<{
42
+ name: z.ZodString;
43
+ channel: z.ZodString;
44
+ options: z.ZodOptional<z.ZodArray<z.ZodString>>;
45
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
46
+ resume: z.ZodOptional<z.ZodBoolean>;
47
+ }, z.core.$strip>;
48
+ type ProfileSpec = z.infer<typeof profileSpecSchema>;
49
+ declare const localConfigSchema: z.ZodObject<{
50
+ $schema: z.ZodOptional<z.ZodString>;
51
+ id: z.ZodOptional<z.ZodString>;
52
+ channels: z.ZodArray<z.ZodObject<{
53
+ name: z.ZodString;
54
+ connectors: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
55
+ type: z.ZodLiteral<"slack">;
56
+ name: z.ZodString;
57
+ minify: z.ZodOptional<z.ZodBoolean>;
58
+ }, z.core.$strip>, z.ZodObject<{
59
+ type: z.ZodLiteral<"discord">;
60
+ name: z.ZodString;
61
+ }, z.core.$strip>, z.ZodObject<{
62
+ type: z.ZodLiteral<"gh">;
63
+ name: z.ZodString;
64
+ pollInterval: z.ZodOptional<z.ZodNumber>;
65
+ }, z.core.$strip>, z.ZodObject<{
66
+ type: z.ZodLiteral<"schedule">;
67
+ name: z.ZodString;
68
+ }, z.core.$strip>], "type">>>;
69
+ }, z.core.$strip>>;
70
+ profiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
71
+ name: z.ZodString;
72
+ channel: z.ZodString;
73
+ options: z.ZodOptional<z.ZodArray<z.ZodString>>;
74
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
75
+ resume: z.ZodOptional<z.ZodBoolean>;
76
+ }, z.core.$strip>>>;
77
+ }, z.core.$strip>;
78
+ type LocalConfig = z.infer<typeof localConfigSchema>;
79
+ declare const LOCAL_CONFIG_FILENAME = "funnel.json";
80
+ //#endregion
81
+ //#region lib/services/local-config/local-config.d.ts
82
+ type Deps$1 = {
83
+ fs: FunnelFileSystem;
84
+ };
85
+ /**
86
+ * Reads `funnel.json` from a directory. Returns `null` when the file is
87
+ * absent so callers can fall through to other resolution paths (default
88
+ * profile, help). Throws on present-but-invalid files so misconfiguration
89
+ * surfaces loudly instead of silently launching the wrong channel.
90
+ */
91
+ declare class FunnelLocalConfig {
92
+ private readonly fs;
93
+ constructor(deps: Deps$1);
94
+ read(cwd: string): LocalConfig | null;
95
+ private assertProfilesValid;
96
+ }
97
+ //#endregion
98
+ //#region lib/engine/token-prompter/token-prompter.d.ts
99
+ /**
100
+ * Asks the user for a secret value on stdin. Used as a last resort when a
101
+ * funnel.json token field is absent and not present in `~/.funnel`. The Node
102
+ * implementation refuses to prompt when stdin is not a TTY so non-interactive
103
+ * launches (CI, agent spawning agent, daemons) fail fast instead of hanging.
104
+ */
105
+ declare abstract class FunnelTokenPrompter {
106
+ abstract promptSecret(label: string): Promise<string>;
107
+ }
108
+ //#endregion
109
+ //#region lib/services/local-config/local-config-sync.d.ts
110
+ type Deps = {
111
+ channels: FunnelChannels;
112
+ prompter: FunnelTokenPrompter;
113
+ };
114
+ type ConnectorSyncOutcome = {
115
+ name: string;
116
+ changed: boolean;
117
+ };
118
+ type LocalConfigSyncResult = {
119
+ touched: ConnectorSyncOutcome[];
120
+ removed: string[];
121
+ };
122
+ /**
123
+ * Reconciles a single funnel.json channel spec with `~/.funnel/settings.json`.
124
+ * The spec is the source of truth for the channel it declares:
125
+ *
126
+ * - missing channel → created
127
+ * - declared connector matched by name → tokens reconciled
128
+ * - declared connector with no name match → added (prompting for its tokens)
129
+ * - any connector left in the channel that the spec did not touch → removed
130
+ *
131
+ * Connectors are matched by NAME only — there is no rename-by-token path. A spec
132
+ * that renames a connector (same token, new name) is reconciled as "add the new
133
+ * name, remove the old one". Because the collision check runs at add time while
134
+ * the old connector is still present, re-using its token at the new name throws
135
+ * a token-collision error; remove the old connector via the CLI first.
136
+ *
137
+ * Removal only fires when the channel spec has a `connectors` field. An
138
+ * absent field means "do not manage connectors from here" and leaves
139
+ * everything in `~/.funnel` alone. Other channels in funnel.json (not
140
+ * passed to this call) are untouched.
141
+ *
142
+ * Returns the per-connector change set so callers (e.g. the claude launcher)
143
+ * can drive listener hot-reload on the gateway after settings are written.
144
+ */
145
+ declare class FunnelLocalConfigSync {
146
+ private readonly channels;
147
+ private readonly prompter;
148
+ constructor(deps: Deps);
149
+ ensure(channel: ChannelSpec): Promise<LocalConfigSyncResult>;
150
+ private ensureConnector;
151
+ private ensureSlack;
152
+ private ensureDiscord;
153
+ private ensureGh;
154
+ private ensureSchedule;
155
+ private findExistingSlack;
156
+ private findExistingDiscord;
157
+ private removeExtras;
158
+ /**
159
+ * Decides how a single token slot is stored in settings.json. funnel.json
160
+ * never carries tokens, so the only sources are a value already in
161
+ * settings.json (carried over verbatim, whichever form it was — literal or an
162
+ * `env`-var reference set via the CLI) or, on first sync, a TTY prompt for a
163
+ * literal (throws when stdin is not a TTY). Either way the secret lands in the
164
+ * repo-scoped settings, never in the repo itself.
165
+ */
166
+ private resolveSlot;
167
+ }
168
+ //#endregion
169
+ export { FunnelLocalConfig as a, LOCAL_CONFIG_FILENAME as c, channelSpecSchema as d, connectorSpecSchema as f, FunnelTokenPrompter as i, LocalConfig as l, profileSpecSchema as m, FunnelLocalConfigSync as n, ChannelSpec as o, localConfigSchema as p, LocalConfigSyncResult as r, ConnectorSpec as s, ConnectorSyncOutcome as t, ProfileSpec as u };
@@ -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-BGPAS9Be.js";
2
- import { i as funnelJsonSchema, n as NodeFunnelTokenPrompter, r as FunnelLocalConfigWriter, t as MemoryFunnelTokenPrompter } from "./memory-token-prompter-CcShtF8B.js";
1
+ import { a as FunnelLocalConfig, c as LOCAL_CONFIG_FILENAME, d as channelSpecSchema, f as connectorSpecSchema, i as FunnelTokenPrompter, l as LocalConfig, m as profileSpecSchema, n as FunnelLocalConfigSync, o as ChannelSpec, p as localConfigSchema, r as LocalConfigSyncResult, s as ConnectorSpec, t as ConnectorSyncOutcome, u as ProfileSpec } from "./local-config-sync-Dh1Croqe.js";
2
+ import { i as funnelJsonSchema, n as NodeFunnelTokenPrompter, r as FunnelLocalConfigWriter, t as MemoryFunnelTokenPrompter } from "./memory-token-prompter-B4sjyaAq.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-DE1zkMcb.js";
2
- import { n as FunnelLocalConfigWriter, t as MemoryFunnelTokenPrompter } from "./memory-token-prompter-C7vREzCL.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-JyLqOQNX.js";
2
+ import { n as FunnelLocalConfigWriter, t as MemoryFunnelTokenPrompter } from "./memory-token-prompter-CZde7e6y.js";
3
3
  export { FunnelLocalConfig, FunnelLocalConfigSync, FunnelLocalConfigWriter, FunnelTokenPrompter, LOCAL_CONFIG_FILENAME, MemoryFunnelTokenPrompter, NodeFunnelTokenPrompter, channelSpecSchema, connectorSpecSchema, funnelJsonSchema, localConfigSchema, profileSpecSchema };
package/dist/logger.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as FunnelLogSqliteSink } from "./funnel-log-sqlite-sink-B_5_4ybn.js";
1
+ import { t as FunnelLogSqliteSink } from "./funnel-log-sqlite-sink-kqJbx2H7.js";
2
2
  import { dirname } from "node:path";
3
3
  import { appendFileSync, existsSync, mkdirSync, renameSync, statSync, unlinkSync } from "node:fs";
4
4
  //#region lib/logger/funnel-log.ts
@@ -1,12 +1,13 @@
1
- import { n as NodeFunnelProcessRunner } from "./gh-connector-schema-ClPLSYD9.js";
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-DxVjjDoW.js";
4
- import { t as ConnectorDiagnosticSqlReader } from "./diagnostic-sql-reader-CzYgZpq2.js";
5
- import { t as FunnelLogSqliteSink } from "./funnel-log-sqlite-sink-B_5_4ybn.js";
1
+ import { t as gatewayLoopbackUrl } from "./gateway-base-url-Dy4Ykuoh.js";
2
+ import { t as NodeFunnelFileSystem } from "./node-file-system-Blr8pAir.js";
3
+ import { t as NodeFunnelProcessRunner } from "./node-process-runner-DxTvycoK.js";
4
+ import { n as FUNNEL_DIR, o as resolveFunnelPort } from "./settings-store-CUKSeTXC.js";
5
+ import { t as ConnectorDiagnosticSqlReader } from "./diagnostic-sql-reader-C9zR-Csp.js";
6
+ import { t as FunnelLogSqliteSink } from "./funnel-log-sqlite-sink-kqJbx2H7.js";
6
7
  import { dirname, join } from "node:path";
7
8
  import { chmodSync, existsSync, mkdirSync } from "node:fs";
8
- import { z } from "zod";
9
9
  import { homedir, tmpdir } from "node:os";
10
+ import { z } from "zod";
10
11
  import { timingSafeEqual } from "node:crypto";
11
12
  import { createFactory } from "hono/factory";
12
13
  import { HTTPException } from "hono/http-exception";
@@ -1163,8 +1164,8 @@ const defaultOnError = () => {};
1163
1164
  */
1164
1165
  var FunnelGatewayServer = class {
1165
1166
  channels;
1166
- port;
1167
- hostname;
1167
+ configuredPort;
1168
+ configuredHostname;
1168
1169
  dbPath;
1169
1170
  process;
1170
1171
  logger;
@@ -1183,8 +1184,8 @@ var FunnelGatewayServer = class {
1183
1184
  server = null;
1184
1185
  constructor(deps) {
1185
1186
  this.channels = deps.channels;
1186
- this.port = deps.port ?? resolveFunnelPort();
1187
- this.hostname = deps.hostname ?? DEFAULT_HOST;
1187
+ this.configuredPort = deps.port ?? resolveFunnelPort();
1188
+ this.configuredHostname = deps.hostname ?? DEFAULT_HOST;
1188
1189
  this.dbPath = deps.dbPath ?? defaultDbPath();
1189
1190
  this.process = deps.process;
1190
1191
  this.logger = deps.logger;
@@ -1229,15 +1230,26 @@ var FunnelGatewayServer = class {
1229
1230
  now: this.nowMs
1230
1231
  });
1231
1232
  }
1233
+ /**
1234
+ * The resolved listen port: the live Bun server's port once started (so
1235
+ * `port: 0` auto-assignment is visible), the configured value before that.
1236
+ */
1237
+ get port() {
1238
+ return this.server?.port ?? this.configuredPort;
1239
+ }
1240
+ /** The bind address: the live Bun server's hostname once started, the configured value before that. */
1241
+ get hostname() {
1242
+ return this.server?.hostname ?? this.configuredHostname;
1243
+ }
1232
1244
  async start() {
1233
- if (this.server) return this.server;
1234
- if (!this.token && !LOOPBACK_HOSTS.has(this.hostname) && !this.allowInsecureHost) throw new Error(`refusing to start gateway: hostname "${this.hostname}" is reachable off-box but no token is set. Set a token, bind to loopback (127.0.0.1), or pass allowInsecureHost: true.`);
1245
+ if (this.server) return;
1246
+ if (!this.token && !LOOPBACK_HOSTS.has(this.configuredHostname) && !this.allowInsecureHost) throw new Error(`refusing to start gateway: hostname "${this.configuredHostname}" is reachable off-box but no token is set. Set a token, bind to loopback (127.0.0.1), or pass allowInsecureHost: true.`);
1235
1247
  const app = this.buildApp();
1236
1248
  await this.killCompetingSlackIfNeeded();
1237
1249
  this.startedAt = this.nowMs();
1238
1250
  this.server = Bun.serve({
1239
- port: this.port,
1240
- hostname: this.hostname,
1251
+ port: this.configuredPort,
1252
+ hostname: this.configuredHostname,
1241
1253
  development: false,
1242
1254
  fetch: (request, server) => this.handleFetch(request, server, app),
1243
1255
  websocket: {
@@ -1248,7 +1260,6 @@ var FunnelGatewayServer = class {
1248
1260
  });
1249
1261
  this.logServerStarted();
1250
1262
  await this.bootListeners();
1251
- return this.server;
1252
1263
  }
1253
1264
  async stop() {
1254
1265
  await this.supervisor.stopAll();
@@ -1424,6 +1435,10 @@ var FunnelGatewayServer = class {
1424
1435
  * when they resolve. Used by both the connector-listener path (via the
1425
1436
  * supervisor's `notify` callback) and the public `/channels/:channel/publish`
1426
1437
  * route. Returns the assigned event offset.
1438
+ *
1439
+ * Public SDK surface for hosts running this gateway in-process — the no-HTTP
1440
+ * sibling of `funnel.publisher.publish()`, which targets a daemon instead
1441
+ * (see fnl docs programmable-api).
1427
1442
  */
1428
1443
  emit(input) {
1429
1444
  const channelId = this.lookupChannelId(input.channel);
@@ -1571,7 +1586,7 @@ var MemoryFunnelEventLog = class extends FunnelEventLog {
1571
1586
  close() {}
1572
1587
  };
1573
1588
  //#endregion
1574
- //#region lib/gateway/diagnostic-log/diagnostic-log.ts
1589
+ //#region lib/engine/diagnostic-log/diagnostic-log.ts
1575
1590
  /**
1576
1591
  * Points in the listener's connection lifecycle. The single source of truth
1577
1592
  * for the value set: the `status` column schema, the `ConnectorConnectionStatus`
@@ -1662,7 +1677,7 @@ const connectorConnectionEventSchema = z.object({
1662
1677
  */
1663
1678
  var ConnectorDiagnosticLog = class {};
1664
1679
  //#endregion
1665
- //#region lib/gateway/diagnostic-log/sqlite-diagnostic-log.ts
1680
+ //#region lib/engine/diagnostic-log/sqlite-diagnostic-log.ts
1666
1681
  /**
1667
1682
  * Cap on a raw payload kept verbatim. The point of the raw table is to see
1668
1683
  * what Slack/Discord actually sent, and a typical event is a few KB — so 256
@@ -1918,7 +1933,7 @@ const headFields = (payload) => {
1918
1933
  }
1919
1934
  };
1920
1935
  //#endregion
1921
- //#region lib/gateway/diagnostic-log/memory-diagnostic-log.ts
1936
+ //#region lib/engine/diagnostic-log/memory-diagnostic-log.ts
1922
1937
  /**
1923
1938
  * In-process `ConnectorDiagnosticLog` backed by one array per table. Used by tests
1924
1939
  * and embedders that do not need durability. Like the SQLite log it keeps
@@ -1,5 +1,5 @@
1
- import { n as FunnelFileSystem } from "./file-system-DxpnnUVb.js";
2
- import { i as FunnelTokenPrompter } from "./local-config-sync-BGPAS9Be.js";
1
+ import { n as FunnelFileSystem } from "./file-system-o51IsM0W.js";
2
+ import { i as FunnelTokenPrompter } from "./local-config-sync-Dh1Croqe.js";
3
3
 
4
4
  //#region lib/services/local-config/local-config-json-schema.d.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { i as FunnelTokenPrompter, o as LOCAL_CONFIG_FILENAME } from "./local-config-json-schema-DE1zkMcb.js";
1
+ import { i as FunnelTokenPrompter, o as LOCAL_CONFIG_FILENAME } from "./local-config-json-schema-JyLqOQNX.js";
2
2
  import { join } from "node:path";
3
3
  //#region lib/services/local-config/local-config-writer.ts
4
4
  const isRecord = (value) => {
@@ -1,4 +1,4 @@
1
- import { t as FunnelFileSystem } from "./file-system-PWKKU7lA.js";
1
+ import { t as FunnelFileSystem } from "./file-system-Wvzc2ePY.js";
2
2
  import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  //#region lib/engine/fs/node-file-system.ts
4
4
  const SECRET_MODE = 384;
@@ -0,0 +1,25 @@
1
+ //#region lib/engine/http/http-client.ts
2
+ var FunnelHttpClient = class {};
3
+ //#endregion
4
+ //#region lib/engine/http/node-http-client.ts
5
+ var NodeFunnelHttpClient = class extends FunnelHttpClient {
6
+ constructor() {
7
+ super();
8
+ Object.freeze(this);
9
+ }
10
+ async fetch(request) {
11
+ const res = await globalThis.fetch(request.url, {
12
+ method: request.method,
13
+ headers: request.headers,
14
+ body: request.body
15
+ });
16
+ return {
17
+ status: res.status,
18
+ ok: res.ok,
19
+ text: () => res.text(),
20
+ json: () => res.json()
21
+ };
22
+ }
23
+ };
24
+ //#endregion
25
+ export { FunnelHttpClient as n, NodeFunnelHttpClient as t };
@@ -1,5 +1,4 @@
1
1
  import { openSync } from "node:fs";
2
- import { z } from "zod";
3
2
  //#region lib/engine/process/process-runner.ts
4
3
  /**
5
4
  * Process boundary covering one-shot runs, sync runs, foreground attach, and
@@ -256,14 +255,4 @@ var NodeFunnelProcessRunner = class extends FunnelProcessRunner {
256
255
  }
257
256
  };
258
257
  //#endregion
259
- //#region lib/engine/connectors/gh-connector-schema.ts
260
- const ghConnectorSchema = z.object({
261
- id: z.string(),
262
- name: z.string(),
263
- type: z.literal("gh"),
264
- pollInterval: z.number().int().positive().optional(),
265
- createdAt: z.string().datetime().optional(),
266
- updatedAt: z.string().datetime().optional()
267
- });
268
- //#endregion
269
- export { NodeFunnelProcessRunner as n, FunnelProcessRunner as r, ghConnectorSchema as t };
258
+ export { FunnelProcessRunner as n, NodeFunnelProcessRunner as t };
@@ -1,6 +1,6 @@
1
- import { r as ProfileConfig } from "./settings-schema-1hh11jnN.js";
2
- import { n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-DPwqOVUm.js";
3
- import { n as FunnelFileSystem } from "./file-system-DxpnnUVb.js";
1
+ import { r as ProfileConfig } from "./settings-schema-D1xcOqRu.js";
2
+ import { n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-BIFB_j2f.js";
3
+ import { n as FunnelFileSystem } from "./file-system-o51IsM0W.js";
4
4
 
5
5
  //#region lib/engine/profiles/profiles.d.ts
6
6
  type Deps = {
@@ -1,4 +1,4 @@
1
- import { t as NodeFunnelFileSystem } from "./node-file-system-BcrmWN9I.js";
1
+ import { t as NodeFunnelFileSystem } from "./node-file-system-Blr8pAir.js";
2
2
  import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  //#region lib/engine/profiles/profiles.ts
@@ -1,2 +1,2 @@
1
- import { t as FunnelProfiles } from "./profiles-g2qGVOWv.js";
1
+ import { t as FunnelProfiles } from "./profiles-Cy5wXQ0L.js";
2
2
  export { FunnelProfiles };
package/dist/profiles.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as FunnelProfiles } from "./profiles-MnXvYfZF.js";
1
+ import { t as FunnelProfiles } from "./profiles-DSzTeKQw.js";
2
2
  export { FunnelProfiles };
@@ -1,2 +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-COExL9MD.js";
1
+ import { a as RecoveryListenerControl, i as RecoveryGatewayControl, n as RecoveryAction, o as RecoveryResult, r as RecoveryChannelSource, t as FunnelRecovery } from "./funnel-recovery-DnLrdWO9.js";
2
2
  export { FunnelRecovery, RecoveryAction, RecoveryChannelSource, RecoveryGatewayControl, RecoveryListenerControl, RecoveryResult };
package/dist/recovery.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as FunnelRecovery } from "./funnel-recovery-D9CxD5Zs.js";
1
+ import { t as FunnelRecovery } from "./funnel-recovery-BFdPjL6Z.js";
2
2
  export { FunnelRecovery };
@@ -1,6 +1,9 @@
1
- import { t as FunnelConnectorListener } from "./connector-listener-CpHBecCj.js";
2
- import { t as NodeFunnelFileSystem } from "./node-file-system-BcrmWN9I.js";
3
- import { dirname } from "node:path";
1
+ import { t as NodeFunnelFileSystem } from "./node-file-system-Blr8pAir.js";
2
+ import { t as FunnelConnectorListener } from "./connector-listener-DR3aKOuK.js";
3
+ import { n as scheduleConnectorSchema, r as scheduleEntrySchema } from "./schedule-connector-schema-CfyuMCMh.js";
4
+ import { t as errorMessageOf } from "./error-message-of-Byi4y0Uf.js";
5
+ import { dirname, join } from "node:path";
6
+ import { z } from "zod";
4
7
  //#region lib/engine/connectors/match-cron.ts
5
8
  const parseField = (expr, min, max) => {
6
9
  const values = /* @__PURE__ */ new Set();
@@ -80,9 +83,9 @@ const matchCron = (expr, date) => {
80
83
  const defaultFs = new NodeFunnelFileSystem();
81
84
  /**
82
85
  * Per-connector lastFiredAt persistence for the schedule listener. The path is
83
- * passed in by FunnelConnectorFactory so this store does not know about the
84
- * funnel directory layout (`channels/<id>/connectors/<id>/state.json` lives
85
- * outside this class).
86
+ * passed in by the schedule connector descriptor (via the registry's
87
+ * connectorDir) so this store does not know about the funnel directory layout
88
+ * (`channels/<id>/connectors/<id>/state.json` lives outside this class).
86
89
  */
87
90
  var ScheduleStateStore = class {
88
91
  path;
@@ -220,7 +223,7 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
220
223
  this.logger?.error("schedule onFired callback failed", {
221
224
  connector: this.config.name,
222
225
  id: entry.id,
223
- error: error instanceof Error ? error.message : String(error)
226
+ error: errorMessageOf(error)
224
227
  });
225
228
  }
226
229
  }
@@ -259,7 +262,7 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
259
262
  return matches;
260
263
  }
261
264
  logInvalidCron(entry, error) {
262
- const message = error instanceof Error ? error.message : String(error);
265
+ const message = errorMessageOf(error);
263
266
  this.recordConnection("error", `invalid cron "${entry.cron}" (entry ${entry.id}): ${message}`);
264
267
  this.logger?.error("invalid cron expression in schedule", {
265
268
  connector: this.config.name,
@@ -309,4 +312,101 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
309
312
  }
310
313
  };
311
314
  //#endregion
312
- export { ScheduleStateStore as n, matchCron as r, FunnelScheduleListener as t };
315
+ //#region lib/engine/connectors/schedule-connector.ts
316
+ const addEntryArgsSchema = z.object({
317
+ cron: z.string(),
318
+ prompt: z.string(),
319
+ id: z.string().optional(),
320
+ enabled: z.boolean().optional(),
321
+ catchupPolicy: scheduleEntrySchema.shape.catchupPolicy.optional()
322
+ });
323
+ const removeEntryArgsSchema = z.object({ id: z.string() });
324
+ /**
325
+ * Schedule connector descriptor. Pass `scheduleConnector()` to
326
+ * `new Funnel({ connectors: [...] })` to enable the type. Schedule has no
327
+ * outbound adapter; its per-entry CRUD is exposed via `operations`
328
+ * (listEntries / addEntry / removeEntry) and reached through
329
+ * `funnel.channels.connectorOp(...)`.
330
+ */
331
+ const scheduleConnector = (options = {}) => ({
332
+ type: "schedule",
333
+ toolExposed: false,
334
+ createListener(config, deps) {
335
+ const parsed = scheduleConnectorSchema.parse(config);
336
+ return new FunnelScheduleListener({
337
+ config: parsed,
338
+ lastFiredStore: new ScheduleStateStore({
339
+ path: join(deps.connectorDir(deps.channelId, parsed.id), "state.json"),
340
+ fs: deps.fs
341
+ }),
342
+ channelId: deps.channelId,
343
+ logger: deps.logger,
344
+ diagnosticLog: deps.diagnosticLog,
345
+ onFired: options.onFired
346
+ });
347
+ },
348
+ createAdapter: null,
349
+ secretTokens() {
350
+ return [];
351
+ },
352
+ buildConfig(input, context) {
353
+ return scheduleConnectorSchema.parse({
354
+ id: context.id,
355
+ type: "schedule",
356
+ name: input.name,
357
+ entries: Array.isArray(input.entries) ? input.entries : [],
358
+ createdAt: context.now,
359
+ updatedAt: context.now
360
+ });
361
+ },
362
+ applyUpdate(config, _fields, context) {
363
+ const current = scheduleConnectorSchema.parse(config);
364
+ return scheduleConnectorSchema.parse({
365
+ ...current,
366
+ updatedAt: context.now
367
+ });
368
+ },
369
+ operations: {
370
+ listEntries(props) {
371
+ const parsed = scheduleConnectorSchema.parse(props.config);
372
+ return {
373
+ config: props.config,
374
+ result: parsed.entries
375
+ };
376
+ },
377
+ addEntry(props) {
378
+ const parsed = scheduleConnectorSchema.parse(props.config);
379
+ const args = addEntryArgsSchema.parse(props.args);
380
+ const entry = scheduleEntrySchema.parse({
381
+ id: args.id ?? props.context.generateId(),
382
+ cron: args.cron,
383
+ prompt: args.prompt,
384
+ ...args.enabled !== void 0 ? { enabled: args.enabled } : {},
385
+ ...args.catchupPolicy !== void 0 ? { catchupPolicy: args.catchupPolicy } : {}
386
+ });
387
+ return {
388
+ config: scheduleConnectorSchema.parse({
389
+ ...parsed,
390
+ entries: [...parsed.entries, entry],
391
+ updatedAt: props.context.now
392
+ }),
393
+ result: entry
394
+ };
395
+ },
396
+ removeEntry(props) {
397
+ const parsed = scheduleConnectorSchema.parse(props.config);
398
+ const args = removeEntryArgsSchema.parse(props.args);
399
+ if (!parsed.entries.some((entry) => entry.id === args.id)) throw new Error(`schedule entry "${args.id}" not found`);
400
+ return {
401
+ config: scheduleConnectorSchema.parse({
402
+ ...parsed,
403
+ entries: parsed.entries.filter((entry) => entry.id !== args.id),
404
+ updatedAt: props.context.now
405
+ }),
406
+ result: null
407
+ };
408
+ }
409
+ }
410
+ });
411
+ //#endregion
412
+ export { matchCron as i, FunnelScheduleListener as n, ScheduleStateStore as r, scheduleConnector as t };
@@ -1,4 +1,4 @@
1
- import { a as Settings } from "./settings-schema-1hh11jnN.js";
1
+ import { a as Settings } from "./settings-schema-D1xcOqRu.js";
2
2
 
3
3
  //#region lib/engine/id/id-generator.d.ts
4
4
  /**