@interactive-inc/claude-funnel 0.8.1 → 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/README.md +179 -80
- package/dist/bin.js +726 -658
- package/dist/connector-adapter-CXB-q_XC.d.ts +11 -0
- package/dist/connector-adapter-D5Utumgz.js +4 -0
- package/dist/connectors/discord.d.ts +76 -0
- package/dist/connectors/discord.js +2 -0
- package/dist/connectors/gh.d.ts +38 -0
- package/dist/connectors/gh.js +2 -0
- package/dist/connectors/schedule.d.ts +53 -0
- package/dist/connectors/schedule.js +2 -0
- package/dist/connectors/slack.d.ts +34 -0
- package/dist/connectors/slack.js +2 -0
- package/dist/discord-connector-schema-Dww2I4zH.d.ts +14 -0
- package/dist/discord-connector-schema-ygf5Df-2.js +173 -0
- package/dist/file-system-Co60LrmR.d.ts +74 -0
- package/dist/gateway/daemon.js +233 -183
- package/dist/gh-connector-schema-2ml29MBC.js +218 -0
- package/dist/gh-connector-schema-BZFAS-p-.d.ts +45 -0
- package/dist/index.d.ts +3881 -36
- package/dist/index.js +6217 -3483
- package/dist/logger-CTlXs7z4.d.ts +33 -0
- package/dist/node-logger-DQz_BGOD.js +61 -0
- package/dist/schedule-connector-schema-CkuIQ0JQ.js +325 -0
- package/dist/slack-connector-schema-Cd22WiHB.js +153 -0
- package/dist/slack-event-processor-CS-bAit9.d.ts +43 -0
- package/package.json +34 -28
- package/dist/cli/factory.d.ts +0 -7
- package/dist/cli/router/query-to-cli-args.d.ts +0 -1
- package/dist/cli/router/to-request.d.ts +0 -5
- package/dist/cli/router/validator.d.ts +0 -5
- package/dist/cli/routes/channels.$channel.connectors.$connector.d.ts +0 -42
- package/dist/cli/routes/channels.$channel.connectors.$connector.rename.$newName.d.ts +0 -46
- package/dist/cli/routes/channels.$channel.connectors.$connector.request.d.ts +0 -54
- package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.d.ts +0 -66
- package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.d.ts +0 -42
- package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.d.ts +0 -46
- package/dist/cli/routes/channels.$channel.connectors.add.$connector.d.ts +0 -90
- package/dist/cli/routes/channels.$channel.connectors.d.ts +0 -38
- package/dist/cli/routes/channels.$channel.connectors.remove.$connector.d.ts +0 -42
- package/dist/cli/routes/channels.$channel.connectors.set.$connector.d.ts +0 -62
- package/dist/cli/routes/channels.$channel.d.ts +0 -38
- package/dist/cli/routes/channels.$channel.rename.$newName.d.ts +0 -42
- package/dist/cli/routes/channels.$channel.set.delivery.$mode.d.ts +0 -28
- package/dist/cli/routes/channels.add.$channel.d.ts +0 -46
- package/dist/cli/routes/channels.d.ts +0 -16
- package/dist/cli/routes/channels.remove.$channel.d.ts +0 -38
- package/dist/cli/routes/claude.d.ts +0 -32
- package/dist/cli/routes/gateway.d.ts +0 -20
- package/dist/cli/routes/gateway.listeners.d.ts +0 -17
- package/dist/cli/routes/gateway.logs.d.ts +0 -24
- package/dist/cli/routes/gateway.restart.d.ts +0 -24
- package/dist/cli/routes/gateway.run.d.ts +0 -24
- package/dist/cli/routes/gateway.start.d.ts +0 -24
- package/dist/cli/routes/gateway.status.d.ts +0 -13
- package/dist/cli/routes/gateway.stop.d.ts +0 -16
- package/dist/cli/routes/index.d.ts +0 -1222
- package/dist/cli/routes/profiles.$profile.as-default.d.ts +0 -38
- package/dist/cli/routes/profiles.$profile.rename.$newName.d.ts +0 -42
- package/dist/cli/routes/profiles.$profile.run.d.ts +0 -46
- package/dist/cli/routes/profiles.add.$profile.d.ts +0 -54
- package/dist/cli/routes/profiles.d.ts +0 -16
- package/dist/cli/routes/profiles.remove.$profile.d.ts +0 -38
- package/dist/cli/routes/profiles.set.$profile.d.ts +0 -54
- package/dist/cli/routes/status.d.ts +0 -16
- package/dist/cli/routes/update.d.ts +0 -16
- package/dist/connectors/connector-adapter.d.ts +0 -8
- package/dist/connectors/connector-config-schema.d.ts +0 -43
- package/dist/connectors/connector-factory.d.ts +0 -32
- package/dist/connectors/connector-listener.d.ts +0 -17
- package/dist/connectors/discord-adapter.d.ts +0 -14
- package/dist/connectors/discord-connector-schema.d.ts +0 -10
- package/dist/connectors/discord-event-processor.d.ts +0 -26
- package/dist/connectors/discord-listener.d.ts +0 -17
- package/dist/connectors/gh-adapter.d.ts +0 -11
- package/dist/connectors/gh-connector-schema.d.ts +0 -10
- package/dist/connectors/gh-listener.d.ts +0 -26
- package/dist/connectors/match-cron.d.ts +0 -1
- package/dist/connectors/schedule-connector-schema.d.ts +0 -45
- package/dist/connectors/schedule-listener.d.ts +0 -30
- package/dist/connectors/schedule-state-store.d.ts +0 -19
- package/dist/connectors/slack-adapter.d.ts +0 -15
- package/dist/connectors/slack-connector-schema.d.ts +0 -11
- package/dist/connectors/slack-event-processor.d.ts +0 -27
- package/dist/connectors/slack-listener.d.ts +0 -17
- package/dist/engine/channels/channels.d.ts +0 -106
- package/dist/engine/claude/claude.d.ts +0 -49
- package/dist/engine/claude/gateway-controller.d.ts +0 -6
- package/dist/engine/fs/file-system.d.ts +0 -24
- package/dist/engine/fs/memory-file-system.d.ts +0 -31
- package/dist/engine/fs/node-file-system.d.ts +0 -15
- package/dist/engine/http/http-client.d.ts +0 -15
- package/dist/engine/http/memory-http-client.d.ts +0 -12
- package/dist/engine/http/node-http-client.d.ts +0 -5
- package/dist/engine/id/id-generator.d.ts +0 -7
- package/dist/engine/id/memory-id-generator.d.ts +0 -11
- package/dist/engine/id/node-id-generator.d.ts +0 -4
- package/dist/engine/logger/logger.d.ts +0 -11
- package/dist/engine/logger/memory-logger.d.ts +0 -14
- package/dist/engine/logger/node-logger.d.ts +0 -15
- package/dist/engine/logger/noop-logger.d.ts +0 -7
- package/dist/engine/mcp/channel-server.d.ts +0 -1
- package/dist/engine/mcp/mcp.d.ts +0 -22
- package/dist/engine/process/memory-process-runner.d.ts +0 -43
- package/dist/engine/process/node-process-runner.d.ts +0 -9
- package/dist/engine/process/process-runner.d.ts +0 -29
- package/dist/engine/profiles/profile-channel-checker.d.ts +0 -7
- package/dist/engine/profiles/profiles.d.ts +0 -31
- package/dist/engine/settings/mock-settings-reader.d.ts +0 -9
- package/dist/engine/settings/settings-reader.d.ts +0 -5
- package/dist/engine/settings/settings-schema.d.ts +0 -132
- package/dist/engine/settings/settings-store.d.ts +0 -18
- package/dist/engine/time/clock.d.ts +0 -9
- package/dist/engine/time/memory-clock.d.ts +0 -12
- package/dist/engine/time/node-clock.d.ts +0 -4
- package/dist/funnel.d.ts +0 -95
- package/dist/gateway/auth-middleware.d.ts +0 -14
- package/dist/gateway/broadcaster.d.ts +0 -122
- package/dist/gateway/daemon.d.ts +0 -2
- package/dist/gateway/factory.d.ts +0 -7
- package/dist/gateway/funnel-event-store.d.ts +0 -81
- package/dist/gateway/gateway-server.d.ts +0 -94
- package/dist/gateway/gateway-token.d.ts +0 -33
- package/dist/gateway/gateway.d.ts +0 -58
- package/dist/gateway/kill-competing-slack-gateways.d.ts +0 -9
- package/dist/gateway/listener-supervisor.d.ts +0 -85
- package/dist/gateway/listeners-client.d.ts +0 -53
- package/dist/gateway/resolve-daemon-script.d.ts +0 -11
- package/dist/gateway/routes/channels.connectors.call.d.ts +0 -41
- package/dist/gateway/routes/health.d.ts +0 -17
- package/dist/gateway/routes/index.d.ts +0 -209
- package/dist/gateway/routes/listeners.list.d.ts +0 -14
- package/dist/gateway/routes/listeners.restart.d.ts +0 -34
- package/dist/gateway/routes/listeners.start.d.ts +0 -34
- package/dist/gateway/routes/listeners.stop.d.ts +0 -34
- package/dist/gateway/routes/route-deps.d.ts +0 -10
- package/dist/gateway/routes/status.d.ts +0 -30
- package/dist/gateway/routes/validator.d.ts +0 -19
- package/dist/logger/leuco-human-file-writer.d.ts +0 -33
- package/dist/logger/leuco-human-logger.d.ts +0 -46
- package/dist/logger/leuco-human-record.d.ts +0 -15
- package/dist/logger/leuco-human-stdout-writer.d.ts +0 -20
- package/dist/logger/leuco-human-writer.d.ts +0 -13
- package/dist/logger/leuco-logger-memory-sink.d.ts +0 -33
- package/dist/logger/leuco-logger-record.d.ts +0 -13
- package/dist/logger/leuco-logger-sink.d.ts +0 -34
- package/dist/logger/leuco-logger-sqlite-sink.d.ts +0 -102
- package/dist/logger/leuco-logger.d.ts +0 -56
- package/lib/bin.ts +0 -78
- package/lib/cli/factory.ts +0 -10
- package/lib/cli/router/query-to-cli-args.ts +0 -20
- package/lib/cli/router/to-request.ts +0 -112
- 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.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 -69
- 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 -202
- 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 -46
- package/lib/cli/routes/profiles.remove.$profile.ts +0 -20
- package/lib/cli/routes/profiles.set.$profile.ts +0 -46
- 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/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/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/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/engine/channels/channels.ts +0 -520
- package/lib/engine/claude/claude.ts +0 -199
- 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 -204
- package/lib/engine/mcp/mcp.ts +0 -126
- 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 -46
- 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 -187
- package/lib/gateway/auth-middleware.ts +0 -44
- package/lib/gateway/broadcaster.ts +0 -319
- 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 -414
- 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/resolve-daemon-script.ts +0 -26
- package/lib/gateway/routes/channels.connectors.call.ts +0 -39
- package/lib/gateway/routes/health.ts +0 -13
- package/lib/gateway/routes/index.ts +0 -24
- 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 -11
- package/lib/gateway/routes/status.ts +0 -15
- package/lib/gateway/routes/validator.ts +0 -17
- package/lib/index.ts +0 -52
- 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,204 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
-
import { homedir } from "node:os"
|
|
3
|
-
import { join } from "node:path"
|
|
4
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
|
|
5
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
6
|
-
import {
|
|
7
|
-
CallToolRequestSchema,
|
|
8
|
-
ListToolsRequestSchema,
|
|
9
|
-
} from "@modelcontextprotocol/sdk/types.js"
|
|
10
|
-
import { FUNNEL_MCP_NAME } from "@/engine/mcp/mcp"
|
|
11
|
-
import { settingsSchema } from "@/engine/settings/settings-schema"
|
|
12
|
-
|
|
13
|
-
const GATEWAY_BASE_URL = process.env.FUNNEL_GATEWAY_URL ?? "http://localhost:9742"
|
|
14
|
-
const GATEWAY_WS_URL = `${GATEWAY_BASE_URL.replace(/^http/, "ws")}/ws`
|
|
15
|
-
const RECONNECT_DELAY = 1000
|
|
16
|
-
const MAX_RECONNECT_DELAY = 10000
|
|
17
|
-
const SETTINGS_PATH = join(homedir(), ".funnel", "settings.json")
|
|
18
|
-
const TOOL_CONNECTOR_TYPES = new Set(["slack", "gh", "discord"])
|
|
19
|
-
|
|
20
|
-
const readGatewayToken = (): string | null => {
|
|
21
|
-
const fromEnv = process.env.FUNNEL_GATEWAY_TOKEN
|
|
22
|
-
|
|
23
|
-
if (fromEnv && fromEnv.length > 0) return fromEnv
|
|
24
|
-
|
|
25
|
-
const path = join(homedir(), ".funnel", "gateway.token")
|
|
26
|
-
|
|
27
|
-
if (!existsSync(path)) return null
|
|
28
|
-
|
|
29
|
-
const value = readFileSync(path, "utf-8").trim()
|
|
30
|
-
|
|
31
|
-
return value.length > 0 ? value : null
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const readChannelConnectors = (
|
|
35
|
-
channelId: string,
|
|
36
|
-
): { channelName: string; connectors: { name: string; type: string }[] } | null => {
|
|
37
|
-
if (!existsSync(SETTINGS_PATH)) return null
|
|
38
|
-
|
|
39
|
-
const raw = JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"))
|
|
40
|
-
const parsed = settingsSchema.safeParse(raw)
|
|
41
|
-
|
|
42
|
-
if (!parsed.success) return null
|
|
43
|
-
|
|
44
|
-
const channel = parsed.data.channels.find((c) => c.id === channelId)
|
|
45
|
-
|
|
46
|
-
if (!channel) return null
|
|
47
|
-
|
|
48
|
-
const connectors = channel.connectors
|
|
49
|
-
.filter((c) => TOOL_CONNECTOR_TYPES.has(c.type))
|
|
50
|
-
.map((c) => ({ name: c.name, type: c.type }))
|
|
51
|
-
|
|
52
|
-
return { channelName: channel.name, connectors }
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const usageHintForType = (type: string): string => {
|
|
56
|
-
if (type === "slack") {
|
|
57
|
-
return "Slack Web API. method=POST path=chat.postMessage body={channel,text,thread_ts?}"
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (type === "discord") {
|
|
61
|
-
return "Discord REST API. method=POST path=/channels/<id>/messages body={content,...}"
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (type === "gh") {
|
|
65
|
-
return "GitHub REST via gh CLI. method=POST path=repos/owner/repo/issues/N/comments body={body}"
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return "Generic adapter call."
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export const startChannelServer = async (): Promise<void> => {
|
|
72
|
-
const channelId = process.env.FUNNEL_CHANNEL_ID
|
|
73
|
-
const channel = channelId ? readChannelConnectors(channelId) : null
|
|
74
|
-
const token = readGatewayToken()
|
|
75
|
-
|
|
76
|
-
const server = new Server(
|
|
77
|
-
{ name: FUNNEL_MCP_NAME, version: "1.0.0" },
|
|
78
|
-
{
|
|
79
|
-
capabilities: {
|
|
80
|
-
experimental: { "claude/channel": {} },
|
|
81
|
-
tools: {},
|
|
82
|
-
},
|
|
83
|
-
instructions: [
|
|
84
|
-
`Events arrive inside <channel source="${FUNNEL_MCP_NAME}"> tags. Use meta.event_type to discriminate.`,
|
|
85
|
-
"",
|
|
86
|
-
"To reply or act, call the connector tool exposed by this MCP (one tool per connector configured on this channel). Each tool takes { method, path, body } matching the underlying adapter's CallInput.",
|
|
87
|
-
].join("\n"),
|
|
88
|
-
},
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
92
|
-
const tools = (channel?.connectors ?? []).map((c) => ({
|
|
93
|
-
name: c.name,
|
|
94
|
-
description: `Call the "${c.name}" (${c.type}) connector. ${usageHintForType(c.type)}`,
|
|
95
|
-
inputSchema: {
|
|
96
|
-
type: "object" as const,
|
|
97
|
-
properties: {
|
|
98
|
-
method: { type: "string", description: "HTTP verb or API method (e.g. POST, chat.postMessage)" },
|
|
99
|
-
path: { type: "string", description: "API path or method name (adapter-specific)" },
|
|
100
|
-
body: { type: "object", description: "Request body / params (adapter-specific)" },
|
|
101
|
-
},
|
|
102
|
-
required: ["method", "path"],
|
|
103
|
-
},
|
|
104
|
-
}))
|
|
105
|
-
|
|
106
|
-
return { tools }
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
110
|
-
if (!channel) {
|
|
111
|
-
throw new Error("FUNNEL_CHANNEL_ID is not set or channel not found in settings.json")
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const connectorName = request.params.name
|
|
115
|
-
const args = (request.params.arguments ?? {}) as Record<string, unknown>
|
|
116
|
-
const method = typeof args.method === "string" ? args.method : ""
|
|
117
|
-
const path = typeof args.path === "string" ? args.path : ""
|
|
118
|
-
const body = args.body ?? {}
|
|
119
|
-
|
|
120
|
-
if (!method || !path) {
|
|
121
|
-
throw new Error("`method` and `path` are required")
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const url = `${GATEWAY_BASE_URL}/channels/${encodeURIComponent(channel.channelName)}/connectors/${encodeURIComponent(connectorName)}/call`
|
|
125
|
-
const headers: Record<string, string> = { "content-type": "application/json" }
|
|
126
|
-
|
|
127
|
-
if (token) headers.authorization = `Bearer ${token}`
|
|
128
|
-
|
|
129
|
-
const res = await fetch(url, {
|
|
130
|
-
method: "POST",
|
|
131
|
-
headers,
|
|
132
|
-
body: JSON.stringify({ method, path, body }),
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
const text = await res.text()
|
|
136
|
-
|
|
137
|
-
if (!res.ok) {
|
|
138
|
-
throw new Error(`gateway call failed (${res.status}): ${text}`)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
content: [{ type: "text", text }],
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
const transport = new StdioServerTransport()
|
|
147
|
-
|
|
148
|
-
await server.connect(transport)
|
|
149
|
-
|
|
150
|
-
if (!channelId) return
|
|
151
|
-
|
|
152
|
-
const baseUrl = `${GATEWAY_WS_URL}?channel=${encodeURIComponent(channelId)}`
|
|
153
|
-
const protocols = token ? [`funnel.token.${token}`] : undefined
|
|
154
|
-
let reconnectDelay = RECONNECT_DELAY
|
|
155
|
-
let lastOffset = 0
|
|
156
|
-
|
|
157
|
-
const connect = () => {
|
|
158
|
-
const sinceQuery = lastOffset > 0 ? `&since=${lastOffset}` : ""
|
|
159
|
-
const wsUrl = `${baseUrl}${sinceQuery}`
|
|
160
|
-
const ws = new WebSocket(wsUrl, protocols)
|
|
161
|
-
|
|
162
|
-
ws.addEventListener("open", () => {
|
|
163
|
-
reconnectDelay = RECONNECT_DELAY
|
|
164
|
-
process.stderr.write(`funnel: connected (${wsUrl})\n`)
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
ws.addEventListener("message", async (event) => {
|
|
168
|
-
try {
|
|
169
|
-
const payload = JSON.parse(String(event.data))
|
|
170
|
-
const eventType = payload.meta?.event_type ?? "unknown"
|
|
171
|
-
|
|
172
|
-
if (typeof payload.offset === "number" && payload.offset > lastOffset) {
|
|
173
|
-
lastOffset = payload.offset
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
process.stderr.write(`funnel: received event (${eventType})\n`)
|
|
177
|
-
|
|
178
|
-
await server.notification({
|
|
179
|
-
method: "notifications/claude/channel",
|
|
180
|
-
params: {
|
|
181
|
-
content: payload.content,
|
|
182
|
-
meta: payload.meta,
|
|
183
|
-
},
|
|
184
|
-
})
|
|
185
|
-
} catch (error) {
|
|
186
|
-
process.stderr.write(
|
|
187
|
-
`funnel: error: ${error instanceof Error ? error.message : String(error)}\n`,
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
ws.addEventListener("close", () => {
|
|
193
|
-
process.stderr.write(`funnel: disconnected, reconnecting in ${reconnectDelay}ms\n`)
|
|
194
|
-
setTimeout(connect, reconnectDelay)
|
|
195
|
-
reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY)
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
ws.addEventListener("error", () => {
|
|
199
|
-
// close handler will reconnect
|
|
200
|
-
})
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
connect()
|
|
204
|
-
}
|
package/lib/engine/mcp/mcp.ts
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { join } from "node:path"
|
|
2
|
-
import { z } from "zod"
|
|
3
|
-
import { FunnelFileSystem } from "@/engine/fs/file-system"
|
|
4
|
-
import { NodeFunnelFileSystem } from "@/engine/fs/node-file-system"
|
|
5
|
-
|
|
6
|
-
export const FUNNEL_MCP_COMMAND = "funnel"
|
|
7
|
-
export const FUNNEL_MCP_NAME = "funnel"
|
|
8
|
-
|
|
9
|
-
const mcpEntrySchema = z.object({
|
|
10
|
-
command: z.string().optional(),
|
|
11
|
-
args: z.array(z.string()).optional(),
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
const mcpConfigSchema = z.object({
|
|
15
|
-
mcpServers: z.record(z.string(), mcpEntrySchema).optional(),
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
type McpEntry = z.infer<typeof mcpEntrySchema>
|
|
19
|
-
type McpConfig = z.infer<typeof mcpConfigSchema>
|
|
20
|
-
|
|
21
|
-
type Deps = {
|
|
22
|
-
fs?: FunnelFileSystem
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const defaultFs = new NodeFunnelFileSystem()
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Installs/uninstalls the funnel MCP entry into a target repository's
|
|
29
|
-
* `.mcp.json`. Detects an existing entry by command match so renaming is
|
|
30
|
-
* preserved across re-installs.
|
|
31
|
-
*/
|
|
32
|
-
export class FunnelMcp {
|
|
33
|
-
private readonly fs: FunnelFileSystem
|
|
34
|
-
|
|
35
|
-
constructor(deps: Deps = {}) {
|
|
36
|
-
this.fs = deps.fs ?? defaultFs
|
|
37
|
-
Object.freeze(this)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
install(repoPath: string): void {
|
|
41
|
-
if (!this.fs.existsSync(repoPath)) {
|
|
42
|
-
throw new Error(`repository does not exist: ${repoPath}`)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const config = this.readConfig(repoPath)
|
|
46
|
-
const servers = config.mcpServers ?? {}
|
|
47
|
-
|
|
48
|
-
const existingName = this.findServerName(servers)
|
|
49
|
-
const targetName = existingName ?? FUNNEL_MCP_NAME
|
|
50
|
-
|
|
51
|
-
servers[targetName] = {
|
|
52
|
-
command: FUNNEL_MCP_COMMAND,
|
|
53
|
-
args: ["mcp"],
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
this.writeConfig(repoPath, { ...config, mcpServers: servers })
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
uninstall(repoPath: string): void {
|
|
60
|
-
if (!this.fs.existsSync(repoPath)) return
|
|
61
|
-
|
|
62
|
-
const config = this.readConfig(repoPath)
|
|
63
|
-
const servers = config.mcpServers ?? {}
|
|
64
|
-
|
|
65
|
-
const name = this.findServerName(servers)
|
|
66
|
-
|
|
67
|
-
if (!name) return
|
|
68
|
-
|
|
69
|
-
const next = { ...servers }
|
|
70
|
-
|
|
71
|
-
delete next[name]
|
|
72
|
-
|
|
73
|
-
this.writeConfig(repoPath, { ...config, mcpServers: next })
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
findInstalledName(cwd: string): string | null {
|
|
77
|
-
const config = this.readConfig(cwd)
|
|
78
|
-
|
|
79
|
-
return this.findServerName(config.mcpServers ?? {})
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private findServerName(servers: Record<string, McpEntry>): string | null {
|
|
83
|
-
for (const entry of Object.entries(servers)) {
|
|
84
|
-
const name = entry[0]
|
|
85
|
-
const value = entry[1]
|
|
86
|
-
|
|
87
|
-
if (value?.command === FUNNEL_MCP_COMMAND) return name
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return null
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private readConfig(repoPath: string): McpConfig {
|
|
94
|
-
const mcpPath = join(repoPath, ".mcp.json")
|
|
95
|
-
|
|
96
|
-
if (!this.fs.existsSync(mcpPath)) return {}
|
|
97
|
-
|
|
98
|
-
const content = this.fs.readFileSync(mcpPath).trim()
|
|
99
|
-
|
|
100
|
-
if (!content) return {}
|
|
101
|
-
|
|
102
|
-
let parsed: unknown
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
parsed = JSON.parse(content)
|
|
106
|
-
} catch (error) {
|
|
107
|
-
throw new Error(
|
|
108
|
-
`invalid .mcp.json (${mcpPath}): ${error instanceof Error ? error.message : String(error)}`,
|
|
109
|
-
)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const result = mcpConfigSchema.safeParse(parsed)
|
|
113
|
-
|
|
114
|
-
if (!result.success) {
|
|
115
|
-
throw new Error(`invalid .mcp.json (${mcpPath}): ${result.error.message}`)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return result.data
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private writeConfig(repoPath: string, config: McpConfig): void {
|
|
122
|
-
const mcpPath = join(repoPath, ".mcp.json")
|
|
123
|
-
|
|
124
|
-
this.fs.writeFileSync(mcpPath, `${JSON.stringify(config, null, 2)}\n`)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type AttachOptions,
|
|
3
|
-
type DetachOptions,
|
|
4
|
-
FunnelProcessRunner,
|
|
5
|
-
type RunOptions,
|
|
6
|
-
type RunResult,
|
|
7
|
-
} from "@/engine/process/process-runner"
|
|
8
|
-
|
|
9
|
-
export type MemoryProcessResponse = {
|
|
10
|
-
exitCode?: number
|
|
11
|
-
stdout?: string
|
|
12
|
-
stderr?: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type MemoryProcessHandler = (
|
|
16
|
-
command: string[],
|
|
17
|
-
) => MemoryProcessResponse | Promise<MemoryProcessResponse>
|
|
18
|
-
|
|
19
|
-
export type MemoryProcessSyncHandler = (command: string[]) => MemoryProcessResponse
|
|
20
|
-
|
|
21
|
-
export type MemoryProcessCall =
|
|
22
|
-
| { kind: "run"; command: string[]; options: RunOptions }
|
|
23
|
-
| { kind: "runSync"; command: string[] }
|
|
24
|
-
| { kind: "attach"; command: string[]; options: AttachOptions }
|
|
25
|
-
| { kind: "detach"; command: string[]; options: DetachOptions }
|
|
26
|
-
| { kind: "kill"; command: string[] }
|
|
27
|
-
|
|
28
|
-
const empty: MemoryProcessResponse = { exitCode: 0, stdout: "", stderr: "" }
|
|
29
|
-
|
|
30
|
-
export class MemoryFunnelProcessRunner extends FunnelProcessRunner {
|
|
31
|
-
readonly calls: MemoryProcessCall[] = []
|
|
32
|
-
readonly killed: { pid: number; signal: string }[] = []
|
|
33
|
-
private handler: MemoryProcessHandler = () => empty
|
|
34
|
-
private syncHandler: MemoryProcessSyncHandler = () => empty
|
|
35
|
-
|
|
36
|
-
on(handler: MemoryProcessHandler): this {
|
|
37
|
-
this.handler = handler
|
|
38
|
-
|
|
39
|
-
return this
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
onSync(handler: MemoryProcessSyncHandler): this {
|
|
43
|
-
this.syncHandler = handler
|
|
44
|
-
|
|
45
|
-
return this
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async run(command: string[], options: RunOptions = {}): Promise<RunResult> {
|
|
49
|
-
this.calls.push({ kind: "run", command, options })
|
|
50
|
-
|
|
51
|
-
const result = await this.handler(command)
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
exitCode: result.exitCode ?? 0,
|
|
55
|
-
stdout: result.stdout ?? "",
|
|
56
|
-
stderr: result.stderr ?? "",
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
runSync(command: string[]): RunResult {
|
|
61
|
-
this.calls.push({ kind: "runSync", command })
|
|
62
|
-
|
|
63
|
-
const result = this.syncHandler(command)
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
exitCode: result.exitCode ?? 0,
|
|
67
|
-
stdout: result.stdout ?? "",
|
|
68
|
-
stderr: result.stderr ?? "",
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async attach(command: string[], options: AttachOptions = {}): Promise<number> {
|
|
73
|
-
this.calls.push({ kind: "attach", command, options })
|
|
74
|
-
|
|
75
|
-
const result = await this.handler(command)
|
|
76
|
-
|
|
77
|
-
return result.exitCode ?? 0
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
detach(command: string[], options: DetachOptions = {}): void {
|
|
81
|
-
this.calls.push({ kind: "detach", command, options })
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
kill(pid: number, signal: string = "SIGTERM"): void {
|
|
85
|
-
this.calls.push({ kind: "kill", command: [String(pid), signal] })
|
|
86
|
-
this.killed.push({ pid, signal })
|
|
87
|
-
}
|
|
88
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type AttachOptions,
|
|
3
|
-
type DetachOptions,
|
|
4
|
-
FunnelProcessRunner,
|
|
5
|
-
type RunOptions,
|
|
6
|
-
type RunResult,
|
|
7
|
-
} from "@/engine/process/process-runner"
|
|
8
|
-
|
|
9
|
-
const toEnv = (env?: Record<string, string>): Record<string, string> | undefined => {
|
|
10
|
-
if (!env) return undefined
|
|
11
|
-
|
|
12
|
-
const merged: Record<string, string> = {}
|
|
13
|
-
|
|
14
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
15
|
-
if (typeof value === "string") merged[key] = value
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
for (const [key, value] of Object.entries(env)) {
|
|
19
|
-
merged[key] = value
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return merged
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class NodeFunnelProcessRunner extends FunnelProcessRunner {
|
|
26
|
-
constructor() {
|
|
27
|
-
super()
|
|
28
|
-
Object.freeze(this)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
runSync(command: string[]): RunResult {
|
|
32
|
-
const result = Bun.spawnSync(command, {
|
|
33
|
-
stdout: "pipe",
|
|
34
|
-
stderr: "pipe",
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
exitCode: result.exitCode ?? 0,
|
|
39
|
-
stdout: result.stdout.toString(),
|
|
40
|
-
stderr: result.stderr.toString(),
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async run(command: string[], options: RunOptions = {}): Promise<RunResult> {
|
|
45
|
-
const proc = Bun.spawn(command, {
|
|
46
|
-
cwd: options.cwd,
|
|
47
|
-
env: toEnv(options.env),
|
|
48
|
-
stdin: options.input !== undefined ? "pipe" : "ignore",
|
|
49
|
-
stdout: "pipe",
|
|
50
|
-
stderr: "pipe",
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
if (options.input !== undefined && proc.stdin) {
|
|
54
|
-
proc.stdin.write(options.input)
|
|
55
|
-
proc.stdin.end()
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const exitCode = await proc.exited
|
|
59
|
-
const stdout = await new Response(proc.stdout).text()
|
|
60
|
-
const stderr = await new Response(proc.stderr).text()
|
|
61
|
-
|
|
62
|
-
return { exitCode, stdout, stderr }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async attach(command: string[], options: AttachOptions = {}): Promise<number> {
|
|
66
|
-
const proc = Bun.spawn(command, {
|
|
67
|
-
cwd: options.cwd,
|
|
68
|
-
env: toEnv(options.env),
|
|
69
|
-
stdio: ["inherit", "inherit", "inherit"],
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
return await proc.exited
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
detach(command: string[], options: DetachOptions = {}): void {
|
|
76
|
-
const proc = Bun.spawn(command, {
|
|
77
|
-
env: toEnv(options.env),
|
|
78
|
-
stdio: ["ignore", "ignore", "ignore"],
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
proc.unref()
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
kill(pid: number, signal: string = "SIGTERM"): void {
|
|
85
|
-
try {
|
|
86
|
-
process.kill(pid, signal)
|
|
87
|
-
} catch {
|
|
88
|
-
// ignore
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
export type RunOptions = {
|
|
2
|
-
cwd?: string
|
|
3
|
-
env?: Record<string, string>
|
|
4
|
-
input?: string
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export type RunResult = {
|
|
8
|
-
exitCode: number
|
|
9
|
-
stdout: string
|
|
10
|
-
stderr: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export type AttachOptions = {
|
|
14
|
-
cwd?: string
|
|
15
|
-
env?: Record<string, string>
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type DetachOptions = {
|
|
19
|
-
env?: Record<string, string>
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Process boundary covering one-shot runs, sync runs, foreground attach, and
|
|
24
|
-
* detached background spawns. Default is NodeFunnelProcessRunner (Bun.spawn);
|
|
25
|
-
* MemoryFunnelProcessRunner records calls and lets tests stub responses.
|
|
26
|
-
*/
|
|
27
|
-
export abstract class FunnelProcessRunner {
|
|
28
|
-
abstract run(command: string[], options?: RunOptions): Promise<RunResult>
|
|
29
|
-
abstract runSync(command: string[]): RunResult
|
|
30
|
-
abstract attach(command: string[], options?: AttachOptions): Promise<number>
|
|
31
|
-
abstract detach(command: string[], options?: DetachOptions): void
|
|
32
|
-
abstract kill(pid: number, signal?: string): void
|
|
33
|
-
}
|
|
@@ -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
|
-
}
|