@interactive-inc/claude-funnel 0.4.1 → 0.8.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 +233 -111
- package/dist/bin.js +1417 -0
- package/dist/gateway/daemon.js +513 -0
- package/dist/highlights-eq9cgrbb.scm +604 -0
- package/dist/highlights-ghv9g403.scm +205 -0
- package/dist/highlights-hk7bwhj4.scm +284 -0
- package/dist/highlights-r812a2qc.scm +150 -0
- package/dist/highlights-x6tmsnaa.scm +115 -0
- package/dist/injections-73j83es3.scm +27 -0
- package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
- package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
- package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
- package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
- package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
- package/lib/bin.ts +78 -0
- package/lib/{modules → cli}/router/to-request.ts +13 -20
- package/lib/cli/routes/channels.$channel.connectors.$connector.rename.$newName.ts +27 -0
- package/lib/cli/routes/channels.$channel.connectors.$connector.request.ts +40 -0
- package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.ts +41 -0
- package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.ts +22 -0
- package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.ts +23 -0
- package/lib/cli/routes/channels.$channel.connectors.$connector.ts +26 -0
- package/lib/cli/routes/channels.$channel.connectors.add.$connector.ts +92 -0
- package/lib/cli/routes/channels.$channel.connectors.remove.$connector.ts +22 -0
- package/lib/cli/routes/channels.$channel.connectors.set.$connector.ts +63 -0
- package/lib/cli/routes/channels.$channel.connectors.ts +26 -0
- package/lib/cli/routes/channels.$channel.rename.$newName.ts +22 -0
- package/lib/cli/routes/channels.$channel.set.delivery.$mode.ts +34 -0
- package/lib/cli/routes/channels.$channel.ts +34 -0
- package/lib/cli/routes/channels.add.$channel.ts +33 -0
- package/lib/cli/routes/channels.remove.$channel.ts +20 -0
- package/lib/cli/routes/channels.ts +39 -0
- package/lib/cli/routes/claude.ts +69 -0
- package/lib/cli/routes/gateway.listeners.ts +41 -0
- package/lib/cli/routes/gateway.logs.ts +123 -0
- package/lib/{routes/gateway/restart.ts → cli/routes/gateway.restart.ts} +20 -5
- package/lib/cli/routes/gateway.run.ts +41 -0
- package/lib/cli/routes/gateway.start.ts +50 -0
- package/lib/cli/routes/gateway.status.ts +19 -0
- package/lib/cli/routes/gateway.stop.ts +32 -0
- package/lib/cli/routes/gateway.ts +55 -0
- package/lib/cli/routes/index.ts +202 -0
- package/lib/cli/routes/profiles.$profile.as-default.ts +22 -0
- package/lib/cli/routes/profiles.$profile.rename.$newName.ts +22 -0
- package/lib/cli/routes/profiles.$profile.run.ts +36 -0
- package/lib/cli/routes/profiles.add.$profile.ts +46 -0
- package/lib/cli/routes/profiles.remove.$profile.ts +20 -0
- package/lib/cli/routes/profiles.set.$profile.ts +46 -0
- package/lib/cli/routes/profiles.ts +40 -0
- package/lib/cli/routes/status.ts +93 -0
- package/lib/cli/routes/update.ts +27 -0
- package/lib/connectors/connector-config-schema.ts +16 -0
- package/lib/connectors/connector-factory.ts +94 -0
- package/lib/connectors/connector-listener.ts +20 -0
- package/lib/{modules/connectors/funnel-discord-adapter.ts → connectors/discord-adapter.ts} +6 -11
- package/lib/{modules/connectors → connectors}/discord-connector-schema.ts +4 -1
- package/lib/connectors/discord-listener.ts +111 -0
- package/lib/{modules/connectors/funnel-gh-adapter.ts → connectors/gh-adapter.ts} +3 -6
- package/lib/{modules/connectors → connectors}/gh-connector-schema.ts +4 -1
- package/lib/{modules/connectors/funnel-gh-listener.ts → connectors/gh-listener.ts} +57 -22
- package/lib/{modules/connectors → connectors}/match-cron.ts +10 -4
- package/lib/connectors/schedule-connector-schema.ts +33 -0
- package/lib/connectors/schedule-listener.ts +207 -0
- package/lib/connectors/schedule-state-store.ts +54 -0
- package/lib/connectors/slack-adapter.ts +36 -0
- package/lib/{modules/connectors → connectors}/slack-connector-schema.ts +4 -1
- package/lib/{modules/connectors/funnel-slack-event-processor.ts → connectors/slack-event-processor.ts} +15 -9
- package/lib/{modules/connectors/funnel-slack-listener.ts → connectors/slack-listener.ts} +39 -14
- package/lib/engine/channels/channels.ts +520 -0
- package/lib/{modules/claude/funnel-claude.ts → engine/claude/claude.ts} +47 -62
- package/lib/engine/claude/gateway-controller.ts +4 -0
- package/lib/{modules/fs/funnel-file-system.ts → engine/fs/file-system.ts} +9 -0
- package/lib/{modules/fs/memory-funnel-file-system.ts → engine/fs/memory-file-system.ts} +20 -3
- package/lib/{modules/fs/node-funnel-file-system.ts → engine/fs/node-file-system.ts} +14 -2
- package/lib/{modules/http/memory-funnel-http-client.ts → engine/http/memory-http-client.ts} +1 -5
- package/lib/{modules/http/node-funnel-http-client.ts → engine/http/node-http-client.ts} +1 -5
- package/lib/engine/id/id-generator.ts +7 -0
- package/lib/engine/id/memory-id-generator.ts +20 -0
- package/lib/engine/id/node-id-generator.ts +7 -0
- package/lib/engine/logger/logger.ts +11 -0
- package/lib/engine/logger/memory-logger.ts +28 -0
- package/lib/engine/logger/node-logger.ts +49 -0
- package/lib/engine/logger/noop-logger.ts +9 -0
- package/lib/engine/mcp/channel-server.ts +204 -0
- package/lib/{modules/mcp/funnel-mcp.ts → engine/mcp/mcp.ts} +29 -10
- package/lib/{modules/process/memory-funnel-process-runner.ts → engine/process/memory-process-runner.ts} +1 -1
- package/lib/{modules/process/node-funnel-process-runner.ts → engine/process/node-process-runner.ts} +12 -21
- package/lib/{modules/process/funnel-process-runner.ts → engine/process/process-runner.ts} +5 -0
- package/lib/engine/profiles/profile-channel-checker.ts +7 -0
- package/lib/engine/profiles/profiles.ts +126 -0
- package/lib/{modules/settings/mock-funnel-settings-reader.ts → engine/settings/mock-settings-reader.ts} +4 -3
- package/lib/{modules/settings/funnel-settings-reader.ts → engine/settings/settings-reader.ts} +1 -1
- package/lib/engine/settings/settings-schema.ts +46 -0
- package/lib/engine/settings/settings-store.ts +110 -0
- package/lib/engine/time/clock.ts +15 -0
- package/lib/engine/time/memory-clock.ts +26 -0
- package/lib/engine/time/node-clock.ts +7 -0
- package/lib/funnel.ts +148 -56
- package/lib/gateway/auth-middleware.ts +44 -0
- package/lib/gateway/broadcaster.ts +319 -0
- package/lib/gateway/daemon.ts +47 -0
- package/lib/gateway/factory.ts +10 -0
- package/lib/gateway/funnel-event-store.ts +155 -0
- package/lib/gateway/gateway-server.ts +414 -0
- package/lib/gateway/gateway-token.ts +79 -0
- package/lib/{modules/gateway/funnel-gateway.ts → gateway/gateway.ts} +70 -27
- package/lib/{modules/gateway → gateway}/kill-competing-slack-gateways.ts +7 -3
- package/lib/gateway/listener-supervisor.ts +339 -0
- package/lib/gateway/listeners-client.ts +128 -0
- package/lib/gateway/resolve-daemon-script.ts +26 -0
- package/lib/gateway/routes/channels.connectors.call.ts +39 -0
- package/lib/gateway/routes/health.ts +13 -0
- package/lib/gateway/routes/index.ts +24 -0
- package/lib/gateway/routes/listeners.list.ts +6 -0
- package/lib/gateway/routes/listeners.restart.ts +15 -0
- package/lib/gateway/routes/listeners.start.ts +15 -0
- package/lib/gateway/routes/listeners.stop.ts +15 -0
- package/lib/gateway/routes/route-deps.ts +11 -0
- package/lib/gateway/routes/status.ts +15 -0
- package/lib/gateway/routes/validator.ts +17 -0
- package/lib/index.ts +50 -92
- package/lib/logger/leuco-human-file-writer.ts +65 -0
- package/lib/logger/leuco-human-logger.ts +98 -0
- package/lib/logger/leuco-human-record.ts +16 -0
- package/lib/logger/leuco-human-stdout-writer.ts +26 -0
- package/lib/logger/leuco-human-writer.ts +14 -0
- package/lib/logger/leuco-logger-memory-sink.ts +67 -0
- package/lib/logger/leuco-logger-record.ts +13 -0
- package/lib/logger/leuco-logger-sink.ts +33 -0
- package/lib/logger/leuco-logger-sqlite-sink.ts +355 -0
- package/lib/logger/leuco-logger.ts +135 -0
- package/lib/tui/app.tsx +357 -0
- package/lib/tui/components/add-row.tsx +18 -0
- package/lib/tui/components/brand.tsx +27 -0
- package/lib/tui/components/card.tsx +44 -0
- package/lib/tui/components/detail-bar.tsx +46 -0
- package/lib/tui/components/editable-field.tsx +33 -0
- package/lib/tui/components/empty-state.tsx +11 -0
- package/lib/tui/components/gateway-status.tsx +66 -0
- package/lib/tui/components/keymap.tsx +29 -0
- package/lib/tui/components/menu-item.tsx +73 -0
- package/lib/tui/components/menu.tsx +26 -0
- package/lib/tui/components/panel-header.tsx +22 -0
- package/lib/tui/components/readonly-field.tsx +18 -0
- package/lib/tui/components/section-header.tsx +25 -0
- package/lib/tui/components/selection-accent.tsx +32 -0
- package/lib/tui/components/session-item.tsx +33 -0
- package/lib/tui/components/session-list.tsx +33 -0
- package/lib/tui/components/ui/hascii/accordion-item.tsx +88 -0
- package/lib/tui/components/ui/hascii/accordion.tsx +96 -0
- package/lib/tui/components/ui/hascii/alert-dialog.tsx +43 -0
- package/lib/tui/components/ui/hascii/badge.tsx +51 -0
- package/lib/tui/components/ui/hascii/breadcrumb.tsx +58 -0
- package/lib/tui/components/ui/hascii/button.tsx +194 -0
- package/lib/tui/components/ui/hascii/card-content.tsx +14 -0
- package/lib/tui/components/ui/hascii/card-description.tsx +13 -0
- package/lib/tui/components/ui/hascii/card-footer.tsx +14 -0
- package/lib/tui/components/ui/hascii/card-header.tsx +14 -0
- package/lib/tui/components/ui/hascii/card-title.tsx +13 -0
- package/lib/tui/components/ui/hascii/card.tsx +27 -0
- package/lib/tui/components/ui/hascii/checkbox.tsx +65 -0
- package/lib/tui/components/ui/hascii/command.tsx +159 -0
- package/lib/tui/components/ui/hascii/dialog-content.tsx +14 -0
- package/lib/tui/components/ui/hascii/dialog-description.tsx +13 -0
- package/lib/tui/components/ui/hascii/dialog-footer.tsx +14 -0
- package/lib/tui/components/ui/hascii/dialog-header.tsx +14 -0
- package/lib/tui/components/ui/hascii/dialog-title.tsx +13 -0
- package/lib/tui/components/ui/hascii/dialog.tsx +27 -0
- package/lib/tui/components/ui/hascii/file-tree.tsx +142 -0
- package/lib/tui/components/ui/hascii/focus-group.tsx +62 -0
- package/lib/tui/components/ui/hascii/form-item.tsx +43 -0
- package/lib/tui/components/ui/hascii/input-otp.tsx +86 -0
- package/lib/tui/components/ui/hascii/input.tsx +130 -0
- package/lib/tui/components/ui/hascii/pagination.tsx +105 -0
- package/lib/tui/components/ui/hascii/progress.tsx +28 -0
- package/lib/tui/components/ui/hascii/select.tsx +131 -0
- package/lib/tui/components/ui/hascii/separator.tsx +35 -0
- package/lib/tui/components/ui/hascii/sidebar-content.tsx +23 -0
- package/lib/tui/components/ui/hascii/sidebar-header.tsx +14 -0
- package/lib/tui/components/ui/hascii/sidebar-menu-item.tsx +67 -0
- package/lib/tui/components/ui/hascii/sidebar.tsx +24 -0
- package/lib/tui/components/ui/hascii/skeleton.tsx +60 -0
- package/lib/tui/components/ui/hascii/slider.tsx +91 -0
- package/lib/tui/components/ui/hascii/snackbar.tsx +75 -0
- package/lib/tui/components/ui/hascii/sparkline.tsx +53 -0
- package/lib/tui/components/ui/hascii/spinner.tsx +47 -0
- package/lib/tui/components/ui/hascii/stepper.tsx +54 -0
- package/lib/tui/components/ui/hascii/switch.tsx +66 -0
- package/lib/tui/components/ui/hascii/table.tsx +95 -0
- package/lib/tui/components/ui/hascii/tabs.tsx +59 -0
- package/lib/tui/components/ui/hascii/toggle-group-item.tsx +45 -0
- package/lib/tui/components/ui/hascii/toggle-group.tsx +99 -0
- package/lib/tui/components/ui/hascii/tree.tsx +104 -0
- package/lib/tui/components/view-shell.tsx +44 -0
- package/lib/tui/filter-input.tsx +33 -0
- package/lib/tui/hooks/hascii/use-pressable.ts +54 -0
- package/lib/tui/parse-comma-list.ts +14 -0
- package/lib/tui/profile-launcher.tsx +61 -0
- package/lib/tui/scrollbar-options.ts +19 -0
- package/lib/tui/sidebar.tsx +50 -0
- package/lib/tui/theme.ts +40 -0
- package/lib/tui/tui.tsx +20 -0
- package/lib/tui/types.ts +38 -0
- package/lib/tui/unique-name.ts +18 -0
- package/lib/tui/use-event-stream.ts +133 -0
- package/lib/tui/use-snapshot.ts +99 -0
- package/lib/tui/utils/hascii/form-item-context.tsx +23 -0
- package/lib/tui/utils/hascii/input-focus-context.tsx +31 -0
- package/lib/tui/utils/hascii/theme-context.tsx +26 -0
- package/lib/tui/utils/hascii/theme.ts +176 -0
- package/lib/tui/views/channels-view.tsx +108 -0
- package/lib/tui/views/connectors-view.tsx +164 -0
- package/lib/tui/views/events-view.tsx +160 -0
- package/lib/tui/views/listeners-view.tsx +80 -0
- package/lib/tui/views/profiles-view.tsx +152 -0
- package/package.json +51 -34
- package/lib/modules/channels/channel-connector-ref-updater.ts +0 -4
- package/lib/modules/channels/funnel-channels.ts +0 -155
- package/lib/modules/connectors/connector-config-schema.ts +0 -16
- package/lib/modules/connectors/connector-existence-checker.ts +0 -3
- package/lib/modules/connectors/funnel-callable-connector-store.ts +0 -9
- package/lib/modules/connectors/funnel-connector-listener.ts +0 -5
- package/lib/modules/connectors/funnel-connector-stores.ts +0 -24
- package/lib/modules/connectors/funnel-connector-type-store.ts +0 -24
- package/lib/modules/connectors/funnel-connectors.ts +0 -145
- package/lib/modules/connectors/funnel-discord-listener.ts +0 -65
- package/lib/modules/connectors/funnel-discord-store.ts +0 -84
- package/lib/modules/connectors/funnel-gh-store.ts +0 -84
- package/lib/modules/connectors/funnel-json-connector-store.ts +0 -100
- package/lib/modules/connectors/funnel-schedule-listener.ts +0 -124
- package/lib/modules/connectors/funnel-schedule-store.ts +0 -178
- package/lib/modules/connectors/funnel-slack-adapter.ts +0 -31
- package/lib/modules/connectors/funnel-slack-store.ts +0 -86
- package/lib/modules/connectors/migrate-legacy-connectors.ts +0 -77
- package/lib/modules/connectors/schedule-connector-schema.ts +0 -18
- package/lib/modules/connectors/schedule-last-fired-store.ts +0 -48
- package/lib/modules/gateway/daemon.ts +0 -207
- package/lib/modules/gateway/funnel-broadcaster.ts +0 -37
- package/lib/modules/gateway/funnel-event-logger.ts +0 -59
- package/lib/modules/logger.ts +0 -26
- package/lib/modules/mcp/channel-server.ts +0 -76
- package/lib/modules/profiles/funnel-profiles.ts +0 -123
- package/lib/modules/profiles/profile-channel-checker.ts +0 -3
- package/lib/modules/profiles/profile-channel-ref-updater.ts +0 -3
- package/lib/modules/repos/funnel-repositories.ts +0 -107
- package/lib/modules/schedule/funnel-schedule.ts +0 -34
- package/lib/modules/settings/funnel-settings-store.ts +0 -56
- package/lib/modules/settings/settings-schema.ts +0 -33
- package/lib/modules/tui/app.tsx +0 -44
- package/lib/modules/tui/tui.tsx +0 -13
- package/lib/routes/channels/add.help.ts +0 -3
- package/lib/routes/channels/add.ts +0 -21
- package/lib/routes/channels/connectors-attach.help.ts +0 -3
- package/lib/routes/channels/connectors-attach.ts +0 -17
- package/lib/routes/channels/connectors-detach.help.ts +0 -3
- package/lib/routes/channels/connectors-detach.ts +0 -17
- package/lib/routes/channels/group.help.ts +0 -16
- package/lib/routes/channels/group.ts +0 -22
- package/lib/routes/channels/remove.help.ts +0 -3
- package/lib/routes/channels/remove.ts +0 -17
- package/lib/routes/channels/rename.help.ts +0 -5
- package/lib/routes/channels/rename.ts +0 -17
- package/lib/routes/channels/routes.ts +0 -19
- package/lib/routes/channels/show.help.ts +0 -1
- package/lib/routes/channels/show.ts +0 -26
- package/lib/routes/claude/claude.help.ts +0 -16
- package/lib/routes/claude/claude.ts +0 -76
- package/lib/routes/claude/routes.ts +0 -4
- package/lib/routes/connectors/add.help.ts +0 -28
- package/lib/routes/connectors/add.ts +0 -64
- package/lib/routes/connectors/group.help.ts +0 -14
- package/lib/routes/connectors/group.ts +0 -18
- package/lib/routes/connectors/remove.help.ts +0 -3
- package/lib/routes/connectors/remove.ts +0 -17
- package/lib/routes/connectors/rename.help.ts +0 -5
- package/lib/routes/connectors/rename.ts +0 -17
- package/lib/routes/connectors/routes.ts +0 -23
- package/lib/routes/connectors/schedules-add.help.ts +0 -11
- package/lib/routes/connectors/schedules-add.ts +0 -33
- package/lib/routes/connectors/schedules-group.help.ts +0 -1
- package/lib/routes/connectors/schedules-group.ts +0 -38
- package/lib/routes/connectors/schedules-remove.help.ts +0 -3
- package/lib/routes/connectors/schedules-remove.ts +0 -17
- package/lib/routes/connectors/set.help.ts +0 -8
- package/lib/routes/connectors/set.ts +0 -72
- package/lib/routes/connectors/show.help.ts +0 -1
- package/lib/routes/connectors/show.ts +0 -41
- package/lib/routes/gateway/group.help.ts +0 -15
- package/lib/routes/gateway/group.ts +0 -28
- package/lib/routes/gateway/logs.help.ts +0 -13
- package/lib/routes/gateway/logs.ts +0 -100
- package/lib/routes/gateway/restart.help.ts +0 -10
- package/lib/routes/gateway/routes.ts +0 -18
- package/lib/routes/gateway/run.help.ts +0 -12
- package/lib/routes/gateway/run.ts +0 -35
- package/lib/routes/gateway/start.help.ts +0 -15
- package/lib/routes/gateway/start.ts +0 -32
- package/lib/routes/gateway/status.help.ts +0 -9
- package/lib/routes/gateway/status.ts +0 -28
- package/lib/routes/gateway/stop.help.ts +0 -8
- package/lib/routes/gateway/stop.ts +0 -21
- package/lib/routes/profiles/add.help.ts +0 -3
- package/lib/routes/profiles/add.ts +0 -33
- package/lib/routes/profiles/group.help.ts +0 -16
- package/lib/routes/profiles/group.ts +0 -25
- package/lib/routes/profiles/launch.help.ts +0 -4
- package/lib/routes/profiles/launch.ts +0 -36
- package/lib/routes/profiles/remove.help.ts +0 -3
- package/lib/routes/profiles/remove.ts +0 -17
- package/lib/routes/profiles/rename.help.ts +0 -5
- package/lib/routes/profiles/rename.ts +0 -17
- package/lib/routes/profiles/routes.ts +0 -18
- package/lib/routes/profiles/set.help.ts +0 -5
- package/lib/routes/profiles/set.ts +0 -32
- package/lib/routes/repos/add.help.ts +0 -6
- package/lib/routes/repos/add.ts +0 -20
- package/lib/routes/repos/group.help.ts +0 -11
- package/lib/routes/repos/group.ts +0 -18
- package/lib/routes/repos/remove.help.ts +0 -3
- package/lib/routes/repos/remove.ts +0 -17
- package/lib/routes/repos/rename.help.ts +0 -5
- package/lib/routes/repos/rename.ts +0 -17
- package/lib/routes/repos/routes.ts +0 -17
- package/lib/routes/repos/set.help.ts +0 -5
- package/lib/routes/repos/set.ts +0 -21
- package/lib/routes/repos/show.help.ts +0 -1
- package/lib/routes/repos/show.ts +0 -19
- package/lib/routes/request/discord-help.ts +0 -9
- package/lib/routes/request/discord.help.ts +0 -19
- package/lib/routes/request/discord.ts +0 -65
- package/lib/routes/request/group.help.ts +0 -15
- package/lib/routes/request/group.ts +0 -9
- package/lib/routes/request/routes.ts +0 -14
- package/lib/routes/request/slack-help.ts +0 -9
- package/lib/routes/request/slack.help.ts +0 -19
- package/lib/routes/request/slack.ts +0 -61
- package/lib/routes/status/routes.ts +0 -4
- package/lib/routes/status/status.help.ts +0 -6
- package/lib/routes/status/status.ts +0 -77
- package/lib/routes/update/routes.ts +0 -4
- package/lib/routes/update/update.help.ts +0 -5
- package/lib/routes/update/update.ts +0 -21
- package/lib/routes.ts +0 -40
- /package/lib/{factory.ts → cli/factory.ts} +0 -0
- /package/lib/{modules → cli}/router/query-to-cli-args.ts +0 -0
- /package/lib/{modules → cli}/router/validator.ts +0 -0
- /package/lib/{modules/connectors/funnel-connector-adapter.ts → connectors/connector-adapter.ts} +0 -0
- /package/lib/{modules/connectors/funnel-discord-event-processor.ts → connectors/discord-event-processor.ts} +0 -0
- /package/lib/{modules/http/funnel-http-client.ts → engine/http/http-client.ts} +0 -0
|
@@ -1,58 +1,65 @@
|
|
|
1
1
|
import { join } from "node:path"
|
|
2
|
-
import type { FunnelChannels } from "@/
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import { FUNNEL_DIR } from "@/
|
|
12
|
-
|
|
13
|
-
const CLAUDE_PID_DIR = join(FUNNEL_DIR, "claude")
|
|
2
|
+
import type { FunnelChannels } from "@/engine/channels/channels"
|
|
3
|
+
import type { GatewayController } from "@/engine/claude/gateway-controller"
|
|
4
|
+
import { FunnelFileSystem } from "@/engine/fs/file-system"
|
|
5
|
+
import { NodeFunnelFileSystem } from "@/engine/fs/node-file-system"
|
|
6
|
+
import { FunnelLogger } from "@/engine/logger/logger"
|
|
7
|
+
import { NodeFunnelLogger } from "@/engine/logger/node-logger"
|
|
8
|
+
import type { FunnelMcp } from "@/engine/mcp/mcp"
|
|
9
|
+
import { FunnelProcessRunner } from "@/engine/process/process-runner"
|
|
10
|
+
import { NodeFunnelProcessRunner } from "@/engine/process/node-process-runner"
|
|
11
|
+
import { FUNNEL_DIR } from "@/engine/settings/settings-store"
|
|
14
12
|
|
|
15
13
|
export type LaunchOptions = {
|
|
16
14
|
channel: string
|
|
17
|
-
|
|
15
|
+
cwd?: string
|
|
18
16
|
subAgent?: string
|
|
19
|
-
envFiles?: string[]
|
|
20
17
|
userArgs?: string[]
|
|
21
18
|
profileName?: string
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
type Deps = {
|
|
25
22
|
channels: FunnelChannels
|
|
26
|
-
repositories: FunnelRepositories
|
|
27
23
|
mcp: FunnelMcp
|
|
28
|
-
gateway:
|
|
24
|
+
gateway: GatewayController
|
|
29
25
|
process?: FunnelProcessRunner
|
|
30
26
|
fs?: FunnelFileSystem
|
|
27
|
+
logger?: FunnelLogger
|
|
28
|
+
dir?: string
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
const defaultProcess = new NodeFunnelProcessRunner()
|
|
34
32
|
const defaultFs = new NodeFunnelFileSystem()
|
|
35
|
-
|
|
33
|
+
const defaultLogger = new NodeFunnelLogger()
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Launches Claude Code with funnel pre-wired: ensures the gateway is running,
|
|
37
|
+
* installs the funnel MCP into the target repo's `.mcp.json` if missing,
|
|
38
|
+
* injects `FUNNEL_CHANNEL_ID` into the child env, and writes a per-profile
|
|
39
|
+
* PID file to enforce singleton launches.
|
|
40
|
+
*/
|
|
36
41
|
export class FunnelClaude {
|
|
37
42
|
private readonly channels: FunnelChannels
|
|
38
|
-
private readonly repositories: FunnelRepositories
|
|
39
43
|
private readonly mcp: FunnelMcp
|
|
40
|
-
private readonly gateway:
|
|
44
|
+
private readonly gateway: GatewayController
|
|
41
45
|
private readonly process: FunnelProcessRunner
|
|
42
46
|
private readonly fs: FunnelFileSystem
|
|
47
|
+
private readonly logger: FunnelLogger
|
|
48
|
+
private readonly pidDir: string
|
|
43
49
|
|
|
44
50
|
constructor(deps: Deps) {
|
|
45
51
|
this.channels = deps.channels
|
|
46
|
-
this.repositories = deps.repositories
|
|
47
52
|
this.mcp = deps.mcp
|
|
48
53
|
this.gateway = deps.gateway
|
|
49
54
|
this.process = deps.process ?? defaultProcess
|
|
50
55
|
this.fs = deps.fs ?? defaultFs
|
|
56
|
+
this.logger = deps.logger ?? defaultLogger
|
|
57
|
+
this.pidDir = join(deps.dir ?? FUNNEL_DIR, "claude")
|
|
51
58
|
Object.freeze(this)
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
async launch(options: LaunchOptions): Promise<number> {
|
|
55
|
-
const channel = this.channels.get(options.channel)
|
|
62
|
+
const channel = this.channels.get(options.channel) ?? this.channels.getById(options.channel)
|
|
56
63
|
|
|
57
64
|
if (!channel) {
|
|
58
65
|
throw new Error(`channel "${options.channel}" not found`)
|
|
@@ -62,18 +69,16 @@ export class FunnelClaude {
|
|
|
62
69
|
throw new Error(`profile "${options.profileName}" is already running`)
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
const cwd = options.
|
|
66
|
-
? this.repositories.resolvePath(options.repo)
|
|
67
|
-
: globalThis.process.cwd()
|
|
72
|
+
const cwd = options.cwd ?? globalThis.process.cwd()
|
|
68
73
|
|
|
69
74
|
if (!this.mcp.findInstalledName(cwd)) {
|
|
70
75
|
this.mcp.install(cwd)
|
|
71
76
|
|
|
72
|
-
logger.info(`added funnel MCP to .mcp.json`, { cwd })
|
|
77
|
+
this.logger.info(`added funnel MCP to .mcp.json`, { cwd })
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
if (!this.gateway.isRunning()) {
|
|
76
|
-
logger.info(`starting gateway automatically`)
|
|
81
|
+
this.logger.info(`starting gateway automatically`)
|
|
77
82
|
await this.gateway.start()
|
|
78
83
|
}
|
|
79
84
|
|
|
@@ -83,11 +88,11 @@ export class FunnelClaude {
|
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
const claudeArgs = this.buildArgs(options, cwd)
|
|
86
|
-
const env = this.buildEnv(
|
|
91
|
+
const env = this.buildEnv(channel.id)
|
|
87
92
|
|
|
88
|
-
logger.info(`claude launch`, {
|
|
93
|
+
this.logger.info(`claude launch`, {
|
|
89
94
|
channel: options.channel,
|
|
90
|
-
|
|
95
|
+
channelId: channel.id,
|
|
91
96
|
subAgent: options.subAgent,
|
|
92
97
|
cwd,
|
|
93
98
|
})
|
|
@@ -108,7 +113,7 @@ export class FunnelClaude {
|
|
|
108
113
|
}
|
|
109
114
|
|
|
110
115
|
private pidPath(profileName: string): string {
|
|
111
|
-
return join(
|
|
116
|
+
return join(this.pidDir, `${profileName}.pid`)
|
|
112
117
|
}
|
|
113
118
|
|
|
114
119
|
private readPid(profileName: string): number | null {
|
|
@@ -129,7 +134,7 @@ export class FunnelClaude {
|
|
|
129
134
|
}
|
|
130
135
|
|
|
131
136
|
private writePidFile(profileName: string): void {
|
|
132
|
-
this.fs.mkdirSync(
|
|
137
|
+
this.fs.mkdirSync(this.pidDir, { recursive: true })
|
|
133
138
|
this.fs.writeFileSync(this.pidPath(profileName), String(globalThis.process.pid))
|
|
134
139
|
}
|
|
135
140
|
|
|
@@ -140,11 +145,12 @@ export class FunnelClaude {
|
|
|
140
145
|
}
|
|
141
146
|
|
|
142
147
|
private installCleanup(profileName: string): void {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
+
// Default Bun behavior on SIGINT/SIGTERM is process.exit(130/143), which
|
|
149
|
+
// fires the "exit" event. Hooking only "exit" keeps the PID file cleanup
|
|
150
|
+
// running while letting the signal terminate the process normally —
|
|
151
|
+
// adding our own SIGINT handler would suppress the default exit and leave
|
|
152
|
+
// funnel hanging until claude responds.
|
|
153
|
+
globalThis.process.once("exit", () => this.removePidFile(profileName))
|
|
148
154
|
}
|
|
149
155
|
|
|
150
156
|
private isProcessAlive(pid: number): boolean {
|
|
@@ -179,35 +185,14 @@ export class FunnelClaude {
|
|
|
179
185
|
return result
|
|
180
186
|
}
|
|
181
187
|
|
|
182
|
-
private buildEnv(
|
|
183
|
-
const env: Record<string, string> = {
|
|
184
|
-
|
|
185
|
-
if (options.envFiles) {
|
|
186
|
-
for (const file of options.envFiles) {
|
|
187
|
-
const filePath = `${cwd}/${file}`
|
|
188
|
-
|
|
189
|
-
if (!this.fs.existsSync(filePath)) continue
|
|
190
|
-
|
|
191
|
-
const content = this.fs.readFileSync(filePath)
|
|
192
|
-
|
|
193
|
-
for (const line of content.split("\n")) {
|
|
194
|
-
const trimmed = line.trim()
|
|
195
|
-
|
|
196
|
-
if (!trimmed || trimmed.startsWith("#")) continue
|
|
197
|
-
|
|
198
|
-
const eqIndex = trimmed.indexOf("=")
|
|
199
|
-
|
|
200
|
-
if (eqIndex < 0) continue
|
|
201
|
-
|
|
202
|
-
const key = trimmed.slice(0, eqIndex)
|
|
203
|
-
const value = trimmed.slice(eqIndex + 1).replace(/^["']|["']$/g, "")
|
|
188
|
+
private buildEnv(channelId: string): Record<string, string> {
|
|
189
|
+
const env: Record<string, string> = {}
|
|
204
190
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
191
|
+
for (const [key, value] of Object.entries(globalThis.process.env)) {
|
|
192
|
+
if (typeof value === "string") env[key] = value
|
|
208
193
|
}
|
|
209
194
|
|
|
210
|
-
env.FUNNEL_CHANNEL_ID =
|
|
195
|
+
env.FUNNEL_CHANNEL_ID = channelId
|
|
211
196
|
|
|
212
197
|
return env
|
|
213
198
|
}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
export type FileStat = {
|
|
2
2
|
mtimeMs: number
|
|
3
|
+
/** POSIX mode bits (e.g. 0o600). `null` when the underlying FS does not expose mode. */
|
|
4
|
+
mode: number | null
|
|
3
5
|
}
|
|
4
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Filesystem boundary used everywhere funnel reads or writes.
|
|
9
|
+
* Default is NodeFunnelFileSystem (real `node:fs`); MemoryFunnelFileSystem
|
|
10
|
+
* provides a sandbox for tests and embedded use.
|
|
11
|
+
*/
|
|
5
12
|
export abstract class FunnelFileSystem {
|
|
6
13
|
abstract existsSync(path: string): boolean
|
|
7
14
|
abstract readFileSync(path: string): string
|
|
8
15
|
abstract writeFileSync(path: string, data: string): void
|
|
16
|
+
/** Write `data` and ensure the resulting file is owner-only (0600). Use for tokens and any file that may contain secrets. */
|
|
17
|
+
abstract writeSecretFileSync(path: string, data: string): void
|
|
9
18
|
abstract appendFileSync(path: string, data: string): void
|
|
10
19
|
abstract unlink(path: string): void
|
|
11
20
|
abstract mkdirSync(path: string, options?: { recursive?: boolean }): void
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { type FileStat, FunnelFileSystem } from "@/
|
|
1
|
+
import { type FileStat, FunnelFileSystem } from "@/engine/fs/file-system"
|
|
2
2
|
|
|
3
3
|
type Props = {
|
|
4
4
|
dirs?: string[]
|
|
5
5
|
files?: Record<string, string>
|
|
6
6
|
mtimes?: Record<string, number>
|
|
7
|
+
modes?: Record<string, number>
|
|
7
8
|
now?: () => number
|
|
8
9
|
}
|
|
9
10
|
|
|
11
|
+
const SECRET_MODE = 0o600
|
|
12
|
+
|
|
10
13
|
export class MemoryFunnelFileSystem extends FunnelFileSystem {
|
|
11
14
|
private readonly dirs: Set<string>
|
|
12
15
|
private readonly files: Map<string, string>
|
|
13
16
|
private readonly mtimes: Map<string, number>
|
|
17
|
+
private readonly modes: Map<string, number>
|
|
14
18
|
private readonly now: () => number
|
|
15
19
|
|
|
16
20
|
constructor(props: Props = {}) {
|
|
@@ -18,6 +22,7 @@ export class MemoryFunnelFileSystem extends FunnelFileSystem {
|
|
|
18
22
|
this.dirs = new Set(props.dirs ?? [])
|
|
19
23
|
this.files = new Map(Object.entries(props.files ?? {}))
|
|
20
24
|
this.mtimes = new Map(Object.entries(props.mtimes ?? {}))
|
|
25
|
+
this.modes = new Map(Object.entries(props.modes ?? {}))
|
|
21
26
|
this.now = props.now ?? (() => Date.now())
|
|
22
27
|
}
|
|
23
28
|
|
|
@@ -34,6 +39,12 @@ export class MemoryFunnelFileSystem extends FunnelFileSystem {
|
|
|
34
39
|
this.touch(path)
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
writeSecretFileSync(path: string, data: string): void {
|
|
43
|
+
this.files.set(path, data)
|
|
44
|
+
this.modes.set(path, SECRET_MODE)
|
|
45
|
+
this.touch(path)
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
appendFileSync(path: string, data: string): void {
|
|
38
49
|
const prev = this.files.get(path) ?? ""
|
|
39
50
|
this.files.set(path, prev + data)
|
|
@@ -43,9 +54,11 @@ export class MemoryFunnelFileSystem extends FunnelFileSystem {
|
|
|
43
54
|
unlink(path: string): void {
|
|
44
55
|
this.files.delete(path)
|
|
45
56
|
this.mtimes.delete(path)
|
|
57
|
+
this.modes.delete(path)
|
|
46
58
|
}
|
|
47
59
|
|
|
48
|
-
mkdirSync(path: string): void {
|
|
60
|
+
mkdirSync(path: string, options?: { recursive?: boolean }): void {
|
|
61
|
+
void options
|
|
49
62
|
this.dirs.add(path)
|
|
50
63
|
}
|
|
51
64
|
|
|
@@ -71,13 +84,17 @@ export class MemoryFunnelFileSystem extends FunnelFileSystem {
|
|
|
71
84
|
throw new Error(`not found: ${path}`)
|
|
72
85
|
}
|
|
73
86
|
|
|
74
|
-
return { mtimeMs }
|
|
87
|
+
return { mtimeMs, mode: this.modes.get(path) ?? null }
|
|
75
88
|
}
|
|
76
89
|
|
|
77
90
|
setMtime(path: string, mtimeMs: number): void {
|
|
78
91
|
this.mtimes.set(path, mtimeMs)
|
|
79
92
|
}
|
|
80
93
|
|
|
94
|
+
setMode(path: string, mode: number): void {
|
|
95
|
+
this.modes.set(path, mode)
|
|
96
|
+
}
|
|
97
|
+
|
|
81
98
|
private touch(path: string): void {
|
|
82
99
|
if (!this.mtimes.has(path)) this.mtimes.set(path, this.now())
|
|
83
100
|
else this.mtimes.set(path, this.now())
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
appendFileSync,
|
|
3
|
+
chmodSync,
|
|
3
4
|
existsSync,
|
|
4
5
|
mkdirSync,
|
|
5
6
|
readdirSync,
|
|
@@ -8,7 +9,9 @@ import {
|
|
|
8
9
|
unlinkSync,
|
|
9
10
|
writeFileSync,
|
|
10
11
|
} from "node:fs"
|
|
11
|
-
import { type FileStat, FunnelFileSystem } from "@/
|
|
12
|
+
import { type FileStat, FunnelFileSystem } from "@/engine/fs/file-system"
|
|
13
|
+
|
|
14
|
+
const SECRET_MODE = 0o600
|
|
12
15
|
|
|
13
16
|
export class NodeFunnelFileSystem extends FunnelFileSystem {
|
|
14
17
|
constructor() {
|
|
@@ -28,6 +31,15 @@ export class NodeFunnelFileSystem extends FunnelFileSystem {
|
|
|
28
31
|
writeFileSync(path, data)
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
writeSecretFileSync(path: string, data: string): void {
|
|
35
|
+
writeFileSync(path, data, { mode: SECRET_MODE })
|
|
36
|
+
try {
|
|
37
|
+
chmodSync(path, SECRET_MODE)
|
|
38
|
+
} catch {
|
|
39
|
+
// ignore — best-effort tightening for files that already existed with looser perms
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
appendFileSync(path: string, data: string): void {
|
|
32
44
|
appendFileSync(path, data)
|
|
33
45
|
}
|
|
@@ -51,6 +63,6 @@ export class NodeFunnelFileSystem extends FunnelFileSystem {
|
|
|
51
63
|
statSync(path: string): FileStat {
|
|
52
64
|
const stat = statSync(path)
|
|
53
65
|
|
|
54
|
-
return { mtimeMs: stat.mtimeMs }
|
|
66
|
+
return { mtimeMs: stat.mtimeMs, mode: stat.mode & 0o777 }
|
|
55
67
|
}
|
|
56
68
|
}
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FunnelHttpClient,
|
|
3
|
-
type HttpRequest,
|
|
4
|
-
type HttpResponse,
|
|
5
|
-
} from "@/modules/http/funnel-http-client"
|
|
1
|
+
import { FunnelHttpClient, type HttpRequest, type HttpResponse } from "@/engine/http/http-client"
|
|
6
2
|
|
|
7
3
|
export type MemoryHttpResponse = {
|
|
8
4
|
status?: number
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FunnelHttpClient,
|
|
3
|
-
type HttpRequest,
|
|
4
|
-
type HttpResponse,
|
|
5
|
-
} from "@/modules/http/funnel-http-client"
|
|
1
|
+
import { FunnelHttpClient, type HttpRequest, type HttpResponse } from "@/engine/http/http-client"
|
|
6
2
|
|
|
7
3
|
export class NodeFunnelHttpClient extends FunnelHttpClient {
|
|
8
4
|
constructor() {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FunnelIdGenerator } from "@/engine/id/id-generator"
|
|
2
|
+
|
|
3
|
+
type Props = {
|
|
4
|
+
prefix?: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class MemoryFunnelIdGenerator extends FunnelIdGenerator {
|
|
8
|
+
private counter = 0
|
|
9
|
+
private readonly prefix: string
|
|
10
|
+
|
|
11
|
+
constructor(props: Props = {}) {
|
|
12
|
+
super()
|
|
13
|
+
this.prefix = props.prefix ?? "id"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
generate(): string {
|
|
17
|
+
this.counter++
|
|
18
|
+
return `${this.prefix}-${this.counter}`
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured logger with three levels and an optional log-file path.
|
|
3
|
+
* Defaults to NodeFunnelLogger (appends to /tmp/funnel/funnel.log);
|
|
4
|
+
* MemoryFunnelLogger captures entries in memory and NoopFunnelLogger silences output.
|
|
5
|
+
*/
|
|
6
|
+
export abstract class FunnelLogger {
|
|
7
|
+
abstract info(message: string, meta?: Record<string, unknown>): void
|
|
8
|
+
abstract warn(message: string, meta?: Record<string, unknown>): void
|
|
9
|
+
abstract error(message: string, meta?: Record<string, unknown>): void
|
|
10
|
+
abstract readonly file: string | null
|
|
11
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { FunnelLogger } from "@/engine/logger/logger"
|
|
2
|
+
|
|
3
|
+
export type LogEntry = {
|
|
4
|
+
level: "info" | "warn" | "error"
|
|
5
|
+
message: string
|
|
6
|
+
meta?: Record<string, unknown>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class MemoryFunnelLogger extends FunnelLogger {
|
|
10
|
+
readonly file = null
|
|
11
|
+
readonly entries: LogEntry[] = []
|
|
12
|
+
|
|
13
|
+
info(message: string, meta?: Record<string, unknown>): void {
|
|
14
|
+
this.entries.push({ level: "info", message, meta })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
warn(message: string, meta?: Record<string, unknown>): void {
|
|
18
|
+
this.entries.push({ level: "warn", message, meta })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
error(message: string, meta?: Record<string, unknown>): void {
|
|
22
|
+
this.entries.push({ level: "error", message, meta })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
clear(): void {
|
|
26
|
+
this.entries.length = 0
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { appendFileSync, mkdirSync } from "node:fs"
|
|
2
|
+
import { dirname, join } from "node:path"
|
|
3
|
+
import { FunnelLogger } from "@/engine/logger/logger"
|
|
4
|
+
|
|
5
|
+
const DEFAULT_LOG_FILE = join("/tmp/funnel", "funnel.log")
|
|
6
|
+
|
|
7
|
+
type Level = "info" | "warn" | "error"
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
file?: string
|
|
11
|
+
now?: () => Date
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class NodeFunnelLogger extends FunnelLogger {
|
|
15
|
+
readonly file: string
|
|
16
|
+
private readonly now: () => Date
|
|
17
|
+
|
|
18
|
+
constructor(props: Props = {}) {
|
|
19
|
+
super()
|
|
20
|
+
this.file = props.file ?? DEFAULT_LOG_FILE
|
|
21
|
+
this.now = props.now ?? (() => new Date())
|
|
22
|
+
Object.freeze(this)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
info(message: string, meta?: Record<string, unknown>): void {
|
|
26
|
+
this.write("info", message, meta)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
warn(message: string, meta?: Record<string, unknown>): void {
|
|
30
|
+
this.write("warn", message, meta)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
error(message: string, meta?: Record<string, unknown>): void {
|
|
34
|
+
this.write("error", message, meta)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private write(level: Level, message: string, meta?: Record<string, unknown>): void {
|
|
38
|
+
mkdirSync(dirname(this.file), { recursive: true })
|
|
39
|
+
|
|
40
|
+
const entry = {
|
|
41
|
+
time: this.now().toISOString(),
|
|
42
|
+
level,
|
|
43
|
+
message,
|
|
44
|
+
...(meta ? { meta } : {}),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
appendFileSync(this.file, `${JSON.stringify(entry)}\n`)
|
|
48
|
+
}
|
|
49
|
+
}
|