@interactive-inc/claude-funnel 0.10.0 → 0.15.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 +106 -56
- package/dist/bin.js +557 -530
- package/dist/connectors/schedule.d.ts +2 -49
- package/dist/connectors/schedule.js +1 -1
- package/dist/connectors/slack.d.ts +4 -48
- package/dist/connectors/slack.js +1 -1
- package/dist/gateway/daemon.js +213 -211
- package/dist/index.d.ts +465 -173
- package/dist/index.js +692 -154
- package/dist/{schedule-connector-schema-CkuIQ0JQ.js → schedule-connector-schema-FxP7LPlx.js} +11 -0
- package/dist/{file-system-Co60LrmR.d.ts → schedule-listener-BPodvbld.d.ts} +56 -1
- package/dist/{slack-connector-schema-Cd22WiHB.js → slack-connector-schema-B4hsf3AY.js} +10 -1
- package/dist/slack-listener-CHj6uMY-.d.ts +74 -0
- package/package.json +2 -6
- package/schemas/funnel.schema.json +144 -0
- 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,36 +0,0 @@
|
|
|
1
|
-
import { HTTPException } from "hono/http-exception"
|
|
2
|
-
import { z } from "zod"
|
|
3
|
-
import { factory } from "@/cli/factory"
|
|
4
|
-
import { queryToCliArgs } from "@/cli/router/query-to-cli-args"
|
|
5
|
-
import { zValidator } from "@/cli/router/validator"
|
|
6
|
-
|
|
7
|
-
export const launchHelp = `funnel profiles <name> run — launch a profile (sugar for fnl claude)
|
|
8
|
-
|
|
9
|
-
usage: funnel profiles <name> run [additional claude args...]
|
|
10
|
-
funnel profiles <name> (alias)`
|
|
11
|
-
|
|
12
|
-
const RESERVED_KEYS: string[] = []
|
|
13
|
-
|
|
14
|
-
export const profilesLaunchHandler = factory.createHandlers(
|
|
15
|
-
zValidator("param", z.object({ profile: z.string() })),
|
|
16
|
-
zValidator("query", z.object({}).passthrough(), launchHelp),
|
|
17
|
-
async (c) => {
|
|
18
|
-
const param = c.req.valid("param")
|
|
19
|
-
const funnel = c.var.funnel
|
|
20
|
-
const profile = funnel.profiles.get(param.profile)
|
|
21
|
-
|
|
22
|
-
if (!profile) {
|
|
23
|
-
throw new HTTPException(404, { message: `profile "${param.profile}" not found` })
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const exitCode = await funnel.claude.launch({
|
|
27
|
-
channel: profile.channelId,
|
|
28
|
-
cwd: profile.path,
|
|
29
|
-
subAgent: profile.subAgent,
|
|
30
|
-
userArgs: queryToCliArgs(c.req.url, RESERVED_KEYS),
|
|
31
|
-
profileName: profile.name,
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
process.exit(exitCode)
|
|
35
|
-
},
|
|
36
|
-
)
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { HTTPException } from "hono/http-exception"
|
|
2
|
-
import { z } from "zod"
|
|
3
|
-
import { factory } from "@/cli/factory"
|
|
4
|
-
import { zValidator } from "@/cli/router/validator"
|
|
5
|
-
|
|
6
|
-
export const addHelp = `funnel profiles add — add a profile
|
|
7
|
-
|
|
8
|
-
usage: funnel profiles add <name> --path <path> --sub-agent <agent> --channel <channel-name> [--brief]
|
|
9
|
-
|
|
10
|
-
options:
|
|
11
|
-
--path working directory passed to claude as cwd
|
|
12
|
-
--sub-agent sub-agent name passed to claude --agent
|
|
13
|
-
--channel channel name (resolved to channel id internally)
|
|
14
|
-
--brief forward --brief to claude on launch (enables SendUserMessage tool)`
|
|
15
|
-
|
|
16
|
-
export const profilesAddHandler = factory.createHandlers(
|
|
17
|
-
zValidator("param", z.object({ profile: z.string() })),
|
|
18
|
-
zValidator(
|
|
19
|
-
"query",
|
|
20
|
-
z.object({
|
|
21
|
-
path: z.string(),
|
|
22
|
-
"sub-agent": z.string(),
|
|
23
|
-
channel: z.string(),
|
|
24
|
-
brief: z.coerce.boolean().optional(),
|
|
25
|
-
}),
|
|
26
|
-
addHelp,
|
|
27
|
-
),
|
|
28
|
-
(c) => {
|
|
29
|
-
const param = c.req.valid("param")
|
|
30
|
-
const query = c.req.valid("query")
|
|
31
|
-
const funnel = c.var.funnel
|
|
32
|
-
|
|
33
|
-
const channel = funnel.channels.get(query.channel)
|
|
34
|
-
|
|
35
|
-
if (!channel) {
|
|
36
|
-
throw new HTTPException(400, { message: `channel "${query.channel}" not found` })
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
funnel.profiles.add({
|
|
40
|
-
name: param.profile,
|
|
41
|
-
path: query.path,
|
|
42
|
-
subAgent: query["sub-agent"],
|
|
43
|
-
channelId: channel.id,
|
|
44
|
-
...(query.brief !== undefined ? { brief: query.brief } : {}),
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
return c.text(`added profile "${param.profile}"`)
|
|
48
|
-
},
|
|
49
|
-
)
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { z } from "zod"
|
|
2
|
-
import { factory } from "@/cli/factory"
|
|
3
|
-
import { zValidator } from "@/cli/router/validator"
|
|
4
|
-
|
|
5
|
-
export const removeHelp = `funnel profiles remove — remove a profile
|
|
6
|
-
|
|
7
|
-
usage: funnel profiles remove <name>`
|
|
8
|
-
|
|
9
|
-
export const profilesRemoveHandler = factory.createHandlers(
|
|
10
|
-
zValidator("param", z.object({ profile: z.string() })),
|
|
11
|
-
zValidator("query", z.object({}), removeHelp),
|
|
12
|
-
(c) => {
|
|
13
|
-
const param = c.req.valid("param")
|
|
14
|
-
const funnel = c.var.funnel
|
|
15
|
-
|
|
16
|
-
funnel.profiles.remove(param.profile)
|
|
17
|
-
|
|
18
|
-
return c.text(`removed profile "${param.profile}"`)
|
|
19
|
-
},
|
|
20
|
-
)
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { HTTPException } from "hono/http-exception"
|
|
2
|
-
import { z } from "zod"
|
|
3
|
-
import { factory } from "@/cli/factory"
|
|
4
|
-
import { zValidator } from "@/cli/router/validator"
|
|
5
|
-
|
|
6
|
-
export const setHelp = `funnel profiles <name> set — update a profile
|
|
7
|
-
|
|
8
|
-
usage: funnel profiles <name> set [--path <path>] [--sub-agent <agent>] [--channel <channel-name>] [--brief | --no-brief]`
|
|
9
|
-
|
|
10
|
-
export const profilesSetHandler = factory.createHandlers(
|
|
11
|
-
zValidator("param", z.object({ profile: z.string() })),
|
|
12
|
-
zValidator(
|
|
13
|
-
"query",
|
|
14
|
-
z.object({
|
|
15
|
-
path: z.string().optional(),
|
|
16
|
-
"sub-agent": z.string().optional(),
|
|
17
|
-
channel: z.string().optional(),
|
|
18
|
-
brief: z.coerce.boolean().optional(),
|
|
19
|
-
"no-brief": z.coerce.boolean().optional(),
|
|
20
|
-
}),
|
|
21
|
-
setHelp,
|
|
22
|
-
),
|
|
23
|
-
(c) => {
|
|
24
|
-
const param = c.req.valid("param")
|
|
25
|
-
const query = c.req.valid("query")
|
|
26
|
-
const funnel = c.var.funnel
|
|
27
|
-
|
|
28
|
-
const channel = query.channel !== undefined ? funnel.channels.get(query.channel) : null
|
|
29
|
-
|
|
30
|
-
if (query.channel !== undefined && !channel) {
|
|
31
|
-
throw new HTTPException(400, { message: `channel "${query.channel}" not found` })
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const brief = query["no-brief"] ? false : query.brief
|
|
35
|
-
|
|
36
|
-
funnel.profiles.update(param.profile, {
|
|
37
|
-
path: query.path,
|
|
38
|
-
subAgent: query["sub-agent"],
|
|
39
|
-
channelId: channel?.id,
|
|
40
|
-
...(brief !== undefined ? { brief } : {}),
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
return c.text(`updated profile "${param.profile}"`)
|
|
44
|
-
},
|
|
45
|
-
)
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { z } from "zod"
|
|
2
|
-
import { factory } from "@/cli/factory"
|
|
3
|
-
import { zValidator } from "@/cli/router/validator"
|
|
4
|
-
|
|
5
|
-
export const groupHelp = `funnel profiles — manage launch profiles
|
|
6
|
-
|
|
7
|
-
usage: funnel profiles [subcommand]
|
|
8
|
-
|
|
9
|
-
subcommands:
|
|
10
|
-
(none) list (first entry is the default)
|
|
11
|
-
add <name> --path <path> --sub-agent <agent> --channel <channel>
|
|
12
|
-
<name> set [--path ...] [--sub-agent ...] [--channel ...]
|
|
13
|
-
<name> as-default move profile to the front (becomes default)
|
|
14
|
-
rename <old> <new> rename
|
|
15
|
-
remove <name> remove
|
|
16
|
-
<name> run launch (sugar for fnl claude -p <name>)
|
|
17
|
-
<name> launch (alias for run)
|
|
18
|
-
|
|
19
|
-
examples:
|
|
20
|
-
funnel profiles add cto --path /repo/myapp --sub-agent cto --channel prod-inbox
|
|
21
|
-
funnel profiles cto as-default
|
|
22
|
-
funnel profiles cto run`
|
|
23
|
-
|
|
24
|
-
export const profilesGroupHandler = factory.createHandlers(
|
|
25
|
-
zValidator("query", z.object({}), groupHelp),
|
|
26
|
-
(c) => {
|
|
27
|
-
const funnel = c.var.funnel
|
|
28
|
-
const profiles = funnel.profiles.list()
|
|
29
|
-
|
|
30
|
-
if (profiles.length === 0) return c.text("no profiles")
|
|
31
|
-
|
|
32
|
-
const lines = profiles.map((profile, index) => {
|
|
33
|
-
const tag = index === 0 ? " (default)" : ""
|
|
34
|
-
|
|
35
|
-
return `${profile.name}${tag} [path=${profile.path}, sub-agent=${profile.subAgent}, channel=${profile.channelId}]`
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
return c.text(lines.join("\n"))
|
|
39
|
-
},
|
|
40
|
-
)
|
package/lib/cli/routes/status.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { z } from "zod"
|
|
2
|
-
import { factory } from "@/cli/factory"
|
|
3
|
-
import { zValidator } from "@/cli/router/validator"
|
|
4
|
-
|
|
5
|
-
export const statusHelp = `funnel status — show overall connection status
|
|
6
|
-
|
|
7
|
-
usage: funnel status
|
|
8
|
-
|
|
9
|
-
Lists configured connectors / channels / profiles, gateway running status,
|
|
10
|
-
and active MCP WebSocket clients.`
|
|
11
|
-
|
|
12
|
-
type GatewayClient = { channel: string; connectors: string[] }
|
|
13
|
-
|
|
14
|
-
type GatewayStatus = {
|
|
15
|
-
ok: boolean
|
|
16
|
-
clients: GatewayClient[]
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const isGatewayStatus = (value: unknown): value is GatewayStatus => {
|
|
20
|
-
if (value === null || typeof value !== "object") return false
|
|
21
|
-
if (!("clients" in value) || !Array.isArray(value.clients)) return false
|
|
22
|
-
|
|
23
|
-
return value.clients.every(
|
|
24
|
-
(client: unknown) =>
|
|
25
|
-
typeof client === "object" &&
|
|
26
|
-
client !== null &&
|
|
27
|
-
"channel" in client &&
|
|
28
|
-
typeof client.channel === "string" &&
|
|
29
|
-
"connectors" in client &&
|
|
30
|
-
Array.isArray(client.connectors),
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export const statusHandler = factory.createHandlers(
|
|
35
|
-
zValidator("query", z.object({}), statusHelp),
|
|
36
|
-
async (c) => {
|
|
37
|
-
const funnel = c.var.funnel
|
|
38
|
-
const channels = funnel.channels.list()
|
|
39
|
-
const profiles = funnel.profiles.list()
|
|
40
|
-
const gatewayStatus = funnel.gateway.getStatus()
|
|
41
|
-
|
|
42
|
-
const lines: string[] = []
|
|
43
|
-
|
|
44
|
-
lines.push("= funnel status =")
|
|
45
|
-
lines.push("")
|
|
46
|
-
|
|
47
|
-
lines.push(`channels: ${channels.length}`)
|
|
48
|
-
for (const ch of channels) {
|
|
49
|
-
const attached =
|
|
50
|
-
ch.connectors.length > 0
|
|
51
|
-
? ch.connectors.map((c) => `${c.name}:${c.type}`).join(", ")
|
|
52
|
-
: "(none)"
|
|
53
|
-
lines.push(` - ${ch.name} [${attached}]`)
|
|
54
|
-
}
|
|
55
|
-
lines.push("")
|
|
56
|
-
|
|
57
|
-
lines.push(`profiles: ${profiles.length}`)
|
|
58
|
-
for (const [index, profile] of profiles.entries()) {
|
|
59
|
-
const tag = index === 0 ? " (default)" : ""
|
|
60
|
-
const channel = funnel.channels.getById(profile.channelId)
|
|
61
|
-
const channelLabel = channel ? channel.name : `id:${profile.channelId}`
|
|
62
|
-
|
|
63
|
-
lines.push(
|
|
64
|
-
` - ${profile.name}${tag} [path=${profile.path}, sub-agent=${profile.subAgent}, channel=${channelLabel}]`,
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
lines.push("")
|
|
68
|
-
|
|
69
|
-
if (!gatewayStatus.running) {
|
|
70
|
-
lines.push("gateway: not running")
|
|
71
|
-
} else {
|
|
72
|
-
lines.push(`gateway: running (pid ${gatewayStatus.pid}, port ${gatewayStatus.port})`)
|
|
73
|
-
|
|
74
|
-
const res = await fetch(`http://localhost:${gatewayStatus.port}/status`).catch(() => null)
|
|
75
|
-
|
|
76
|
-
if (res && res.ok) {
|
|
77
|
-
const body: unknown = await res.json()
|
|
78
|
-
|
|
79
|
-
if (isGatewayStatus(body)) {
|
|
80
|
-
lines.push(` clients: ${body.clients.length}`)
|
|
81
|
-
|
|
82
|
-
for (const client of body.clients) {
|
|
83
|
-
const connectorList =
|
|
84
|
-
client.connectors.length > 0 ? client.connectors.join(", ") : "(none)"
|
|
85
|
-
lines.push(` - channel=${client.channel || "(unset)"} [${connectorList}]`)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return c.text(lines.join("\n"))
|
|
92
|
-
},
|
|
93
|
-
)
|
package/lib/cli/routes/update.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { HTTPException } from "hono/http-exception"
|
|
2
|
-
import { z } from "zod"
|
|
3
|
-
import { factory } from "@/cli/factory"
|
|
4
|
-
import { NodeFunnelProcessRunner } from "@/engine/process/node-process-runner"
|
|
5
|
-
import { zValidator } from "@/cli/router/validator"
|
|
6
|
-
|
|
7
|
-
export const updateHelp = `funnel update — update funnel to the latest version
|
|
8
|
-
|
|
9
|
-
usage: funnel update
|
|
10
|
-
|
|
11
|
-
Runs "bun i -g @interactive-inc/claude-funnel".`
|
|
12
|
-
|
|
13
|
-
const PACKAGE = "@interactive-inc/claude-funnel"
|
|
14
|
-
|
|
15
|
-
export const updateHandler = factory.createHandlers(
|
|
16
|
-
zValidator("query", z.object({}), updateHelp),
|
|
17
|
-
async (c) => {
|
|
18
|
-
const runner = new NodeFunnelProcessRunner()
|
|
19
|
-
const exitCode = await runner.attach(["bun", "i", "-g", PACKAGE])
|
|
20
|
-
|
|
21
|
-
if (exitCode !== 0) {
|
|
22
|
-
throw new HTTPException(500, { message: `update failed (exit ${exitCode})` })
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return c.text(`updated ${PACKAGE}`)
|
|
26
|
-
},
|
|
27
|
-
)
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { z } from "zod"
|
|
2
|
-
import { discordConnectorSchema } from "@/connectors/discord-connector-schema"
|
|
3
|
-
import { ghConnectorSchema } from "@/connectors/gh-connector-schema"
|
|
4
|
-
import { scheduleConnectorSchema } from "@/connectors/schedule-connector-schema"
|
|
5
|
-
import { slackConnectorSchema } from "@/connectors/slack-connector-schema"
|
|
6
|
-
|
|
7
|
-
export const connectorConfigSchema = z.discriminatedUnion("type", [
|
|
8
|
-
slackConnectorSchema,
|
|
9
|
-
ghConnectorSchema,
|
|
10
|
-
discordConnectorSchema,
|
|
11
|
-
scheduleConnectorSchema,
|
|
12
|
-
])
|
|
13
|
-
|
|
14
|
-
export type ConnectorConfig = z.infer<typeof connectorConfigSchema>
|
|
15
|
-
|
|
16
|
-
export type ConnectorType = ConnectorConfig["type"]
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import type { FunnelConnectorAdapter } from "@/connectors/connector-adapter"
|
|
2
|
-
import type { ConnectorConfig } from "@/connectors/connector-config-schema"
|
|
3
|
-
import type { FunnelConnectorListener } from "@/connectors/connector-listener"
|
|
4
|
-
import { FunnelDiscordAdapter } from "@/connectors/discord-adapter"
|
|
5
|
-
import { FunnelDiscordListener } from "@/connectors/discord-listener"
|
|
6
|
-
import { FunnelGhAdapter } from "@/connectors/gh-adapter"
|
|
7
|
-
import { FunnelGhListener } from "@/connectors/gh-listener"
|
|
8
|
-
import { FunnelScheduleListener } from "@/connectors/schedule-listener"
|
|
9
|
-
import { ScheduleStateStore } from "@/connectors/schedule-state-store"
|
|
10
|
-
import { FunnelSlackAdapter } from "@/connectors/slack-adapter"
|
|
11
|
-
import { FunnelSlackListener } from "@/connectors/slack-listener"
|
|
12
|
-
import { FunnelFileSystem } from "@/engine/fs/file-system"
|
|
13
|
-
import { NodeFunnelFileSystem } from "@/engine/fs/node-file-system"
|
|
14
|
-
import { FunnelLogger } from "@/engine/logger/logger"
|
|
15
|
-
import { NodeFunnelLogger } from "@/engine/logger/node-logger"
|
|
16
|
-
import { FunnelProcessRunner } from "@/engine/process/process-runner"
|
|
17
|
-
import { NodeFunnelProcessRunner } from "@/engine/process/node-process-runner"
|
|
18
|
-
import { FUNNEL_DIR } from "@/engine/settings/settings-store"
|
|
19
|
-
import { join } from "node:path"
|
|
20
|
-
|
|
21
|
-
type Deps = {
|
|
22
|
-
fs?: FunnelFileSystem
|
|
23
|
-
process?: FunnelProcessRunner
|
|
24
|
-
logger?: FunnelLogger
|
|
25
|
-
dir?: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const defaultFs = new NodeFunnelFileSystem()
|
|
29
|
-
const defaultProcess = new NodeFunnelProcessRunner()
|
|
30
|
-
const defaultLogger = new NodeFunnelLogger()
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Pure factory for per-type listeners and adapters. The factory has no CRUD
|
|
34
|
-
* responsibility — connector configs live inside settings.json under their
|
|
35
|
-
* channel, and FunnelChannels passes them in by value.
|
|
36
|
-
*
|
|
37
|
-
* `dir` is the funnel home (defaults to ~/.funnel); per-connector state files
|
|
38
|
-
* land at `<dir>/channels/<channel-id>/connectors/<connector-id>/state.json`.
|
|
39
|
-
*/
|
|
40
|
-
export class FunnelConnectorFactory {
|
|
41
|
-
private readonly fs: FunnelFileSystem
|
|
42
|
-
private readonly process: FunnelProcessRunner
|
|
43
|
-
private readonly logger: FunnelLogger
|
|
44
|
-
private readonly dir: string
|
|
45
|
-
|
|
46
|
-
constructor(deps: Deps = {}) {
|
|
47
|
-
this.fs = deps.fs ?? defaultFs
|
|
48
|
-
this.process = deps.process ?? defaultProcess
|
|
49
|
-
this.logger = deps.logger ?? defaultLogger
|
|
50
|
-
this.dir = deps.dir ?? FUNNEL_DIR
|
|
51
|
-
Object.freeze(this)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
createListener(channelId: string, config: ConnectorConfig): FunnelConnectorListener {
|
|
55
|
-
if (config.type === "slack") {
|
|
56
|
-
return new FunnelSlackListener({ config, logger: this.logger })
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (config.type === "gh") {
|
|
60
|
-
return new FunnelGhListener({ config, process: this.process, logger: this.logger })
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (config.type === "discord") {
|
|
64
|
-
return new FunnelDiscordListener({ config, logger: this.logger })
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const lastFiredStore = new ScheduleStateStore({
|
|
68
|
-
path: join(this.connectorDir(channelId, config.id), "state.json"),
|
|
69
|
-
fs: this.fs,
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
return new FunnelScheduleListener({
|
|
73
|
-
config,
|
|
74
|
-
lastFiredStore,
|
|
75
|
-
logger: this.logger,
|
|
76
|
-
})
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
createAdapter(config: ConnectorConfig): FunnelConnectorAdapter | null {
|
|
80
|
-
if (config.type === "slack") return new FunnelSlackAdapter({ config })
|
|
81
|
-
if (config.type === "gh") return new FunnelGhAdapter({ process: this.process })
|
|
82
|
-
if (config.type === "discord") return new FunnelDiscordAdapter({ config })
|
|
83
|
-
|
|
84
|
-
return null
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
connectorDir(channelId: string, connectorId: string): string {
|
|
88
|
-
return join(this.dir, "channels", channelId, "connectors", connectorId)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
channelDir(channelId: string): string {
|
|
92
|
-
return join(this.dir, "channels", channelId)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export type NotifyFn = (content: string, meta?: Record<string, string>) => Promise<void>
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Long-lived event source for one connector.
|
|
5
|
-
*
|
|
6
|
-
* `start()` opens the underlying connection (Slack Socket Mode, Discord
|
|
7
|
-
* Gateway, GH polling, schedule tick) and pushes events through `notify`.
|
|
8
|
-
* `stop()` releases the resources so the supervisor can recreate the listener
|
|
9
|
-
* with new config without restarting the whole gateway. `isAlive()` lets the
|
|
10
|
-
* supervisor periodically health-check and auto-restart dead listeners; the
|
|
11
|
-
* default optimistic implementation is fine for poll/tick-based listeners
|
|
12
|
-
* that self-heal.
|
|
13
|
-
*/
|
|
14
|
-
export abstract class FunnelConnectorListener {
|
|
15
|
-
abstract start(notify: NotifyFn): Promise<void>
|
|
16
|
-
abstract stop(): Promise<void>
|
|
17
|
-
isAlive(): boolean {
|
|
18
|
-
return true
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { FunnelConnectorAdapter, type CallInput } from "@/connectors/connector-adapter"
|
|
2
|
-
import { FunnelHttpClient } from "@/engine/http/http-client"
|
|
3
|
-
import { NodeFunnelHttpClient } from "@/engine/http/node-http-client"
|
|
4
|
-
import type { DiscordConnectorConfig } from "@/connectors/discord-connector-schema"
|
|
5
|
-
|
|
6
|
-
const DISCORD_API_BASE = "https://discord.com/api/v10"
|
|
7
|
-
|
|
8
|
-
type Deps = {
|
|
9
|
-
config: DiscordConnectorConfig
|
|
10
|
-
http?: FunnelHttpClient
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const defaultHttp = new NodeFunnelHttpClient()
|
|
14
|
-
|
|
15
|
-
export class FunnelDiscordAdapter extends FunnelConnectorAdapter {
|
|
16
|
-
private readonly token: string
|
|
17
|
-
private readonly http: FunnelHttpClient
|
|
18
|
-
|
|
19
|
-
constructor(deps: Deps) {
|
|
20
|
-
super()
|
|
21
|
-
this.token = deps.config.botToken
|
|
22
|
-
this.http = deps.http ?? defaultHttp
|
|
23
|
-
Object.freeze(this)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async call(input: CallInput): Promise<unknown> {
|
|
27
|
-
const method = (input.method || "GET").toUpperCase()
|
|
28
|
-
const path = input.path.startsWith("/") ? input.path : `/${input.path}`
|
|
29
|
-
const body = input.body
|
|
30
|
-
const hasBody =
|
|
31
|
-
body !== null && typeof body === "object" && method !== "GET" && Object.keys(body).length > 0
|
|
32
|
-
|
|
33
|
-
const res = await this.http.fetch({
|
|
34
|
-
method,
|
|
35
|
-
url: `${DISCORD_API_BASE}${path}`,
|
|
36
|
-
headers: {
|
|
37
|
-
Authorization: `Bot ${this.token}`,
|
|
38
|
-
"Content-Type": "application/json",
|
|
39
|
-
},
|
|
40
|
-
body: hasBody ? JSON.stringify(input.body) : undefined,
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
if (!res.ok) {
|
|
44
|
-
throw new Error(`Discord API failed (${res.status}): ${await res.text()}`)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (res.status === 204) return null
|
|
48
|
-
|
|
49
|
-
return await res.json()
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { z } from "zod"
|
|
2
|
-
|
|
3
|
-
export const discordConnectorSchema = z.object({
|
|
4
|
-
id: z.string(),
|
|
5
|
-
name: z.string(),
|
|
6
|
-
type: z.literal("discord"),
|
|
7
|
-
botToken: z.string().min(10),
|
|
8
|
-
createdAt: z.string().datetime().optional(),
|
|
9
|
-
updatedAt: z.string().datetime().optional(),
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
export type DiscordConnectorConfig = z.infer<typeof discordConnectorSchema>
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
export type DiscordInboundMessage = {
|
|
2
|
-
authorId: string
|
|
3
|
-
authorIsBot: boolean
|
|
4
|
-
channelId: string
|
|
5
|
-
guildId: string | null
|
|
6
|
-
mentionedUserIds: string[]
|
|
7
|
-
raw: unknown
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type DiscordProcessedSkip = { skip: true }
|
|
11
|
-
|
|
12
|
-
export type DiscordProcessedEmit = {
|
|
13
|
-
skip: false
|
|
14
|
-
content: string
|
|
15
|
-
meta: Record<string, string>
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type DiscordProcessed = DiscordProcessedSkip | DiscordProcessedEmit
|
|
19
|
-
|
|
20
|
-
type Props = {
|
|
21
|
-
ownUserId: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class FunnelDiscordEventProcessor {
|
|
25
|
-
private readonly ownUserId: string
|
|
26
|
-
|
|
27
|
-
constructor(props: Props) {
|
|
28
|
-
this.ownUserId = props.ownUserId
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
process(message: DiscordInboundMessage): DiscordProcessed {
|
|
32
|
-
if (message.authorIsBot) return { skip: true }
|
|
33
|
-
|
|
34
|
-
const mentioned = this.ownUserId ? message.mentionedUserIds.includes(this.ownUserId) : false
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
skip: false,
|
|
38
|
-
content: JSON.stringify(message.raw),
|
|
39
|
-
meta: {
|
|
40
|
-
event_type: "discord",
|
|
41
|
-
channel_id: message.channelId,
|
|
42
|
-
user_id: message.authorId,
|
|
43
|
-
mentioned: String(mentioned),
|
|
44
|
-
guild_id: message.guildId ?? "",
|
|
45
|
-
},
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { Client, GatewayIntentBits, Partials } from "discord.js"
|
|
2
|
-
import { FunnelConnectorListener, type NotifyFn } from "@/connectors/connector-listener"
|
|
3
|
-
import { FunnelDiscordEventProcessor } from "@/connectors/discord-event-processor"
|
|
4
|
-
import { FunnelLogger } from "@/engine/logger/logger"
|
|
5
|
-
import { NodeFunnelLogger } from "@/engine/logger/node-logger"
|
|
6
|
-
import type { DiscordConnectorConfig } from "@/connectors/discord-connector-schema"
|
|
7
|
-
|
|
8
|
-
type Deps = {
|
|
9
|
-
config: DiscordConnectorConfig
|
|
10
|
-
logger?: FunnelLogger
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const defaultLogger = new NodeFunnelLogger()
|
|
14
|
-
|
|
15
|
-
export class FunnelDiscordListener extends FunnelConnectorListener {
|
|
16
|
-
private readonly config: DiscordConnectorConfig
|
|
17
|
-
private readonly logger: FunnelLogger
|
|
18
|
-
private client: Client | null = null
|
|
19
|
-
|
|
20
|
-
constructor(deps: Deps) {
|
|
21
|
-
super()
|
|
22
|
-
this.config = deps.config
|
|
23
|
-
this.logger = deps.logger ?? defaultLogger
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async start(notify: NotifyFn): Promise<void> {
|
|
27
|
-
const client = new Client({
|
|
28
|
-
intents: [
|
|
29
|
-
GatewayIntentBits.Guilds,
|
|
30
|
-
GatewayIntentBits.GuildMessages,
|
|
31
|
-
GatewayIntentBits.MessageContent,
|
|
32
|
-
GatewayIntentBits.DirectMessages,
|
|
33
|
-
],
|
|
34
|
-
partials: [Partials.Channel],
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
client.on("messageCreate", async (message) => {
|
|
38
|
-
const ownUserId = client.user?.id ?? ""
|
|
39
|
-
const mentionedUserIds = [...message.mentions.users.keys()]
|
|
40
|
-
|
|
41
|
-
this.logger.info("discord messageCreate", {
|
|
42
|
-
author: message.author.id,
|
|
43
|
-
authorIsBot: String(message.author.bot),
|
|
44
|
-
channelId: message.channelId,
|
|
45
|
-
guildId: message.guildId ?? "",
|
|
46
|
-
mentions: mentionedUserIds.join(","),
|
|
47
|
-
ownUserId,
|
|
48
|
-
mentioned: String(mentionedUserIds.includes(ownUserId)),
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
const processor = new FunnelDiscordEventProcessor({ ownUserId })
|
|
52
|
-
|
|
53
|
-
const result = processor.process({
|
|
54
|
-
authorId: message.author.id,
|
|
55
|
-
authorIsBot: message.author.bot,
|
|
56
|
-
channelId: message.channelId,
|
|
57
|
-
guildId: message.guildId,
|
|
58
|
-
mentionedUserIds,
|
|
59
|
-
raw: message.toJSON(),
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
if (result.skip) {
|
|
63
|
-
this.logger.info("discord skip", { reason: "bot author" })
|
|
64
|
-
return
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
await notify(result.content, result.meta)
|
|
69
|
-
} catch (error) {
|
|
70
|
-
this.logger.error("discord notify error", {
|
|
71
|
-
error: error instanceof Error ? error.message : String(error),
|
|
72
|
-
})
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
client.on("ready", (readyClient) => {
|
|
77
|
-
this.logger.info("discord ready", {
|
|
78
|
-
userId: readyClient.user.id,
|
|
79
|
-
tag: readyClient.user.tag,
|
|
80
|
-
guilds: String(readyClient.guilds.cache.size),
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
client.on("error", (error) => {
|
|
85
|
-
this.logger.error("discord client error", {
|
|
86
|
-
error: error instanceof Error ? error.message : String(error),
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
await client.login(this.config.botToken)
|
|
91
|
-
this.client = client
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async stop(): Promise<void> {
|
|
95
|
-
if (!this.client) return
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
await this.client.destroy()
|
|
99
|
-
} catch (error) {
|
|
100
|
-
this.logger.error("discord stop error", {
|
|
101
|
-
error: error instanceof Error ? error.message : String(error),
|
|
102
|
-
})
|
|
103
|
-
} finally {
|
|
104
|
-
this.client = null
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
override isAlive(): boolean {
|
|
109
|
-
return this.client !== null
|
|
110
|
-
}
|
|
111
|
-
}
|