@interactive-inc/claude-funnel 0.16.1 → 0.18.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.
@@ -14,6 +14,9 @@ type RunResult = {
14
14
  type AttachOptions = {
15
15
  cwd?: string;
16
16
  env?: Record<string, string>;
17
+ /** Invoked synchronously after the child process has been spawned, with its PID.
18
+ * Useful for hosts that need to register the spawned process before it exits. */
19
+ onSpawned?: (pid: number) => void;
17
20
  };
18
21
  type DetachOptions = {
19
22
  env?: Record<string, string>;
@@ -52,7 +52,7 @@ var NodeFunnelProcessRunner = class extends FunnelProcessRunner {
52
52
  };
53
53
  }
54
54
  async attach(command, options = {}) {
55
- return await Bun.spawn(command, {
55
+ const proc = Bun.spawn(command, {
56
56
  cwd: options.cwd,
57
57
  env: toEnv(options.env),
58
58
  stdio: [
@@ -60,7 +60,9 @@ var NodeFunnelProcessRunner = class extends FunnelProcessRunner {
60
60
  "inherit",
61
61
  "inherit"
62
62
  ]
63
- }).exited;
63
+ });
64
+ if (options.onSpawned) options.onSpawned(proc.pid);
65
+ return await proc.exited;
64
66
  }
65
67
  detach(command, options = {}) {
66
68
  Bun.spawn(command, {
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { n as FunnelConnectorAdapter, t as CallInput } from "./connector-adapter-CXB-q_XC.js";
2
2
  import { n as discordConnectorSchema, t as DiscordConnectorConfig } from "./discord-connector-schema-Dww2I4zH.js";
3
3
  import { n as FunnelConnectorListener, r as NotifyFn, t as FunnelLogger } from "./logger-CTlXs7z4.js";
4
- import { a as FunnelProcessRunner, i as DetachOptions, n as ghConnectorSchema, o as RunOptions, r as AttachOptions, s as RunResult, t as GhConnectorConfig } from "./gh-connector-schema-BZFAS-p-.js";
4
+ import { a as FunnelProcessRunner, i as DetachOptions, n as ghConnectorSchema, o as RunOptions, r as AttachOptions, s as RunResult, t as GhConnectorConfig } from "./gh-connector-schema-BNyTaASt.js";
5
5
  import { a as FunnelFileSystem, c as ScheduleEntry, d as scheduleEntrySchema, i as FileStat, l as scheduleCatchupPolicySchema, n as ScheduleOnFired, o as ScheduleCatchupPolicy, s as ScheduleConnectorConfig, u as scheduleConnectorSchema } from "./schedule-listener-BPodvbld.js";
6
6
  import { a as SlackProcessed, c as SlackRawEvent, i as FunnelSlackEventProcessor, l as SlackConnectorConfig, n as SlackOnAppCreated, o as SlackProcessedEmit, r as SlackPreprocessEvent, s as SlackProcessedSkip, u as slackConnectorSchema } from "./slack-listener-CHj6uMY-.js";
7
7
  import { z } from "zod";
@@ -150,6 +150,8 @@ declare const channelConfigSchema: z.ZodObject<{
150
150
  fanout: "fanout";
151
151
  exclusive: "exclusive";
152
152
  }>>;
153
+ options: z.ZodDefault<z.ZodArray<z.ZodString>>;
154
+ env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
153
155
  connectors: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
154
156
  id: z.ZodString;
155
157
  name: z.ZodString;
@@ -195,9 +197,7 @@ type ChannelConfig = z.infer<typeof channelConfigSchema>;
195
197
  declare const profileConfigSchema: z.ZodObject<{
196
198
  name: z.ZodString;
197
199
  path: z.ZodString;
198
- subAgent: z.ZodString;
199
200
  channelId: z.ZodString;
200
- brief: z.ZodOptional<z.ZodBoolean>;
201
201
  }, z.core.$strip>;
202
202
  type ProfileConfig = z.infer<typeof profileConfigSchema>;
203
203
  declare const SETTINGS_VERSION = 1;
@@ -210,6 +210,8 @@ declare const settingsSchema: z.ZodObject<{
210
210
  fanout: "fanout";
211
211
  exclusive: "exclusive";
212
212
  }>>;
213
+ options: z.ZodDefault<z.ZodArray<z.ZodString>>;
214
+ env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
213
215
  connectors: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
214
216
  id: z.ZodString;
215
217
  name: z.ZodString;
@@ -254,9 +256,7 @@ declare const settingsSchema: z.ZodObject<{
254
256
  profiles: z.ZodDefault<z.ZodArray<z.ZodObject<{
255
257
  name: z.ZodString;
256
258
  path: z.ZodString;
257
- subAgent: z.ZodString;
258
259
  channelId: z.ZodString;
259
- brief: z.ZodOptional<z.ZodBoolean>;
260
260
  }, z.core.$strip>>>;
261
261
  }, z.core.$strip>;
262
262
  type Settings = z.infer<typeof settingsSchema>;
@@ -318,8 +318,12 @@ declare class FunnelChannels {
318
318
  add(input: {
319
319
  name: string;
320
320
  delivery?: ChannelDeliveryMode;
321
+ options?: string[];
322
+ env?: Record<string, string>;
321
323
  }): ChannelConfig;
322
324
  setDelivery(name: string, delivery: ChannelDeliveryMode): void;
325
+ setOptions(name: string, options: string[]): void;
326
+ setEnv(name: string, env: Record<string, string>): void;
323
327
  remove(name: string): void;
324
328
  rename(oldName: string, newName: string): void;
325
329
  listConnectors(channelName: string): ConnectorConfig[];
@@ -392,11 +396,12 @@ declare class FunnelMcp {
392
396
  type LaunchOptions = {
393
397
  channel: string;
394
398
  cwd?: string;
395
- subAgent?: string;
396
399
  userArgs?: string[];
397
- profileName?: string; /** Forward `--brief` to claude on launch (enables the SendUserMessage tool). */
398
- brief?: boolean; /** Extra env vars merged under process.env (process.env wins on collision). */
399
- extraEnv?: Record<string, string>;
400
+ profileName?: string;
401
+ /** Invoked synchronously after the child claude process has been spawned, with its PID.
402
+ * Useful for hosts that need to register the spawned process before it exits
403
+ * (e.g. multi-session registries that track per-claude liveness). */
404
+ onSpawned?: (pid: number) => void;
400
405
  };
401
406
  type Deps$12 = {
402
407
  channels: FunnelChannels;
@@ -508,8 +513,6 @@ declare const channelSpecSchema: z.ZodObject<{
508
513
  type ChannelSpec = z.infer<typeof channelSpecSchema>;
509
514
  declare const localConfigSchema: z.ZodObject<{
510
515
  $schema: z.ZodOptional<z.ZodString>;
511
- options: z.ZodOptional<z.ZodArray<z.ZodString>>;
512
- env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
513
516
  channels: z.ZodArray<z.ZodObject<{
514
517
  name: z.ZodString;
515
518
  options: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -2410,10 +2413,8 @@ declare const createCliApp: (funnel: Funnel) => _$hono_hono_base0.HonoBase<Env,
2410
2413
  };
2411
2414
  } & {
2412
2415
  query: {
2413
- path: string | string[];
2414
- "sub-agent": string | string[];
2415
- channel: string | string[];
2416
- brief?: string | string[] | undefined;
2416
+ path: string;
2417
+ channel: string;
2417
2418
  };
2418
2419
  };
2419
2420
  output: string;
@@ -2426,10 +2427,8 @@ declare const createCliApp: (funnel: Funnel) => _$hono_hono_base0.HonoBase<Env,
2426
2427
  };
2427
2428
  } & {
2428
2429
  query: {
2429
- path: string | string[];
2430
- "sub-agent": string | string[];
2431
- channel: string | string[];
2432
- brief?: string | string[] | undefined;
2430
+ path: string;
2431
+ channel: string;
2433
2432
  };
2434
2433
  };
2435
2434
  output: `added profile "${string}"`;
@@ -2456,10 +2455,7 @@ declare const createCliApp: (funnel: Funnel) => _$hono_hono_base0.HonoBase<Env,
2456
2455
  } & {
2457
2456
  query: {
2458
2457
  path?: string | undefined;
2459
- "sub-agent"?: string | undefined;
2460
2458
  channel?: string | undefined;
2461
- brief?: string | string[] | undefined;
2462
- "no-brief"?: string | string[] | undefined;
2463
2459
  };
2464
2460
  };
2465
2461
  output: string;
@@ -2473,10 +2469,7 @@ declare const createCliApp: (funnel: Funnel) => _$hono_hono_base0.HonoBase<Env,
2473
2469
  } & {
2474
2470
  query: {
2475
2471
  path?: string | undefined;
2476
- "sub-agent"?: string | undefined;
2477
2472
  channel?: string | undefined;
2478
- brief?: string | string[] | undefined;
2479
- "no-brief"?: string | string[] | undefined;
2480
2473
  };
2481
2474
  };
2482
2475
  output: `updated profile "${string}"`;
@@ -3701,10 +3694,8 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3701
3694
  };
3702
3695
  } & {
3703
3696
  query: {
3704
- path: string | string[];
3705
- "sub-agent": string | string[];
3706
- channel: string | string[];
3707
- brief?: string | string[] | undefined;
3697
+ path: string;
3698
+ channel: string;
3708
3699
  };
3709
3700
  };
3710
3701
  output: string;
@@ -3717,10 +3708,8 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3717
3708
  };
3718
3709
  } & {
3719
3710
  query: {
3720
- path: string | string[];
3721
- "sub-agent": string | string[];
3722
- channel: string | string[];
3723
- brief?: string | string[] | undefined;
3711
+ path: string;
3712
+ channel: string;
3724
3713
  };
3725
3714
  };
3726
3715
  output: `added profile "${string}"`;
@@ -3747,10 +3736,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3747
3736
  } & {
3748
3737
  query: {
3749
3738
  path?: string | undefined;
3750
- "sub-agent"?: string | undefined;
3751
3739
  channel?: string | undefined;
3752
- brief?: string | string[] | undefined;
3753
- "no-brief"?: string | string[] | undefined;
3754
3740
  };
3755
3741
  };
