@interactive-inc/claude-funnel 0.25.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -14
- package/dist/bin.js +428 -429
- package/dist/connectors/discord.js +1 -1
- package/dist/connectors/gh.js +1 -1
- package/dist/connectors/schedule.js +1 -1
- package/dist/connectors/slack.js +1 -1
- package/dist/{discord-connector-schema-CR8RJ08_.js → discord-connector-schema-CpuI6rmE.js} +8 -9
- package/dist/gateway/daemon.js +181 -182
- package/dist/{gh-connector-schema-CAC24s0r.js → gh-connector-schema-CQRIvPpz.js} +5 -6
- package/dist/index.d.ts +245 -200
- package/dist/index.js +363 -248
- package/dist/logger-D1A3_JXV.js +27 -0
- package/dist/{schedule-connector-schema-BZpH6ZmR.js → schedule-connector-schema-CuCjP7z4.js} +4 -5
- package/dist/{slack-connector-schema-B0NyhxqQ.js → slack-connector-schema-BWL7dWlY.js} +8 -6
- package/funnel.schema.json +0 -4
- package/package.json +2 -3
- package/dist/node-logger-B97ZiGwj.js +0 -74
package/README.md
CHANGED
|
@@ -34,13 +34,13 @@ external sources outbound replies
|
|
|
34
34
|
agent (subscribes to one channel)
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Two concepts make up the transport model:
|
|
38
38
|
|
|
39
39
|
Channel — a named subscription box (transport only). Holds one or more connectors and a delivery mode; it does not carry launch flags. An agent session subscribes to exactly one channel. Delivery is `fanout` (every subscriber sees every event, the default) or `exclusive` (one event per subscriber, round-robin — for worker pools).
|
|
40
40
|
|
|
41
41
|
Connector — a single attachment from a channel to an external source. Four types ship today: `slack`, `gh`, `discord`, `schedule`. The first three are bidirectional (events in, replies out); `schedule` is one-way (cron ticks in).
|
|
42
42
|
|
|
43
|
-
Profile — a saved launch preset
|
|
43
|
+
Profile sits on top of that model as a launch convenience, not part of it — you never need one to run an agent (`fnl claude --channel <name>` is enough). It is a saved launch preset bundling `{ path, channelId, options, env, resume }` so `fnl claude --profile cto` reproduces a known setup: which directory to launch from, which channel to bind, and the launch recipe (args prepended to the claude argv, env layered under the process, session reuse). A profile carries a stable uuid `id` (the unit the PID file and the resumable session key off, so renaming it strands neither); `name` is just the handle you type. The first profile in the list is the default. Because a profile already binds a channel, `--profile` and `--channel` cannot be combined.
|
|
44
44
|
|
|
45
45
|
The daemon is where all external connections live. It runs on port 9742, supervises connectors with auto-restart, broadcasts events to subscribed agent sessions over WebSocket, and serves the reply API that MCP calls. Starting or stopping an agent never starts or stops external connections.
|
|
46
46
|
|
|
@@ -116,13 +116,11 @@ Or drop a `funnel.json` in the repo and `fnl claude` (no args) inside the repo w
|
|
|
116
116
|
],
|
|
117
117
|
"profiles": [
|
|
118
118
|
{
|
|
119
|
-
"name": "ops-pm",
|
|
120
119
|
"channel": "ops",
|
|
121
120
|
"options": ["--brief", "--agent", "pm"],
|
|
122
121
|
"env": { "ANTHROPIC_MODEL": "claude-sonnet-4-6" }
|
|
123
122
|
},
|
|
124
123
|
{
|
|
125
|
-
"name": "review-reviewer",
|
|
126
124
|
"channel": "review",
|
|
127
125
|
"options": ["--agent", "reviewer"]
|
|
128
126
|
}
|
|
@@ -208,7 +206,7 @@ fnl profiles remove <name>
|
|
|
208
206
|
|
|
209
207
|
fnl claude launch the first channel from ./funnel.json, or the default profile
|
|
210
208
|
fnl claude --channel <name> with funnel.json: pick that channel; without: raw launch
|
|
211
|
-
fnl claude --profile <name> launch a named profile (ignores funnel.json)
|
|
209
|
+
fnl claude --profile <name> launch a named profile (ignores funnel.json; cannot combine with --channel)
|
|
212
210
|
fnl claude [...] positionals and any flag other than -p / --profile / --channel
|
|
213
211
|
(e.g. --agent, --resume, -c, --model) pass through to claude
|
|
214
212
|
fnl mcp run as an MCP server (invoked from .mcp.json)
|
|
@@ -265,10 +263,13 @@ Connector =
|
|
|
265
263
|
| { type: "discord", name, botToken } Discord Gateway
|
|
266
264
|
| { type: "schedule", name, entries[] } cron-driven; entries = { id, cron, prompt, enabled?, catchupPolicy? }
|
|
267
265
|
|
|
268
|
-
Profile = { name, path, channelId, options[], env, resume }
|
|
266
|
+
Profile = { id, name, path, channelId, options[], env, resume, sessionId? }
|
|
269
267
|
named launch preset: where to launch (path), which channel to bind, and the launch recipe —
|
|
270
268
|
options[] prepends to the claude argv, env layers under the process (process.env wins on
|
|
271
|
-
collision), resume toggles session reuse. the first profile is the default.
|
|
269
|
+
collision), resume toggles session reuse. the first profile is the default. id is a stable
|
|
270
|
+
uuid (the key the PID file and resumable session hang off, so a rename strands neither);
|
|
271
|
+
name is the CLI handle. sessionId is execution state, not config — the claude session this
|
|
272
|
+
profile last launched, written by the launcher and read back on the next resume.
|
|
272
273
|
|
|
273
274
|
LocalConfig = { channels: ChannelSpec[], profiles?: ProfileSpec[] }
|
|
274
275
|
per-repo file (funnel.json). channels[] required; first entry is default, --channel selects.
|
|
@@ -279,16 +280,17 @@ ChannelSpec = { name, connectors? }
|
|
|
279
280
|
literal, an env-var reference at `env.<field>` resolved from process.env and ./.env.local, or
|
|
280
281
|
omission for a TTY prompt persisted to ~/.funnel.
|
|
281
282
|
|
|
282
|
-
ProfileSpec = {
|
|
283
|
+
ProfileSpec = { channel, options?, env?, resume? }
|
|
283
284
|
launch recipe bound to a channel by name. applied inline on launch (the first spec bound to the
|
|
284
|
-
chosen channel); not persisted into the global
|
|
285
|
+
chosen channel — selected by its channel binding, not by name); not persisted into the global
|
|
286
|
+
profiles[] list.
|
|
285
287
|
|
|
286
288
|
Settings = { channels[], profiles[] } → ~/.funnel/settings.json
|
|
287
289
|
```
|
|
288
290
|
|
|
289
291
|
## File layout
|
|
290
292
|
|
|
291
|
-
Persistent state lives under `~/.funnel/`. Volatile logs and the event
|
|
293
|
+
Persistent state lives under `~/.funnel/`. Volatile logs and the event log live under `/tmp/funnel/`.
|
|
292
294
|
|
|
293
295
|
```
|
|
294
296
|
~/.funnel/
|
|
@@ -296,7 +298,7 @@ Persistent state lives under `~/.funnel/`. Volatile logs and the event store liv
|
|
|
296
298
|
├── gateway.pid daemon PID
|
|
297
299
|
├── gateway.token Bearer token for daemon HTTP / WS
|
|
298
300
|
├── claude/
|
|
299
|
-
│ └── <profile>.pid
|
|
301
|
+
│ └── <profile-id>.pid prevents double-launch of the same profile (keyed by profile id)
|
|
300
302
|
└── channels/
|
|
301
303
|
└── <channel-id>/
|
|
302
304
|
└── connectors/
|
|
@@ -304,7 +306,7 @@ Persistent state lives under `~/.funnel/`. Volatile logs and the event store liv
|
|
|
304
306
|
└── state.json per-connector durable state (e.g. schedule lastFiredAt)
|
|
305
307
|
|
|
306
308
|
/tmp/funnel/
|
|
307
|
-
├── events.db SQLite event
|
|
309
|
+
├── events.db SQLite event log with replay-by-offset
|
|
308
310
|
├── funnel.log diagnostic log (daemon lifecycle, listener boot, connects)
|
|
309
311
|
└── gateway.log daemon stdout/stderr
|
|
310
312
|
```
|
|
@@ -372,13 +374,21 @@ Run the gateway in-process (no daemon spawn — useful for tests or embedding):
|
|
|
372
374
|
```ts
|
|
373
375
|
const server = funnel.gatewayServer({ port: 9742 })
|
|
374
376
|
await server.start() // Bun.serve (HTTP + WS) + listener supervisor
|
|
375
|
-
const unsubscribe = server.
|
|
376
|
-
console.log(meta?.connector, content)
|
|
377
|
+
const unsubscribe = server.onEvent(({ content, meta }) => {
|
|
378
|
+
console.log(meta?.connector, content) // in-process observer for every broadcast event
|
|
377
379
|
})
|
|
378
380
|
await server.stop()
|
|
379
381
|
unsubscribe()
|
|
380
382
|
```
|
|
381
383
|
|
|
384
|
+
Persistence and replay live behind the `FunnelEventLog` port. The default is a `SqliteFunnelEventLog` (durable across daemon restarts: it seeds the broadcaster's offset and serves reconnect replay). Inject `MemoryFunnelEventLog` — or any `FunnelEventLog` — to swap or disable durable replay; `onEvent` is a separate, write-only observation hook and does not replace it:
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
import { MemoryFunnelEventLog } from "@interactive-inc/claude-funnel"
|
|
388
|
+
|
|
389
|
+
const server = funnel.gatewayServer({ port: 9742, eventLog: new MemoryFunnelEventLog() })
|
|
390
|
+
```
|
|
391
|
+
|
|
382
392
|
The daemon exposes `/health`, `/status`, `/listeners*`, `/channels/:channel/connectors/:connector/call`, plus the `/ws?channel=<name>` WebSocket.
|
|
383
393
|
|
|
384
394
|
### Sandboxed Funnel
|