@interactive-inc/claude-funnel 0.24.0 → 0.25.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 CHANGED
@@ -36,11 +36,11 @@ external sources outbound replies
36
36
 
37
37
  Three concepts make up the model:
38
38
 
39
- Channel — a named subscription box. Holds one or more connectors. 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).
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 for an agent. Bundles `{ path, sub-agent, channel }` so `fnl claude --profile cto` reproduces a known setup. The first profile in the list is the default.
43
+ Profile — a saved launch preset. Bundles `{ 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). The first profile in the list is the default.
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
 
@@ -84,11 +84,11 @@ fnl claude --channel ops
84
84
 
85
85
  Every event the connector sees now arrives in the running agent session, and the agent can reply via the `my-slack` MCP tool.
86
86
 
87
- Save it as a profile for one-command launches:
87
+ Save it as a profile for one-command launches — the profile carries the launch recipe:
88
88
 
89
89
  ```bash
90
- fnl profiles add cto --path=/repo/myapp --channel=ops
91
- fnl claude --profile cto # cd + channel binding in one shot
90
+ fnl profiles add cto --path=/repo/myapp --channel=ops --agent=pm --options="--brief"
91
+ fnl claude --profile cto # cd + channel binding + recipe in one shot
92
92
  ```
93
93
 
94
94
  Or drop a `funnel.json` in the repo and `fnl claude` (no args) inside the repo will use it:
@@ -99,10 +99,6 @@ Or drop a `funnel.json` in the repo and `fnl claude` (no args) inside the repo w
99
99
  "channels": [
100
100
  {
101
101
  "name": "ops",
102
- "options": ["--brief", "--agent", "pm"],
103
- "env": {
104
- "ANTHROPIC_MODEL": "claude-sonnet-4-6"
105
- },
106
102
  "connectors": [
107
103
  {
108
104
  "type": "slack",
@@ -115,7 +111,19 @@ Or drop a `funnel.json` in the repo and `fnl claude` (no args) inside the repo w
115
111
  ]
116
112
  },
117
113
  {
118
- "name": "review",
114
+ "name": "review"
115
+ }
116
+ ],
117
+ "profiles": [
118
+ {
119
+ "name": "ops-pm",
120
+ "channel": "ops",
121
+ "options": ["--brief", "--agent", "pm"],
122
+ "env": { "ANTHROPIC_MODEL": "claude-sonnet-4-6" }
123
+ },
124
+ {
125
+ "name": "review-reviewer",
126
+ "channel": "review",
119
127
  "options": ["--agent", "reviewer"]
120
128
  }
121
129
  ]
@@ -124,11 +132,11 @@ Or drop a `funnel.json` in the repo and `fnl claude` (no args) inside the repo w
124
132
 
125
133
  `channels[]` is required and the first entry is the default. `fnl claude --channel review` picks one by name; `fnl claude` with no `--channel` uses the first.
126
134
 
127
- Each channel has its own `options` (prepended to the claude argv before user-supplied CLI args, which still come last — use it for per-channel flags like `--brief`, `--agent <name>`, `--model <name>`) and `env` (layered under the launched claude process — `process.env` from the launching shell wins on collision, so funnel.json sets defaults that the user can still override one-off via the shell).
135
+ A channel declares only transport — its `connectors` and delivery mode. The launch recipe lives on `profiles[]`: each profile binds to a channel by name and carries `options` (prepended to the claude argv before user-supplied CLI args, which still come last — use it for flags like `--brief`, `--agent <name>`, `--model <name>`), `env` (layered under the launched claude process — `process.env` from the launching shell wins on collision), and `resume`. `fnl claude` applies the first profile bound to the chosen channel.
128
136
 
129
137
  The optional `connectors` array on a channel is the source of truth for that channel: missing connectors are created, an existing connector matched by token under a different name is renamed in place, and connectors not declared are removed on launch. An absent `connectors` field leaves `~/.funnel` alone.
130
138
 
131
- On launch, the chosen channel's `options` / `env` / `connectors` are also materialized into the global `~/.funnel/settings.json` Channel entry. Raw launches (`fnl claude --channel <name>` without funnel.json) and profile launches read the same fields from there, so funnel.json and settings.json share one Channel data model.
139
+ On launch, the chosen channel's transport (`connectors`, delivery) is materialized into the global `~/.funnel/settings.json` Channel entry; the profile recipe is passed straight to the launcher and is not persisted there. Raw launches (`fnl claude --channel <name>` without funnel.json) bind transport only and carry no recipe.
132
140
 
133
141
  The optional top-level `$schema` points at the JSON Schema so editors can validate and autocomplete the file. The recommended reference for repos with a local install is `./node_modules/@interactive-inc/claude-funnel/funnel.schema.json` — it works without a network round-trip and editors do not need to prompt for trust. The same file is also published at `https://interactive-inc.github.io/open-claude-funnel/funnel.schema.json` (editors usually require explicit trust on first use), and `fnl schema > funnel.schema.json` regenerates a local copy on demand.
