@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.
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/lib/factory.ts +10 -0
- package/lib/funnel.ts +51 -0
- package/lib/index.ts +86 -0
- package/lib/modules/agents/funnel-agents.ts +105 -0
- package/lib/modules/channels/funnel-channels.ts +113 -0
- package/lib/modules/claude/funnel-claude.ts +136 -0
- package/lib/modules/connectors/funnel-connector-adapter.ts +9 -0
- package/lib/modules/connectors/funnel-connector-listener.ts +5 -0
- package/lib/modules/connectors/funnel-connectors.ts +124 -0
- package/lib/modules/connectors/funnel-discord-adapter.ts +56 -0
- package/lib/modules/connectors/funnel-discord-event-processor.ts +48 -0
- package/lib/modules/connectors/funnel-discord-listener.ts +65 -0
- package/lib/modules/connectors/funnel-gh-adapter.ts +51 -0
- package/lib/modules/connectors/funnel-gh-listener.ts +102 -0
- package/lib/modules/connectors/funnel-slack-adapter.ts +31 -0
- package/lib/modules/connectors/funnel-slack-event-processor.ts +91 -0
- package/lib/modules/connectors/funnel-slack-listener.ts +72 -0
- package/lib/modules/connectors/resolve-listener.ts +13 -0
- package/lib/modules/fs/funnel-file-system.ts +14 -0
- package/lib/modules/fs/memory-funnel-file-system.ts +85 -0
- package/lib/modules/fs/node-funnel-file-system.ts +56 -0
- package/lib/modules/gateway/daemon.ts +190 -0
- package/lib/modules/gateway/funnel-broadcaster.ts +37 -0
- package/lib/modules/gateway/funnel-event-logger.ts +59 -0
- package/lib/modules/gateway/funnel-gateway.ts +166 -0
- package/lib/modules/gateway/kill-competing-slack-gateways.ts +52 -0
- package/lib/modules/http/funnel-http-client.ts +17 -0
- package/lib/modules/http/memory-funnel-http-client.ts +40 -0
- package/lib/modules/http/node-funnel-http-client.ts +27 -0
- package/lib/modules/logger.ts +26 -0
- package/lib/modules/mcp/channel-server.ts +77 -0
- package/lib/modules/mcp/funnel-mcp.ts +107 -0
- package/lib/modules/process/funnel-process-runner.ts +28 -0
- package/lib/modules/process/memory-funnel-process-runner.ts +88 -0
- package/lib/modules/process/node-funnel-process-runner.ts +100 -0
- package/lib/modules/repos/funnel-repositories.ts +107 -0
- package/lib/modules/router/query-to-cli-args.ts +20 -0
- package/lib/modules/router/to-request.ts +122 -0
- package/lib/modules/router/validator.ts +27 -0
- package/lib/modules/settings/funnel-settings-reader.ts +6 -0
- package/lib/modules/settings/funnel-settings-store.ts +57 -0
- package/lib/modules/settings/mock-funnel-settings-reader.ts +27 -0
- package/lib/modules/settings/settings-schema.ts +67 -0
- package/lib/modules/tui/app.tsx +44 -0
- package/lib/modules/tui/tui.tsx +13 -0
- package/lib/routes/agents/add.help.ts +3 -0
- package/lib/routes/agents/add.ts +33 -0
- package/lib/routes/agents/group.help.ts +13 -0
- package/lib/routes/agents/group.ts +25 -0
- package/lib/routes/agents/launch.help.ts +3 -0
- package/lib/routes/agents/launch.ts +35 -0
- package/lib/routes/agents/remove.help.ts +3 -0
- package/lib/routes/agents/remove.ts +17 -0
- package/lib/routes/agents/rename.help.ts +5 -0
- package/lib/routes/agents/rename.ts +17 -0
- package/lib/routes/agents/routes.ts +17 -0
- package/lib/routes/agents/set.help.ts +5 -0
- package/lib/routes/agents/set.ts +32 -0
- package/lib/routes/channels/add.help.ts +3 -0
- package/lib/routes/channels/add.ts +21 -0
- package/lib/routes/channels/connectors-attach.help.ts +3 -0
- package/lib/routes/channels/connectors-attach.ts +17 -0
- package/lib/routes/channels/connectors-detach.help.ts +3 -0
- package/lib/routes/channels/connectors-detach.ts +17 -0
- package/lib/routes/channels/group.help.ts +16 -0
- package/lib/routes/channels/group.ts +22 -0
- package/lib/routes/channels/remove.help.ts +3 -0
- package/lib/routes/channels/remove.ts +17 -0
- package/lib/routes/channels/rename.help.ts +5 -0
- package/lib/routes/channels/rename.ts +17 -0
- package/lib/routes/channels/routes.ts +19 -0
- package/lib/routes/channels/show.help.ts +1 -0
- package/lib/routes/channels/show.ts +26 -0
- package/lib/routes/claude/claude.help.ts +11 -0
- package/lib/routes/claude/claude.ts +39 -0
- package/lib/routes/claude/routes.ts +4 -0
- package/lib/routes/connectors/add.help.ts +22 -0
- package/lib/routes/connectors/add.ts +55 -0
- package/lib/routes/connectors/call.help.ts +17 -0
- package/lib/routes/connectors/call.ts +43 -0
- package/lib/routes/connectors/group.help.ts +14 -0
- package/lib/routes/connectors/group.ts +18 -0
- package/lib/routes/connectors/remove.help.ts +3 -0
- package/lib/routes/connectors/remove.ts +17 -0
- package/lib/routes/connectors/rename.help.ts +5 -0
- package/lib/routes/connectors/rename.ts +17 -0
- package/lib/routes/connectors/routes.ts +19 -0
- package/lib/routes/connectors/set.help.ts +8 -0
- package/lib/routes/connectors/set.ts +30 -0
- package/lib/routes/connectors/show.help.ts +1 -0
- package/lib/routes/connectors/show.ts +32 -0
- package/lib/routes/gateway/group.help.ts +15 -0
- package/lib/routes/gateway/group.ts +28 -0
- package/lib/routes/gateway/logs.help.ts +13 -0
- package/lib/routes/gateway/logs.ts +100 -0
- package/lib/routes/gateway/restart.help.ts +10 -0
- package/lib/routes/gateway/restart.ts +35 -0
- package/lib/routes/gateway/routes.ts +18 -0
- package/lib/routes/gateway/run.help.ts +12 -0
- package/lib/routes/gateway/run.ts +35 -0
- package/lib/routes/gateway/start.help.ts +15 -0
- package/lib/routes/gateway/start.ts +32 -0
- package/lib/routes/gateway/status.help.ts +9 -0
- package/lib/routes/gateway/status.ts +28 -0
- package/lib/routes/gateway/stop.help.ts +8 -0
- package/lib/routes/gateway/stop.ts +21 -0
- package/lib/routes/repos/add.help.ts +5 -0
- package/lib/routes/repos/add.ts +19 -0
- package/lib/routes/repos/group.help.ts +11 -0
- package/lib/routes/repos/group.ts +18 -0
- package/lib/routes/repos/remove.help.ts +3 -0
- package/lib/routes/repos/remove.ts +17 -0
- package/lib/routes/repos/rename.help.ts +5 -0
- package/lib/routes/repos/rename.ts +17 -0
- package/lib/routes/repos/routes.ts +17 -0
- package/lib/routes/repos/set.help.ts +5 -0
- package/lib/routes/repos/set.ts +21 -0
- package/lib/routes/repos/show.help.ts +1 -0
- package/lib/routes/repos/show.ts +19 -0
- package/lib/routes/status/routes.ts +4 -0
- package/lib/routes/status/status.help.ts +6 -0
- package/lib/routes/status/status.ts +77 -0
- package/lib/routes.ts +36 -0
- package/package.json +65 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { zValidator as zv } from "@hono/zod-validator"
|
|
2
|
+
import { HTTPException } from "hono/http-exception"
|
|
3
|
+
import type { ZodType } from "zod"
|
|
4
|
+
|
|
5
|
+
export const zValidator = <Target extends "param" | "query" | "json", T extends ZodType>(
|
|
6
|
+
target: Target,
|
|
7
|
+
schema: T,
|
|
8
|
+
helpText?: string,
|
|
9
|
+
) =>
|
|
10
|
+
zv(target, schema, (result, c) => {
|
|
11
|
+
if (helpText && c.req.query("help")) {
|
|
12
|
+
return c.text(helpText)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (result.success) return
|
|
16
|
+
|
|
17
|
+
const issue = result.error.issues[0]
|
|
18
|
+
|
|
19
|
+
if (!issue) {
|
|
20
|
+
throw new HTTPException(400, { message: "invalid request" })
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const path = issue.path.join(".")
|
|
24
|
+
const message = path ? `${path}: ${issue.message}` : issue.message
|
|
25
|
+
|
|
26
|
+
throw new HTTPException(400, { message })
|
|
27
|
+
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { homedir } from "node:os"
|
|
2
|
+
import { dirname, join } from "node:path"
|
|
3
|
+
import { FunnelFileSystem } from "@/modules/fs/funnel-file-system"
|
|
4
|
+
import { NodeFunnelFileSystem } from "@/modules/fs/node-funnel-file-system"
|
|
5
|
+
import { FunnelSettingsReader } from "@/modules/settings/funnel-settings-reader"
|
|
6
|
+
import { settingsSchema } from "@/modules/settings/settings-schema"
|
|
7
|
+
import type { Settings } from "@/modules/settings/settings-schema"
|
|
8
|
+
|
|
9
|
+
export const FUNNEL_DIR = join(homedir(), ".funnel")
|
|
10
|
+
export const SETTINGS_PATH = join(FUNNEL_DIR, "settings.json")
|
|
11
|
+
|
|
12
|
+
type Deps = {
|
|
13
|
+
path?: string
|
|
14
|
+
fs?: FunnelFileSystem
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const defaultFs = new NodeFunnelFileSystem()
|
|
18
|
+
|
|
19
|
+
export class FunnelSettingsStore extends FunnelSettingsReader {
|
|
20
|
+
private readonly path: string
|
|
21
|
+
private readonly fs: FunnelFileSystem
|
|
22
|
+
|
|
23
|
+
constructor(deps: Deps = {}) {
|
|
24
|
+
super()
|
|
25
|
+
this.path = deps.path ?? SETTINGS_PATH
|
|
26
|
+
this.fs = deps.fs ?? defaultFs
|
|
27
|
+
Object.freeze(this)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
read(): Settings {
|
|
31
|
+
if (!this.fs.existsSync(this.path)) {
|
|
32
|
+
return {
|
|
33
|
+
connectors: [],
|
|
34
|
+
channels: [],
|
|
35
|
+
repositories: [],
|
|
36
|
+
agents: [],
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const content = this.fs.readFileSync(this.path)
|
|
41
|
+
const parsed = JSON.parse(content)
|
|
42
|
+
const result = settingsSchema.safeParse(parsed)
|
|
43
|
+
|
|
44
|
+
if (!result.success) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`invalid settings.json (${this.path}): ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ")}`,
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return result.data
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
write(settings: Settings): void {
|
|
54
|
+
this.fs.mkdirSync(dirname(this.path), { recursive: true })
|
|
55
|
+
this.fs.writeFileSync(this.path, `${JSON.stringify(settings, null, 2)}\n`)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FunnelSettingsReader } from "@/modules/settings/funnel-settings-reader"
|
|
2
|
+
import type { Settings } from "@/modules/settings/settings-schema"
|
|
3
|
+
|
|
4
|
+
export const createSettings = (partial: Partial<Settings> = {}): Settings => ({
|
|
5
|
+
connectors: [],
|
|
6
|
+
channels: [],
|
|
7
|
+
repositories: [],
|
|
8
|
+
agents: [],
|
|
9
|
+
...partial,
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export class MockFunnelSettingsReader extends FunnelSettingsReader {
|
|
13
|
+
private state: Settings
|
|
14
|
+
|
|
15
|
+
constructor(initial?: Partial<Settings>) {
|
|
16
|
+
super()
|
|
17
|
+
this.state = createSettings(initial)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
read(): Settings {
|
|
21
|
+
return this.state
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
write(settings: Settings): void {
|
|
25
|
+
this.state = settings
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
|
|
3
|
+
export const slackConnectorSchema = z.object({
|
|
4
|
+
type: z.literal("slack"),
|
|
5
|
+
name: z.string(),
|
|
6
|
+
botToken: z.string().startsWith("xoxb-"),
|
|
7
|
+
appToken: z.string().startsWith("xapp-"),
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export type SlackConnectorConfig = z.infer<typeof slackConnectorSchema>
|
|
11
|
+
|
|
12
|
+
export const ghConnectorSchema = z.object({
|
|
13
|
+
type: z.literal("gh"),
|
|
14
|
+
name: z.string(),
|
|
15
|
+
pollInterval: z.number().int().positive().optional(),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export type GhConnectorConfig = z.infer<typeof ghConnectorSchema>
|
|
19
|
+
|
|
20
|
+
export const discordConnectorSchema = z.object({
|
|
21
|
+
type: z.literal("discord"),
|
|
22
|
+
name: z.string(),
|
|
23
|
+
botToken: z.string().min(10),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export type DiscordConnectorConfig = z.infer<typeof discordConnectorSchema>
|
|
27
|
+
|
|
28
|
+
export const connectorConfigSchema = z.discriminatedUnion("type", [
|
|
29
|
+
slackConnectorSchema,
|
|
30
|
+
ghConnectorSchema,
|
|
31
|
+
discordConnectorSchema,
|
|
32
|
+
])
|
|
33
|
+
|
|
34
|
+
export type ConnectorConfig = z.infer<typeof connectorConfigSchema>
|
|
35
|
+
|
|
36
|
+
export const channelConfigSchema = z.object({
|
|
37
|
+
name: z.string(),
|
|
38
|
+
connectors: z.array(z.string()).default([]),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
export type ChannelConfig = z.infer<typeof channelConfigSchema>
|
|
42
|
+
|
|
43
|
+
export const repositoryConfigSchema = z.object({
|
|
44
|
+
name: z.string(),
|
|
45
|
+
path: z.string(),
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
export type RepositoryConfig = z.infer<typeof repositoryConfigSchema>
|
|
49
|
+
|
|
50
|
+
export const agentConfigSchema = z.object({
|
|
51
|
+
name: z.string(),
|
|
52
|
+
channel: z.string(),
|
|
53
|
+
repo: z.string().optional(),
|
|
54
|
+
subAgent: z.string().optional(),
|
|
55
|
+
envFiles: z.array(z.string()).optional(),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
export type AgentConfig = z.infer<typeof agentConfigSchema>
|
|
59
|
+
|
|
60
|
+
export const settingsSchema = z.object({
|
|
61
|
+
connectors: z.array(connectorConfigSchema).default([]),
|
|
62
|
+
channels: z.array(channelConfigSchema).default([]),
|
|
63
|
+
repositories: z.array(repositoryConfigSchema).default([]),
|
|
64
|
+
agents: z.array(agentConfigSchema).default([]),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
export type Settings = z.infer<typeof settingsSchema>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/react"
|
|
2
|
+
|
|
3
|
+
const zinc = {
|
|
4
|
+
50: "#fafafa",
|
|
5
|
+
600: "#52525b",
|
|
6
|
+
950: "#09090b",
|
|
7
|
+
} as const
|
|
8
|
+
|
|
9
|
+
const LABEL = "funnel"
|
|
10
|
+
|
|
11
|
+
export function App() {
|
|
12
|
+
const renderer = useRenderer()
|
|
13
|
+
|
|
14
|
+
const dimensions = useTerminalDimensions()
|
|
15
|
+
|
|
16
|
+
useKeyboard((key) => {
|
|
17
|
+
if (key.name === "q" || key.name === "escape" || (key.ctrl && key.name === "c")) {
|
|
18
|
+
renderer.destroy()
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const marginLeft = Math.max(0, Math.floor((dimensions.width - LABEL.length) / 2))
|
|
23
|
+
|
|
24
|
+
const marginTop = Math.max(0, Math.floor(dimensions.height / 2) - 1)
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<box
|
|
28
|
+
style={{
|
|
29
|
+
width: "100%",
|
|
30
|
+
height: "100%",
|
|
31
|
+
flexDirection: "column",
|
|
32
|
+
backgroundColor: zinc[950],
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
<box style={{ height: marginTop, backgroundColor: zinc[950] }} />
|
|
36
|
+
<text fg={zinc[50]} bg={zinc[950]} style={{ marginLeft }}>
|
|
37
|
+
{LABEL}
|
|
38
|
+
</text>
|
|
39
|
+
<text fg={zinc[600]} bg={zinc[950]} position="absolute" bottom={0}>
|
|
40
|
+
press q to quit
|
|
41
|
+
</text>
|
|
42
|
+
</box>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createCliRenderer } from "@opentui/core"
|
|
2
|
+
import { createRoot } from "@opentui/react"
|
|
3
|
+
import { App } from "@/modules/tui/app"
|
|
4
|
+
|
|
5
|
+
export async function launchTui(): Promise<void> {
|
|
6
|
+
const renderer = await createCliRenderer()
|
|
7
|
+
|
|
8
|
+
createRoot(renderer).render(<App />)
|
|
9
|
+
|
|
10
|
+
await new Promise<void>((resolve) => {
|
|
11
|
+
renderer.once("destroy", () => resolve())
|
|
12
|
+
})
|
|
13
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import { factory } from "@/factory"
|
|
3
|
+
import { zValidator } from "@/modules/router/validator"
|
|
4
|
+
import { help } from "@/routes/agents/add.help"
|
|
5
|
+
|
|
6
|
+
export const agentsAddHandler = factory.createHandlers(
|
|
7
|
+
zValidator("param", z.object({ name: z.string() })),
|
|
8
|
+
zValidator(
|
|
9
|
+
"query",
|
|
10
|
+
z.object({
|
|
11
|
+
channel: z.string(),
|
|
12
|
+
repo: z.string().optional(),
|
|
13
|
+
"sub-agent": z.string().optional(),
|
|
14
|
+
"env-file": z.string().optional(),
|
|
15
|
+
}),
|
|
16
|
+
help,
|
|
17
|
+
),
|
|
18
|
+
(c) => {
|
|
19
|
+
const param = c.req.valid("param")
|
|
20
|
+
const query = c.req.valid("query")
|
|
21
|
+
const funnel = c.var.funnel
|
|
22
|
+
|
|
23
|
+
funnel.agents.add({
|
|
24
|
+
name: param.name,
|
|
25
|
+
channel: query.channel,
|
|
26
|
+
repo: query.repo,
|
|
27
|
+
subAgent: query["sub-agent"],
|
|
28
|
+
envFiles: query["env-file"] ? [query["env-file"]] : undefined,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return c.text(`added agent "${param.name}"`)
|
|
32
|
+
},
|
|
33
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
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`
|
|
@@ -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/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
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { HTTPException } from "hono/http-exception"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { factory } from "@/factory"
|
|
4
|
+
import { queryToCliArgs } from "@/modules/router/query-to-cli-args"
|
|
5
|
+
import { zValidator } from "@/modules/router/validator"
|
|
6
|
+
import { help } from "@/routes/agents/launch.help"
|
|
7
|
+
|
|
8
|
+
const RESERVED_KEYS = ["channel", "repo", "sub-agent", "env-file"]
|
|
9
|
+
|
|
10
|
+
export const agentsLaunchHandler = factory.createHandlers(
|
|
11
|
+
zValidator("param", z.object({ name: z.string() })),
|
|
12
|
+
zValidator("query", z.object({}).passthrough(), help),
|
|
13
|
+
async (c) => {
|
|
14
|
+
const param = c.req.valid("param")
|
|
15
|
+
const funnel = c.var.funnel
|
|
16
|
+
const agent = funnel.agents.get(param.name)
|
|
17
|
+
|
|
18
|
+
if (!agent) throw new HTTPException(404, { message: `agent "${param.name}" not found` })
|
|
19
|
+
|
|
20
|
+
const overrideChannel = c.req.query("channel")
|
|
21
|
+
const overrideRepo = c.req.query("repo")
|
|
22
|
+
const overrideSubAgent = c.req.query("sub-agent")
|
|
23
|
+
const overrideEnvFile = c.req.query("env-file")
|
|
24
|
+
|
|
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,
|
|
30
|
+
userArgs: queryToCliArgs(c.req.url, RESERVED_KEYS),
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
process.exit(exitCode)
|
|
34
|
+
},
|
|
35
|
+
)
|
|
@@ -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/agents/remove.help"
|
|
5
|
+
|
|
6
|
+
export const agentsRemoveHandler = 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.agents.remove(param.name)
|
|
14
|
+
|
|
15
|
+
return c.text(`removed agent "${param.name}"`)
|
|
16
|
+
},
|
|
17
|
+
)
|
|
@@ -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/agents/rename.help"
|
|
5
|
+
|
|
6
|
+
export const agentsRenameHandler = 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.agents.rename(param.name, param["newName"])
|
|
14
|
+
|
|
15
|
+
return c.text(`renamed agent "${param.name}" to "${param["newName"]}"`)
|
|
16
|
+
},
|
|
17
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
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)
|
|
@@ -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/agents/set.help"
|
|
5
|
+
|
|
6
|
+
export const agentsSetHandler = factory.createHandlers(
|
|
7
|
+
zValidator("param", z.object({ name: z.string() })),
|
|
8
|
+
zValidator(
|
|
9
|
+
"query",
|
|
10
|
+
z.object({
|
|
11
|
+
channel: z.string().optional(),
|
|
12
|
+
repo: z.string().optional(),
|
|
13
|
+
"sub-agent": z.string().optional(),
|
|
14
|
+
"env-file": z.string().optional(),
|
|
15
|
+
}),
|
|
16
|
+
help,
|
|
17
|
+
),
|
|
18
|
+
(c) => {
|
|
19
|
+
const param = c.req.valid("param")
|
|
20
|
+
const query = c.req.valid("query")
|
|
21
|
+
const funnel = c.var.funnel
|
|
22
|
+
|
|
23
|
+
funnel.agents.update(param.name, {
|
|
24
|
+
channel: query.channel,
|
|
25
|
+
repo: query.repo,
|
|
26
|
+
subAgent: query["sub-agent"],
|
|
27
|
+
envFiles: query["env-file"] !== undefined ? [query["env-file"]] : undefined,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
return c.text(`updated agent "${param.name}"`)
|
|
31
|
+
},
|
|
32
|
+
)
|
|
@@ -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/channels/add.help"
|
|
5
|
+
|
|
6
|
+
export const channelsAddHandler = factory.createHandlers(
|
|
7
|
+
zValidator("param", z.object({ name: z.string() })),
|
|
8
|
+
zValidator("query", z.object({ connector: z.string().optional() }), help),
|
|
9
|
+
(c) => {
|
|
10
|
+
const param = c.req.valid("param")
|
|
11
|
+
const query = c.req.valid("query")
|
|
12
|
+
const funnel = c.var.funnel
|
|
13
|
+
|
|
14
|
+
funnel.channels.add({
|
|
15
|
+
name: param.name,
|
|
16
|
+
connectors: query.connector ? [query.connector] : [],
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
return c.text(`added channel "${param.name}"`)
|
|
20
|
+
},
|
|
21
|
+
)
|
|
@@ -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/channels/connectors-attach.help"
|
|
5
|
+
|
|
6
|
+
export const channelsConnectorsAttachHandler = factory.createHandlers(
|
|
7
|
+
zValidator("param", z.object({ name: z.string(), connector: 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.channels.attachConnector(param.name, param.connector)
|
|
14
|
+
|
|
15
|
+
return c.text(`attached connector "${param.connector}" to channel "${param.name}"`)
|
|
16
|
+
},
|
|
17
|
+
)
|
|
@@ -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/channels/connectors-detach.help"
|
|
5
|
+
|
|
6
|
+
export const channelsConnectorsDetachHandler = factory.createHandlers(
|
|
7
|
+
zValidator("param", z.object({ name: z.string(), connector: 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.channels.detachConnector(param.name, param.connector)
|
|
14
|
+
|
|
15
|
+
return c.text(`detached connector "${param.connector}" from channel "${param.name}"`)
|
|
16
|
+
},
|
|
17
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const help = `funnel channels — manage subscription boxes
|
|
2
|
+
|
|
3
|
+
usage: funnel channels [subcommand]
|
|
4
|
+
|
|
5
|
+
subcommands:
|
|
6
|
+
(none) list
|
|
7
|
+
add <name> add
|
|
8
|
+
remove <name> remove
|
|
9
|
+
<name> show details
|
|
10
|
+
<name> connectors attach <c> subscribe to a connector
|
|
11
|
+
<name> connectors detach <c> unsubscribe from a connector
|
|
12
|
+
|
|
13
|
+
examples:
|
|
14
|
+
funnel channels add prod-inbox
|
|
15
|
+
funnel channels prod-inbox connectors attach prod-slack
|
|
16
|
+
funnel channels prod-inbox`
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import { factory } from "@/factory"
|
|
3
|
+
import { zValidator } from "@/modules/router/validator"
|
|
4
|
+
import { help } from "@/routes/channels/group.help"
|
|
5
|
+
|
|
6
|
+
export const channelsGroupHandler = factory.createHandlers(
|
|
7
|
+
zValidator("query", z.object({}), help),
|
|
8
|
+
(c) => {
|
|
9
|
+
const funnel = c.var.funnel
|
|
10
|
+
const channels = funnel.channels.list()
|
|
11
|
+
|
|
12
|
+
if (channels.length === 0) return c.text("no channels")
|
|
13
|
+
|
|
14
|
+
const lines = channels.map((ch) => {
|
|
15
|
+
const connectors = ch.connectors.length > 0 ? ch.connectors.join(", ") : "(none)"
|
|
16
|
+
|
|
17
|
+
return `${ch.name} [${connectors}]`
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
return c.text(lines.join("\n"))
|
|
21
|
+
},
|
|
22
|
+
)
|
|
@@ -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/channels/remove.help"
|
|
5
|
+
|
|
6
|
+
export const channelsRemoveHandler = 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.channels.remove(param.name)
|
|
14
|
+
|
|
15
|
+
return c.text(`removed channel "${param.name}"`)
|
|
16
|
+
},
|
|
17
|
+
)
|
|
@@ -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/channels/rename.help"
|
|
5
|
+
|
|
6
|
+
export const channelsRenameHandler = 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.channels.rename(param.name, param["newName"])
|
|
14
|
+
|
|
15
|
+
return c.text(`renamed channel "${param.name}" to "${param["newName"]}"`)
|
|
16
|
+
},
|
|
17
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { factory } from "@/factory"
|
|
2
|
+
import { channelsAddHandler } from "@/routes/channels/add"
|
|
3
|
+
import { channelsConnectorsAttachHandler } from "@/routes/channels/connectors-attach"
|
|
4
|
+
import { channelsConnectorsDetachHandler } from "@/routes/channels/connectors-detach"
|
|
5
|
+
import { channelsGroupHandler } from "@/routes/channels/group"
|
|
6
|
+
import { channelsRemoveHandler } from "@/routes/channels/remove"
|
|
7
|
+
import { channelsRenameHandler } from "@/routes/channels/rename"
|
|
8
|
+
import { channelsShowHandler } from "@/routes/channels/show"
|
|
9
|
+
|
|
10
|
+
export const channelsRoutes = factory
|
|
11
|
+
.createApp()
|
|
12
|
+
.get("/", ...channelsGroupHandler)
|
|
13
|
+
.put("/:name/rename/:newName", ...channelsRenameHandler)
|
|
14
|
+
.put("/rename/:name/:newName", ...channelsRenameHandler)
|
|
15
|
+
.put("/:name/connectors/attach/:connector", ...channelsConnectorsAttachHandler)
|
|
16
|
+
.delete("/:name/connectors/detach/:connector", ...channelsConnectorsDetachHandler)
|
|
17
|
+
.post("/:name", ...channelsAddHandler)
|
|
18
|
+
.delete("/:name", ...channelsRemoveHandler)
|
|
19
|
+
.get("/:name", ...channelsShowHandler)
|