@interactive-inc/claude-funnel 0.10.1 → 0.15.2

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.
@@ -160,6 +160,7 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
160
160
  lastFiredStore;
161
161
  logger;
162
162
  now;
163
+ onFired;
163
164
  timer = null;
164
165
  stopped = false;
165
166
  constructor(deps) {
@@ -168,6 +169,7 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
168
169
  this.lastFiredStore = deps.lastFiredStore;
169
170
  this.logger = deps.logger ?? defaultLogger;
170
171
  this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
172
+ this.onFired = deps.onFired ?? null;
171
173
  }
172
174
  async start(notify) {
173
175
  this.stopped = false;
@@ -243,6 +245,15 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
243
245
  };
244
246
  if (catchup) meta.catchup = "true";
245
247
  await notify(entry.prompt, meta);
248
+ if (this.onFired) try {
249
+ await this.onFired(entry, firedAt);
250
+ } catch (error) {
251
+ this.logger.error("schedule onFired callback failed", {
252
+ connector: this.config.name,
253
+ id: entry.id,
254
+ error: error instanceof Error ? error.message : String(error)
255
+ });
256
+ }
246
257
  }
247
258
  findMostRecentMatch(cron, from, until, entryId) {
248
259
  const maxIterations = Math.min(MAX_CATCHUP_MINUTES, Math.floor((until.getTime() - from.getTime()) / 6e4) + 1);
@@ -1,3 +1,4 @@
1
+ import { n as FunnelConnectorListener, r as NotifyFn, t as FunnelLogger } from "./logger-CTlXs7z4.js";
1
2
  import { z } from "zod";
2
3
 
3
4
  //#region lib/connectors/schedule-connector-schema.d.ts
@@ -71,4 +72,58 @@ declare abstract class FunnelFileSystem {
71
72
  abstract statSync(path: string): FileStat;
72
73
  }
73
74
  //#endregion
74
- export { ScheduleEntry as a, scheduleEntrySchema as c, ScheduleConnectorConfig as i, FunnelFileSystem as n, scheduleCatchupPolicySchema as o, ScheduleCatchupPolicy as r, scheduleConnectorSchema as s, FileStat as t };
75
+ //#region lib/connectors/schedule-state-store.d.ts
76
+ type Deps$1 = {
77
+ path: string;
78
+ fs?: FunnelFileSystem;
79
+ };
80
+ /**
81
+ * Per-connector lastFiredAt persistence for the schedule listener. The path is
82
+ * passed in by FunnelConnectorFactory so this store does not know about the
83
+ * funnel directory layout (`channels/<id>/connectors/<id>/state.json` lives
84
+ * outside this class).
85
+ */
86
+ declare class ScheduleStateStore {
87
+ private readonly path;
88
+ private readonly fs;
89
+ constructor(deps: Deps$1);
90
+ load(): Map<string, Date>;
91
+ save(state: Map<string, Date>): void;
92
+ }
93
+ //#endregion
94
+ //#region lib/connectors/schedule-listener.d.ts
95
+ type ScheduleOnFired = (entry: ScheduleEntry, firedAt: Date) => void | Promise<void>;
96
+ type Deps = {
97
+ config: ScheduleConnectorConfig;
98
+ lastFiredStore: ScheduleStateStore;
99
+ logger?: FunnelLogger;
100
+ now?: () => Date;
101
+ /**
102
+ * Invoked after a schedule entry fires successfully. Use to remove one-shot
103
+ * entries from the connector config, or to log per-fire side effects.
104
+ * Errors from this callback are caught and logged; they do not abort the tick.
105
+ */
106
+ onFired?: ScheduleOnFired;
107
+ };
108
+ declare class FunnelScheduleListener extends FunnelConnectorListener {
109
+ private readonly config;
110
+ private readonly lastFiredStore;
111
+ private readonly logger;
112
+ private readonly now;
113
+ private readonly onFired;
114
+ private timer;
115
+ private stopped;
116
+ constructor(deps: Deps);
117
+ start(notify: NotifyFn): Promise<void>;
118
+ stop(): Promise<void>;
119
+ isAlive(): boolean;
120
+ tick(notify: NotifyFn): Promise<void>;
121
+ private fireEntry;
122
+ private notifyOne;
123
+ private findMostRecentMatch;
124
+ private findAllMatches;
125
+ private logInvalidCron;
126
+ private truncateToMinute;
127
+ }
128
+ //#endregion
129
+ export { FunnelFileSystem as a, ScheduleEntry as c, scheduleEntrySchema as d, FileStat as i, scheduleCatchupPolicySchema as l, ScheduleOnFired as n, ScheduleCatchupPolicy as o, ScheduleStateStore as r, ScheduleConnectorConfig as s, FunnelScheduleListener as t, scheduleConnectorSchema as u };
@@ -85,11 +85,15 @@ const defaultLogger = new NodeFunnelLogger();
85
85
  var FunnelSlackListener = class extends FunnelConnectorListener {
86
86
  config;
87
87
  logger;
88
+ onAppCreated;
89
+ preprocessEvent;
88
90
  app = null;
89
91
  constructor(deps) {
90
92
  super();
91
93
  this.config = deps.config;
92
94
  this.logger = deps.logger ?? defaultLogger;
95
+ this.onAppCreated = deps.onAppCreated ?? null;
96
+ this.preprocessEvent = deps.preprocessEvent ?? null;
93
97
  }
94
98
  async start(notify) {
95
99
  const app = new App({
@@ -103,10 +107,14 @@ var FunnelSlackListener = class extends FunnelConnectorListener {
103
107
  ownBotUserId: authResult.user_id ?? "",
104
108
  ownBotId: authResult.bot_id ?? ""
105
109
  });
110
+ const preprocess = this.preprocessEvent;
106
111
  app.use(async (args) => {
107
112
  const parsed = middlewareArgsSchema.safeParse(args);
108
113
  if (!parsed.success || !parsed.data.event) return;
109
- const result = processor.process(parsed.data.event);
114
+ const rawEvent = parsed.data.event;
115
+ const event = preprocess ? preprocess(rawEvent) : rawEvent;
116
+ if (event === null) return;
117
+ const result = processor.process(event);
110
118
  if (result.skip) return;
111
119
  if (result.shouldReact) try {
112
120
  await app.client.reactions.add({
@@ -121,6 +129,7 @@ var FunnelSlackListener = class extends FunnelConnectorListener {
121
129
  app.error(async (error) => {
122
130
  this.logger.error("Slack error", { error: error instanceof Error ? error.message : String(error) });
123
131
  });
132
+ if (this.onAppCreated) await this.onAppCreated(app);
124
133
  await app.start();
125
134
  this.app = app;
126
135
  }
@@ -0,0 +1,74 @@
1
+ import { n as FunnelConnectorListener, r as NotifyFn, t as FunnelLogger } from "./logger-CTlXs7z4.js";
2
+ import { z } from "zod";
3
+ import { App } from "@slack/bolt";
4
+
5
+ //#region lib/connectors/slack-connector-schema.d.ts
6
+ declare const slackConnectorSchema: z.ZodObject<{
7
+ id: z.ZodString;
8
+ name: z.ZodString;
9
+ type: z.ZodLiteral<"slack">;
10
+ botToken: z.ZodString;
11
+ appToken: z.ZodString;
12
+ createdAt: z.ZodOptional<z.ZodString>;
13
+ updatedAt: z.ZodOptional<z.ZodString>;
14
+ }, z.core.$strip>;
15
+ type SlackConnectorConfig = z.infer<typeof slackConnectorSchema>;
16
+ //#endregion
17
+ //#region lib/connectors/slack-event-processor.d.ts
18
+ type SlackRawEvent = Record<string, unknown>;
19
+ type SlackProcessedSkip = {
20
+ skip: true;
21
+ };
22
+ type SlackProcessedEmit = {
23
+ skip: false;
24
+ content: string;
25
+ meta: Record<string, string>;
26
+ shouldReact: boolean;
27
+ channel: string;
28
+ timestamp: string;
29
+ };
30
+ type SlackProcessed = SlackProcessedSkip | SlackProcessedEmit;
31
+ type Props = {
32
+ ownBotUserId: string;
33
+ ownBotId: string;
34
+ now?: () => number;
35
+ };
36
+ declare class FunnelSlackEventProcessor {
37
+ private readonly ownBotUserId;
38
+ private readonly ownBotId;
39
+ private readonly now;
40
+ private readonly dedup;
41
+ constructor(props: Props);
42
+ process(event: SlackRawEvent): SlackProcessed;
43
+ }
44
+ //#endregion
45
+ //#region lib/connectors/slack-listener.d.ts
46
+ type SlackOnAppCreated = (app: App) => void | Promise<void>;
47
+ type SlackPreprocessEvent = (event: SlackRawEvent) => SlackRawEvent | null;
48
+ type Deps = {
49
+ config: SlackConnectorConfig;
50
+ logger?: FunnelLogger;
51
+ /**
52
+ * Invoked after the Bolt App is constructed, before it starts.
53
+ * Use to attach app.action handlers, custom middleware, etc.
54
+ */
55
+ onAppCreated?: SlackOnAppCreated;
56
+ /**
57
+ * Transform or drop the raw Slack event before the built-in processor sees it.
58
+ * Return null to drop the event entirely.
59
+ */
60
+ preprocessEvent?: SlackPreprocessEvent;
61
+ };
62
+ declare class FunnelSlackListener extends FunnelConnectorListener {
63
+ private readonly config;
64
+ private readonly logger;
65
+ private readonly onAppCreated;
66
+ private readonly preprocessEvent;
67
+ private app;
68
+ constructor(deps: Deps);
69
+ start(notify: NotifyFn): Promise<void>;
70
+ stop(): Promise<void>;
71
+ isAlive(): boolean;
72
+ }
73
+ //#endregion
74
+ export { SlackProcessed as a, SlackRawEvent as c, FunnelSlackEventProcessor as i, SlackConnectorConfig as l, SlackOnAppCreated as n, SlackProcessedEmit as o, SlackPreprocessEvent as r, SlackProcessedSkip as s, FunnelSlackListener as t, slackConnectorSchema as u };
@@ -0,0 +1,144 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "$schema": {
6
+ "type": "string"
7
+ },
8
+ "channel": {
9
+ "type": "string"
10
+ },
11
+ "options": {
12
+ "type": "array",
13
+ "items": {
14
+ "type": "string"
15
+ }
16
+ },
17
+ "env": {
18
+ "type": "object",
19
+ "propertyNames": {
20
+ "type": "string"
21
+ },
22
+ "additionalProperties": {
23
+ "type": "string"
24
+ }
25
+ },
26
+ "connectors": {
27
+ "type": "array",
28
+ "items": {
29
+ "oneOf": [
30
+ {
31
+ "type": "object",
32
+ "properties": {
33
+ "type": {
34
+ "type": "string",
35
+ "const": "slack"
36
+ },
37
+ "name": {
38
+ "type": "string"
39
+ },
40
+ "botToken": {
41
+ "type": "string"
42
+ },
43
+ "appToken": {
44
+ "type": "string"
45
+ },
46
+ "env": {
47
+ "type": "object",
48
+ "properties": {
49
+ "botToken": {
50
+ "type": "string"
51
+ },
52
+ "appToken": {
53
+ "type": "string"
54
+ }
55
+ },
56
+ "additionalProperties": false
57
+ }
58
+ },
59
+ "required": [
60
+ "type",
61
+ "name"
62
+ ],
63
+ "additionalProperties": false
64
+ },
65
+ {
66
+ "type": "object",
67
+ "properties": {
68
+ "type": {
69
+ "type": "string",
70
+ "const": "discord"
71
+ },
72
+ "name": {
73
+ "type": "string"
74
+ },
75
+ "botToken": {
76
+ "type": "string"
77
+ },
78
+ "env": {
79
+ "type": "object",
80
+ "properties": {
81
+ "botToken": {
82
+ "type": "string"
83
+ }
84
+ },
85
+ "additionalProperties": false
86
+ }
87
+ },
88
+ "required": [
89
+ "type",
90
+ "name"
91
+ ],
92
+ "additionalProperties": false
93
+ },
94
+ {
95
+ "type": "object",
96
+ "properties": {
97
+ "type": {
98
+ "type": "string",
99
+ "const": "gh"
100
+ },
101
+ "name": {
102
+ "type": "string"
103
+ },
104
+ "pollInterval": {
105
+ "type": "integer",
106
+ "exclusiveMinimum": 0,
107
+ "maximum": 9007199254740991
108
+ }
109
+ },
110
+ "required": [
111
+ "type",
112
+ "name"
113
+ ],
114
+ "additionalProperties": false
115
+ },
116
+ {
117
+ "type": "object",
118
+ "properties": {
119
+ "type": {
120
+ "type": "string",
121
+ "const": "schedule"
122
+ },
123
+ "name": {
124
+ "type": "string"
125
+ }
126
+ },
127
+ "required": [
128
+ "type",
129
+ "name"
130
+ ],
131
+ "additionalProperties": false
132
+ }
133
+ ]
134
+ }
135
+ }
136
+ },
137
+ "required": [
138
+ "channel"
139
+ ],
140
+ "additionalProperties": false,
141
+ "title": "Funnel per-repo launch config",
142
+ "description": "Used by `fnl claude` when no --profile / --channel is given. Declares the channel to subscribe to, optional sub-agent and brief flag, environment variables to layer under process.env, and optional connectors to materialize into ~/.funnel/settings.json on launch."
143
+ }
144
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interactive-inc/claude-funnel",
3
- "version": "0.10.1",
3
+ "version": "0.15.2",
4
4
  "description": "Hub CLI that routes external events (Slack / GitHub / Discord) to Claude Code agents through subscription channels over MCP.",
5
5
  "keywords": [
6
6
  "bun",
@@ -28,6 +28,7 @@
28
28
  },
29
29
  "files": [
30
30
  "dist/**/*",
31
+ "funnel.schema.json",
31
32
  "README.md",
32
33
  "LICENSE"
33
34
  ],
@@ -1,43 +0,0 @@
1
- import { z } from "zod";
2
-
3
- //#region lib/connectors/slack-connector-schema.d.ts
4
- declare const slackConnectorSchema: z.ZodObject<{
5
- id: z.ZodString;
6
- name: z.ZodString;
7
- type: z.ZodLiteral<"slack">;
8
- botToken: z.ZodString;
9
- appToken: z.ZodString;
10
- createdAt: z.ZodOptional<z.ZodString>;
11
- updatedAt: z.ZodOptional<z.ZodString>;
12
- }, z.core.$strip>;
13
- type SlackConnectorConfig = z.infer<typeof slackConnectorSchema>;
14
- //#endregion
15
- //#region lib/connectors/slack-event-processor.d.ts
16
- type SlackRawEvent = Record<string, unknown>;
17
- type SlackProcessedSkip = {
18
- skip: true;
19
- };
20
- type SlackProcessedEmit = {
21
- skip: false;
22
- content: string;
23
- meta: Record<string, string>;
24
- shouldReact: boolean;
25
- channel: string;
26
- timestamp: string;
27
- };
28
- type SlackProcessed = SlackProcessedSkip | SlackProcessedEmit;
29
- type Props = {
30
- ownBotUserId: string;
31
- ownBotId: string;
32
- now?: () => number;
33
- };
34
- declare class FunnelSlackEventProcessor {
35
- private readonly ownBotUserId;
36
- private readonly ownBotId;
37
- private readonly now;
38
- private readonly dedup;
39
- constructor(props: Props);
40
- process(event: SlackRawEvent): SlackProcessed;
41
- }
42
- //#endregion
43
- export { SlackRawEvent as a, SlackProcessedSkip as i, SlackProcessed as n, SlackConnectorConfig as o, SlackProcessedEmit as r, slackConnectorSchema as s, FunnelSlackEventProcessor as t };