134
142
 
@@ -188,10 +196,12 @@ fnl channels <ch> connectors <c> schedules add <id> --cron="<expr>" --prompt="<t
188
196
  fnl channels <ch> connectors <c> schedules remove <id>
189
197
 
190
198
  fnl profiles list (first entry is the default)
191
- fnl profiles add <name> --path=<dir> --channel=<channel-name>
199
+ fnl profiles add <name> --path=<dir> --channel=<channel-name> \
200
+ [--agent=<name>] [--options="<argv>"] [--env="K=V,K2=V2"] [--no-resume]
192
201
  fnl profiles <name> launch (alias for `<name> run`)
193
202
  fnl profiles <name> run launch (sugar for `fnl claude --profile <name>`)
194
- fnl profiles <name> set [--path=...] [--channel=...]
203
+ fnl profiles <name> set [--path=...] [--channel=...] \
204
+ [--agent=...] [--options="..."] [--env="..."] [--resume|--no-resume]
195
205
  fnl profiles <name> as-default move to the front of the list
196
206
  fnl profiles rename <old> <new>
197
207
  fnl profiles remove <name>
@@ -245,10 +255,9 @@ To invoke a connector from outside an agent, the same path is reachable as `fnl
245
255
  ## Data model
246
256
 
247
257
  ```
248
- Channel = { id, name, delivery, options[], env, connectors[] }
249
- subscription box plus launch settings. delivery is `fanout` (every WS client sees every event)
250
- or `exclusive` (round-robin one client per event). options[] prepends to the claude argv on
251
- launch; env layers under the launched process (process.env wins on collision)
258
+ Channel = { id, name, delivery, connectors[] }
259
+ subscription box (transport only). delivery is `fanout` (every WS client sees every event)
260
+ or `exclusive` (round-robin one client per event). carries no launch settings.
252
261
 
253
262
  Connector =
254
263
  | { type: "slack", name, botToken, appToken } Slack Socket Mode
@@ -256,17 +265,23 @@ Connector =
256
265
  | { type: "discord", name, botToken } Discord Gateway
257
266
  | { type: "schedule", name, entries[] } cron-driven; entries = { id, cron, prompt, enabled?, catchupPolicy? }
258
267
 
259
- Profile = { name, path, channelId }
260
- named launch preset (just where to launch a channel from); the first profile is the default
268
+ Profile = { name, path, channelId, options[], env, resume }
269
+ named launch preset: where to launch (path), which channel to bind, and the launch recipe
270
+ 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.
261
272
 
262
- LocalConfig = { channels: ChannelSpec[] }
273
+ LocalConfig = { channels: ChannelSpec[], profiles?: ProfileSpec[] }
263
274
  per-repo file (funnel.json). channels[] required; first entry is default, --channel selects.
264
275
 
265
- ChannelSpec = { name, options?, env?, connectors? }
266
- mirrors Channel above (no id, since funnel.json declares by name). options/env/connectors
267
- materialize into the matching Channel in ~/.funnel/settings.json on launch. Connector token
268
- fields accept a literal, an env-var reference at `env.<field>` resolved from process.env and
269
- ./.env.local, or omission for a TTY prompt persisted to ~/.funnel.
276
+ ChannelSpec = { name, connectors? }
277
+ transport declaration (no id, since funnel.json declares by name). connectors materialize into
278
+ the matching Channel in ~/.funnel/settings.json on launch. Connector token fields accept a
279
+ literal, an env-var reference at `env.<field>` resolved from process.env and ./.env.local, or
280
+ omission for a TTY prompt persisted to ~/.funnel.
281
+
282
+ ProfileSpec = { name, channel, options?, env?, resume? }
283
+ 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 profiles[] list.
270
285
 
271
286
  Settings = { channels[], profiles[] } → ~/.funnel/settings.json
272
287
  ```