@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.
Files changed (56) hide show
  1. package/README.md +18 -12
  2. package/lib/funnel.ts +3 -3
  3. package/lib/index.ts +4 -2
  4. package/lib/modules/channels/funnel-channels.ts +4 -4
  5. package/lib/modules/claude/funnel-claude.ts +79 -1
  6. package/lib/modules/mcp/channel-server.ts +1 -2
  7. package/lib/modules/{agents/funnel-agents.ts → profiles/funnel-profiles.ts} +25 -25
  8. package/lib/modules/repos/funnel-repositories.ts +4 -4
  9. package/lib/modules/router/to-request.ts +2 -5
  10. package/lib/modules/settings/funnel-settings-store.ts +1 -1
  11. package/lib/modules/settings/mock-funnel-settings-reader.ts +1 -1
  12. package/lib/modules/settings/settings-schema.ts +3 -3
  13. package/lib/routes/claude/claude.help.ts +9 -4
  14. package/lib/routes/claude/claude.ts +44 -7
  15. package/lib/routes/connectors/routes.ts +0 -2
  16. package/lib/routes/profiles/add.help.ts +3 -0
  17. package/lib/routes/{agents → profiles}/add.ts +4 -4
  18. package/lib/routes/profiles/group.help.ts +16 -0
  19. package/lib/routes/profiles/group.ts +25 -0
  20. package/lib/routes/profiles/launch.help.ts +4 -0
  21. package/lib/routes/{agents → profiles}/launch.ts +9 -8
  22. package/lib/routes/profiles/remove.help.ts +3 -0
  23. package/lib/routes/{agents → profiles}/remove.ts +4 -4
  24. package/lib/routes/profiles/rename.help.ts +5 -0
  25. package/lib/routes/{agents → profiles}/rename.ts +4 -4
  26. package/lib/routes/profiles/routes.ts +18 -0
  27. package/lib/routes/profiles/set.help.ts +5 -0
  28. package/lib/routes/{agents → profiles}/set.ts +4 -4
  29. package/lib/routes/repos/add.help.ts +3 -2
  30. package/lib/routes/repos/add.ts +3 -2
  31. package/lib/routes/repos/group.help.ts +1 -1
  32. package/lib/routes/request/discord-help.ts +9 -0
  33. package/lib/routes/request/discord.help.ts +19 -0
  34. package/lib/routes/request/discord.ts +65 -0
  35. package/lib/routes/request/group.help.ts +15 -0
  36. package/lib/routes/request/group.ts +9 -0
  37. package/lib/routes/request/routes.ts +14 -0
  38. package/lib/routes/request/slack-help.ts +9 -0
  39. package/lib/routes/request/slack.help.ts +19 -0
  40. package/lib/routes/{connectors/call.ts → request/slack.ts} +24 -6
  41. package/lib/routes/status/status.help.ts +1 -1
  42. package/lib/routes/status/status.ts +7 -7
  43. package/lib/routes/update/routes.ts +4 -0
  44. package/lib/routes/update/update.help.ts +5 -0
  45. package/lib/routes/update/update.ts +21 -0
  46. package/lib/routes.ts +6 -2
  47. package/package.json +1 -1
  48. package/lib/routes/agents/add.help.ts +0 -3
  49. package/lib/routes/agents/group.help.ts +0 -13
  50. package/lib/routes/agents/group.ts +0 -25
  51. package/lib/routes/agents/launch.help.ts +0 -3
  52. package/lib/routes/agents/remove.help.ts +0 -3
  53. package/lib/routes/agents/rename.help.ts +0 -5
  54. package/lib/routes/agents/routes.ts +0 -17
  55. package/lib/routes/agents/set.help.ts +0 -5
  56. 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
+ )
@@ -0,0 +1,4 @@
1
+ export const help = `funnel profiles <name> run — launch a profile (sugar for fnl claude)
2
+
3
+ usage: funnel profiles <name> run [additional claude args...]
4
+ funnel profiles <name> (alias)`
@@ -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/agents/launch.help"
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 agentsLaunchHandler = factory.createHandlers(
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 agent = funnel.agents.get(param.name)
16
+ const profile = funnel.profiles.get(param.name)
17
17
 
18
- if (!agent) throw new HTTPException(404, { message: `agent "${param.name}" not found` })
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 ?? agent.channel,
27
- repo: overrideRepo ?? agent.repo,
28
- subAgent: overrideSubAgent ?? agent.subAgent,
29
- envFiles: overrideEnvFile ? [overrideEnvFile] : agent.envFiles,
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)
@@ -0,0 +1,3 @@
1
+ export const help = `funnel profiles remove — remove a profile
2
+
3
+ usage: funnel profiles remove <name>`
@@ -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/agents/remove.help"
4
+ import { help } from "@/routes/profiles/remove.help"
5
5
 
