@interactive-inc/claude-funnel 0.15.2 → 0.16.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.
package/dist/index.d.ts CHANGED
@@ -476,9 +476,8 @@ declare const connectorSpecSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
476
476
  name: z.ZodString;
477
477
  }, z.core.$strip>], "type">;
478
478
  type ConnectorSpec = z.infer<typeof connectorSpecSchema>;
479
- declare const localConfigSchema: z.ZodObject<{
480
- $schema: z.ZodOptional<z.ZodString>;
481
- channel: z.ZodString;
479
+ declare const channelSpecSchema: z.ZodObject<{
480
+ name: z.ZodString;
482
481
  options: z.ZodOptional<z.ZodArray<z.ZodString>>;
483
482
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
484
483
  connectors: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -506,6 +505,41 @@ declare const localConfigSchema: z.ZodObject<{
506
505
  name: z.ZodString;
507
506
  }, z.core.$strip>], "type">>>;
508
507
  }, z.core.$strip>;
508
+ type ChannelSpec = z.infer<typeof channelSpecSchema>;
509
+ declare const localConfigSchema: z.ZodObject<{
510
+ $schema: z.ZodOptional<z.ZodString>;
511
+ options: z.ZodOptional<z.ZodArray<z.ZodString>>;
512
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
513
+ channels: z.ZodArray<z.ZodObject<{
514
+ name: z.ZodString;
515
+ options: z.ZodOptional<z.ZodArray<z.ZodString>>;
516
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
517
+ connectors: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
518
+ type: z.ZodLiteral<"slack">;
519
+ name: z.ZodString;
520
+ botToken: z.ZodOptional<z.ZodString>;
521
+ appToken: z.ZodOptional<z.ZodString>;
522
+ env: z.ZodOptional<z.ZodObject<{
523
+ botToken: z.ZodOptional<z.ZodString>;
524
+ appToken: z.ZodOptional<z.ZodString>;
525
+ }, z.core.$strip>>;
526
+ }, z.core.$strip>, z.ZodObject<{
527
+ type: z.ZodLiteral<"discord">;
528
+ name: z.ZodString;
529
+ botToken: z.ZodOptional<z.ZodString>;
530
+ env: z.ZodOptional<z.ZodObject<{
531
+ botToken: z.ZodOptional<z.ZodString>;
532
+ }, z.core.$strip>>;
533
+ }, z.core.$strip>, z.ZodObject<{
534
+ type: z.ZodLiteral<"gh">;
535
+ name: z.ZodString;
536
+ pollInterval: z.ZodOptional<z.ZodNumber>;
537
+ }, z.core.$strip>, z.ZodObject<{
538
+ type: z.ZodLiteral<"schedule">;
539
+ name: z.ZodString;
540
+ }, z.core.$strip>], "type">>>;
541
+ }, z.core.$strip>>;
542
+ }, z.core.$strip>;
509
543
  type LocalConfig = z.infer<typeof localConfigSchema>;
510
544
  declare const LOCAL_CONFIG_FILENAME = "funnel.json";
511
545
  declare const LOCAL_ENV_FILENAME = ".env.local";
@@ -545,8 +579,8 @@ type Deps$9 = {
545
579
  env?: NodeJS.ProcessEnv;
546
580
  };
547
581
  /**
548
- * Reconciles a `funnel.json` spec with `~/.funnel/settings.json`. The spec
549
- * is the source of truth for the channel it declares:
582
+ * Reconciles a single funnel.json channel spec with `~/.funnel/settings.json`.
583
+ * The spec is the source of truth for the channel it declares:
550
584
  *
551
585
  * - missing channel → created
552
586
  * - declared connector matched by name → tokens reconciled
@@ -555,9 +589,10 @@ type Deps$9 = {
555
589
  * - declared connector with no match → added
556
590
  * - any connector left in the channel that the spec did not touch → removed
557
591
  *
558
- * Removal only fires when funnel.json has a `connectors` field. An absent
559
- * field means "do not manage connectors from here" and leaves everything in
560
- * `~/.funnel` alone.
592
+ * Removal only fires when the channel spec has a `connectors` field. An
593
+ * absent field means "do not manage connectors from here" and leaves
594
+ * everything in `~/.funnel` alone. Other channels in funnel.json (not
595
+ * passed to this call) are untouched.
561
596
  */
