@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.
- package/README.md +22 -24
- package/dist/bin.js +294 -288
- package/dist/connectors/gh.d.ts +1 -1
- package/dist/connectors/gh.js +1 -1
- package/dist/gateway/daemon.js +112 -112
- package/dist/{gh-connector-schema-BZFAS-p-.d.ts → gh-connector-schema-BNyTaASt.d.ts} +3 -0
- package/dist/{gh-connector-schema-2ml29MBC.js → gh-connector-schema-CD5HIkrd.js} +4 -2
- package/dist/index.d.ts +23 -40
- package/dist/index.js +80 -76
- package/funnel.schema.json +0 -15
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
})
|
|
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-
|
|
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;
|
|
398
|
-
|
|
399
|
-
|
|
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
|
|
2414
|
-
|
|
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
|
|
2430
|
-
|
|
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
|
|
3705
|
-
|
|
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
|
|
3721
|
-
|
|
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-
|
|
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
|
-
|
|
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,
|
|
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(
|
|
632
|
-
const result = [...
|
|
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,
|
|
652
|
+
buildEnv(channelId, channelEnv) {
|
|
640
653
|
const env = {};
|
|
641
|
-
|
|
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
|
-
|
|
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> --
|
|
4412
|
+
usage: funnel profiles add <name> --path <path> --channel <channel-name>
|
|
4391
4413
|
|
|
4392
4414
|
options:
|
|
4393
|
-
--path
|
|
4394
|
-
--
|
|
4395
|
-
|
|
4396
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>] [--
|
|
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
|
-
|
|
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
|
-
|
|
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> --
|
|
4505
|
-
<name> set [--path ...] [--
|
|
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 --
|
|
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},
|
|
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},
|
|
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}
|
|
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) {
|
package/funnel.schema.json
CHANGED
|
@@ -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