3756
3742
  output: string;
@@ -3764,10 +3750,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3764
3750
  } & {
3765
3751
  query: {
3766
3752
  path?: string | undefined;
3767
- "sub-agent"?: string | undefined;
3768
3753
  channel?: string | undefined;
3769
- brief?: string | string[] | undefined;
3770
- "no-brief"?: string | string[] | undefined;
3771
3754
  };
3772
3755
  };
3773
3756
  output: `updated profile "${string}"`;
@@ -4212,4 +4195,4 @@ ${string}`;
4212
4195
  //#region lib/tui/tui.d.ts
4213
4196
  declare function launchTui(funnel: Funnel): Promise<void>;
4214
4197
  //#endregion
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 };
4198
+ 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, type GatewayEmitInput, type GatewayRouteDeps, type Env$1 as GatewayServerEnv, 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
@@ -1,6 +1,6 @@
1
1
  import { i as FunnelDiscordAdapter, n as FunnelDiscordListener, t as discordConnectorSchema } from "./discord-connector-schema-ygf5Df-2.js";
2
2
  import { n as FunnelLogger, r as FunnelConnectorListener, t as NodeFunnelLogger } from "./node-logger-DQz_BGOD.js";
3
- import { a as FunnelProcessRunner, i as NodeFunnelProcessRunner, n as FunnelGhListener, r as FunnelGhAdapter, t as ghConnectorSchema } from "./gh-connector-schema-2ml29MBC.js";
3
+ import { a as FunnelProcessRunner, i as NodeFunnelProcessRunner, n as FunnelGhListener, r as FunnelGhAdapter, t as ghConnectorSchema } from "./gh-connector-schema-CD5HIkrd.js";
4
4
  import { a as ScheduleStateStore, i as FunnelScheduleListener, n as scheduleConnectorSchema, o as NodeFunnelFileSystem, r as scheduleEntrySchema, s as FunnelFileSystem, t as scheduleCatchupPolicySchema } from "./schedule-connector-schema-FxP7LPlx.js";
5
5
  import { i as FunnelSlackAdapter, n as FunnelSlackListener, r as FunnelSlackEventProcessor, t as slackConnectorSchema } from "./slack-connector-schema-B4hsf3AY.js";
6
6
  import { dirname, join, resolve } from "node:path";
@@ -50,15 +50,16 @@ const channelConfigSchema = z.object({
50
50
  id: z.string(),
51
51
  name: z.string(),
52
52
  delivery: channelDeliveryModeSchema.default("fanout"),
53
+ /** Args prepended to the claude argv on every launch bound to this channel. */
54
+ options: z.array(z.string()).default([]),
55
+ /** Env vars layered under the launched claude process. process.env wins on collision. */
56
+ env: z.record(z.string(), z.string()).default({}),
53
57
  connectors: z.array(connectorConfigSchema).default([])
54
58
  });
55
59
  const profileConfigSchema = z.object({
56
60
  name: z.string(),
57
61
  path: z.string(),
58
- subAgent: z.string(),
59
- channelId: z.string(),
60
- /** Forwards `--brief` to claude on launch (enables the SendUserMessage tool). */
61
- brief: z.boolean().optional()
62
+ channelId: z.string()
62
63
  });
63
64
  const SETTINGS_VERSION = 1;
64
65
  const settingsSchema = z.object({
@@ -305,6 +306,8 @@ var FunnelChannels = class {
305
306
  id: this.idGenerator.generate(),
306
307
  name: input.name,
307
308
  delivery: input.delivery ?? "fanout",
309
+ options: input.options ?? [],
310
+ env: input.env ?? {},
308
311
  connectors: []
309
312
  };
310
313
  settings.channels.push(channel);
@@ -317,6 +320,18 @@ var FunnelChannels = class {
317
320
  channel.delivery = delivery;
318
321
  this.store.write(settings);
319
322
  }
323
+ setOptions(name, options) {
324
+ const settings = this.store.read();
325
+ const channel = this.requireChannel(settings, name);
326
+ channel.options = options;
327
+ this.store.write(settings);
328
+ }
329
+ setEnv(name, env) {
330
+ const settings = this.store.read();
331
+ const channel = this.requireChannel(settings, name);
332
+ channel.env = env;
333
+ this.store.write(settings);
334
+ }
320
335
  remove(name) {
321
336
  const settings = this.store.read();
322
337
  const index = settings.channels.findIndex((c) => c.name === name);
@@ -567,18 +582,18 @@ var FunnelClaude = class {
567
582
  this.writePidFile(options.profileName);
568
583
  this.installCleanup(options.profileName);
569
584
  }
570
- const claudeArgs = this.buildArgs(options, cwd);
571
- const env = this.buildEnv(channel.id, options.extraEnv);
585
+ const claudeArgs = this.buildArgs(channel.options, options.userArgs ?? [], cwd);
586
+ const env = this.buildEnv(channel.id, channel.env);
572
587
  this.logger.info(`claude launch`, {
573
588
  channel: options.channel,
574
589
  channelId: channel.id,
575
- subAgent: options.subAgent,
576
590
  cwd
577
591
  });
578
592
  try {
579
593
  return await this.process.attach(["claude", ...claudeArgs], {
580
594
  cwd,
581
- env
595
+ env,
596
+ onSpawned: options.onSpawned
582
597
  });
583
598
  } finally {
584
599
  if (options.profileName) this.removePidFile(options.profileName);
@@ -628,17 +643,15 @@ var FunnelClaude = class {
628
643
  if (!state) return false;
629
644
  return !state.startsWith("Z");
630
645
  }
631
- buildArgs(options, cwd) {
632
- const result = [...options.userArgs ?? []];
646
+ buildArgs(channelOptions, userArgs, cwd) {
647
+ const result = [...channelOptions, ...userArgs];
633
648
  const mcpName = this.mcp.findInstalledName(cwd);
634
649
  if (mcpName && !result.includes("--dangerously-load-development-channels") && !result.includes("--channels")) result.push("--dangerously-load-development-channels", `server:${mcpName}`);
635
- if (!result.includes("--agent") && options.subAgent) result.push("--agent", options.subAgent);
636
- if (options.brief && !result.includes("--brief")) result.push("--brief");
637
650
  return result;
638
651
  }
639
- buildEnv(channelId, extraEnv) {
652
+ buildEnv(channelId, channelEnv) {
640
653
  const env = {};
641
- if (extraEnv) for (const [key, value] of Object.entries(extraEnv)) env[key] = value;
654
+ for (const [key, value] of Object.entries(channelEnv)) env[key] = value;
642
655
  for (const [key, value] of Object.entries(globalThis.process.env)) if (typeof value === "string") env[key] = value;
643
656
  env.FUNNEL_CHANNEL_ID = channelId;
644
657
  return env;
@@ -783,16 +796,14 @@ const connectorSpecSchema = z.discriminatedUnion("type", [
783
796
  ]);
784
797
  const channelSpecSchema = z.object({
785
798
  name: z.string(),
799
+ /** Args prepended to the claude argv on every launch bound to this channel. */
786
800
  options: z.array(z.string()).optional(),
801
+ /** Env vars layered under the launched claude process. process.env wins on collision. */
787
802
  env: z.record(z.string(), z.string()).optional(),
788
803
  connectors: z.array(connectorSpecSchema).optional()
789
804
  });
790
805
  const localConfigSchema = z.object({
791
806
  $schema: z.string().optional(),
792
- /** Extra args forwarded to every channel's launch before the channel's own options. User-supplied CLI args still come last. */
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. */
795
- env: z.record(z.string(), z.string()).optional(),
796
807
  /** Declared channels. First entry is the default; --channel <name> selects by name. */
797
808
  channels: z.array(channelSpecSchema).min(1)
798
809
  });
@@ -881,6 +892,17 @@ var FunnelLocalConfig = class {
881
892
  var FunnelTokenPrompter = class {};
882
893
  //#endregion
883
894
  //#region lib/engine/local-config/local-config-sync.ts
895
+ const arraysEqual = (a, b) => {
896
+ if (a.length !== b.length) return false;
897
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
898
+ return true;
899
+ };
900
+ const recordsEqual = (a, b) => {
901
+ const keys = Object.keys(a);
902
+ if (keys.length !== Object.keys(b).length) return false;
903
+ for (const key of keys) if (a[key] !== b[key]) return false;
904
+ return true;
905
+ };
884
906
  /**
885
907
  * Reconciles a single funnel.json channel spec with `~/.funnel/settings.json`.
886
908
  * The spec is the source of truth for the channel it declares:
@@ -910,7 +932,18 @@ var FunnelLocalConfigSync = class {
910
932
  Object.freeze(this);
911
933
  }
912
934
  async ensure(channel, cwd) {
913
- if (!this.channels.get(channel.name)) this.channels.add({ name: channel.name });
935
+ const existing = this.channels.get(channel.name);
936
+ if (!existing) this.channels.add({
937
+ name: channel.name,
938
+ options: channel.options ?? [],
939
+ env: channel.env ?? {}
940
+ });
941
+ else {
942
+ const nextOptions = channel.options ?? [];
943
+ const nextEnv = channel.env ?? {};
944
+ if (!arraysEqual(existing.options, nextOptions)) this.channels.setOptions(channel.name, nextOptions);
945
+ if (!recordsEqual(existing.env, nextEnv)) this.channels.setEnv(channel.name, nextEnv);
946
+ }
914
947
  if (channel.connectors === void 0) return;
915
948
  const dotenv = this.dotenv.read(cwd);
916
949
  const touched = /* @__PURE__ */ new Set();
@@ -1222,6 +1255,7 @@ var MemoryFunnelProcessRunner = class extends FunnelProcessRunner {
1222
1255
  command,
1223
1256
  options
1224
1257
  });
1258
+ if (options.onSpawned) options.onSpawned(1);
1225
1259
  return (await this.handler(command)).exitCode ?? 0;
1226
1260
  }
1227
1261
  detach(command, options = {}) {
@@ -1315,7 +1349,6 @@ var FunnelProfiles = class {
1315
1349
  profile.channelId = fields.channelId;
1316
1350
  }
1317
1351
  if (fields.path !== void 0) profile.path = fields.path;
1318
- if (fields.subAgent !== void 0) profile.subAgent = fields.subAgent;
1319
1352
  this.store.write(settings);
1320
1353
  }
1321
1354
  };
@@ -3361,6 +3394,7 @@ var Funnel = class Funnel {
3361
3394
  process: this.process,
3362
3395
  clock: this.clock,
3363
3396
  logger: this.logger,
3397
+ dir: this.paths.dir,
3364
3398
  killCompetingSlack: options.killCompetingSlack,
3365
3399
  token: options.token ?? this.gatewayToken.ensure(),
3366
3400
  extraRoutes: options.extraRoutes
@@ -4121,10 +4155,8 @@ const claudeHandler = factory.createHandlers(zValidator$1("query", z.object({
4121
4155
  const exitCode = await funnel.claude.launch({
4122
4156
  channel: profile.channelId,
4123
4157
  cwd: profile.path,
4124
- subAgent: profile.subAgent,
4125
4158
  userArgs,
4126
- profileName: profile.name,
4127
- brief: profile.brief
4159
+ profileName: profile.name
4128
4160
  });
4129
4161
  process.exit(exitCode);
4130
4162
  }
@@ -4137,15 +4169,7 @@ const claudeHandler = factory.createHandlers(zValidator$1("query", z.object({
4137
4169
  const exitCode = await funnel.claude.launch({
4138
4170
  channel: picked.name,
4139
4171
  cwd,
4140
- userArgs: [
4141
- ...local.options ?? [],
4142
- ...picked.options ?? [],
4143
- ...userArgs
4144
- ],
4145
- extraEnv: {
4146
- ...local.env ?? {},
4147
- ...picked.env ?? {}
4148
- }
4172
+ userArgs
4149
4173
  });
4150
4174
  process.exit(exitCode);
4151
4175
  }
@@ -4154,10 +4178,8 @@ const claudeHandler = factory.createHandlers(zValidator$1("query", z.object({
4154
4178
  const exitCode = await funnel.claude.launch({
4155
4179
  channel: defaultProfile.channelId,
4156
4180
  cwd: defaultProfile.path,
4157
- subAgent: defaultProfile.subAgent,
4158
4181
  userArgs,
4159
- profileName: defaultProfile.name,
4160
- brief: defaultProfile.brief
4182
+ profileName: defaultProfile.name
4161
4183
  });
4162
4184
  process.exit(exitCode);
4163
4185
  });
@@ -4387,18 +4409,18 @@ examples:
4387
4409
  //#region lib/cli/routes/profiles.add.$profile.ts
4388
4410
  const addHelp = `funnel profiles add — add a profile