562
597
  declare class FunnelLocalConfigSync {
563
598
  private readonly channels;
@@ -565,7 +600,7 @@ declare class FunnelLocalConfigSync {
565
600
  private readonly prompter;
566
601
  private readonly env;
567
602
  constructor(deps: Deps$9);
568
- ensure(local: LocalConfig, cwd: string): Promise<void>;
603
+ ensure(channel: ChannelSpec, cwd: string): Promise<void>;
569
604
  private ensureConnector;
570
605
  private ensureSlack;
571
606
  private ensureDiscord;
@@ -1613,7 +1648,7 @@ declare const createCliApp: (funnel: Funnel) => _$hono_hono_base0.HonoBase<Env,
1613
1648
  channel?: string | undefined;
1614
1649
  };
1615
1650
  };
1616
- output: "funnel claude — launch Claude Code\n\nusage:\n funnel claude launch using funnel.json in cwd, or the default profile\n funnel claude -p <name> launch a named profile\n funnel claude --profile <name> (long form)\n funnel claude --channel <name> raw launch (no profile, cwd = current dir)\n funnel claude [...] any other argument is forwarded to the claude CLI\n\nresolution order when no --profile / --channel is given:\n 1. ./funnel.json in the current directory\n 2. the default profile (first entry in fnl profiles)\n\nfunnel-specific options (everything else passes through to claude verbatim):\n -p, --profile profile name to launch\n --channel channel name (raw launch, ignored when --profile is given)\n -h, --help show this help\n\nPositional args, unknown short flags (e.g. -c, -r), and claude's own flags\n(--agent, --resume, --model, --print, --output-format ...) are all forwarded.\nOn launch the FUNNEL_CHANNEL_ID env var is set and MCP connects to the gateway.";
1651
+ output: "funnel claude — launch Claude Code\n\nusage:\n funnel claude launch the first channel from funnel.json, or the default profile\n funnel claude --channel <name> with funnel.json: select that channel; without: raw launch\n funnel claude -p <name> launch a named profile\n funnel claude --profile <name> (long form)\n funnel claude [...] any other argument is forwarded to the claude CLI\n\nresolution order:\n 1. --help print this help\n 2. --profile <name> named profile (ignores funnel.json)\n 3. ./funnel.json in the current directory + --channel selects (or first wins)\n 4. --channel <name> with no funnel.json → raw launch using an existing settings.json channel\n 5. the default profile (first entry in fnl profiles)\n\nfunnel-specific options (everything else passes through to claude verbatim):\n -p, --profile profile name to launch\n --channel channel name (selects from funnel.json, or raw-launches if no funnel.json)\n -h, --help show this help\n\nPositional args, unknown short flags (e.g. -c, -r), and claude's own flags\n(--agent, --resume, --model, --print, --output-format ...) are all forwarded.\nOn launch the FUNNEL_CHANNEL_ID env var is set and MCP connects to the gateway.";
1617
1652
  outputFormat: "text";
1618
1653
  status: _$hono_utils_http_status0.ContentfulStatusCode;
1619
1654
  };
@@ -2904,7 +2939,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
2904
2939
  channel?: string | undefined;
2905
2940
  };
2906
2941
  };
