@interactive-inc/claude-funnel 0.2.0 → 0.3.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 +18 -12
- package/lib/funnel.ts +3 -3
- package/lib/index.ts +4 -2
- package/lib/modules/channels/funnel-channels.ts +4 -4
- package/lib/modules/claude/funnel-claude.ts +79 -1
- package/lib/modules/mcp/channel-server.ts +1 -2
- package/lib/modules/{agents/funnel-agents.ts → profiles/funnel-profiles.ts} +25 -25
- package/lib/modules/repos/funnel-repositories.ts +4 -4
- package/lib/modules/router/to-request.ts +2 -5
- package/lib/modules/settings/funnel-settings-store.ts +1 -1
- package/lib/modules/settings/mock-funnel-settings-reader.ts +1 -1
- package/lib/modules/settings/settings-schema.ts +3 -3
- package/lib/routes/claude/claude.help.ts +9 -4
- package/lib/routes/claude/claude.ts +44 -7
- package/lib/routes/connectors/routes.ts +0 -2
- package/lib/routes/profiles/add.help.ts +3 -0
- package/lib/routes/{agents → profiles}/add.ts +4 -4
- package/lib/routes/profiles/group.help.ts +16 -0
- package/lib/routes/profiles/group.ts +25 -0
- package/lib/routes/profiles/launch.help.ts +4 -0
- package/lib/routes/{agents → profiles}/launch.ts +9 -8
- package/lib/routes/profiles/remove.help.ts +3 -0
- package/lib/routes/{agents → profiles}/remove.ts +4 -4
- package/lib/routes/profiles/rename.help.ts +5 -0
- package/lib/routes/{agents → profiles}/rename.ts +4 -4
- package/lib/routes/profiles/routes.ts +18 -0
- package/lib/routes/profiles/set.help.ts +5 -0
- package/lib/routes/{agents → profiles}/set.ts +4 -4
- package/lib/routes/repos/add.help.ts +3 -2
- package/lib/routes/repos/add.ts +3 -2
- package/lib/routes/repos/group.help.ts +1 -1
- package/lib/routes/request/discord-help.ts +9 -0
- package/lib/routes/request/discord.help.ts +19 -0
- package/lib/routes/request/discord.ts +65 -0
- package/lib/routes/request/group.help.ts +15 -0
- package/lib/routes/request/group.ts +9 -0
- package/lib/routes/request/routes.ts +14 -0
- package/lib/routes/request/slack-help.ts +9 -0
- package/lib/routes/request/slack.help.ts +19 -0
- package/lib/routes/{connectors/call.ts → request/slack.ts} +24 -6
- package/lib/routes/status/status.help.ts +1 -1
- package/lib/routes/status/status.ts +7 -7
- package/lib/routes/update/routes.ts +4 -0
- package/lib/routes/update/update.help.ts +5 -0
- package/lib/routes/update/update.ts +21 -0
- package/lib/routes.ts +6 -2
- package/package.json +1 -1
- package/lib/routes/agents/add.help.ts +0 -3
- package/lib/routes/agents/group.help.ts +0 -13
- package/lib/routes/agents/group.ts +0 -25
- package/lib/routes/agents/launch.help.ts +0 -3
- package/lib/routes/agents/remove.help.ts +0 -3
- package/lib/routes/agents/rename.help.ts +0 -5
- package/lib/routes/agents/routes.ts +0 -17
- package/lib/routes/agents/set.help.ts +0 -5
- package/lib/routes/connectors/call.help.ts +0 -17
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import { factory } from "@/factory"
|
|
3
|
+
import { zValidator } from "@/modules/router/validator"
|
|
4
|
+
import { help } from "@/routes/profiles/group.help"
|
|
5
|
+
|
|
6
|
+
export const profilesGroupHandler = factory.createHandlers(
|
|
7
|
+
zValidator("query", z.object({}), help),
|
|
8
|
+
(c) => {
|
|
9
|
+
const funnel = c.var.funnel
|
|
10
|
+
const profiles = funnel.profiles.list()
|
|
11
|
+
|
|
12
|
+
if (profiles.length === 0) return c.text("no profiles")
|
|
13
|
+
|
|
14
|
+
const lines = profiles.map((profile) => {
|
|
15
|
+
const parts = [`channel=${profile.channel}`]
|
|
16
|
+
|
|
17
|
+
if (profile.repo) parts.push(`repo=${profile.repo}`)
|
|
18
|
+
if (profile.subAgent) parts.push(`subAgent=${profile.subAgent}`)
|
|
19
|
+
|
|
20
|
+
return `${profile.name} [${parts.join(", ")}]`
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
return c.text(lines.join("\n"))
|
|
24
|
+
},
|
|
25
|
+
)
|
|
@@ -3,19 +3,19 @@ import { z } from "zod"
|
|
|
3
3
|
import { factory } from "@/factory"
|
|
4
4
|
import { queryToCliArgs } from "@/modules/router/query-to-cli-args"
|
|
5
5
|
import { zValidator } from "@/modules/router/validator"
|
|
6
|
-
import { help } from "@/routes/
|
|
6
|
+
import { help } from "@/routes/profiles/launch.help"
|
|
7
7
|
|
|
8
8
|
const RESERVED_KEYS = ["channel", "repo", "sub-agent", "env-file"]
|
|
9
9
|
|
|
10
|
-
export const
|
|
10
|
+
export const profilesLaunchHandler = factory.createHandlers(
|
|
11
11
|
zValidator("param", z.object({ name: z.string() })),
|
|
12
12
|
zValidator("query", z.object({}).passthrough(), help),
|
|
13
13
|
async (c) => {
|
|
14
14
|
const param = c.req.valid("param")
|
|
15
15
|
const funnel = c.var.funnel
|
|
16
|
-
const
|
|
16
|
+
const profile = funnel.profiles.get(param.name)
|
|
17
17
|
|
|
18
|
-
if (!
|
|
18
|
+
if (!profile) throw new HTTPException(404, { message: `profile "${param.name}" not found` })
|
|
19
19
|
|
|
20
20
|
const overrideChannel = c.req.query("channel")
|
|
21
21
|
const overrideRepo = c.req.query("repo")
|
|
@@ -23,11 +23,12 @@ export const agentsLaunchHandler = factory.createHandlers(
|
|
|
23
23
|
const overrideEnvFile = c.req.query("env-file")
|
|
24
24
|
|
|
25
25
|
const exitCode = await funnel.claude.launch({
|
|
26
|
-
channel: overrideChannel ??
|
|
27
|
-
repo: overrideRepo ??
|
|
28
|
-
subAgent: overrideSubAgent ??
|
|
29
|
-
envFiles: overrideEnvFile ? [overrideEnvFile] :
|
|
26
|
+
channel: overrideChannel ?? profile.channel,
|
|
27
|
+
repo: overrideRepo ?? profile.repo,
|
|
28
|
+
subAgent: overrideSubAgent ?? profile.subAgent,
|
|
29
|
+
envFiles: overrideEnvFile ? [overrideEnvFile] : profile.envFiles,
|
|
30
30
|
userArgs: queryToCliArgs(c.req.url, RESERVED_KEYS),
|
|
31
|
+
profileName: profile.name,
|
|
31
32
|
})
|
|
32
33
|
|
|
33
34
|
process.exit(exitCode)
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { z } from "zod"
|
|
2
2
|
import { factory } from "@/factory"
|
|
3
3
|
import { zValidator } from "@/modules/router/validator"
|
|
4
|
-
import { help } from "@/routes/
|
|
4
|
+
import { help } from "@/routes/profiles/remove.help"
|
|
5
5
|
|
|
6
|
-
export const
|
|
6
|
+
export const profilesRemoveHandler = factory.createHandlers(
|
|
7
7
|
zValidator("param", z.object({ name: z.string() })),
|
|
8
8
|
zValidator("query", z.object({}), help),
|
|
9
9
|
(c) => {
|
|
10
10
|
const param = c.req.valid("param")
|
|
11
11
|
const funnel = c.var.funnel
|
|
12
12
|
|
|
13
|
-
funnel.
|
|
13
|
+
funnel.profiles.remove(param.name)
|
|
14
14
|
|
|
15
|
-
return c.text(`removed
|
|
15
|
+
return c.text(`removed profile "${param.name}"`)
|
|
16
16
|
},
|
|
17
17
|
)
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { z } from "zod"
|
|
2
2
|
import { factory } from "@/factory"
|
|
3
3
|
import { zValidator } from "@/modules/router/validator"
|
|
4
|
-
import { help } from "@/routes/
|
|
4
|
+
import { help } from "@/routes/profiles/rename.help"
|
|
5
5
|
|
|
6
|
-
export const
|
|
6
|
+
export const profilesRenameHandler = factory.createHandlers(
|
|
7
7
|
zValidator("param", z.object({ name: z.string(), newName: z.string() })),
|
|
8
8
|
zValidator("query", z.object({}), help),
|
|
9
9
|
(c) => {
|
|
10
10
|
const param = c.req.valid("param")
|
|
11
11
|
const funnel = c.var.funnel
|
|
12
12
|
|
|
13
|
-
funnel.
|
|
13
|
+
funnel.profiles.rename(param.name, param["newName"])
|
|
14
14
|
|
|
15
|
-
return c.text(`renamed
|
|
15
|
+
return c.text(`renamed profile "${param.name}" to "${param["newName"]}"`)
|
|
16
16
|
},
|
|
17
17
|
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { factory } from "@/factory"
|
|
2
|
+
import { profilesAddHandler } from "@/routes/profiles/add"
|
|
3
|
+
import { profilesGroupHandler } from "@/routes/profiles/group"
|
|
4
|
+
import { profilesLaunchHandler } from "@/routes/profiles/launch"
|
|
5
|
+
import { profilesRemoveHandler } from "@/routes/profiles/remove"
|
|
6
|
+
import { profilesRenameHandler } from "@/routes/profiles/rename"
|
|
7
|
+
import { profilesSetHandler } from "@/routes/profiles/set"
|
|
8
|
+
|
|
9
|
+
export const profilesRoutes = factory
|
|
10
|
+
.createApp()
|
|
11
|
+
.get("/", ...profilesGroupHandler)
|
|
12
|
+
.put("/:name/rename/:newName", ...profilesRenameHandler)
|
|
13
|
+
.put("/rename/:name/:newName", ...profilesRenameHandler)
|
|
14
|
+
.post("/:name", ...profilesAddHandler)
|
|
15
|
+
.put("/:name", ...profilesSetHandler)
|
|
16
|
+
.delete("/:name", ...profilesRemoveHandler)
|
|
17
|
+
.get("/:name/run", ...profilesLaunchHandler)
|
|
18
|
+
.get("/:name", ...profilesLaunchHandler)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from "zod"
|
|
2
2
|
import { factory } from "@/factory"
|
|
3
3
|
import { zValidator } from "@/modules/router/validator"
|
|
4
|
-
import { help } from "@/routes/
|
|
4
|
+
import { help } from "@/routes/profiles/set.help"
|
|
5
5
|
|
|
6
|
-
export const
|
|
6
|
+
export const profilesSetHandler = factory.createHandlers(
|
|
7
7
|
zValidator("param", z.object({ name: z.string() })),
|
|
8
8
|
zValidator(
|
|
9
9
|
"query",
|
|
@@ -20,13 +20,13 @@ export const agentsSetHandler = factory.createHandlers(
|
|
|
20
20
|
const query = c.req.valid("query")
|
|
21
21
|
const funnel = c.var.funnel
|
|
22
22
|
|
|
23
|
-
funnel.
|
|
23
|
+
funnel.profiles.update(param.name, {
|
|
24
24
|
channel: query.channel,
|
|
25
25
|
repo: query.repo,
|
|
26
26
|
subAgent: query["sub-agent"],
|
|
27
27
|
envFiles: query["env-file"] !== undefined ? [query["env-file"]] : undefined,
|
|
28
28
|
})
|
|
29
29
|
|
|
30
|
-
return c.text(`updated
|
|
30
|
+
return c.text(`updated profile "${param.name}"`)
|
|
31
31
|
},
|
|
32
32
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export const help = `funnel repos add — add a repo
|
|
2
2
|
|
|
3
|
-
usage: funnel repos add <name> --path <path>
|
|
3
|
+
usage: funnel repos add <name> [--path <path>]
|
|
4
4
|
|
|
5
|
-
Writes the funnel MCP server into the .mcp.json of the given directory
|
|
5
|
+
Writes the funnel MCP server into the .mcp.json of the given directory.
|
|
6
|
+
When --path is omitted, the current working directory is used.`
|
package/lib/routes/repos/add.ts
CHANGED
|
@@ -6,13 +6,14 @@ import { help } from "@/routes/repos/add.help"
|
|
|
6
6
|
|
|
7
7
|
export const reposAddHandler = factory.createHandlers(
|
|
8
8
|
zValidator("param", z.object({ name: z.string() })),
|
|
9
|
-
zValidator("query", z.object({ path: z.string() }), help),
|
|
9
|
+
zValidator("query", z.object({ path: z.string().optional() }), help),
|
|
10
10
|
(c) => {
|
|
11
11
|
const param = c.req.valid("param")
|
|
12
12
|
const query = c.req.valid("query")
|
|
13
13
|
const funnel = c.var.funnel
|
|
14
|
+
const path = resolve(query.path ?? process.cwd())
|
|
14
15
|
|
|
15
|
-
funnel.repositories.add({ name: param.name, path
|
|
16
|
+
funnel.repositories.add({ name: param.name, path })
|
|
16
17
|
|
|
17
18
|
return c.text(`added repo "${param.name}"`)
|
|
18
19
|
},
|
|
@@ -4,7 +4,7 @@ usage: funnel repos [subcommand]
|
|
|
4
4
|
|
|
5
5
|
subcommands:
|
|
6
6
|
(none) list
|
|
7
|
-
add <name> --path <p>
|
|
7
|
+
add <name> [--path <p>] add (path defaults to cwd; writes funnel into .mcp.json)
|
|
8
8
|
<name> set --path <p> change path
|
|
9
9
|
rename <old> <new> rename
|
|
10
10
|
remove <name> remove
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import { factory } from "@/factory"
|
|
3
|
+
import { zValidator } from "@/modules/router/validator"
|
|
4
|
+
import { help } from "@/routes/request/discord.help"
|
|
5
|
+
|
|
6
|
+
export const requestDiscordHelpHandler = factory.createHandlers(
|
|
7
|
+
zValidator("query", z.object({}), help),
|
|
8
|
+
(c) => c.text(help),
|
|
9
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const help = `funnel request discord — call the Discord REST API via a connector
|
|
2
|
+
|
|
3
|
+
usage: funnel request discord <method> <path> [<json-body>] --connector <name>
|
|
4
|
+
|
|
5
|
+
<method> is the HTTP verb (post / put / delete / patch / get).
|
|
6
|
+
<path> is the Discord API path relative to https://discord.com/api/v10 (without leading /).
|
|
7
|
+
<name> must match the connector's name (typically meta.connector on an inbound event).
|
|
8
|
+
|
|
9
|
+
Reply only when meta.mentioned="true" unless instructed otherwise.
|
|
10
|
+
|
|
11
|
+
examples (reply):
|
|
12
|
+
funnel request discord post channels/<meta.channel_id>/messages \\
|
|
13
|
+
'{"content":"...","message_reference":{"message_id":"<id>"}}' \\
|
|
14
|
+
--connector <meta.connector>
|
|
15
|
+
|
|
16
|
+
examples (react / delete / edit):
|
|
17
|
+
funnel request discord put channels/<cid>/messages/<mid>/reactions/<emoji>/@me --connector <name>
|
|
18
|
+
funnel request discord delete channels/<cid>/messages/<mid> --connector <name>
|
|
19
|
+
funnel request discord patch channels/<cid>/messages/<mid> '{"content":"edit"}' --connector <name>`
|
|
@@ -0,0 +1,65 @@
|
|
|
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/request/discord.help"
|
|
6
|
+
|
|
7
|
+
const ALLOWED_METHODS = new Set(["get", "post", "put", "patch", "delete"])
|
|
8
|
+
|
|
9
|
+
const parseBody = (raw: string | undefined): unknown => {
|
|
10
|
+
if (!raw) return {}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(raw)
|
|
14
|
+
} catch (error) {
|
|
15
|
+
throw new HTTPException(400, {
|
|
16
|
+
message: `body is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const requestDiscordHandler = factory.createHandlers(
|
|
22
|
+
zValidator("param", z.object({ method: z.string() })),
|
|
23
|
+
zValidator(
|
|
24
|
+
"query",
|
|
25
|
+
z.object({
|
|
26
|
+
connector: z.string(),
|
|
27
|
+
path: z.string(),
|
|
28
|
+
body: z.string().optional(),
|
|
29
|
+
}),
|
|
30
|
+
help,
|
|
31
|
+
),
|
|
32
|
+
async (c) => {
|
|
33
|
+
const param = c.req.valid("param")
|
|
34
|
+
const query = c.req.valid("query")
|
|
35
|
+
const funnel = c.var.funnel
|
|
36
|
+
|
|
37
|
+
const method = param.method.toLowerCase()
|
|
38
|
+
|
|
39
|
+
if (!ALLOWED_METHODS.has(method)) {
|
|
40
|
+
throw new HTTPException(400, {
|
|
41
|
+
message: `unsupported method "${param.method}" (allowed: ${[...ALLOWED_METHODS].join(", ")})`,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const connector = funnel.connectors.get(query.connector)
|
|
46
|
+
|
|
47
|
+
if (!connector) {
|
|
48
|
+
throw new HTTPException(404, { message: `connector "${query.connector}" not found` })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (connector.type !== "discord") {
|
|
52
|
+
throw new HTTPException(400, {
|
|
53
|
+
message: `connector "${query.connector}" is type "${connector.type}", not "discord"`,
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const result = await funnel.connectors.call(query.connector, {
|
|
58
|
+
method: method.toUpperCase(),
|
|
59
|
+
path: query.path,
|
|
60
|
+
body: parseBody(query.body),
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
return c.text(JSON.stringify(result, null, 2))
|
|
64
|
+
},
|
|
65
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const help = `funnel request — send an outbound API call via a connector
|
|
2
|
+
|
|
3
|
+
usage: funnel request <platform> <method> <path> [<json-body>] --connector <name>
|
|
4
|
+
|
|
5
|
+
platforms:
|
|
6
|
+
slack
|
|
7
|
+
discord
|
|
8
|
+
|
|
9
|
+
examples:
|
|
10
|
+
funnel request slack post chat.postMessage '{"channel":"...","text":"..."}' --connector my-slack
|
|
11
|
+
funnel request discord post channels/<cid>/messages '{"content":"..."}' --connector my-discord
|
|
12
|
+
|
|
13
|
+
more:
|
|
14
|
+
funnel request slack --help
|
|
15
|
+
funnel request discord --help`
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import { factory } from "@/factory"
|
|
3
|
+
import { zValidator } from "@/modules/router/validator"
|
|
4
|
+
import { help } from "@/routes/request/group.help"
|
|
5
|
+
|
|
6
|
+
export const requestGroupHandler = factory.createHandlers(
|
|
7
|
+
zValidator("query", z.object({}), help),
|
|
8
|
+
(c) => c.text(help),
|
|
9
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { factory } from "@/factory"
|
|
2
|
+
import { requestDiscordHandler } from "@/routes/request/discord"
|
|
3
|
+
import { requestDiscordHelpHandler } from "@/routes/request/discord-help"
|
|
4
|
+
import { requestGroupHandler } from "@/routes/request/group"
|
|
5
|
+
import { requestSlackHandler } from "@/routes/request/slack"
|
|
6
|
+
import { requestSlackHelpHandler } from "@/routes/request/slack-help"
|
|
7
|
+
|
|
8
|
+
export const requestRoutes = factory
|
|
9
|
+
.createApp()
|
|
10
|
+
.get("/", ...requestGroupHandler)
|
|
11
|
+
.get("/slack", ...requestSlackHelpHandler)
|
|
12
|
+
.get("/slack/:method", ...requestSlackHandler)
|
|
13
|
+
.get("/discord", ...requestDiscordHelpHandler)
|
|
14
|
+
.get("/discord/:method", ...requestDiscordHandler)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import { factory } from "@/factory"
|
|
3
|
+
import { zValidator } from "@/modules/router/validator"
|
|
4
|
+
import { help } from "@/routes/request/slack.help"
|
|
5
|
+
|
|
6
|
+
export const requestSlackHelpHandler = factory.createHandlers(
|
|
7
|
+
zValidator("query", z.object({}), help),
|
|
8
|
+
(c) => c.text(help),
|
|
9
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const help = `funnel request slack — call the Slack Web API via a connector
|
|
2
|
+
|
|
3
|
+
usage: funnel request slack post <path> [<json-body>] --connector <name>
|
|
4
|
+
|
|
5
|
+
Slack always uses POST. <path> is the Web API method name (e.g. chat.postMessage).
|
|
6
|
+
<name> must match the connector's name (typically meta.connector on an inbound event).
|
|
7
|
+
|
|
8
|
+
Reply only when meta.mentioned="true" unless instructed otherwise.
|
|
9
|
+
|
|
10
|
+
examples (reply):
|
|
11
|
+
funnel request slack post chat.postMessage \\
|
|
12
|
+
'{"channel":"<meta.channel_id>","text":"...","thread_ts":"<meta.thread_ts>"}' \\
|
|
13
|
+
--connector <meta.connector>
|
|
14
|
+
|
|
15
|
+
examples (edit / delete / react / lookup):
|
|
16
|
+
funnel request slack post chat.update '{"channel":"...","ts":"...","text":"edit"}' --connector <name>
|
|
17
|
+
funnel request slack post chat.delete '{"channel":"...","ts":"..."}' --connector <name>
|
|
18
|
+
funnel request slack post reactions.add '{"channel":"...","timestamp":"...","name":"eyes"}' --connector <name>
|
|
19
|
+
funnel request slack post users.info '{"user":"U..."}' --connector <name>`
|
|
@@ -2,7 +2,7 @@ import { HTTPException } from "hono/http-exception"
|
|
|
2
2
|
import { z } from "zod"
|
|
3
3
|
import { factory } from "@/factory"
|
|
4
4
|
import { zValidator } from "@/modules/router/validator"
|
|
5
|
-
import { help } from "@/routes/
|
|
5
|
+
import { help } from "@/routes/request/slack.help"
|
|
6
6
|
|
|
7
7
|
const parseBody = (raw: string | undefined): unknown => {
|
|
8
8
|
if (!raw) return {}
|
|
@@ -16,12 +16,12 @@ const parseBody = (raw: string | undefined): unknown => {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export const
|
|
20
|
-
zValidator("param", z.object({
|
|
19
|
+
export const requestSlackHandler = factory.createHandlers(
|
|
20
|
+
zValidator("param", z.object({ method: z.string() })),
|
|
21
21
|
zValidator(
|
|
22
22
|
"query",
|
|
23
23
|
z.object({
|
|
24
|
-
|
|
24
|
+
connector: z.string(),
|
|
25
25
|
path: z.string(),
|
|
26
26
|
body: z.string().optional(),
|
|
27
27
|
}),
|
|
@@ -32,8 +32,26 @@ export const connectorsCallHandler = factory.createHandlers(
|
|
|
32
32
|
const query = c.req.valid("query")
|
|
33
33
|
const funnel = c.var.funnel
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
if (param.method.toLowerCase() !== "post") {
|
|
36
|
+
throw new HTTPException(400, {
|
|
37
|
+
message: `slack only supports POST (got "${param.method}")`,
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const connector = funnel.connectors.get(query.connector)
|
|
42
|
+
|
|
43
|
+
if (!connector) {
|
|
44
|
+
throw new HTTPException(404, { message: `connector "${query.connector}" not found` })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (connector.type !== "slack") {
|
|
48
|
+
throw new HTTPException(400, {
|
|
49
|
+
message: `connector "${query.connector}" is type "${connector.type}", not "slack"`,
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = await funnel.connectors.call(query.connector, {
|
|
54
|
+
method: "POST",
|
|
37
55
|
path: query.path,
|
|
38
56
|
body: parseBody(query.body),
|
|
39
57
|
})
|
|
@@ -2,5 +2,5 @@ export const help = `funnel status — show overall connection status
|
|
|
2
2
|
|
|
3
3
|
usage: funnel status
|
|
4
4
|
|
|
5
|
-
Lists configured connectors / channels /
|
|
5
|
+
Lists configured connectors / channels / profiles / repos, gateway running status,
|
|
6
6
|
and active MCP WebSocket clients.`
|
|
@@ -14,7 +14,7 @@ export const statusHandler = factory.createHandlers(
|
|
|
14
14
|
const funnel = c.var.funnel
|
|
15
15
|
const connectors = funnel.connectors.list()
|
|
16
16
|
const channels = funnel.channels.list()
|
|
17
|
-
const
|
|
17
|
+
const profiles = funnel.profiles.list()
|
|
18
18
|
const repos = funnel.repositories.list()
|
|
19
19
|
const gatewayStatus = funnel.gateway.getStatus()
|
|
20
20
|
|
|
@@ -36,14 +36,14 @@ export const statusHandler = factory.createHandlers(
|
|
|
36
36
|
}
|
|
37
37
|
lines.push("")
|
|
38
38
|
|
|
39
|
-
lines.push(`
|
|
40
|
-
for (const
|
|
41
|
-
const parts = [`channel=${
|
|
39
|
+
lines.push(`profiles: ${profiles.length}`)
|
|
40
|
+
for (const profile of profiles) {
|
|
41
|
+
const parts = [`channel=${profile.channel}`]
|
|
42
42
|
|
|
43
|
-
if (
|
|
44
|
-
if (
|
|
43
|
+
if (profile.repo) parts.push(`repo=${profile.repo}`)
|
|
44
|
+
if (profile.subAgent) parts.push(`subAgent=${profile.subAgent}`)
|
|
45
45
|
|
|
46
|
-
lines.push(` - ${
|
|
46
|
+
lines.push(` - ${profile.name} [${parts.join(", ")}]`)
|
|
47
47
|
}
|
|
48
48
|
lines.push("")
|
|
49
49
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import { factory } from "@/factory"
|
|
3
|
+
import { NodeFunnelProcessRunner } from "@/modules/process/node-funnel-process-runner"
|
|
4
|
+
import { zValidator } from "@/modules/router/validator"
|
|
5
|
+
import { help } from "@/routes/update/update.help"
|
|
6
|
+
|
|
7
|
+
const PACKAGE = "@interactive-inc/claude-funnel"
|
|
8
|
+
|
|
9
|
+
export const updateHandler = factory.createHandlers(
|
|
10
|
+
zValidator("query", z.object({}), help),
|
|
11
|
+
async (c) => {
|
|
12
|
+
const runner = new NodeFunnelProcessRunner()
|
|
13
|
+
const exitCode = await runner.attach(["bun", "i", "-g", PACKAGE])
|
|
14
|
+
|
|
15
|
+
if (exitCode !== 0) {
|
|
16
|
+
return c.text(`update failed (exit ${exitCode})`, 500)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return c.text(`updated ${PACKAGE}`)
|
|
20
|
+
},
|
|
21
|
+
)
|
package/lib/routes.ts
CHANGED
|
@@ -2,13 +2,15 @@ import { HTTPException } from "hono/http-exception"
|
|
|
2
2
|
import { factory } from "@/factory"
|
|
3
3
|
import { Funnel } from "@/funnel"
|
|
4
4
|
import { FunnelSettingsStore } from "@/modules/settings/funnel-settings-store"
|
|
5
|
-
import { agentsRoutes } from "@/routes/agents/routes"
|
|
6
5
|
import { channelsRoutes } from "@/routes/channels/routes"
|
|
7
6
|
import { claudeRoutes } from "@/routes/claude/routes"
|
|
8
7
|
import { connectorsRoutes } from "@/routes/connectors/routes"
|
|
9
8
|
import { gatewayRoutes } from "@/routes/gateway/routes"
|
|
9
|
+
import { profilesRoutes } from "@/routes/profiles/routes"
|
|
10
10
|
import { reposRoutes } from "@/routes/repos/routes"
|
|
11
|
+
import { requestRoutes } from "@/routes/request/routes"
|
|
11
12
|
import { statusRoutes } from "@/routes/status/routes"
|
|
13
|
+
import { updateRoutes } from "@/routes/update/routes"
|
|
12
14
|
|
|
13
15
|
const base = factory.createApp()
|
|
14
16
|
|
|
@@ -31,6 +33,8 @@ export const app = base
|
|
|
31
33
|
.route("/connectors", connectorsRoutes)
|
|
32
34
|
.route("/channels", channelsRoutes)
|
|
33
35
|
.route("/repos", reposRoutes)
|
|
34
|
-
.route("/
|
|
36
|
+
.route("/profiles", profilesRoutes)
|
|
37
|
+
.route("/request", requestRoutes)
|
|
35
38
|
.route("/gateway", gatewayRoutes)
|
|
36
39
|
.route("/status", statusRoutes)
|
|
40
|
+
.route("/update", updateRoutes)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@interactive-inc/claude-funnel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Hub CLI that routes external events (Slack / GitHub / Discord) to Claude Code agents through subscription channels over MCP.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Interactive Inc.",
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export const help = `funnel agents — agent presets (extra)
|
|
2
|
-
|
|
3
|
-
usage: funnel agents [subcommand]
|
|
4
|
-
|
|
5
|
-
subcommands:
|
|
6
|
-
(none) list
|
|
7
|
-
add <name> --channel <ch> [--repo <r>] [--sub-agent <s>] [--env-file <f>]
|
|
8
|
-
remove <name> remove
|
|
9
|
-
<name> launch (sugar for fnl claude)
|
|
10
|
-
|
|
11
|
-
examples:
|
|
12
|
-
funnel agents add cto --channel prod-inbox --repo myapp --sub-agent cto
|
|
13
|
-
funnel agents cto`
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { z } from "zod"
|
|
2
|
-
import { factory } from "@/factory"
|
|
3
|
-
import { zValidator } from "@/modules/router/validator"
|
|
4
|
-
import { help } from "@/routes/agents/group.help"
|
|
5
|
-
|
|
6
|
-
export const agentsGroupHandler = factory.createHandlers(
|
|
7
|
-
zValidator("query", z.object({}), help),
|
|
8
|
-
(c) => {
|
|
9
|
-
const funnel = c.var.funnel
|
|
10
|
-
const agents = funnel.agents.list()
|
|
11
|
-
|
|
12
|
-
if (agents.length === 0) return c.text("no agents")
|
|
13
|
-
|
|
14
|
-
const lines = agents.map((agent) => {
|
|
15
|
-
const parts = [`channel=${agent.channel}`]
|
|
16
|
-
|
|
17
|
-
if (agent.repo) parts.push(`repo=${agent.repo}`)
|
|
18
|
-
if (agent.subAgent) parts.push(`subAgent=${agent.subAgent}`)
|
|
19
|
-
|
|
20
|
-
return `${agent.name} [${parts.join(", ")}]`
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
return c.text(lines.join("\n"))
|
|
24
|
-
},
|
|
25
|
-
)
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { factory } from "@/factory"
|
|
2
|
-
import { agentsAddHandler } from "@/routes/agents/add"
|
|
3
|
-
import { agentsGroupHandler } from "@/routes/agents/group"
|
|
4
|
-
import { agentsLaunchHandler } from "@/routes/agents/launch"
|
|
5
|
-
import { agentsRemoveHandler } from "@/routes/agents/remove"
|
|
6
|
-
import { agentsRenameHandler } from "@/routes/agents/rename"
|
|
7
|
-
import { agentsSetHandler } from "@/routes/agents/set"
|
|
8
|
-
|
|
9
|
-
export const agentsRoutes = factory
|
|
10
|
-
.createApp()
|
|
11
|
-
.get("/", ...agentsGroupHandler)
|
|
12
|
-
.put("/:name/rename/:newName", ...agentsRenameHandler)
|
|
13
|
-
.put("/rename/:name/:newName", ...agentsRenameHandler)
|
|
14
|
-
.post("/:name", ...agentsAddHandler)
|
|
15
|
-
.put("/:name", ...agentsSetHandler)
|
|
16
|
-
.delete("/:name", ...agentsRemoveHandler)
|
|
17
|
-
.get("/:name", ...agentsLaunchHandler)
|