4389
4411
 
4390
- usage: funnel profiles add <name> --path <path> --sub-agent <agent> --channel <channel-name> [--brief]
4412
+ usage: funnel profiles add <name> --path <path> --channel <channel-name>
4391
4413
 
4392
4414
  options:
4393
- --path working directory passed to claude as cwd
4394
- --sub-agent sub-agent name passed to claude --agent
4395
- --channel channel name (resolved to channel id internally)
4396
- --brief forward --brief to claude on launch (enables SendUserMessage tool)`;
4415
+ --path working directory passed to claude as cwd
4416
+ --channel channel name (resolved to channel id internally)
4417
+
4418
+ Per-launch flags like --agent or --brief now live on the channel itself
4419
+ (set with \`fnl channels <name> set options ...\`), so profiles are only
4420
+ \`{ name, path, channelId }\`.`;
4397
4421
  const profilesAddHandler = factory.createHandlers(zValidator$1("param", z.object({ profile: z.string() })), zValidator$1("query", z.object({
4398
4422
  path: z.string(),
4399
- "sub-agent": z.string(),
4400
- channel: z.string(),
4401
- brief: z.coerce.boolean().optional()
4423
+ channel: z.string()
4402
4424
  }), addHelp), (c) => {
4403
4425
  const param = c.req.valid("param");
4404
4426
  const query = c.req.valid("query");
@@ -4408,9 +4430,7 @@ const profilesAddHandler = factory.createHandlers(zValidator$1("param", z.object
4408
4430
  funnel.profiles.add({
4409
4431
  name: param.profile,
4410
4432
  path: query.path,
4411
- subAgent: query["sub-agent"],
4412
- channelId: channel.id,
4413
- ...query.brief !== void 0 ? { brief: query.brief } : {}
4433
+ channelId: channel.id
4414
4434
  });
4415
4435
  return c.text(`added profile "${param.profile}"`);
4416
4436
  });
@@ -4453,7 +4473,6 @@ const profilesLaunchHandler = factory.createHandlers(zValidator$1("param", z.obj
4453
4473
  const exitCode = await funnel.claude.launch({
4454
4474
  channel: profile.channelId,
4455
4475
  cwd: profile.path,
4456
- subAgent: profile.subAgent,
4457
4476
  userArgs: queryToCliArgs(c.req.url, RESERVED_KEYS),
4458
4477
  profileName: profile.name
4459
4478
  });
@@ -4473,25 +4492,19 @@ const profilesRemoveHandler = factory.createHandlers(zValidator$1("param", z.obj
4473
4492
  //#region lib/cli/routes/profiles.set.$profile.ts
4474
4493
  const setHelp = `funnel profiles <name> set — update a profile
4475
4494
 
4476
- usage: funnel profiles <name> set [--path <path>] [--sub-agent <agent>] [--channel <channel-name>] [--brief | --no-brief]`;
4495
+ usage: funnel profiles <name> set [--path <path>] [--channel <channel-name>]`;
4477
4496
  const profilesSetHandler = factory.createHandlers(zValidator$1("param", z.object({ profile: z.string() })), zValidator$1("query", z.object({
4478
4497
  path: z.string().optional(),
4479
- "sub-agent": z.string().optional(),
4480
- channel: z.string().optional(),
4481
- brief: z.coerce.boolean().optional(),
4482
- "no-brief": z.coerce.boolean().optional()
4498
+ channel: z.string().optional()
4483
4499
  }), setHelp), (c) => {
4484
4500
  const param = c.req.valid("param");
4485
4501
  const query = c.req.valid("query");
4486
4502
  const funnel = c.var.funnel;
4487
4503
  const channel = query.channel !== void 0 ? funnel.channels.get(query.channel) : null;
4488
4504
  if (query.channel !== void 0 && !channel) throw new HTTPException(400, { message: `channel "${query.channel}" not found` });
4489
- const brief = query["no-brief"] ? false : query.brief;
4490
4505
  funnel.profiles.update(param.profile, {
4491
4506
  path: query.path,
4492
- subAgent: query["sub-agent"],
4493
- channelId: channel?.id,
4494
- ...brief !== void 0 ? { brief } : {}
4507
+ channelId: channel?.id
4495
4508
  });
4496
4509
  return c.text(`updated profile "${param.profile}"`);
4497
4510
  });
@@ -4501,23 +4514,27 @@ usage: funnel profiles [subcommand]
4501
4514
 
4502
4515
  subcommands:
4503
4516
  (none) list (first entry is the default)
4504
- add <name> --path <path> --sub-agent <agent> --channel <channel>
4505
- <name> set [--path ...] [--sub-agent ...] [--channel ...]
4517
+ add <name> --path <path> --channel <channel>
4518
+ <name> set [--path ...] [--channel ...]
4506
4519
  <name> as-default move profile to the front (becomes default)
4507
4520
  rename <old> <new> rename
4508
4521
  remove <name> remove
4509
4522
  <name> run launch (sugar for fnl claude -p <name>)
4510
4523
  <name> launch (alias for run)
4511
4524
 
4525
+ Per-launch flags like --agent or --brief now live on the channel itself
4526
+ (set with \`fnl channels <name> set options ...\`), so profiles are only
4527
+ \`{ name, path, channelId }\`.
4528
+
4512
4529
  examples:
4513
- funnel profiles add cto --path /repo/myapp --sub-agent cto --channel prod-inbox
4530
+ funnel profiles add cto --path /repo/myapp --channel prod-inbox
4514
4531
  funnel profiles cto as-default
4515
4532
  funnel profiles cto run`), (c) => {
4516
4533
  const profiles = c.var.funnel.profiles.list();
4517
4534
  if (profiles.length === 0) return c.text("no profiles");
4518
4535
  const lines = profiles.map((profile, index) => {
4519
4536
  const tag = index === 0 ? " (default)" : "";
4520
- return `${profile.name}${tag} [path=${profile.path}, sub-agent=${profile.subAgent}, channel=${profile.channelId}]`;
4537
+ return `${profile.name}${tag} [path=${profile.path}, channel=${profile.channelId}]`;
4521
4538
  });
4522
4539
  return c.text(lines.join("\n"));
4523
4540
  });
@@ -4571,7 +4588,7 @@ const statusHandler = factory.createHandlers(zValidator$1("query", z.object({}),
4571
4588
  const tag = index === 0 ? " (default)" : "";
4572
4589
  const channel = funnel.channels.getById(profile.channelId);
4573
4590
  const channelLabel = channel ? channel.name : `id:${profile.channelId}`;
4574
- lines.push(` - ${profile.name}${tag} [path=${profile.path}, sub-agent=${profile.subAgent}, channel=${channelLabel}]`);
4591
+ lines.push(` - ${profile.name}${tag} [path=${profile.path}, channel=${channelLabel}]`);
4575
4592
  }
4576
4593
  lines.push("");
4577
4594
  if (!gatewayStatus.running) lines.push("gateway: not running");
@@ -4949,7 +4966,7 @@ function ProfileLauncher(props) {
4949
4966
  children: profile.name
4950
4967
  }), /* @__PURE__ */ jsx("span", {
4951
4968
  fg: funnel.faint,
4952
- children: ` → channel ${profile.channelId} · path ${profile.path} · sub-agent ${profile.subAgent}`
4969
+ children: ` → channel ${profile.channelId} · path ${profile.path}`
4953
4970
  })]
4954
4971
  }, profile.name);
4955
4972
  })