2907
- output: "funnel claude — launch Claude Code\n\nusage:\n funnel claude launch using funnel.json in cwd, or the default profile\n funnel claude -p <name> launch a named profile\n funnel claude --profile <name> (long form)\n funnel claude --channel <name> raw launch (no profile, cwd = current dir)\n funnel claude [...] any other argument is forwarded to the claude CLI\n\nresolution order when no --profile / --channel is given:\n 1. ./funnel.json in the current directory\n 2. the default profile (first entry in fnl profiles)\n\nfunnel-specific options (everything else passes through to claude verbatim):\n -p, --profile profile name to launch\n --channel channel name (raw launch, ignored when --profile is given)\n -h, --help show this help\n\nPositional args, unknown short flags (e.g. -c, -r), and claude's own flags\n(--agent, --resume, --model, --print, --output-format ...) are all forwarded.\nOn launch the FUNNEL_CHANNEL_ID env var is set and MCP connects to the gateway.";
2942
+ output: "funnel claude — launch Claude Code\n\nusage:\n funnel claude launch the first channel from funnel.json, or the default profile\n funnel claude --channel <name> with funnel.json: select that channel; without: raw launch\n funnel claude -p <name> launch a named profile\n funnel claude --profile <name> (long form)\n funnel claude [...] any other argument is forwarded to the claude CLI\n\nresolution order:\n 1. --help print this help\n 2. --profile <name> named profile (ignores funnel.json)\n 3. ./funnel.json in the current directory + --channel selects (or first wins)\n 4. --channel <name> with no funnel.json → raw launch using an existing settings.json channel\n 5. the default profile (first entry in fnl profiles)\n\nfunnel-specific options (everything else passes through to claude verbatim):\n -p, --profile profile name to launch\n --channel channel name (selects from funnel.json, or raw-launches if no funnel.json)\n -h, --help show this help\n\nPositional args, unknown short flags (e.g. -c, -r), and claude's own flags\n(--agent, --resume, --model, --print, --output-format ...) are all forwarded.\nOn launch the FUNNEL_CHANNEL_ID env var is set and MCP connects to the gateway.";
2908
2943
  outputFormat: "text";
2909
2944
  status: _$hono_utils_http_status0.ContentfulStatusCode;
2910
2945
  };
