@interactive-inc/claude-funnel 0.2.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.
Files changed (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/lib/factory.ts +10 -0
  4. package/lib/funnel.ts +51 -0
  5. package/lib/index.ts +86 -0
  6. package/lib/modules/agents/funnel-agents.ts +105 -0
  7. package/lib/modules/channels/funnel-channels.ts +113 -0
  8. package/lib/modules/claude/funnel-claude.ts +136 -0
  9. package/lib/modules/connectors/funnel-connector-adapter.ts +9 -0
  10. package/lib/modules/connectors/funnel-connector-listener.ts +5 -0
  11. package/lib/modules/connectors/funnel-connectors.ts +124 -0
  12. package/lib/modules/connectors/funnel-discord-adapter.ts +56 -0
  13. package/lib/modules/connectors/funnel-discord-event-processor.ts +48 -0
  14. package/lib/modules/connectors/funnel-discord-listener.ts +65 -0
  15. package/lib/modules/connectors/funnel-gh-adapter.ts +51 -0
  16. package/lib/modules/connectors/funnel-gh-listener.ts +102 -0
  17. package/lib/modules/connectors/funnel-slack-adapter.ts +31 -0
  18. package/lib/modules/connectors/funnel-slack-event-processor.ts +91 -0
  19. package/lib/modules/connectors/funnel-slack-listener.ts +72 -0
  20. package/lib/modules/connectors/resolve-listener.ts +13 -0
  21. package/lib/modules/fs/funnel-file-system.ts +14 -0
  22. package/lib/modules/fs/memory-funnel-file-system.ts +85 -0
  23. package/lib/modules/fs/node-funnel-file-system.ts +56 -0
  24. package/lib/modules/gateway/daemon.ts +190 -0
  25. package/lib/modules/gateway/funnel-broadcaster.ts +37 -0
  26. package/lib/modules/gateway/funnel-event-logger.ts +59 -0
  27. package/lib/modules/gateway/funnel-gateway.ts +166 -0
  28. package/lib/modules/gateway/kill-competing-slack-gateways.ts +52 -0
  29. package/lib/modules/http/funnel-http-client.ts +17 -0
  30. package/lib/modules/http/memory-funnel-http-client.ts +40 -0
  31. package/lib/modules/http/node-funnel-http-client.ts +27 -0
  32. package/lib/modules/logger.ts +26 -0
  33. package/lib/modules/mcp/channel-server.ts +77 -0
  34. package/lib/modules/mcp/funnel-mcp.ts +107 -0
  35. package/lib/modules/process/funnel-process-runner.ts +28 -0
  36. package/lib/modules/process/memory-funnel-process-runner.ts +88 -0
  37. package/lib/modules/process/node-funnel-process-runner.ts +100 -0
  38. package/lib/modules/repos/funnel-repositories.ts +107 -0
  39. package/lib/modules/router/query-to-cli-args.ts +20 -0
  40. package/lib/modules/router/to-request.ts +122 -0
  41. package/lib/modules/router/validator.ts +27 -0
  42. package/lib/modules/settings/funnel-settings-reader.ts +6 -0
  43. package/lib/modules/settings/funnel-settings-store.ts +57 -0
  44. package/lib/modules/settings/mock-funnel-settings-reader.ts +27 -0
  45. package/lib/modules/settings/settings-schema.ts +67 -0
  46. package/lib/modules/tui/app.tsx +44 -0
  47. package/lib/modules/tui/tui.tsx +13 -0
  48. package/lib/routes/agents/add.help.ts +3 -0
  49. package/lib/routes/agents/add.ts +33 -0
  50. package/lib/routes/agents/group.help.ts +13 -0
  51. package/lib/routes/agents/group.ts +25 -0
  52. package/lib/routes/agents/launch.help.ts +3 -0
  53. package/lib/routes/agents/launch.ts +35 -0
  54. package/lib/routes/agents/remove.help.ts +3 -0
  55. package/lib/routes/agents/remove.ts +17 -0
  56. package/lib/routes/agents/rename.help.ts +5 -0
  57. package/lib/routes/agents/rename.ts +17 -0
  58. package/lib/routes/agents/routes.ts +17 -0
  59. package/lib/routes/agents/set.help.ts +5 -0
  60. package/lib/routes/agents/set.ts +32 -0
  61. package/lib/routes/channels/add.help.ts +3 -0
  62. package/lib/routes/channels/add.ts +21 -0
  63. package/lib/routes/channels/connectors-attach.help.ts +3 -0
  64. package/lib/routes/channels/connectors-attach.ts +17 -0
  65. package/lib/routes/channels/connectors-detach.help.ts +3 -0
  66. package/lib/routes/channels/connectors-detach.ts +17 -0
  67. package/lib/routes/channels/group.help.ts +16 -0
  68. package/lib/routes/channels/group.ts +22 -0
  69. package/lib/routes/channels/remove.help.ts +3 -0
  70. package/lib/routes/channels/remove.ts +17 -0
  71. package/lib/routes/channels/rename.help.ts +5 -0
  72. package/lib/routes/channels/rename.ts +17 -0
  73. package/lib/routes/channels/routes.ts +19 -0
  74. package/lib/routes/channels/show.help.ts +1 -0
  75. package/lib/routes/channels/show.ts +26 -0
  76. package/lib/routes/claude/claude.help.ts +11 -0
  77. package/lib/routes/claude/claude.ts +39 -0
  78. package/lib/routes/claude/routes.ts +4 -0
  79. package/lib/routes/connectors/add.help.ts +22 -0
  80. package/lib/routes/connectors/add.ts +55 -0
  81. package/lib/routes/connectors/call.help.ts +17 -0
  82. package/lib/routes/connectors/call.ts +43 -0
  83. package/lib/routes/connectors/group.help.ts +14 -0
  84. package/lib/routes/connectors/group.ts +18 -0
  85. package/lib/routes/connectors/remove.help.ts +3 -0
  86. package/lib/routes/connectors/remove.ts +17 -0
  87. package/lib/routes/connectors/rename.help.ts +5 -0
  88. package/lib/routes/connectors/rename.ts +17 -0
  89. package/lib/routes/connectors/routes.ts +19 -0
  90. package/lib/routes/connectors/set.help.ts +8 -0
  91. package/lib/routes/connectors/set.ts +30 -0
  92. package/lib/routes/connectors/show.help.ts +1 -0
  93. package/lib/routes/connectors/show.ts +32 -0
  94. package/lib/routes/gateway/group.help.ts +15 -0
  95. package/lib/routes/gateway/group.ts +28 -0
  96. package/lib/routes/gateway/logs.help.ts +13 -0
  97. package/lib/routes/gateway/logs.ts +100 -0
  98. package/lib/routes/gateway/restart.help.ts +10 -0
  99. package/lib/routes/gateway/restart.ts +35 -0
  100. package/lib/routes/gateway/routes.ts +18 -0
  101. package/lib/routes/gateway/run.help.ts +12 -0
  102. package/lib/routes/gateway/run.ts +35 -0
  103. package/lib/routes/gateway/start.help.ts +15 -0
  104. package/lib/routes/gateway/start.ts +32 -0
  105. package/lib/routes/gateway/status.help.ts +9 -0
  106. package/lib/routes/gateway/status.ts +28 -0
  107. package/lib/routes/gateway/stop.help.ts +8 -0
  108. package/lib/routes/gateway/stop.ts +21 -0
  109. package/lib/routes/repos/add.help.ts +5 -0
  110. package/lib/routes/repos/add.ts +19 -0
  111. package/lib/routes/repos/group.help.ts +11 -0
  112. package/lib/routes/repos/group.ts +18 -0
  113. package/lib/routes/repos/remove.help.ts +3 -0
  114. package/lib/routes/repos/remove.ts +17 -0
  115. package/lib/routes/repos/rename.help.ts +5 -0
  116. package/lib/routes/repos/rename.ts +17 -0
  117. package/lib/routes/repos/routes.ts +17 -0
  118. package/lib/routes/repos/set.help.ts +5 -0
  119. package/lib/routes/repos/set.ts +21 -0
  120. package/lib/routes/repos/show.help.ts +1 -0
  121. package/lib/routes/repos/show.ts +19 -0
  122. package/lib/routes/status/routes.ts +4 -0
  123. package/lib/routes/status/status.help.ts +6 -0
  124. package/lib/routes/status/status.ts +77 -0
  125. package/lib/routes.ts +36 -0
  126. package/package.json +65 -0
@@ -0,0 +1,35 @@
1
+ import { resolve } from "node:path"
2
+ import { z } from "zod"
3
+ import { factory } from "@/factory"
4
+ import { zValidator } from "@/modules/router/validator"
5
+ import { help } from "@/routes/gateway/run.help"
6
+
7
+ export const gatewayRunHandler = factory.createHandlers(
8
+ zValidator(
9
+ "query",
10
+ z.object({
11
+ "no-caffeine": z.string().optional(),
12
+ }),
13
+ help,
14
+ ),
15
+ async (c) => {
16
+ const query = c.req.valid("query")
17
+ const gatewayScript = resolve(import.meta.dir, "../../modules/gateway/daemon.ts")
18
+
19
+ const useCaffeinate = query["no-caffeine"] !== "true" && process.platform === "darwin"
20
+ const command = useCaffeinate
21
+ ? ["caffeinate", "-i", "bun", gatewayScript]
22
+ : ["bun", gatewayScript]
23
+
24
+ const proc = Bun.spawn(command, {
25
+ stdio: ["inherit", "inherit", "inherit"],
26
+ })
27
+
28
+ process.on("SIGINT", () => proc.kill("SIGINT"))
29
+ process.on("SIGTERM", () => proc.kill("SIGTERM"))
30
+
31
+ const exitCode = await proc.exited
32
+
33
+ process.exit(exitCode)
34
+ },
35
+ )
@@ -0,0 +1,15 @@
1
+ export const help = `funnel gateway start — start the gateway in background
2
+
3
+ usage: funnel gateway start [--no-caffeine]
4
+
5
+ Daemonized with nohup, so it keeps running after the terminal is closed.
6
+ On macOS wraps the process with caffeinate -i by default to prevent idle sleep.
7
+ Use --no-caffeine to disable caffeinate.
8
+
9
+ port: 9742 (override via FUNNEL_PORT)
10
+ pid: ~/.funnel/gateway.pid
11
+ log: /tmp/funnel/gateway.log
12
+
13
+ examples:
14
+ funnel gateway start
15
+ funnel gateway start --no-caffeine`
@@ -0,0 +1,32 @@
1
+ import { z } from "zod"
2
+ import { factory } from "@/factory"
3
+ import { zValidator } from "@/modules/router/validator"
4
+ import { help } from "@/routes/gateway/start.help"
5
+
6
+ export const gatewayStartHandler = factory.createHandlers(
7
+ zValidator(
8
+ "query",
9
+ z.object({
10
+ "no-caffeine": z.string().optional(),
11
+ }),
12
+ help,
13
+ ),
14
+ async (c) => {
15
+ const query = c.req.valid("query")
16
+ const funnel = c.var.funnel
17
+
18
+ if (funnel.gateway.isRunning()) {
19
+ const status = funnel.gateway.getStatus()
20
+
21
+ return c.text(`funnel gateway: already running (pid ${status.pid})`)
22
+ }
23
+
24
+ const started = await funnel.gateway.start({
25
+ caffeinate: query["no-caffeine"] !== "true",
26
+ })
27
+
28
+ return started
29
+ ? c.text("funnel gateway: started")
30
+ : c.text("funnel gateway: failed to start", 500)
31
+ },
32
+ )
@@ -0,0 +1,9 @@
1
+ export const help = `funnel gateway status — show gateway running status
2
+
3
+ usage: funnel gateway status
4
+
5
+ When running, prints PID, port, and connected channel count. When not running, exits with 503.
6
+
7
+ examples:
8
+ funnel gateway status
9
+ funnel gateway`
@@ -0,0 +1,28 @@
1
+ import { z } from "zod"
2
+ import { factory } from "@/factory"
3
+ import { zValidator } from "@/modules/router/validator"
4
+ import { help } from "@/routes/gateway/status.help"
5
+
6
+ export const gatewayStatusHandler = factory.createHandlers(
7
+ zValidator("query", z.object({}), help),
8
+ async (c) => {
9
+ const funnel = c.var.funnel
10
+ const status = funnel.gateway.getStatus()
11
+
12
+ if (!status.running) {
13
+ return c.text("funnel gateway: not running", 503)
14
+ }
15
+
16
+ const res = await fetch(`http://localhost:${status.port}/health`).catch(() => null)
17
+
18
+ if (!res) {
19
+ return c.text(`funnel gateway: running (pid ${status.pid}) — health check failed`)
20
+ }
21
+
22
+ const health = (await res.json()) as Record<string, unknown>
23
+
24
+ return c.text(
25
+ `funnel gateway: running (pid ${status.pid})\n port: ${status.port}\n clients: ${health.clients ?? 0}`,
26
+ )
27
+ },
28
+ )
@@ -0,0 +1,8 @@
1
+ export const help = `funnel gateway stop — stop the gateway
2
+
3
+ usage: funnel gateway stop
4
+
5
+ Terminates the process whose PID is stored in ~/.funnel/gateway.pid.
6
+
7
+ examples:
8
+ funnel gateway stop`
@@ -0,0 +1,21 @@
1
+ import { z } from "zod"
2
+ import { factory } from "@/factory"
3
+ import { zValidator } from "@/modules/router/validator"
4
+ import { help } from "@/routes/gateway/stop.help"
5
+
6
+ export const gatewayStopHandler = factory.createHandlers(
7
+ zValidator("query", z.object({}), help),
8
+ async (c) => {
9
+ const funnel = c.var.funnel
10
+
11
+ if (!funnel.gateway.isRunning()) {
12
+ return c.text("funnel gateway: no running process")
13
+ }
14
+
15
+ const stopped = await funnel.gateway.stop()
16
+
17
+ return stopped
18
+ ? c.text("funnel gateway: stopped")
19
+ : c.text("funnel gateway: failed to stop", 500)
20
+ },
21
+ )
@@ -0,0 +1,5 @@
1
+ export const help = `funnel repos add — add a repo
2
+
3
+ usage: funnel repos add <name> --path <path>
4
+
5
+ Writes the funnel MCP server into the .mcp.json of the given directory.`
@@ -0,0 +1,19 @@
1
+ import { resolve } from "node:path"
2
+ import { z } from "zod"
3
+ import { factory } from "@/factory"
4
+ import { zValidator } from "@/modules/router/validator"
5
+ import { help } from "@/routes/repos/add.help"
6
+
7
+ export const reposAddHandler = factory.createHandlers(
8
+ zValidator("param", z.object({ name: z.string() })),
9
+ zValidator("query", z.object({ path: z.string() }), help),
10
+ (c) => {
11
+ const param = c.req.valid("param")
12
+ const query = c.req.valid("query")
13
+ const funnel = c.var.funnel
14
+
15
+ funnel.repositories.add({ name: param.name, path: resolve(query.path) })
16
+
17
+ return c.text(`added repo "${param.name}"`)
18
+ },
19
+ )
@@ -0,0 +1,11 @@
1
+ export const help = `funnel repos — manage repositories (extra)
2
+
3
+ usage: funnel repos [subcommand]
4
+
5
+ subcommands:
6
+ (none) list
7
+ add <name> --path <p> add (writes funnel into .mcp.json)
8
+ <name> set --path <p> change path
9
+ rename <old> <new> rename
10
+ remove <name> remove
11
+ <name> show details`
@@ -0,0 +1,18 @@
1
+ import { z } from "zod"
2
+ import { factory } from "@/factory"
3
+ import { zValidator } from "@/modules/router/validator"
4
+ import { help } from "@/routes/repos/group.help"
5
+
6
+ export const reposGroupHandler = factory.createHandlers(
7
+ zValidator("query", z.object({}), help),
8
+ (c) => {
9
+ const funnel = c.var.funnel
10
+ const repos = funnel.repositories.list()
11
+
12
+ if (repos.length === 0) return c.text("no repos")
13
+
14
+ const lines = repos.map((repo) => `${repo.name} ${repo.path}`)
15
+
16
+ return c.text(lines.join("\n"))
17
+ },
18
+ )
@@ -0,0 +1,3 @@
1
+ export const help = `funnel repos remove — remove a repo
2
+
3
+ usage: funnel repos remove <name>`
@@ -0,0 +1,17 @@
1
+ import { z } from "zod"
2
+ import { factory } from "@/factory"
3
+ import { zValidator } from "@/modules/router/validator"
4
+ import { help } from "@/routes/repos/remove.help"
5
+
6
+ export const reposRemoveHandler = factory.createHandlers(
7
+ zValidator("param", z.object({ name: z.string() })),
8
+ zValidator("query", z.object({}), help),
9
+ (c) => {
10
+ const param = c.req.valid("param")
11
+ const funnel = c.var.funnel
12
+
13
+ funnel.repositories.remove(param.name)
14
+
15
+ return c.text(`removed repo "${param.name}"`)
16
+ },
17
+ )
@@ -0,0 +1,5 @@
1
+ export const help = `funnel repos rename — rename a repo
2
+
3
+ usage:
4
+ funnel repos rename <old> <new>
5
+ funnel repos <old> rename <new>`
@@ -0,0 +1,17 @@
1
+ import { z } from "zod"
2
+ import { factory } from "@/factory"
3
+ import { zValidator } from "@/modules/router/validator"
4
+ import { help } from "@/routes/repos/rename.help"
5
+
6
+ export const reposRenameHandler = factory.createHandlers(
7
+ zValidator("param", z.object({ name: z.string(), newName: z.string() })),
8
+ zValidator("query", z.object({}), help),
9
+ (c) => {
10
+ const param = c.req.valid("param")
11
+ const funnel = c.var.funnel
12
+
13
+ funnel.repositories.rename(param.name, param["newName"])
14
+
15
+ return c.text(`renamed repo "${param.name}" to "${param["newName"]}"`)
16
+ },
17
+ )
@@ -0,0 +1,17 @@
1
+ import { factory } from "@/factory"
2
+ import { reposAddHandler } from "@/routes/repos/add"
3
+ import { reposGroupHandler } from "@/routes/repos/group"
4
+ import { reposRemoveHandler } from "@/routes/repos/remove"
5
+ import { reposRenameHandler } from "@/routes/repos/rename"
6
+ import { reposSetHandler } from "@/routes/repos/set"
7
+ import { reposShowHandler } from "@/routes/repos/show"
8
+
9
+ export const reposRoutes = factory
10
+ .createApp()
11
+ .get("/", ...reposGroupHandler)
12
+ .put("/:name/rename/:newName", ...reposRenameHandler)
13
+ .put("/rename/:name/:newName", ...reposRenameHandler)
14
+ .post("/:name", ...reposAddHandler)
15
+ .put("/:name", ...reposSetHandler)
16
+ .delete("/:name", ...reposRemoveHandler)
17
+ .get("/:name", ...reposShowHandler)
@@ -0,0 +1,5 @@
1
+ export const help = `funnel repos <name> set — update a repo
2
+
3
+ usage: funnel repos <name> set --path <path>
4
+
5
+ When the path changes, the old path's .mcp.json has funnel MCP removed and the new path has it written.`
@@ -0,0 +1,21 @@
1
+ import { resolve } from "node:path"
2
+ import { z } from "zod"
3
+ import { factory } from "@/factory"
4
+ import { zValidator } from "@/modules/router/validator"
5
+ import { help } from "@/routes/repos/set.help"
6
+
7
+ export const reposSetHandler = factory.createHandlers(
8
+ zValidator("param", z.object({ name: z.string() })),
9
+ zValidator("query", z.object({ path: z.string().optional() }), help),
10
+ (c) => {
11
+ const param = c.req.valid("param")
12
+ const query = c.req.valid("query")
13
+ const funnel = c.var.funnel
14
+
15
+ funnel.repositories.update(param.name, {
16
+ path: query.path !== undefined ? resolve(query.path) : undefined,
17
+ })
18
+
19
+ return c.text(`updated repo "${param.name}"`)
20
+ },
21
+ )
@@ -0,0 +1 @@
1
+ export const help = `funnel repos <name> — show repo details`
@@ -0,0 +1,19 @@
1
+ import { HTTPException } from "hono/http-exception"
2
+ import { z } from "zod"
3
+ import { factory } from "@/factory"
4
+ import { zValidator } from "@/modules/router/validator"
5
+ import { help } from "@/routes/repos/show.help"
6
+
7
+ export const reposShowHandler = factory.createHandlers(
8
+ zValidator("param", z.object({ name: z.string() })),
9
+ zValidator("query", z.object({}), help),
10
+ (c) => {
11
+ const param = c.req.valid("param")
12
+ const funnel = c.var.funnel
13
+ const repo = funnel.repositories.get(param.name)
14
+
15
+ if (!repo) throw new HTTPException(404, { message: `repo "${param.name}" not found` })
16
+
17
+ return c.text(`name: ${repo.name}\npath: ${repo.path}`)
18
+ },
19
+ )
@@ -0,0 +1,4 @@
1
+ import { factory } from "@/factory"
2
+ import { statusHandler } from "@/routes/status/status"
3
+
4
+ export const statusRoutes = factory.createApp().get("/", ...statusHandler)
@@ -0,0 +1,6 @@
1
+ export const help = `funnel status — show overall connection status
2
+
3
+ usage: funnel status
4
+
5
+ Lists configured connectors / channels / agents / repos, gateway running status,
6
+ and active MCP WebSocket clients.`
@@ -0,0 +1,77 @@
1
+ import { z } from "zod"
2
+ import { factory } from "@/factory"
3
+ import { zValidator } from "@/modules/router/validator"
4
+ import { help } from "@/routes/status/status.help"
5
+
6
+ type GatewayStatus = {
7
+ ok: boolean
8
+ clients: { channel: string; connectors: string[] }[]
9
+ }
10
+
11
+ export const statusHandler = factory.createHandlers(
12
+ zValidator("query", z.object({}), help),
13
+ async (c) => {
14
+ const funnel = c.var.funnel
15
+ const connectors = funnel.connectors.list()
16
+ const channels = funnel.channels.list()
17
+ const agents = funnel.agents.list()
18
+ const repos = funnel.repositories.list()
19
+ const gatewayStatus = funnel.gateway.getStatus()
20
+
21
+ const lines: string[] = []
22
+
23
+ lines.push("= funnel status =")
24
+ lines.push("")
25
+
26
+ lines.push(`connectors: ${connectors.length}`)
27
+ for (const con of connectors) {
28
+ lines.push(` - ${con.name} (${con.type})`)
29
+ }
30
+ lines.push("")
31
+
32
+ lines.push(`channels: ${channels.length}`)
33
+ for (const ch of channels) {
34
+ const attached = ch.connectors.length > 0 ? ch.connectors.join(", ") : "(none)"
35
+ lines.push(` - ${ch.name} [${attached}]`)
36
+ }
37
+ lines.push("")
38
+
39
+ lines.push(`agents: ${agents.length}`)
40
+ for (const agent of agents) {
41
+ const parts = [`channel=${agent.channel}`]
42
+
43
+ if (agent.repo) parts.push(`repo=${agent.repo}`)
44
+ if (agent.subAgent) parts.push(`subAgent=${agent.subAgent}`)
45
+
46
+ lines.push(` - ${agent.name} [${parts.join(", ")}]`)
47
+ }
48
+ lines.push("")
49
+
50
+ lines.push(`repos: ${repos.length}`)
51
+ for (const repo of repos) {
52
+ lines.push(` - ${repo.name} ${repo.path}`)
53
+ }
54
+ lines.push("")
55
+
56
+ if (!gatewayStatus.running) {
57
+ lines.push("gateway: not running")
58
+ } else {
59
+ lines.push(`gateway: running (pid ${gatewayStatus.pid}, port ${gatewayStatus.port})`)
60
+
61
+ const res = await fetch(`http://localhost:${gatewayStatus.port}/status`).catch(() => null)
62
+
63
+ if (res && res.ok) {
64
+ const body = (await res.json()) as GatewayStatus
65
+ lines.push(` clients: ${body.clients.length}`)
66
+
67
+ for (const client of body.clients) {
68
+ const connectorList =
69
+ client.connectors.length > 0 ? client.connectors.join(", ") : "(none)"
70
+ lines.push(` - channel=${client.channel || "(unset)"} [${connectorList}]`)
71
+ }
72
+ }
73
+ }
74
+
75
+ return c.text(lines.join("\n"))
76
+ },
77
+ )
package/lib/routes.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { HTTPException } from "hono/http-exception"
2
+ import { factory } from "@/factory"
3
+ import { Funnel } from "@/funnel"
4
+ import { FunnelSettingsStore } from "@/modules/settings/funnel-settings-store"
5
+ import { agentsRoutes } from "@/routes/agents/routes"
6
+ import { channelsRoutes } from "@/routes/channels/routes"
7
+ import { claudeRoutes } from "@/routes/claude/routes"
8
+ import { connectorsRoutes } from "@/routes/connectors/routes"
9
+ import { gatewayRoutes } from "@/routes/gateway/routes"
10
+ import { reposRoutes } from "@/routes/repos/routes"
11
+ import { statusRoutes } from "@/routes/status/routes"
12
+
13
+ const base = factory.createApp()
14
+
15
+ base.use((c, next) => {
16
+ c.set("funnel", new Funnel({ store: new FunnelSettingsStore() }))
17
+
18
+ return next()
19
+ })
20
+
21
+ base.onError((error, c) => {
22
+ if (error instanceof HTTPException) {
23
+ return c.text(`error: ${error.message}`, error.status)
24
+ }
25
+
26
+ return c.text(`error: ${error instanceof Error ? error.message : String(error)}`, 400)
27
+ })
28
+
29
+ export const app = base
30
+ .route("/claude", claudeRoutes)
31
+ .route("/connectors", connectorsRoutes)
32
+ .route("/channels", channelsRoutes)
33
+ .route("/repos", reposRoutes)
34
+ .route("/agents", agentsRoutes)
35
+ .route("/gateway", gatewayRoutes)
36
+ .route("/status", statusRoutes)
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@interactive-inc/claude-funnel",
3
+ "version": "0.2.0",
4
+ "description": "Hub CLI that routes external events (Slack / GitHub / Discord) to Claude Code agents through subscription channels over MCP.",
5
+ "license": "MIT",
6
+ "author": "Interactive Inc.",
7
+ "homepage": "https://github.com/interactive-inc/claude-funnel#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/interactive-inc/claude-funnel.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/interactive-inc/claude-funnel/issues"
14
+ },
15
+ "keywords": [
16
+ "claude",
17
+ "claude-code",
18
+ "mcp",
19
+ "slack",
20
+ "discord",
21
+ "github",
22
+ "cli",
23
+ "bun"
24
+ ],
25
+ "type": "module",
26
+ "module": "lib/index.ts",
27
+ "bin": {
28
+ "fnl": "./lib/index.ts",
29
+ "funnel": "./lib/index.ts"
30
+ },
31
+ "files": [
32
+ "lib/**/*.ts",
33
+ "lib/**/*.tsx",
34
+ "!lib/**/*.test.ts",
35
+ "!lib/**/*.test.tsx",
36
+ "README.md",
37
+ "LICENSE"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "engines": {
43
+ "bun": ">=1.3.0"
44
+ },
45
+ "dependencies": {
46
+ "@hono/zod-validator": "^0.7.6",
47
+ "@modelcontextprotocol/sdk": "^1.29.0",
48
+ "@opentui/core": "^0.1.97",
49
+ "@opentui/react": "^0.1.97",
50
+ "@slack/bolt": "^4.7.0",
51
+ "@slack/web-api": "^7.15.0",
52
+ "@tanstack/react-query": "^5.99.0",
53
+ "@types/react": "^19.2.14",
54
+ "discord.js": "^14.26.3",
55
+ "hono": "^4.12.12",
56
+ "react": "^19.2.5",
57
+ "yaml": "^2.8.3",
58
+ "zod": "^4.3.6"
59
+ },
60
+ "devDependencies": {
61
+ "@types/bun": "^1.3.12",
62
+ "@typescript/native-preview": "^7.0.0-dev.20260411.1",
63
+ "vite-plus": "^0.1.18"
64
+ }
65
+ }