6
- export const agentsRemoveHandler = factory.createHandlers(
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.agents.remove(param.name)
13
+ funnel.profiles.remove(param.name)
14
14
 
15
- return c.text(`removed agent "${param.name}"`)
15
+ return c.text(`removed profile "${param.name}"`)
16
16
  },
17
17
  )
@@ -0,0 +1,5 @@
1
+ export const help = `funnel profiles rename — rename a profile
2
+
3
+ usage:
4
+ funnel profiles rename <old> <new>
5
+ funnel profiles <old> rename <new>`
@@ -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/agents/rename.help"
4
+ import { help } from "@/routes/profiles/rename.help"
5
5
 
6
- export const agentsRenameHandler = factory.createHandlers(
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.agents.rename(param.name, param["newName"])
13
+ funnel.profiles.rename(param.name, param["newName"])
14
14
 
15
- return c.text(`renamed agent "${param.name}" to "${param["newName"]}"`)
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)
@@ -0,0 +1,5 @@
1
+ export const help = `funnel profiles <name> set — update a profile
2
+
3
+ usage: funnel profiles <name> set [--channel <ch>] [--repo <r>] [--sub-agent <s>] [--env-file <f>]
4
+
5
+ pass an empty string to --repo / --sub-agent to unset them.`
@@ -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/agents/set.help"
4
+ import { help } from "@/routes/profiles/set.help"
5
5
 
6
- export const agentsSetHandler = factory.createHandlers(
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.agents.update(param.name, {
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 agent "${param.name}"`)
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.`
@@ -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: resolve(query.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> add (writes funnel into .mcp.json)
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/connectors/call.help"
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 connectorsCallHandler = factory.createHandlers(
20
- zValidator("param", z.object({ name: z.string() })),
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
- method: z.string(),
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
- const result = await funnel.connectors.call(param.name, {
36
- method: query.method,
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 / agents / repos, gateway running status,
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 agents = funnel.agents.list()
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(`agents: ${agents.length}`)
40
- for (const agent of agents) {
41
- const parts = [`channel=${agent.channel}`]
39
+ lines.push(`profiles: ${profiles.length}`)
40
+ for (const profile of profiles) {
41
+ const parts = [`channel=${profile.channel}`]
42
42
 
43
- if (agent.repo) parts.push(`repo=${agent.repo}`)
44
- if (agent.subAgent) parts.push(`subAgent=${agent.subAgent}`)
43
+ if (profile.repo) parts.push(`repo=${profile.repo}`)
44
+ if (profile.subAgent) parts.push(`subAgent=${profile.subAgent}`)
45
45
 
46
- lines.push(` - ${agent.name} [${parts.join(", ")}]`)
46
+ lines.push(` - ${profile.name} [${parts.join(", ")}]`)
47
47
  }
48
48
  lines.push("")
49
49
 
@@ -0,0 +1,4 @@
1
+ import { factory } from "@/factory"
2
+ import { updateHandler } from "@/routes/update/update"
3
+
4
+ export const updateRoutes = factory.createApp().get("/", ...updateHandler)
@@ -0,0 +1,5 @@
1
+ export const help = `funnel update — update funnel to the latest version
2
+
3
+ usage: funnel update
4
+
5
+ Runs "bun i -g @interactive-inc/claude-funnel".`
@@ -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("/agents", agentsRoutes)
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.2.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,3 +0,0 @@
1
- export const help = `funnel agents add — add an agent preset
2
-
3
- usage: funnel agents add <name> --channel <ch> [--repo <r>] [--sub-agent <s>] [--env-file <f>]`
@@ -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,3 +0,0 @@
1
- export const help = `funnel agents <name> — launch an agent (sugar for fnl claude)
2
-
3
- usage: funnel agents <name> [additional claude args...]`
@@ -1,3 +0,0 @@
1
- export const help = `funnel agents remove — remove an agent preset
2
-
3
- usage: funnel agents remove <name>`
@@ -1,5 +0,0 @@
1
- export const help = `funnel agents rename — rename an agent preset
2
-
3
- usage:
4
- funnel agents rename <old> <new>
5
- funnel agents <old> rename <new>`
@@ -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)