@interactive-inc/claude-funnel 0.10.0 → 0.10.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/bin.js +448 -448
- package/dist/connectors/slack.d.ts +1 -29
- package/dist/gateway/daemon.js +166 -166
- package/dist/index.d.ts +4 -11
- package/dist/index.js +133 -120
- package/dist/slack-event-processor-CS-bAit9.d.ts +43 -0
- package/package.json +1 -6
- package/dist/slack-connector-schema-D7zAHN8k.d.ts +0 -15
- package/lib/bin.ts +0 -3
- package/lib/cli/factory.ts +0 -10
- package/lib/cli/index.ts +0 -85
- package/lib/cli/router/query-to-cli-args.ts +0 -20
- package/lib/cli/router/to-request.ts +0 -113
- package/lib/cli/router/validator.ts +0 -27
- package/lib/cli/routes/channels.$channel.connectors.$connector.rename.$newName.ts +0 -27
- package/lib/cli/routes/channels.$channel.connectors.$connector.request.ts +0 -40
- package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.ts +0 -41
- package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.ts +0 -22
- package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.ts +0 -23
- package/lib/cli/routes/channels.$channel.connectors.$connector.ts +0 -26
- package/lib/cli/routes/channels.$channel.connectors.add.$connector.ts +0 -92
- package/lib/cli/routes/channels.$channel.connectors.remove.$connector.ts +0 -22
- package/lib/cli/routes/channels.$channel.connectors.set.$connector.ts +0 -63
- package/lib/cli/routes/channels.$channel.connectors.ts +0 -26
- package/lib/cli/routes/channels.$channel.publish.ts +0 -52
- package/lib/cli/routes/channels.$channel.rename.$newName.ts +0 -22
- package/lib/cli/routes/channels.$channel.set.delivery.$mode.ts +0 -34
- package/lib/cli/routes/channels.$channel.ts +0 -34
- package/lib/cli/routes/channels.add.$channel.ts +0 -33
- package/lib/cli/routes/channels.remove.$channel.ts +0 -20
- package/lib/cli/routes/channels.ts +0 -39
- package/lib/cli/routes/claude.ts +0 -70
- package/lib/cli/routes/gateway.listeners.ts +0 -41
- package/lib/cli/routes/gateway.logs.ts +0 -123
- package/lib/cli/routes/gateway.restart.ts +0 -50
- package/lib/cli/routes/gateway.run.ts +0 -41
- package/lib/cli/routes/gateway.start.ts +0 -50
- package/lib/cli/routes/gateway.status.ts +0 -19
- package/lib/cli/routes/gateway.stop.ts +0 -32
- package/lib/cli/routes/gateway.ts +0 -55
- package/lib/cli/routes/index.ts +0 -219
- package/lib/cli/routes/profiles.$profile.as-default.ts +0 -22
- package/lib/cli/routes/profiles.$profile.rename.$newName.ts +0 -22
- package/lib/cli/routes/profiles.$profile.run.ts +0 -36
- package/lib/cli/routes/profiles.add.$profile.ts +0 -49
- package/lib/cli/routes/profiles.remove.$profile.ts +0 -20
- package/lib/cli/routes/profiles.set.$profile.ts +0 -45
- package/lib/cli/routes/profiles.ts +0 -40
- package/lib/cli/routes/status.ts +0 -93
- package/lib/cli/routes/update.ts +0 -27
- package/lib/connectors/connector-adapter.ts +0 -9
- package/lib/connectors/connector-config-schema.ts +0 -16
- package/lib/connectors/connector-factory.ts +0 -94
- package/lib/connectors/connector-listener.ts +0 -20
- package/lib/connectors/discord-adapter.ts +0 -51
- package/lib/connectors/discord-connector-schema.ts +0 -12
- package/lib/connectors/discord-event-processor.ts +0 -48
- package/lib/connectors/discord-listener.ts +0 -111
- package/lib/connectors/discord.ts +0 -4
- package/lib/connectors/gh-adapter.ts +0 -48
- package/lib/connectors/gh-connector-schema.ts +0 -12
- package/lib/connectors/gh-listener.ts +0 -137
- package/lib/connectors/gh.ts +0 -3
- package/lib/connectors/match-cron.ts +0 -78
- package/lib/connectors/schedule-connector-schema.ts +0 -33
- package/lib/connectors/schedule-listener.ts +0 -207
- package/lib/connectors/schedule-state-store.ts +0 -54
- package/lib/connectors/schedule.ts +0 -4
- package/lib/connectors/slack-adapter.ts +0 -36
- package/lib/connectors/slack-connector-schema.ts +0 -13
- package/lib/connectors/slack-event-processor.ts +0 -97
- package/lib/connectors/slack-listener.ts +0 -97
- package/lib/connectors/slack.ts +0 -4
- package/lib/engine/channels/channels.ts +0 -520
- package/lib/engine/claude/claude.ts +0 -205
- package/lib/engine/claude/gateway-controller.ts +0 -4
- package/lib/engine/fs/file-system.ts +0 -23
- package/lib/engine/fs/memory-file-system.ts +0 -102
- package/lib/engine/fs/node-file-system.ts +0 -68
- package/lib/engine/http/http-client.ts +0 -17
- package/lib/engine/http/memory-http-client.ts +0 -36
- package/lib/engine/http/node-http-client.ts +0 -23
- package/lib/engine/id/id-generator.ts +0 -7
- package/lib/engine/id/memory-id-generator.ts +0 -20
- package/lib/engine/id/node-id-generator.ts +0 -7
- package/lib/engine/logger/logger.ts +0 -11
- package/lib/engine/logger/memory-logger.ts +0 -28
- package/lib/engine/logger/node-logger.ts +0 -49
- package/lib/engine/logger/noop-logger.ts +0 -9
- package/lib/engine/mcp/channel-server.ts +0 -123
- package/lib/engine/mcp/channel-subscriber.ts +0 -82
- package/lib/engine/mcp/mcp.ts +0 -126
- package/lib/engine/mcp/read-channel-connectors.ts +0 -34
- package/lib/engine/mcp/read-gateway-token.ts +0 -16
- package/lib/engine/mcp/usage-hint-for-type.ts +0 -15
- package/lib/engine/process/memory-process-runner.ts +0 -88
- package/lib/engine/process/node-process-runner.ts +0 -91
- package/lib/engine/process/process-runner.ts +0 -33
- package/lib/engine/profiles/profile-channel-checker.ts +0 -7
- package/lib/engine/profiles/profiles.ts +0 -126
- package/lib/engine/settings/mock-settings-reader.ts +0 -27
- package/lib/engine/settings/settings-reader.ts +0 -6
- package/lib/engine/settings/settings-schema.ts +0 -48
- package/lib/engine/settings/settings-store.ts +0 -110
- package/lib/engine/time/clock.ts +0 -15
- package/lib/engine/time/memory-clock.ts +0 -26
- package/lib/engine/time/node-clock.ts +0 -7
- package/lib/funnel.ts +0 -294
- package/lib/gateway/auth-middleware.ts +0 -44
- package/lib/gateway/broadcaster.ts +0 -319
- package/lib/gateway/channel-publisher.ts +0 -67
- package/lib/gateway/daemon.ts +0 -47
- package/lib/gateway/factory.ts +0 -10
- package/lib/gateway/funnel-event-store.ts +0 -155
- package/lib/gateway/gateway-server.ts +0 -426
- package/lib/gateway/gateway-token.ts +0 -79
- package/lib/gateway/gateway.ts +0 -209
- package/lib/gateway/kill-competing-slack-gateways.ts +0 -56
- package/lib/gateway/listener-supervisor.ts +0 -339
- package/lib/gateway/listeners-client.ts +0 -128
- package/lib/gateway/publish-schema.ts +0 -27
- package/lib/gateway/resolve-daemon-script.ts +0 -26
- package/lib/gateway/routes/channels.connectors.call.ts +0 -39
- package/lib/gateway/routes/channels.publish.ts +0 -44
- package/lib/gateway/routes/health.ts +0 -13
- package/lib/gateway/routes/index.ts +0 -26
- package/lib/gateway/routes/listeners.list.ts +0 -6
- package/lib/gateway/routes/listeners.restart.ts +0 -15
- package/lib/gateway/routes/listeners.start.ts +0 -15
- package/lib/gateway/routes/listeners.stop.ts +0 -15
- package/lib/gateway/routes/route-deps.ts +0 -19
- package/lib/gateway/routes/status.ts +0 -15
- package/lib/gateway/routes/validator.ts +0 -17
- package/lib/index.ts +0 -67
- package/lib/logger/leuco-human-file-writer.ts +0 -65
- package/lib/logger/leuco-human-logger.ts +0 -98
- package/lib/logger/leuco-human-record.ts +0 -16
- package/lib/logger/leuco-human-stdout-writer.ts +0 -26
- package/lib/logger/leuco-human-writer.ts +0 -14
- package/lib/logger/leuco-logger-memory-sink.ts +0 -67
- package/lib/logger/leuco-logger-record.ts +0 -13
- package/lib/logger/leuco-logger-sink.ts +0 -33
- package/lib/logger/leuco-logger-sqlite-sink.ts +0 -355
- package/lib/logger/leuco-logger.ts +0 -135
- package/lib/tui/app.tsx +0 -357
- package/lib/tui/components/add-row.tsx +0 -18
- package/lib/tui/components/brand.tsx +0 -27
- package/lib/tui/components/card.tsx +0 -44
- package/lib/tui/components/detail-bar.tsx +0 -46
- package/lib/tui/components/editable-field.tsx +0 -33
- package/lib/tui/components/empty-state.tsx +0 -11
- package/lib/tui/components/gateway-status.tsx +0 -66
- package/lib/tui/components/keymap.tsx +0 -29
- package/lib/tui/components/menu-item.tsx +0 -73
- package/lib/tui/components/menu.tsx +0 -26
- package/lib/tui/components/panel-header.tsx +0 -22
- package/lib/tui/components/readonly-field.tsx +0 -18
- package/lib/tui/components/section-header.tsx +0 -25
- package/lib/tui/components/selection-accent.tsx +0 -32
- package/lib/tui/components/session-item.tsx +0 -33
- package/lib/tui/components/session-list.tsx +0 -33
- package/lib/tui/components/ui/hascii/accordion-item.tsx +0 -88
- package/lib/tui/components/ui/hascii/accordion.tsx +0 -96
- package/lib/tui/components/ui/hascii/alert-dialog.tsx +0 -43
- package/lib/tui/components/ui/hascii/badge.tsx +0 -51
- package/lib/tui/components/ui/hascii/breadcrumb.tsx +0 -58
- package/lib/tui/components/ui/hascii/button.tsx +0 -194
- package/lib/tui/components/ui/hascii/card-content.tsx +0 -14
- package/lib/tui/components/ui/hascii/card-description.tsx +0 -13
- package/lib/tui/components/ui/hascii/card-footer.tsx +0 -14
- package/lib/tui/components/ui/hascii/card-header.tsx +0 -14
- package/lib/tui/components/ui/hascii/card-title.tsx +0 -13
- package/lib/tui/components/ui/hascii/card.tsx +0 -27
- package/lib/tui/components/ui/hascii/checkbox.tsx +0 -65
- package/lib/tui/components/ui/hascii/command.tsx +0 -159
- package/lib/tui/components/ui/hascii/dialog-content.tsx +0 -14
- package/lib/tui/components/ui/hascii/dialog-description.tsx +0 -13
- package/lib/tui/components/ui/hascii/dialog-footer.tsx +0 -14
- package/lib/tui/components/ui/hascii/dialog-header.tsx +0 -14
- package/lib/tui/components/ui/hascii/dialog-title.tsx +0 -13
- package/lib/tui/components/ui/hascii/dialog.tsx +0 -27
- package/lib/tui/components/ui/hascii/file-tree.tsx +0 -142
- package/lib/tui/components/ui/hascii/focus-group.tsx +0 -62
- package/lib/tui/components/ui/hascii/form-item.tsx +0 -43
- package/lib/tui/components/ui/hascii/input-otp.tsx +0 -86
- package/lib/tui/components/ui/hascii/input.tsx +0 -130
- package/lib/tui/components/ui/hascii/pagination.tsx +0 -105
- package/lib/tui/components/ui/hascii/progress.tsx +0 -28
- package/lib/tui/components/ui/hascii/select.tsx +0 -131
- package/lib/tui/components/ui/hascii/separator.tsx +0 -35
- package/lib/tui/components/ui/hascii/sidebar-content.tsx +0 -23
- package/lib/tui/components/ui/hascii/sidebar-header.tsx +0 -14
- package/lib/tui/components/ui/hascii/sidebar-menu-item.tsx +0 -67
- package/lib/tui/components/ui/hascii/sidebar.tsx +0 -24
- package/lib/tui/components/ui/hascii/skeleton.tsx +0 -60
- package/lib/tui/components/ui/hascii/slider.tsx +0 -91
- package/lib/tui/components/ui/hascii/snackbar.tsx +0 -75
- package/lib/tui/components/ui/hascii/sparkline.tsx +0 -53
- package/lib/tui/components/ui/hascii/spinner.tsx +0 -47
- package/lib/tui/components/ui/hascii/stepper.tsx +0 -54
- package/lib/tui/components/ui/hascii/switch.tsx +0 -66
- package/lib/tui/components/ui/hascii/table.tsx +0 -95
- package/lib/tui/components/ui/hascii/tabs.tsx +0 -59
- package/lib/tui/components/ui/hascii/toggle-group-item.tsx +0 -45
- package/lib/tui/components/ui/hascii/toggle-group.tsx +0 -99
- package/lib/tui/components/ui/hascii/tree.tsx +0 -104
- package/lib/tui/components/view-shell.tsx +0 -44
- package/lib/tui/filter-input.tsx +0 -33
- package/lib/tui/hooks/hascii/use-pressable.ts +0 -54
- package/lib/tui/parse-comma-list.ts +0 -14
- package/lib/tui/profile-launcher.tsx +0 -61
- package/lib/tui/scrollbar-options.ts +0 -19
- package/lib/tui/sidebar.tsx +0 -50
- package/lib/tui/theme.ts +0 -40
- package/lib/tui/tui.tsx +0 -20
- package/lib/tui/types.ts +0 -38
- package/lib/tui/unique-name.ts +0 -18
- package/lib/tui/use-event-stream.ts +0 -133
- package/lib/tui/use-snapshot.ts +0 -99
- package/lib/tui/utils/hascii/form-item-context.tsx +0 -23
- package/lib/tui/utils/hascii/input-focus-context.tsx +0 -31
- package/lib/tui/utils/hascii/theme-context.tsx +0 -26
- package/lib/tui/utils/hascii/theme.ts +0 -176
- package/lib/tui/views/channels-view.tsx +0 -108
- package/lib/tui/views/connectors-view.tsx +0 -164
- package/lib/tui/views/events-view.tsx +0 -160
- package/lib/tui/views/listeners-view.tsx +0 -80
- package/lib/tui/views/profiles-view.tsx +0 -152
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { FunnelSettingsReader } from "@/engine/settings/settings-reader"
|
|
2
|
-
import type { ProfileConfig } from "@/engine/settings/settings-schema"
|
|
3
|
-
|
|
4
|
-
type Deps = {
|
|
5
|
-
store: FunnelSettingsReader
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Named launch presets for `fnl claude`. Each profile bundles a working
|
|
10
|
-
* directory, a sub-agent name, and the channel id its Claude instance will
|
|
11
|
-
* subscribe to. Implements ProfileChannelChecker so FunnelChannels can refuse
|
|
12
|
-
* to remove a channel that is still referenced.
|
|
13
|
-
*
|
|
14
|
-
* The first entry in the persisted array is treated as the default profile;
|
|
15
|
-
* `asDefault` reorders the array to put a named profile first.
|
|
16
|
-
*
|
|
17
|
-
* `channelId` always stores the channel's stable id (uuid). CLI surfaces
|
|
18
|
-
* resolve channel name → id before calling `add`/`update` here.
|
|
19
|
-
*/
|
|
20
|
-
export class FunnelProfiles {
|
|
21
|
-
private readonly store: FunnelSettingsReader
|
|
22
|
-
|
|
23
|
-
constructor(deps: Deps) {
|
|
24
|
-
this.store = deps.store
|
|
25
|
-
Object.freeze(this)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
list(): ProfileConfig[] {
|
|
29
|
-
return this.store.read().profiles
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
get(name: string): ProfileConfig | null {
|
|
33
|
-
return this.list().find((p) => p.name === name) ?? null
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
getDefault(): ProfileConfig | null {
|
|
37
|
-
return this.list()[0] ?? null
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
add(config: ProfileConfig): void {
|
|
41
|
-
const settings = this.store.read()
|
|
42
|
-
|
|
43
|
-
if (settings.profiles.some((p) => p.name === config.name)) {
|
|
44
|
-
throw new Error(`profile "${config.name}" already exists`)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (!settings.channels.some((c) => c.id === config.channelId)) {
|
|
48
|
-
throw new Error(`channel id "${config.channelId}" not found`)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
settings.profiles.push(config)
|
|
52
|
-
|
|
53
|
-
this.store.write(settings)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
remove(name: string): void {
|
|
57
|
-
const settings = this.store.read()
|
|
58
|
-
|
|
59
|
-
const index = settings.profiles.findIndex((p) => p.name === name)
|
|
60
|
-
|
|
61
|
-
if (index < 0) throw new Error(`profile "${name}" not found`)
|
|
62
|
-
|
|
63
|
-
settings.profiles.splice(index, 1)
|
|
64
|
-
|
|
65
|
-
this.store.write(settings)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
rename(oldName: string, newName: string): void {
|
|
69
|
-
const settings = this.store.read()
|
|
70
|
-
|
|
71
|
-
const profile = settings.profiles.find((p) => p.name === oldName)
|
|
72
|
-
|
|
73
|
-
if (!profile) throw new Error(`profile "${oldName}" not found`)
|
|
74
|
-
|
|
75
|
-
if (settings.profiles.some((p) => p.name === newName)) {
|
|
76
|
-
throw new Error(`profile "${newName}" already exists`)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
profile.name = newName
|
|
80
|
-
|
|
81
|
-
this.store.write(settings)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
asDefault(name: string): void {
|
|
85
|
-
const settings = this.store.read()
|
|
86
|
-
|
|
87
|
-
const index = settings.profiles.findIndex((p) => p.name === name)
|
|
88
|
-
|
|
89
|
-
if (index < 0) throw new Error(`profile "${name}" not found`)
|
|
90
|
-
|
|
91
|
-
if (index === 0) return
|
|
92
|
-
|
|
93
|
-
const [profile] = settings.profiles.splice(index, 1)
|
|
94
|
-
|
|
95
|
-
if (!profile) return
|
|
96
|
-
|
|
97
|
-
settings.profiles.unshift(profile)
|
|
98
|
-
|
|
99
|
-
this.store.write(settings)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
hasChannelRef(channelId: string): boolean {
|
|
103
|
-
return this.store.read().profiles.some((p) => p.channelId === channelId)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
update(name: string, fields: Partial<Omit<ProfileConfig, "name">>): void {
|
|
107
|
-
const settings = this.store.read()
|
|
108
|
-
|
|
109
|
-
const profile = settings.profiles.find((p) => p.name === name)
|
|
110
|
-
|
|
111
|
-
if (!profile) throw new Error(`profile "${name}" not found`)
|
|
112
|
-
|
|
113
|
-
if (fields.channelId !== undefined) {
|
|
114
|
-
if (!settings.channels.some((c) => c.id === fields.channelId)) {
|
|
115
|
-
throw new Error(`channel id "${fields.channelId}" not found`)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
profile.channelId = fields.channelId
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (fields.path !== undefined) profile.path = fields.path
|
|
122
|
-
if (fields.subAgent !== undefined) profile.subAgent = fields.subAgent
|
|
123
|
-
|
|
124
|
-
this.store.write(settings)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { FunnelSettingsReader } from "@/engine/settings/settings-reader"
|
|
2
|
-
import { SETTINGS_VERSION } from "@/engine/settings/settings-schema"
|
|
3
|
-
import type { Settings } from "@/engine/settings/settings-schema"
|
|
4
|
-
|
|
5
|
-
export const createSettings = (partial: Partial<Settings> = {}): Settings => ({
|
|
6
|
-
version: SETTINGS_VERSION,
|
|
7
|
-
channels: [],
|
|
8
|
-
profiles: [],
|
|
9
|
-
...partial,
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
export class MockFunnelSettingsReader extends FunnelSettingsReader {
|
|
13
|
-
private state: Settings
|
|
14
|
-
|
|
15
|
-
constructor(initial?: Partial<Settings>) {
|
|
16
|
-
super()
|
|
17
|
-
this.state = createSettings(initial)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
read(): Settings {
|
|
21
|
-
return this.state
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
write(settings: Settings): void {
|
|
25
|
-
this.state = settings
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { z } from "zod"
|
|
2
|
-
import { connectorConfigSchema } from "@/connectors/connector-config-schema"
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Routing mode when multiple WS clients are subscribed to the same channel.
|
|
6
|
-
*
|
|
7
|
-
* - `fanout` (default): every connected client receives every event. Right when each
|
|
8
|
-
* subscriber has its own job (e.g., TUI mirrors, distinct Claude profiles each running
|
|
9
|
-
* their own pipeline against the same source).
|
|
10
|
-
* - `exclusive`: each event is delivered to exactly one connected client, picked
|
|
11
|
-
* round-robin per channel. Right when subscribers are interchangeable workers and you
|
|
12
|
-
* want each event handled once. Tap=all clients (TUI dashboard) always receive,
|
|
13
|
-
* regardless of mode, so they can passively observe.
|
|
14
|
-
*/
|
|
15
|
-
export const channelDeliveryModeSchema = z.enum(["fanout", "exclusive"])
|
|
16
|
-
|
|
17
|
-
export type ChannelDeliveryMode = z.infer<typeof channelDeliveryModeSchema>
|
|
18
|
-
|
|
19
|
-
export const channelConfigSchema = z.object({
|
|
20
|
-
id: z.string(),
|
|
21
|
-
name: z.string(),
|
|
22
|
-
delivery: channelDeliveryModeSchema.default("fanout"),
|
|
23
|
-
connectors: z.array(connectorConfigSchema).default([]),
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
export type ChannelConfig = z.infer<typeof channelConfigSchema>
|
|
27
|
-
|
|
28
|
-
export const profileConfigSchema = z.object({
|
|
29
|
-
name: z.string(),
|
|
30
|
-
path: z.string(),
|
|
31
|
-
subAgent: z.string(),
|
|
32
|
-
channelId: z.string(),
|
|
33
|
-
/** Forwards `--brief` to claude on launch (enables the SendUserMessage tool). */
|
|
34
|
-
brief: z.boolean().optional(),
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
export type ProfileConfig = z.infer<typeof profileConfigSchema>
|
|
38
|
-
|
|
39
|
-
export const SETTINGS_VERSION = 1
|
|
40
|
-
|
|
41
|
-
export const settingsSchema = z.object({
|
|
42
|
-
/** Schema version. New files always write the current version; older files without one are read as v1. */
|
|
43
|
-
version: z.literal(SETTINGS_VERSION).default(SETTINGS_VERSION),
|
|
44
|
-
channels: z.array(channelConfigSchema).default([]),
|
|
45
|
-
profiles: z.array(profileConfigSchema).default([]),
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
export type Settings = z.infer<typeof settingsSchema>
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { homedir } from "node:os"
|
|
2
|
-
import { dirname, join } from "node:path"
|
|
3
|
-
import { FunnelFileSystem } from "@/engine/fs/file-system"
|
|
4
|
-
import { NodeFunnelFileSystem } from "@/engine/fs/node-file-system"
|
|
5
|
-
import { FunnelSettingsReader } from "@/engine/settings/settings-reader"
|
|
6
|
-
import { SETTINGS_VERSION, settingsSchema } from "@/engine/settings/settings-schema"
|
|
7
|
-
import type { Settings } from "@/engine/settings/settings-schema"
|
|
8
|
-
|
|
9
|
-
export const FUNNEL_DIR = join(homedir(), ".funnel")
|
|
10
|
-
export const SETTINGS_PATH = join(FUNNEL_DIR, "settings.json")
|
|
11
|
-
|
|
12
|
-
type Deps = {
|
|
13
|
-
path?: string
|
|
14
|
-
fs?: FunnelFileSystem
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const defaultFs = new NodeFunnelFileSystem()
|
|
18
|
-
|
|
19
|
-
export class FunnelSettingsStore extends FunnelSettingsReader {
|
|
20
|
-
private readonly path: string
|
|
21
|
-
private readonly fs: FunnelFileSystem
|
|
22
|
-
|
|
23
|
-
constructor(deps: Deps = {}) {
|
|
24
|
-
super()
|
|
25
|
-
this.path = deps.path ?? SETTINGS_PATH
|
|
26
|
-
this.fs = deps.fs ?? defaultFs
|
|
27
|
-
Object.freeze(this)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
read(): Settings {
|
|
31
|
-
if (!this.fs.existsSync(this.path)) {
|
|
32
|
-
return {
|
|
33
|
-
version: SETTINGS_VERSION,
|
|
34
|
-
channels: [],
|
|
35
|
-
profiles: [],
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const content = this.fs.readFileSync(this.path)
|
|
40
|
-
const parsed: unknown = JSON.parse(content)
|
|
41
|
-
|
|
42
|
-
if (this.looksLikeLegacy(parsed)) {
|
|
43
|
-
throw new Error(
|
|
44
|
-
`legacy settings.json detected at ${this.path}. The schema changed (channel.connectors are now nested objects with ids; profile fields renamed). Migration is intentionally not provided. Back up and remove the old file:\n mv ${this.path} ${this.path}.bak`,
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
parsed &&
|
|
50
|
-
typeof parsed === "object" &&
|
|
51
|
-
"version" in parsed &&
|
|
52
|
-
parsed.version !== SETTINGS_VERSION
|
|
53
|
-
) {
|
|
54
|
-
throw new Error(
|
|
55
|
-
`unsupported settings.json version (${this.path}): expected ${SETTINGS_VERSION}, got ${String(parsed.version)}`,
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const result = settingsSchema.safeParse(parsed)
|
|
60
|
-
|
|
61
|
-
if (!result.success) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
`invalid settings.json (${this.path}): ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ")}`,
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return result.data
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
private looksLikeLegacy(parsed: unknown): boolean {
|
|
71
|
-
if (!parsed || typeof parsed !== "object") return false
|
|
72
|
-
|
|
73
|
-
const obj = parsed as Record<string, unknown>
|
|
74
|
-
|
|
75
|
-
if (Array.isArray(obj.channels)) {
|
|
76
|
-
for (const channel of obj.channels) {
|
|
77
|
-
if (!channel || typeof channel !== "object") continue
|
|
78
|
-
const ch = channel as Record<string, unknown>
|
|
79
|
-
|
|
80
|
-
if (Array.isArray(ch.connectors) && ch.connectors.some((x) => typeof x === "string")) {
|
|
81
|
-
return true
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!("id" in ch) && "name" in ch) return true
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (Array.isArray(obj.connectors)) return true
|
|
89
|
-
if (Array.isArray(obj.repositories)) return true
|
|
90
|
-
|
|
91
|
-
if (Array.isArray(obj.profiles)) {
|
|
92
|
-
for (const profile of obj.profiles) {
|
|
93
|
-
if (!profile || typeof profile !== "object") continue
|
|
94
|
-
const p = profile as Record<string, unknown>
|
|
95
|
-
|
|
96
|
-
if ("repository" in p || "envFiles" in p || ("channel" in p && !("channelId" in p))) {
|
|
97
|
-
return true
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return false
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
write(settings: Settings): void {
|
|
106
|
-
this.fs.mkdirSync(dirname(this.path), { recursive: true })
|
|
107
|
-
const versioned: Settings = { ...settings, version: SETTINGS_VERSION }
|
|
108
|
-
this.fs.writeFileSync(this.path, `${JSON.stringify(versioned, null, 2)}\n`)
|
|
109
|
-
}
|
|
110
|
-
}
|
package/lib/engine/time/clock.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Time boundary. Default NodeFunnelClock returns `new Date()`; MemoryFunnelClock
|
|
3
|
-
* is settable and `advance(ms)`-able for deterministic schedule / timeout tests.
|
|
4
|
-
*/
|
|
5
|
-
export abstract class FunnelClock {
|
|
6
|
-
abstract now(): Date
|
|
7
|
-
|
|
8
|
-
millis(): number {
|
|
9
|
-
return this.now().getTime()
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
iso(): string {
|
|
13
|
-
return this.now().toISOString()
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { FunnelClock } from "@/engine/time/clock"
|
|
2
|
-
|
|
3
|
-
type Props = {
|
|
4
|
-
start?: Date
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export class MemoryFunnelClock extends FunnelClock {
|
|
8
|
-
private current: Date
|
|
9
|
-
|
|
10
|
-
constructor(props: Props = {}) {
|
|
11
|
-
super()
|
|
12
|
-
this.current = props.start ?? new Date(0)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
now(): Date {
|
|
16
|
-
return new Date(this.current.getTime())
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
set(date: Date): void {
|
|
20
|
-
this.current = date
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
advance(ms: number): void {
|
|
24
|
-
this.current = new Date(this.current.getTime() + ms)
|
|
25
|
-
}
|
|
26
|
-
}
|
package/lib/funnel.ts
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import { join } from "node:path"
|
|
2
|
-
import { FunnelConnectorFactory } from "@/connectors/connector-factory"
|
|
3
|
-
import { FunnelChannels } from "@/engine/channels/channels"
|
|
4
|
-
import { FunnelClaude } from "@/engine/claude/claude"
|
|
5
|
-
import { FunnelFileSystem } from "@/engine/fs/file-system"
|
|
6
|
-
import { MemoryFunnelFileSystem } from "@/engine/fs/memory-file-system"
|
|
7
|
-
import { NodeFunnelFileSystem } from "@/engine/fs/node-file-system"
|
|
8
|
-
import { FunnelIdGenerator } from "@/engine/id/id-generator"
|
|
9
|
-
import { MemoryFunnelIdGenerator } from "@/engine/id/memory-id-generator"
|
|
10
|
-
import { NodeFunnelIdGenerator } from "@/engine/id/node-id-generator"
|
|
11
|
-
import { FunnelLogger } from "@/engine/logger/logger"
|
|
12
|
-
import { MemoryFunnelLogger } from "@/engine/logger/memory-logger"
|
|
13
|
-
import { NodeFunnelLogger } from "@/engine/logger/node-logger"
|
|
14
|
-
import { FunnelMcp } from "@/engine/mcp/mcp"
|
|
15
|
-
import { FunnelProcessRunner } from "@/engine/process/process-runner"
|
|
16
|
-
import { MemoryFunnelProcessRunner } from "@/engine/process/memory-process-runner"
|
|
17
|
-
import { NodeFunnelProcessRunner } from "@/engine/process/node-process-runner"
|
|
18
|
-
import { FunnelProfiles } from "@/engine/profiles/profiles"
|
|
19
|
-
import { MockFunnelSettingsReader } from "@/engine/settings/mock-settings-reader"
|
|
20
|
-
import { FunnelSettingsReader } from "@/engine/settings/settings-reader"
|
|
21
|
-
import { FUNNEL_DIR, FunnelSettingsStore } from "@/engine/settings/settings-store"
|
|
22
|
-
import { FunnelClock } from "@/engine/time/clock"
|
|
23
|
-
import { MemoryFunnelClock } from "@/engine/time/memory-clock"
|
|
24
|
-
import { NodeFunnelClock } from "@/engine/time/node-clock"
|
|
25
|
-
import { FunnelChannelPublisher } from "@/gateway/channel-publisher"
|
|
26
|
-
import { FunnelGateway } from "@/gateway/gateway"
|
|
27
|
-
import { FunnelGatewayServer } from "@/gateway/gateway-server"
|
|
28
|
-
import { FunnelGatewayToken } from "@/gateway/gateway-token"
|
|
29
|
-
import { FunnelListenersClient } from "@/gateway/listeners-client"
|
|
30
|
-
|
|
31
|
-
const DEFAULT_TMP_DIR = "/tmp/funnel"
|
|
32
|
-
const SANDBOX_DIR = "/sandbox/.funnel"
|
|
33
|
-
const SANDBOX_TMP_DIR = "/sandbox/tmp"
|
|
34
|
-
|
|
35
|
-
type Props = {
|
|
36
|
-
/** Settings persistence (channels with nested connectors / profiles). Defaults to a FunnelSettingsStore rooted at `dir`. */
|
|
37
|
-
store?: FunnelSettingsReader
|
|
38
|
-
/** Filesystem boundary. Replace with MemoryFunnelFileSystem to sandbox all disk I/O. */
|
|
39
|
-
fs?: FunnelFileSystem
|
|
40
|
-
/** Process runner used by gateway / claude / gh listener. Replace with MemoryFunnelProcessRunner for tests. */
|
|
41
|
-
process?: FunnelProcessRunner
|
|
42
|
-
/** Logger flowed into every facet. Replace with MemoryFunnelLogger or NoopFunnelLogger to silence/inspect. */
|
|
43
|
-
logger?: FunnelLogger
|
|
44
|
-
/** Clock used by schedule listener, gh poll watermarks, and gateway timeouts. */
|
|
45
|
-
clock?: FunnelClock
|
|
46
|
-
/** ID generator for channel and connector ids. Use MemoryFunnelIdGenerator for deterministic tests. */
|
|
47
|
-
idGenerator?: FunnelIdGenerator
|
|
48
|
-
/** Funnel home directory (settings.json + per-channel/per-connector dirs). Defaults to ~/.funnel. */
|
|
49
|
-
dir?: string
|
|
50
|
-
/** Temp / runtime directory (gateway logs and PID adjacent files). Defaults to /tmp/funnel. */
|
|
51
|
-
tmpDir?: string
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Facade exposing every funnel facet as a getter.
|
|
56
|
-
*
|
|
57
|
-
* The same `Funnel` is used by the CLI, the TUI, and as a programmable library.
|
|
58
|
-
* All side-effecting boundaries (filesystem, process, logger, clock, id, paths) are
|
|
59
|
-
* injectable via `Props` — passing memory implementations gives a fully sandboxed
|
|
60
|
-
* Funnel that touches no real disk, processes, or wall-clock time.
|
|
61
|
-
*
|
|
62
|
-
* Connectors live nested inside their owning channel (channels[].connectors[]),
|
|
63
|
-
* so connector CRUD is reached via `funnel.channels.addConnector(...)` etc.
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* ```ts
|
|
67
|
-
* const funnel = new Funnel({})
|
|
68
|
-
* const channel = funnel.channels.add({ name: "inbox" })
|
|
69
|
-
* funnel.channels.addConnector("inbox", { type: "slack", name: "ops", botToken, appToken })
|
|
70
|
-
* await funnel.gatewayServer({ port: 9742 }).start()
|
|
71
|
-
* ```
|
|
72
|
-
*/
|
|
73
|
-
export class Funnel {
|
|
74
|
-
private readonly cache = new Map<string, unknown>()
|
|
75
|
-
|
|
76
|
-
constructor(private readonly props: Props = {}) {
|
|
77
|
-
Object.freeze(this)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Sandboxed Funnel wired with in-memory implementations for every IO boundary.
|
|
82
|
-
* Touches no real disk, processes, wall-clock time, or UUIDs — safe for tests
|
|
83
|
-
* and ad-hoc experiments. Override individual fields by passing them in `props`.
|
|
84
|
-
*/
|
|
85
|
-
static inMemory(props: Props = {}): Funnel {
|
|
86
|
-
return new Funnel({
|
|
87
|
-
store: props.store ?? new MockFunnelSettingsReader(),
|
|
88
|
-
fs: props.fs ?? new MemoryFunnelFileSystem(),
|
|
89
|
-
process: props.process ?? new MemoryFunnelProcessRunner(),
|
|
90
|
-
logger: props.logger ?? new MemoryFunnelLogger(),
|
|
91
|
-
clock: props.clock ?? new MemoryFunnelClock(),
|
|
92
|
-
idGenerator: props.idGenerator ?? new MemoryFunnelIdGenerator(),
|
|
93
|
-
dir: props.dir ?? SANDBOX_DIR,
|
|
94
|
-
tmpDir: props.tmpDir ?? SANDBOX_TMP_DIR,
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private memo<T>(key: string, build: () => T): T {
|
|
99
|
-
if (this.cache.has(key)) return this.cache.get(key) as T
|
|
100
|
-
|
|
101
|
-
const value = build()
|
|
102
|
-
this.cache.set(key, value)
|
|
103
|
-
|
|
104
|
-
return value
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Resolved on-disk paths the facade will read/write when methods are called. Pure compute, not memoized. */
|
|
108
|
-
get paths(): { dir: string; tmpDir: string; settings: string } {
|
|
109
|
-
const dir = this.props.dir ?? FUNNEL_DIR
|
|
110
|
-
const tmpDir = this.props.tmpDir ?? DEFAULT_TMP_DIR
|
|
111
|
-
|
|
112
|
-
return { dir, tmpDir, settings: join(dir, "settings.json") }
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** Filesystem boundary. Defaults to NodeFunnelFileSystem. */
|
|
116
|
-
get fs(): FunnelFileSystem {
|
|
117
|
-
return this.memo("fs", () => this.props.fs ?? new NodeFunnelFileSystem())
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Process runner boundary. Defaults to NodeFunnelProcessRunner. */
|
|
121
|
-
get process(): FunnelProcessRunner {
|
|
122
|
-
return this.memo("process", () => this.props.process ?? new NodeFunnelProcessRunner())
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** Logger boundary. Defaults to NodeFunnelLogger. */
|
|
126
|
-
get logger(): FunnelLogger {
|
|
127
|
-
return this.memo("logger", () => this.props.logger ?? new NodeFunnelLogger())
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Clock boundary. Defaults to NodeFunnelClock. */
|
|
131
|
-
get clock(): FunnelClock {
|
|
132
|
-
return this.memo("clock", () => this.props.clock ?? new NodeFunnelClock())
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/** ID generator boundary. Defaults to NodeFunnelIdGenerator. */
|
|
136
|
-
get idGenerator(): FunnelIdGenerator {
|
|
137
|
-
return this.memo("idGenerator", () => this.props.idGenerator ?? new NodeFunnelIdGenerator())
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** Settings reader. If not injected, a FunnelSettingsStore rooted at `dir` is created. */
|
|
141
|
-
get store(): FunnelSettingsReader {
|
|
142
|
-
return this.memo(
|
|
143
|
-
"store",
|
|
144
|
-
() =>
|
|
145
|
-
this.props.store ??
|
|
146
|
-
new FunnelSettingsStore({
|
|
147
|
-
path: this.paths.settings,
|
|
148
|
-
fs: this.fs,
|
|
149
|
-
}),
|
|
150
|
-
)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** Pure factory that constructs per-type listeners and adapters from connector configs. */
|
|
154
|
-
get factory(): FunnelConnectorFactory {
|
|
155
|
-
return this.memo(
|
|
156
|
-
"factory",
|
|
157
|
-
() =>
|
|
158
|
-
new FunnelConnectorFactory({
|
|
159
|
-
fs: this.fs,
|
|
160
|
-
process: this.process,
|
|
161
|
-
logger: this.logger,
|
|
162
|
-
dir: this.paths.dir,
|
|
163
|
-
}),
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/** Channel CRUD + nested connector CRUD + schedule entries + listener/adapter dispatch. */
|
|
168
|
-
get channels(): FunnelChannels {
|
|
169
|
-
return this.memo(
|
|
170
|
-
"channels",
|
|
171
|
-
() =>
|
|
172
|
-
new FunnelChannels({
|
|
173
|
-
store: this.store,
|
|
174
|
-
factory: this.factory,
|
|
175
|
-
profileChecker: this.profiles,
|
|
176
|
-
clock: this.clock,
|
|
177
|
-
idGenerator: this.idGenerator,
|
|
178
|
-
}),
|
|
179
|
-
)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** Launch profiles (named presets for `fnl claude`: path + sub-agent + channel id). */
|
|
183
|
-
get profiles(): FunnelProfiles {
|
|
184
|
-
return this.memo("profiles", () => new FunnelProfiles({ store: this.store }))
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/** funnel MCP installer (writes/removes `.mcp.json` entries in target repos). */
|
|
188
|
-
get mcp(): FunnelMcp {
|
|
189
|
-
return this.memo("mcp", () => new FunnelMcp({ fs: this.fs }))
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/** Launch Claude Code with a channel injected via env, MCP installed, gateway ensured. */
|
|
193
|
-
get claude(): FunnelClaude {
|
|
194
|
-
return this.memo(
|
|
195
|
-
"claude",
|
|
196
|
-
() =>
|
|
197
|
-
new FunnelClaude({
|
|
198
|
-
channels: this.channels,
|
|
199
|
-
mcp: this.mcp,
|
|
200
|
-
gateway: this.gateway,
|
|
201
|
-
fs: this.fs,
|
|
202
|
-
process: this.process,
|
|
203
|
-
logger: this.logger,
|
|
204
|
-
dir: this.paths.dir,
|
|
205
|
-
}),
|
|
206
|
-
)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/** Gateway daemon controller (PID-file, start/stop the separate `bun daemon.ts` process). */
|
|
210
|
-
get gateway(): FunnelGateway {
|
|
211
|
-
return this.memo(
|
|
212
|
-
"gateway",
|
|
213
|
-
() =>
|
|
214
|
-
new FunnelGateway({
|
|
215
|
-
fs: this.fs,
|
|
216
|
-
process: this.process,
|
|
217
|
-
clock: this.clock,
|
|
218
|
-
dir: this.paths.dir,
|
|
219
|
-
tmpDir: this.paths.tmpDir,
|
|
220
|
-
}),
|
|
221
|
-
)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/** Read / generate the daemon's gateway token (mode 0600 file under `dir`). */
|
|
225
|
-
get gatewayToken(): FunnelGatewayToken {
|
|
226
|
-
return this.memo(
|
|
227
|
-
"gatewayToken",
|
|
228
|
-
() => new FunnelGatewayToken({ fs: this.fs, dir: this.paths.dir }),
|
|
229
|
-
)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* HTTP client for `POST /channels/:channel/publish` on the running gateway
|
|
234
|
-
* daemon. Use it to push arbitrary content into a channel from outside any
|
|
235
|
-
* connector. Returns `{ state: "offline" }` if the daemon isn't up.
|
|
236
|
-
*/
|
|
237
|
-
get publisher(): FunnelChannelPublisher {
|
|
238
|
-
return this.memo("publisher", () => {
|
|
239
|
-
const gateway = this.gateway
|
|
240
|
-
const token = this.gatewayToken
|
|
241
|
-
|
|
242
|
-
return new FunnelChannelPublisher({
|
|
243
|
-
port: gateway.getPort(),
|
|
244
|
-
isDaemonRunning: () => gateway.isRunning(),
|
|
245
|
-
getToken: () => token.read(),
|
|
246
|
-
})
|
|
247
|
-
})
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* HTTP client for listener operations on the running gateway daemon.
|
|
252
|
-
* Returns `{ state: "offline" }` when the daemon is offline so hot-reload
|
|
253
|
-
* paths stay write-only without parsing strings.
|
|
254
|
-
*/
|
|
255
|
-
get listeners(): FunnelListenersClient {
|
|
256
|
-
return this.memo("listeners", () => {
|
|
257
|
-
const gateway = this.gateway
|
|
258
|
-
const token = this.gatewayToken
|
|
259
|
-
|
|
260
|
-
return new FunnelListenersClient({
|
|
261
|
-
port: gateway.getPort(),
|
|
262
|
-
isDaemonRunning: () => gateway.isRunning(),
|
|
263
|
-
getToken: () => token.read(),
|
|
264
|
-
})
|
|
265
|
-
})
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* In-process gateway server. Unlike `gateway.start()` (which spawns a daemon),
|
|
270
|
-
* this returns a class that runs `Bun.serve` + listeners inside the current process —
|
|
271
|
-
* useful for tests, embedding, or custom hosts.
|
|
272
|
-
*/
|
|
273
|
-
gatewayServer(
|
|
274
|
-
options: {
|
|
275
|
-
port?: number
|
|
276
|
-
logDir?: string
|
|
277
|
-
killCompetingSlack?: boolean
|
|
278
|
-
/** Override the auth token. Defaults to the persisted gateway.token. Pass "" to disable auth (tests). */
|
|
279
|
-
token?: string
|
|
280
|
-
} = {},
|
|
281
|
-
): FunnelGatewayServer {
|
|
282
|
-
return new FunnelGatewayServer({
|
|
283
|
-
channels: this.channels,
|
|
284
|
-
settings: this.store,
|
|
285
|
-
port: options.port,
|
|
286
|
-
logDir: options.logDir,
|
|
287
|
-
process: this.process,
|
|
288
|
-
clock: this.clock,
|
|
289
|
-
logger: this.logger,
|
|
290
|
-
killCompetingSlack: options.killCompetingSlack,
|
|
291
|
-
token: options.token ?? this.gatewayToken.ensure(),
|
|
292
|
-
})
|
|
293
|
-
}
|
|
294
|
-
}
|