@@ -4177,4 +4212,4 @@ ${string}`;
4177
4212
  //#region lib/tui/tui.d.ts
4178
4213
  declare function launchTui(funnel: Funnel): Promise<void>;
4179
4214
  //#endregion
4180
- export { AttachOptions, BroadcastEvent, BroadcastSubscriber, ChannelConfig, ChannelConnectorView, ChannelDeliveryMode, ChannelServerOptions, ConnectorConfig, ConnectorSpec, ConnectorType, DEFAULT_GATEWAY_TOKEN_PATH, DetachOptions, DiscordConnectorConfig, Env, FUNNEL_DIR, FUNNEL_MCP_COMMAND, FUNNEL_MCP_NAME, FileStat, Funnel, FunnelBroadcaster, FunnelChannelPublisher, FunnelChannels, FunnelClaude, FunnelClock, FunnelConnectorFactory, FunnelConnectorListener, FunnelDotenvReader, FunnelEvent, FunnelEventStore, FunnelFileSystem, FunnelGateway, FunnelGatewayServer, FunnelGatewayToken, FunnelIdGenerator, FunnelListenerSupervisor, FunnelListenersClient, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLogger, FunnelMcp, FunnelProcessRunner, FunnelProfiles, FunnelSettingsReader, FunnelSettingsStore, FunnelSlackEventProcessor, FunnelTokenPrompter, GhConnectorConfig, LOCAL_CONFIG_FILENAME, LOCAL_ENV_FILENAME, LaunchOptions, ListListenersResult, ListenerEntry, ListenerOpResult, LocalConfig, LogEntry, MemoryFunnelClock, MemoryFunnelFileSystem, MemoryFunnelIdGenerator, MemoryFunnelLogger, MemoryFunnelProcessRunner, MemoryFunnelTokenPrompter, MemoryProcessCall, MemoryProcessHandler, MemoryProcessResponse, MemoryProcessSyncHandler, MockFunnelSettingsReader, NodeFunnelClock, NodeFunnelFileSystem, NodeFunnelIdGenerator, NodeFunnelLogger, NodeFunnelProcessRunner, NodeFunnelTokenPrompter, NoopFunnelLogger, NotifyFn, ProfileConfig, PublishRequest, PublishResponse, PublishResult, ReplayableEvent, RunOptions, RunResult, SETTINGS_PATH, SETTINGS_VERSION, ScheduleCatchupPolicy, ScheduleConnectorConfig, ScheduleEntry, ScheduleListenerOptions, Settings, SlackConnectorConfig, SlackListenerOptions, SlackProcessed, SlackProcessedEmit, SlackProcessedSkip, SlackRawEvent, channelConfigSchema, channelDeliveryModeSchema, app as cliApp, connectorConfigSchema, connectorSpecSchema, createCliApp, createSettings, discordConnectorSchema, factory, funnelEventSchema, funnelJsonSchema, ghConnectorSchema, launchTui, localConfigSchema, profileConfigSchema, publishRequestSchema, publishResponseSchema, queryToCliArgs, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema, settingsSchema, slackConnectorSchema, startChannelServer, toRequest };
4215
+ export { AttachOptions, BroadcastEvent, BroadcastSubscriber, ChannelConfig, ChannelConnectorView, ChannelDeliveryMode, ChannelServerOptions, ChannelSpec, ConnectorConfig, ConnectorSpec, ConnectorType, DEFAULT_GATEWAY_TOKEN_PATH, DetachOptions, DiscordConnectorConfig, Env, FUNNEL_DIR, FUNNEL_MCP_COMMAND, FUNNEL_MCP_NAME, FileStat, Funnel, FunnelBroadcaster, FunnelChannelPublisher, FunnelChannels, FunnelClaude, FunnelClock, FunnelConnectorFactory, FunnelConnectorListener, FunnelDotenvReader, FunnelEvent, FunnelEventStore, FunnelFileSystem, FunnelGateway, FunnelGatewayServer, FunnelGatewayToken, FunnelIdGenerator, FunnelListenerSupervisor, FunnelListenersClient, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLogger, FunnelMcp, FunnelProcessRunner, FunnelProfiles, FunnelSettingsReader, FunnelSettingsStore, FunnelSlackEventProcessor, FunnelTokenPrompter, GhConnectorConfig, LOCAL_CONFIG_FILENAME, LOCAL_ENV_FILENAME, LaunchOptions, ListListenersResult, ListenerEntry, ListenerOpResult, LocalConfig, LogEntry, MemoryFunnelClock, MemoryFunnelFileSystem, MemoryFunnelIdGenerator, MemoryFunnelLogger, MemoryFunnelProcessRunner, MemoryFunnelTokenPrompter, MemoryProcessCall, MemoryProcessHandler, MemoryProcessResponse, MemoryProcessSyncHandler, MockFunnelSettingsReader, NodeFunnelClock, NodeFunnelFileSystem, NodeFunnelIdGenerator, NodeFunnelLogger, NodeFunnelProcessRunner, NodeFunnelTokenPrompter, NoopFunnelLogger, NotifyFn, ProfileConfig, PublishRequest, PublishResponse, PublishResult, ReplayableEvent, RunOptions, RunResult, SETTINGS_PATH, SETTINGS_VERSION, ScheduleCatchupPolicy, ScheduleConnectorConfig, ScheduleEntry, ScheduleListenerOptions, Settings, SlackConnectorConfig, SlackListenerOptions, SlackProcessed, SlackProcessedEmit, SlackProcessedSkip, SlackRawEvent, channelConfigSchema, channelDeliveryModeSchema, channelSpecSchema, app as cliApp, connectorConfigSchema, connectorSpecSchema, createCliApp, createSettings, discordConnectorSchema, factory, funnelEventSchema, funnelJsonSchema, ghConnectorSchema, launchTui, localConfigSchema, profileConfigSchema, publishRequestSchema, publishResponseSchema, queryToCliArgs, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema, settingsSchema, slackConnectorSchema, startChannelServer, toRequest };
package/dist/index.js CHANGED
@@ -737,20 +737,16 @@ var MemoryFunnelIdGenerator = class extends FunnelIdGenerator {
737
737
  /**
738
738
  * Per-repo launch config (`funnel.json`).
739
739
  *
740
- * `fnl claude` reads this when no --profile / --channel is given and uses it
741
- * to set the channel binding, sub-agent, and brief flag. When `connectors`
742
- * is declared, missing channels/connectors are materialized into the local
743
- * `~/.funnel/settings.json` on launch.
740
+ * `fnl claude` reads this when no --profile is given and picks one of the
741
+ * declared channels (`--channel <name>` selects by name; otherwise the first
742
+ * entry wins). The chosen channel is materialized into
743
+ * `~/.funnel/settings.json` on launch — token fields in connectors resolve
744
+ * via literal / `env.<field>` / TTY prompt.
744
745
  *
745
- * Token fields per connector resolve in this order:
746
- *
747
- * 1. Literal value at the field itself (e.g. `botToken: "xoxb-..."`)
748
- * 2. Env-var reference at `env.<field>` (e.g. `env: { botToken: "SLACK_BOT_TOKEN" }`);
749
- * resolved from process.env first, then ./.env.local
750
- * 3. Field omitted everywhere → prompted for once on a TTY and persisted to
751
- * `~/.funnel/settings.json`; non-TTY launches fail fast.
752
- *
753
- * `funnel.json` itself is never written to. Only `channel` is required.
746
+ * Top-level `options` and `env` are defaults shared by every channel: each
747
+ * channel's own `options` is appended after the shared ones (CLI semantics
748
+ * keep the later flag winning), and `env` is a shallow merge with the
749
+ * channel's keys overriding the shared ones.
754
750
  */
755
751
  const slackEnvSchema = z.object({
756
752
  botToken: z.string().optional(),
@@ -785,13 +781,20 @@ const connectorSpecSchema = z.discriminatedUnion("type", [
785
781
  ghConnectorSpecSchema,
786
782
  scheduleConnectorSpecSchema
787
783
  ]);
784
+ const channelSpecSchema = z.object({
785
+ name: z.string(),
786
+ options: z.array(z.string()).optional(),
787
+ env: z.record(z.string(), z.string()).optional(),
788
+ connectors: z.array(connectorSpecSchema).optional()
789
+ });
788
790
  const localConfigSchema = z.object({
789
791
  $schema: z.string().optional(),
790
- channel: z.string(),
791
- /** Extra args forwarded to the claude CLI. Prepended before user-supplied CLI args so user args still win on collision (e.g. --model, --agent, --brief, --resume, positional session ids). */
792
+ /** Extra args forwarded to every channel's launch before the channel's own options. User-supplied CLI args still come last. */
792
793
  options: z.array(z.string()).optional(),
794
+ /** Environment variables shared by every channel. Each channel's env merges on top; process.env wins overall. */
793
795
  env: z.record(z.string(), z.string()).optional(),
794
- connectors: z.array(connectorSpecSchema).optional()
796
+ /** Declared channels. First entry is the default; --channel <name> selects by name. */
797
+ channels: z.array(channelSpecSchema).min(1)
795
798
  });
796
799
  const LOCAL_CONFIG_FILENAME = "funnel.json";
797
800
  const LOCAL_ENV_FILENAME = ".env.local";
@@ -879,8 +882,8 @@ var FunnelTokenPrompter = class {};
879
882
  //#endregion
880
883
  //#region lib/engine/local-config/local-config-sync.ts
881
884
  /**
882
- * Reconciles a `funnel.json` spec with `~/.funnel/settings.json`. The spec
883
- * is the source of truth for the channel it declares:
885
+ * Reconciles a single funnel.json channel spec with `~/.funnel/settings.json`.
886
+ * The spec is the source of truth for the channel it declares:
884
887
  *
885
888
  * - missing channel → created
886
889
  * - declared connector matched by name → tokens reconciled
@@ -889,9 +892,10 @@ var FunnelTokenPrompter = class {};
889
892
  * - declared connector with no match → added
890
893
  * - any connector left in the channel that the spec did not touch → removed
891
894
  *
892
- * Removal only fires when funnel.json has a `connectors` field. An absent
893
- * field means "do not manage connectors from here" and leaves everything in
894
- * `~/.funnel` alone.
895
+ * Removal only fires when the channel spec has a `connectors` field. An
896
+ * absent field means "do not manage connectors from here" and leaves
897
+ * everything in `~/.funnel` alone. Other channels in funnel.json (not
898
+ * passed to this call) are untouched.
895
899
  */
896
900
  var FunnelLocalConfigSync = class {
897
901
  channels;
@@ -905,16 +909,16 @@ var FunnelLocalConfigSync = class {
905
909
  this.env = deps.env ?? process.env;
906
910
  Object.freeze(this);
907
911
  }
908
- async ensure(local, cwd) {
909
- if (!this.channels.get(local.channel)) this.channels.add({ name: local.channel });
910
- if (local.connectors === void 0) return;
912
+ async ensure(channel, cwd) {
913
+ if (!this.channels.get(channel.name)) this.channels.add({ name: channel.name });
914
+ if (channel.connectors === void 0) return;
911
915
  const dotenv = this.dotenv.read(cwd);
912
916
  const touched = /* @__PURE__ */ new Set();
913
- for (const spec of local.connectors) {
914
- const id = await this.ensureConnector(local.channel, spec, dotenv);
917
+ for (const spec of channel.connectors) {
918
+ const id = await this.ensureConnector(channel.name, spec, dotenv);
915
919
  touched.add(id);
916
920
  }
917
- this.removeExtras(local.channel, touched);
921
+ this.removeExtras(channel.name, touched);
918
922
  }
919
923
  async ensureConnector(channelName, spec, dotenv) {
920
924
  if (spec.type === "slack") return await this.ensureSlack(channelName, spec, dotenv);
@@ -4075,19 +4079,22 @@ examples:
4075
4079
  const claudeHelp = `funnel claude — launch Claude Code