@@ -6464,9 +6481,6 @@ function ProfilesView(props) {
6464
6481
  } else if (field === "path") {
6465
6482
  const next = raw.trim();
6466
6483
  if (next) props.funnel.profiles.update(profile.name, { path: next });
6467
- } else if (field === "sub-agent") {
6468
- const next = raw.trim();
6469
- if (next) props.funnel.profiles.update(profile.name, { subAgent: next });
6470
6484
  }
6471
6485
  } catch (error) {
6472
6486
  props.funnel.logger.error(error instanceof Error ? error.message : String(error));
@@ -6490,7 +6504,6 @@ function ProfilesView(props) {
6490
6504
  props.funnel.profiles.add({
6491
6505
  name,
6492
6506
  path: "",
6493
- subAgent: "",
6494
6507
  channelId
6495
6508
  });
6496
6509
  props.setFocusedKey(fieldKey(name, "name"));
@@ -6524,14 +6537,6 @@ function ProfilesView(props) {
6524
6537
  onCommit: (raw) => commit(profile, "path", raw),
6525
6538
  placeholder: "repository path"
6526
6539
  }),
6527
- /* @__PURE__ */ jsx(EditableField, {
6528
- label: "sub-agent",
6529
- initialValue: profile.subAgent,
6530
- focused: props.focusedKey === fieldKey(profile.name, "sub-agent"),
6531
- onFocus: () => props.setFocusedKey(fieldKey(profile.name, "sub-agent")),
6532
- onCommit: (raw) => commit(profile, "sub-agent", raw),
6533
- placeholder: "claude --agent value"
6534
- }),
6535
6540
  /* @__PURE__ */ jsx(EditableField, {
6536
6541
  label: "channel",
6537
6542
  initialValue: profile.channelId,
@@ -6657,7 +6662,6 @@ function App(props) {
6657
6662
  await props.funnel.claude.launch({
6658
6663
  channel: profile.channelId,
6659
6664
  cwd: profile.path,
6660
- subAgent: profile.subAgent,
6661
6665
  profileName: profile.name
6662
6666
  });
6663
6667
  } catch (error) {
@@ -5,21 +5,6 @@
5
5
  "$schema": {
6
6
  "type": "string"
7
7
  },
8
- "options": {
9
- "type": "array",
10
- "items": {
11
- "type": "string"
12
- }
13
- },
14
- "env": {
15
- "type": "object",
16
- "propertyNames": {
17
- "type": "string"
18
- },
19
- "additionalProperties": {
20
- "type": "string"
21
- }
22
- },
23
8
  "channels": {
24
9
  "minItems": 1,
25
10
  "type": "array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interactive-inc/claude-funnel",
3
- "version": "0.16.1",
3
+ "version": "0.18.0",
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",