4076
4080
 
4077
4081
  usage:
4078
- funnel claude launch using funnel.json in cwd, or the default profile
4082
+ funnel claude launch the first channel from funnel.json, or the default profile
4083
+ funnel claude --channel <name> with funnel.json: select that channel; without: raw launch
4079
4084
  funnel claude -p <name> launch a named profile
4080
4085
  funnel claude --profile <name> (long form)
4081
- funnel claude --channel <name> raw launch (no profile, cwd = current dir)
4082
4086
  funnel claude [...] any other argument is forwarded to the claude CLI
4083
4087
 
4084
- resolution order when no --profile / --channel is given:
4085
- 1. ./funnel.json in the current directory
4086
- 2. the default profile (first entry in fnl profiles)
4088
+ resolution order:
4089
+ 1. --help print this help
4090
+ 2. --profile <name> named profile (ignores funnel.json)
4091
+ 3. ./funnel.json in the current directory + --channel selects (or first wins)
4092
+ 4. --channel <name> with no funnel.json → raw launch using an existing settings.json channel
4093
+ 5. the default profile (first entry in fnl profiles)
4087
4094
 
4088
4095
  funnel-specific options (everything else passes through to claude verbatim):
4089
4096
  -p, --profile profile name to launch
4090
- --channel channel name (raw launch, ignored when --profile is given)
4097
+ --channel channel name (selects from funnel.json, or raw-launches if no funnel.json)
4091
4098
  -h, --help show this help
4092
4099
 
4093
4100
  Positional args, unknown short flags (e.g. -c, -r), and claude's own flags
@@ -4124,12 +4131,21 @@ const claudeHandler = factory.createHandlers(zValidator$1("query", z.object({
4124
4131
  const cwd = process.cwd();
4125
4132
  const local = funnel.localConfig.read(cwd);
4126
4133
  if (local) {
4127
- await funnel.localConfigSync.ensure(local, cwd);
4134
+ const picked = query.channel !== void 0 ? local.channels.find((c) => c.name === query.channel) : local.channels[0];
4135
+ if (!picked) throw new HTTPException(404, { message: query.channel ? `channel "${query.channel}" is not declared in funnel.json` : `funnel.json declares no channels` });
4136
+ await funnel.localConfigSync.ensure(picked, cwd);
4128
4137
  const exitCode = await funnel.claude.launch({
4129
- channel: local.channel,
4138
+ channel: picked.name,
4130
4139
  cwd,
4131
- userArgs: [...local.options ?? [], ...userArgs],
4132
- extraEnv: local.env
4140
+ userArgs: [
4141
+ ...local.options ?? [],
4142
+ ...picked.options ?? [],
4143
+ ...userArgs
4144
+ ],
4145
+ extraEnv: {
4146
+ ...local.env ?? {},
4147
+ ...picked.env ?? {}
4148
+ }
4133
4149
  });
4134
4150
  process.exit(exitCode);
4135
4151
  }
@@ -6831,4 +6847,4 @@ async function launchTui(funnel) {
6831
6847
  });
6832
6848
  }
6833
6849
  //#endregion
6834
- export { DEFAULT_GATEWAY_TOKEN_PATH, FUNNEL_DIR, FUNNEL_MCP_COMMAND, FUNNEL_MCP_NAME, Funnel, FunnelBroadcaster, FunnelChannelPublisher, FunnelChannels, FunnelClaude, FunnelClock, FunnelConnectorFactory, FunnelConnectorListener, FunnelDotenvReader, FunnelEventStore, FunnelFileSystem, FunnelGateway, FunnelGatewayServer, FunnelGatewayToken, FunnelIdGenerator, FunnelListenerSupervisor, FunnelListenersClient, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLogger, FunnelMcp, FunnelProcessRunner, FunnelProfiles, FunnelSettingsReader, FunnelSettingsStore, FunnelSlackEventProcessor, FunnelTokenPrompter, LOCAL_CONFIG_FILENAME, LOCAL_ENV_FILENAME, MemoryFunnelClock, MemoryFunnelFileSystem, MemoryFunnelIdGenerator, MemoryFunnelLogger, MemoryFunnelProcessRunner, MemoryFunnelTokenPrompter, MockFunnelSettingsReader, NodeFunnelClock, NodeFunnelFileSystem, NodeFunnelIdGenerator, NodeFunnelLogger, NodeFunnelProcessRunner, NodeFunnelTokenPrompter, NoopFunnelLogger, SETTINGS_PATH, SETTINGS_VERSION, channelConfigSchema, channelDeliveryModeSchema, app as cliApp, connectorConfigSchema, connectorSpecSchema, createCliApp, createSettings, discordConnectorSchema, factory, funnelEventSchema, funnelJsonSchema, ghConnectorSchema, launchTui, localConfigSchema, profileConfigSchema, publishRequestSchema, publishResponseSchema, queryToCliArgs, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema, settingsSchema, slackConnectorSchema, startChannelServer, toRequest };
6850
+ export { DEFAULT_GATEWAY_TOKEN_PATH, FUNNEL_DIR, FUNNEL_MCP_COMMAND, FUNNEL_MCP_NAME, Funnel, FunnelBroadcaster, FunnelChannelPublisher, FunnelChannels, FunnelClaude, FunnelClock, FunnelConnectorFactory, FunnelConnectorListener, FunnelDotenvReader, FunnelEventStore, FunnelFileSystem, FunnelGateway, FunnelGatewayServer, FunnelGatewayToken, FunnelIdGenerator, FunnelListenerSupervisor, FunnelListenersClient, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLogger, FunnelMcp, FunnelProcessRunner, FunnelProfiles, FunnelSettingsReader, FunnelSettingsStore, FunnelSlackEventProcessor, FunnelTokenPrompter, LOCAL_CONFIG_FILENAME, LOCAL_ENV_FILENAME, MemoryFunnelClock, MemoryFunnelFileSystem, MemoryFunnelIdGenerator, MemoryFunnelLogger, MemoryFunnelProcessRunner, MemoryFunnelTokenPrompter, MockFunnelSettingsReader, NodeFunnelClock, NodeFunnelFileSystem, NodeFunnelIdGenerator, NodeFunnelLogger, NodeFunnelProcessRunner, NodeFunnelTokenPrompter, NoopFunnelLogger, SETTINGS_PATH, SETTINGS_VERSION, channelConfigSchema, channelDeliveryModeSchema, channelSpecSchema, app as cliApp, connectorConfigSchema, connectorSpecSchema, createCliApp, createSettings, discordConnectorSchema, factory, funnelEventSchema, funnelJsonSchema, ghConnectorSchema, launchTui, localConfigSchema, profileConfigSchema, publishRequestSchema, publishResponseSchema, queryToCliArgs, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema, settingsSchema, slackConnectorSchema, startChannelServer, toRequest };
@@ -5,9 +5,6 @@
5
5
  "$schema": {
6
6
  "type": "string"
7
7
  },
8
- "channel": {
9
- "type": "string"
10
- },
11
8
  "options": {
12
9
  "type": "array",
13
10
  "items": {
@@ -23,119 +20,150 @@
23
20
  "type": "string"
24
21
  }
25
22
  },
26
- "connectors": {
23
+ "channels": {
24
+ "minItems": 1,
27
25
  "type": "array",
28
26
  "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
27
+ "type": "object",
28
+ "properties": {
29
+ "name": {
30
+ "type": "string"
64
31
  },
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
32
+ "options": {
33
+ "type": "array",
34
+ "items": {
35
+ "type": "string"
36
+ }
93
37
  },
94
- {
38
+ "env": {
95
39
  "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
- }
40
+ "propertyNames": {
41
+ "type": "string"
109
42
  },
110
- "required": [
111
- "type",
112
- "name"
113
- ],
114
- "additionalProperties": false
43
+ "additionalProperties": {
44
+ "type": "string"
45
+ }
115
46
  },
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
47
+ "connectors": {
48
+ "type": "array",
49
+ "items": {
50
+ "oneOf": [
51
+ {
52
+ "type": "object",
53
+ "properties": {
54
+ "type": {
55
+ "type": "string",
56
+ "const": "slack"
57
+ },
58
+ "name": {
59
+ "type": "string"
60
+ },
61
+ "botToken": {
62
+ "type": "string"
63
+ },
64
+ "appToken": {
65
+ "type": "string"
66
+ },
67
+ "env": {
68
+ "type": "object",
69
+ "properties": {
70
+ "botToken": {
71
+ "type": "string"
72
+ },
73
+ "appToken": {
74
+ "type": "string"
75
+ }
76
+ },
77
+ "additionalProperties": false
78
+ }
79
+ },
80
+ "required": [
81
+ "type",
82
+ "name"
83
+ ],
84
+ "additionalProperties": false
85
+ },
86
+ {
87
+ "type": "object",
88
+ "properties": {
89
+ "type": {
90
+ "type": "string",
91
+ "const": "discord"
92
+ },
93
+ "name": {
94
+ "type": "string"
95
+ },
96
+ "botToken": {
97
+ "type": "string"
98
+ },
99
+ "env": {
100
+ "type": "object",
101
+ "properties": {
102
+ "botToken": {
103
+ "type": "string"
104
+ }
105
+ },
106
+ "additionalProperties": false
107
+ }
108
+ },
109
+ "required": [
110
+ "type",
111
+ "name"
112
+ ],
113
+ "additionalProperties": false
114
+ },
115
+ {
116
+ "type": "object",
117
+ "properties": {
118
+ "type": {
119
+ "type": "string",
120
+ "const": "gh"
121
+ },
122
+ "name": {
123
+ "type": "string"
124
+ },
125
+ "pollInterval": {
126
+ "type": "integer",
127
+ "exclusiveMinimum": 0,
128
+ "maximum": 9007199254740991
129
+ }
130
+ },
131
+ "required": [
132
+ "type",
133
+ "name"
134
+ ],
135
+ "additionalProperties": false
136
+ },
137
+ {
138
+ "type": "object",
139
+ "properties": {
140
+ "type": {
141
+ "type": "string",
142
+ "const": "schedule"
143
+ },
144
+ "name": {
145
+ "type": "string"
146
+ }
147
+ },
148
+ "required": [
149
+ "type",
150
+ "name"
151
+ ],
152
+ "additionalProperties": false
153
+ }
154
+ ]
155
+ }
132
156
  }
133
- ]
157
+ },
158
+ "required": [
159
+ "name"
160
+ ],
161
+ "additionalProperties": false
134
162
  }
135
163
  }
136
164
  },
137
165
  "required": [
138
- "channel"
166
+ "channels"
139
167
  ],
140
168
  "additionalProperties": false,
141
169
  "title": "Funnel per-repo launch config",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interactive-inc/claude-funnel",
3
- "version": "0.15.2",
3
+ "version": "0.16.1",
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",