@interactive-inc/claude-funnel 0.41.0 → 0.50.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 +34 -9
- package/dist/bin.js +255 -256
- package/dist/claude-CB1WkV77.d.ts +115 -0
- package/dist/claude.d.ts +59 -0
- package/dist/claude.js +322 -0
- package/dist/{connector-diagnostic-log-OPpPi9V9.d.ts → connector-diagnostic-log-yTOojKUR.d.ts} +14 -14
- package/dist/{logger-Czli2OKh.js → connector-listener-DU54DN-f.js} +1 -9
- package/dist/connectors/discord.d.ts +3 -3
- package/dist/connectors/discord.js +2 -1
- package/dist/connectors/gh.d.ts +4 -3
- package/dist/connectors/gh.js +2 -1
- package/dist/connectors/schedule.d.ts +1 -1
- package/dist/connectors/schedule.js +2 -1
- package/dist/connectors/slack.d.ts +2 -2
- package/dist/connectors/slack.js +2 -1
- package/dist/discord-connector-schema-CBDyGdOI.js +21 -0
- package/dist/{discord-connector-schema-BeThExJp.js → discord-listener-_jSE3HsQ.js} +2 -22
- package/dist/file-system-BeOKXjlV.d.ts +26 -0
- package/dist/file-system-PWKKU7lA.js +9 -0
- package/dist/gateway/daemon.js +151 -152
- package/dist/gateway.d.ts +3 -0
- package/dist/gateway.js +2 -0
- package/dist/gh-connector-schema-eoTtHbY6.d.ts +14 -0
- package/dist/{gh-connector-schema-eYE4g77K.js → gh-connector-schema-o3Q1-ojL.js} +1 -176
- package/dist/gh-listener-DH-fClQm.js +178 -0
- package/dist/index-ChomoTZ5.d.ts +3404 -0
- package/dist/index.d.ts +11 -4214
- package/dist/index.js +195 -3869
- package/dist/local-config-json-schema-8IHjS4Q7.js +439 -0
- package/dist/local-config-sync-BdsrDZOu.d.ts +381 -0
- package/dist/local-config.d.ts +3 -0
- package/dist/local-config.js +3 -0
- package/dist/logger-BP6SisKt.js +9 -0
- package/dist/mcp-Dr-nIBwN.js +253 -0
- package/dist/memory-connector-diagnostic-log-CrW1ltLM.js +2245 -0
- package/dist/memory-token-prompter-B5FFCsGP.d.ts +57 -0
- package/dist/memory-token-prompter-CLerGsgM.js +61 -0
- package/dist/node-file-system-BcrmWN9I.js +48 -0
- package/dist/{gh-connector-schema-CQmEWzdV.d.ts → process-runner-DfniuWVU.d.ts} +1 -14
- package/dist/profiles-f0mNmEyP.d.ts +64 -0
- package/dist/profiles-wMRnjSid.js +129 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +2 -0
- package/dist/schedule-connector-schema-iCI61gzU.js +31 -0
- package/dist/{schedule-listener-3M6WkH1Y.d.ts → schedule-listener-CUyUFFR1.d.ts} +22 -46
- package/dist/{schedule-connector-schema-CM-sRkac.js → schedule-listener-ePAjians.js} +3 -86
- package/dist/settings-reader-BSU6JyvM.d.ts +167 -0
- package/dist/settings-reader-DPqrpV7s.js +11 -0
- package/dist/settings-store-D2XSXTyt.js +186 -0
- package/dist/slack-connector-schema-BCNWluHM.js +32 -0
- package/dist/{slack-listener-9UdAn_ui.d.ts → slack-listener-Bv5xI9gC.d.ts} +31 -31
- package/dist/{slack-connector-schema-DDbSGPZn.js → slack-listener-ClQuHhEF.js} +2 -32
- package/package.json +16 -1
- /package/dist/{connector-adapter-VA6undzc.d.ts → connector-adapter-DKgsVuMH.d.ts} +0 -0
- /package/dist/{discord-connector-schema-DF4pL3Sc.d.ts → discord-connector-schema-R0Uu-3ns.d.ts} +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { n as FunnelIdGenerator, r as ChannelConfig } from "./settings-reader-BSU6JyvM.js";
|
|
2
|
+
import { S as FunnelLogger } from "./connector-diagnostic-log-yTOojKUR.js";
|
|
3
|
+
import { r as FunnelProcessRunner } from "./process-runner-DfniuWVU.js";
|
|
4
|
+
|
|
5
|
+
//#region lib/engine/claude/channel-resolver.d.ts
|
|
6
|
+
type ChannelResolver = {
|
|
7
|
+
get(name: string): ChannelConfig | null;
|
|
8
|
+
getById(id: string): ChannelConfig | null;
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region lib/engine/claude/gateway-controller.d.ts
|
|
12
|
+
type GatewayController = {
|
|
13
|
+
isRunning(): boolean;
|
|
14
|
+
start(options?: {
|
|
15
|
+
caffeinate?: boolean;
|
|
16
|
+
}): Promise<boolean>;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region lib/engine/claude/mcp-installer.d.ts
|
|
20
|
+
type McpInstaller = {
|
|
21
|
+
findInstalledName(cwd: string): string | null;
|
|
22
|
+
install(cwd: string): void;
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region lib/engine/claude/process-guard.d.ts
|
|
26
|
+
type ProcessGuard = {
|
|
27
|
+
/** Returns true if a live process is already registered for this profile. */isRunning(profileId: string): boolean; /** Writes the PID file and registers an exit hook to clean it up. */
|
|
28
|
+
acquire(profileId: string): void; /** Removes the PID file. */
|
|
29
|
+
release(profileId: string): void;
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region lib/engine/claude/session-store.d.ts
|
|
33
|
+
type SessionStore = {
|
|
34
|
+
getSessionId(profileId: string): string | null;
|
|
35
|
+
setSessionId(profileId: string, sessionId: string): void; /** Returns true when the session jsonl exists on disk and is non-empty. */
|
|
36
|
+
sessionFileExists(cwd: string, sessionId: string, env: Record<string, string>): boolean;
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region lib/engine/claude/claude.d.ts
|
|
40
|
+
type LaunchOptions = {
|
|
41
|
+
channel: string;
|
|
42
|
+
cwd?: string;
|
|
43
|
+
userArgs?: string[];
|
|
44
|
+
/** Stable id of the launching profile (uuid). Keys the singleton PID file and
|
|
45
|
+
* the resumable session. Absent for a profile-less launch (raw `--channel`),
|
|
46
|
+
* which never enforces singleton-ness and never resumes. */
|
|
47
|
+
profileId?: string; /** Args prepended to the claude argv (typically a profile's recipe). Defaults to none. */
|
|
48
|
+
options?: string[]; /** Env vars layered under the launched claude process. process.env wins on collision. */
|
|
49
|
+
env?: Record<string, string>;
|
|
50
|
+
/** Whether to inject a `--session-id`/`--resume` for this profile.
|
|
51
|
+
* Defaults to false: resuming is opt-in and only meaningful for a profile,
|
|
52
|
+
* since the persisted session is owned by the profile (by id). A launch
|
|
53
|
+
* without a profile always starts a fresh session regardless of this flag. */
|
|
54
|
+
resume?: boolean;
|
|
55
|
+
/** Invoked synchronously after the child claude process has been spawned, with its PID.
|
|
56
|
+
* Useful for hosts that need to register the spawned process before it exits
|
|
57
|
+
* (e.g. multi-session registries that track per-claude liveness). */
|
|
58
|
+
onSpawned?: (pid: number) => void;
|
|
59
|
+
/** Whether to install the funnel MCP entry into `.mcp.json` (default: true).
|
|
60
|
+
* Set to false when the host already provides its own MCP server entry and
|
|
61
|
+
* does not need the funnel binary as an MCP endpoint. */
|
|
62
|
+
installMcp?: boolean;
|
|
63
|
+
};
|
|
64
|
+
type Deps = {
|
|
65
|
+
channels: ChannelResolver;
|
|
66
|
+
mcp: McpInstaller;
|
|
67
|
+
gateway: GatewayController;
|
|
68
|
+
sessions: SessionStore;
|
|
69
|
+
guard: ProcessGuard;
|
|
70
|
+
process?: FunnelProcessRunner;
|
|
71
|
+
idGenerator?: FunnelIdGenerator;
|
|
72
|
+
logger?: FunnelLogger;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Launches Claude Code with funnel pre-wired: ensures the gateway is running,
|
|
76
|
+
* installs the funnel MCP into the target repo's `.mcp.json` if missing,
|
|
77
|
+
* injects `FUNNEL_CHANNEL_ID` into the child env, and delegates singleton
|
|
78
|
+
* enforcement to a ProcessGuard.
|
|
79
|
+
*/
|
|
80
|
+
declare class FunnelClaude {
|
|
81
|
+
private readonly channels;
|
|
82
|
+
private readonly mcp;
|
|
83
|
+
private readonly gateway;
|
|
84
|
+
private readonly sessions;
|
|
85
|
+
private readonly guard;
|
|
86
|
+
private readonly process;
|
|
87
|
+
private readonly idGenerator;
|
|
88
|
+
private readonly logger;
|
|
89
|
+
constructor(deps: Deps);
|
|
90
|
+
launch(options: LaunchOptions): Promise<number>;
|
|
91
|
+
private buildArgs;
|
|
92
|
+
/**
|
|
93
|
+
* Decides whether funnel should resume an existing claude session or start
|
|
94
|
+
* a freshly minted one. Backs off when the user already passed a
|
|
95
|
+
* session-shaping flag, since combining them would either confuse claude
|
|
96
|
+
* or override the explicit user intent.
|
|
97
|
+
*
|
|
98
|
+
* The session is owned by the profile (by id), not by cwd: two profiles
|
|
99
|
+
* pointing at the same repo each keep their own conversation, and a launch
|
|
100
|
+
* with no profile never resumes — so an unrelated session in the same repo
|
|
101
|
+
* can't bleed in. The channel never enters into it; sessions belong to the
|
|
102
|
+
* launch layer (profiles), keeping the transport layer ignorant of them.
|
|
103
|
+
*
|
|
104
|
+
* A persisted id is only resumed when its session jsonl still exists on
|
|
105
|
+
* disk. claude errors out on `--resume <id>` for a missing conversation, and
|
|
106
|
+
* a persisted id can outlive its jsonl (claude pruned it, or the very first
|
|
107
|
+
* launch was aborted after the id was written but before the jsonl
|
|
108
|
+
* appeared). When the file is gone we mint a fresh session instead, which
|
|
109
|
+
* overwrites the dangling entry — so the store self-heals.
|
|
110
|
+
*/
|
|
111
|
+
private resolveSession;
|
|
112
|
+
private buildEnv;
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
export { McpInstaller as a, ProcessGuard as i, LaunchOptions as n, GatewayController as o, SessionStore as r, ChannelResolver as s, FunnelClaude as t };
|
package/dist/claude.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { a as McpInstaller, i as ProcessGuard, n as LaunchOptions, o as GatewayController, r as SessionStore, s as ChannelResolver, t as FunnelClaude } from "./claude-CB1WkV77.js";
|
|
2
|
+
import { r as FunnelProcessRunner } from "./process-runner-DfniuWVU.js";
|
|
3
|
+
import { n as FunnelFileSystem } from "./file-system-BeOKXjlV.js";
|
|
4
|
+
import { t as FunnelProfiles } from "./profiles-f0mNmEyP.js";
|
|
5
|
+
import { C as profileSpecSchema, S as localConfigSchema, _ as LOCAL_CONFIG_FILENAME, b as channelSpecSchema, g as ConnectorSpec, h as ChannelSpec, i as FunnelTokenPrompter, m as FunnelLocalConfig, n as FunnelLocalConfigSync, r as LocalConfigSyncResult, t as ConnectorSyncOutcome, v as LocalConfig, x as connectorSpecSchema, y as ProfileSpec } from "./local-config-sync-BdsrDZOu.js";
|
|
6
|
+
import { i as funnelJsonSchema, n as NodeFunnelTokenPrompter, r as FunnelLocalConfigWriter, t as MemoryFunnelTokenPrompter } from "./memory-token-prompter-B5FFCsGP.js";
|
|
7
|
+
|
|
8
|
+
//#region lib/engine/claude/file-process-guard.d.ts
|
|
9
|
+
type Deps$1 = {
|
|
10
|
+
fs?: FunnelFileSystem;
|
|
11
|
+
process?: FunnelProcessRunner;
|
|
12
|
+
dir?: string;
|
|
13
|
+
};
|
|
14
|
+
declare class FileProcessGuard implements ProcessGuard {
|
|
15
|
+
private readonly fs;
|
|
16
|
+
private readonly process;
|
|
17
|
+
private readonly pidDir;
|
|
18
|
+
constructor(deps?: Deps$1);
|
|
19
|
+
isRunning(profileId: string): boolean;
|
|
20
|
+
acquire(profileId: string): void;
|
|
21
|
+
release(profileId: string): void;
|
|
22
|
+
private pidPath;
|
|
23
|
+
private readPid;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region lib/engine/mcp/mcp.d.ts
|
|
27
|
+
declare const FUNNEL_MCP_COMMAND = "bun";
|
|
28
|
+
declare const FUNNEL_MCP_ARGS: string[];
|
|
29
|
+
declare const FUNNEL_MCP_NAME = "funnel";
|
|
30
|
+
type Deps = {
|
|
31
|
+
fs?: FunnelFileSystem;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Installs/uninstalls the funnel MCP entry into a target repository's
|
|
35
|
+
* `.mcp.json`. Detects an existing entry by command match so renaming is
|
|
36
|
+
* preserved across re-installs.
|
|
37
|
+
*/
|
|
38
|
+
declare class FunnelMcp {
|
|
39
|
+
private readonly fs;
|
|
40
|
+
constructor(deps?: Deps);
|
|
41
|
+
install(repoPath: string): void;
|
|
42
|
+
uninstall(repoPath: string): void;
|
|
43
|
+
findInstalledName(cwd: string): string | null;
|
|
44
|
+
private findServerName;
|
|
45
|
+
private isFunnelEntry;
|
|
46
|
+
private readConfig;
|
|
47
|
+
private writeConfig;
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region lib/engine/mcp/channel-server.d.ts
|
|
51
|
+
type ChannelServerOptions = {
|
|
52
|
+
/** Funnel home directory (settings.json + gateway.token). Defaults to ~/.funnel. */dir?: string; /** Gateway base URL. Defaults to `$FUNNEL_GATEWAY_URL` or `http://127.0.0.1:<port>`. */
|
|
53
|
+
gatewayUrl?: string; /** Channel id to subscribe to. Defaults to `$FUNNEL_CHANNEL_ID`. */
|
|
54
|
+
channelId?: string; /** Auth token. Defaults to `$FUNNEL_GATEWAY_TOKEN` then `<dir>/gateway.token`. */
|
|
55
|
+
token?: string;
|
|
56
|
+
};
|
|
57
|
+
declare const startChannelServer: (options?: ChannelServerOptions) => Promise<void>;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { ChannelResolver, ChannelServerOptions, ChannelSpec, ConnectorSpec, ConnectorSyncOutcome, FUNNEL_MCP_ARGS, FUNNEL_MCP_COMMAND, FUNNEL_MCP_NAME, FileProcessGuard, FunnelClaude, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLocalConfigWriter, FunnelMcp, FunnelProfiles, FunnelTokenPrompter, GatewayController, LOCAL_CONFIG_FILENAME, LaunchOptions, LocalConfig, LocalConfigSyncResult, McpInstaller, MemoryFunnelTokenPrompter, NodeFunnelTokenPrompter, ProcessGuard, ProfileSpec, SessionStore, channelSpecSchema, connectorSpecSchema, funnelJsonSchema, localConfigSchema, profileSpecSchema, startChannelServer };
|
package/dist/claude.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { d as settingsSchema, o as resolveFunnelPort } from "./settings-store-D2XSXTyt.js";
|
|
2
|
+
import { a as FileProcessGuard, i as FunnelMcp, n as FUNNEL_MCP_COMMAND, o as FunnelClaude, r as FUNNEL_MCP_NAME, t as FUNNEL_MCP_ARGS } from "./mcp-Dr-nIBwN.js";
|
|
3
|
+
import { a as FunnelLocalConfig, c as connectorSpecSchema, i as FunnelTokenPrompter, l as localConfigSchema, n as NodeFunnelTokenPrompter, o as LOCAL_CONFIG_FILENAME, r as FunnelLocalConfigSync, s as channelSpecSchema, t as funnelJsonSchema, u as profileSpecSchema } from "./local-config-json-schema-8IHjS4Q7.js";
|
|
4
|
+
import { t as FunnelProfiles } from "./profiles-wMRnjSid.js";
|
|
5
|
+
import { n as FunnelLocalConfigWriter, t as MemoryFunnelTokenPrompter } from "./memory-token-prompter-CLerGsgM.js";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
//#region lib/engine/mcp/channel-subscriber.ts
|
|
13
|
+
const RECONNECT_DELAY = 1e3;
|
|
14
|
+
const MAX_RECONNECT_DELAY = 1e4;
|
|
15
|
+
/**
|
|
16
|
+
* Subscribes to the gateway WebSocket for a single channel and forwards
|
|
17
|
+
* incoming events to the MCP server as `notifications/claude/channel`.
|
|
18
|
+
* Reconnects with exponential backoff and replays missed events via `?since=<offset>`.
|
|
19
|
+
*/
|
|
20
|
+
var FunnelChannelSubscriber = class {
|
|
21
|
+
state = {
|
|
22
|
+
reconnectDelay: RECONNECT_DELAY,
|
|
23
|
+
lastOffset: 0
|
|
24
|
+
};
|
|
25
|
+
constructor(props) {
|
|
26
|
+
this.props = props;
|
|
27
|
+
Object.freeze(this);
|
|
28
|
+
}
|
|
29
|
+
start() {
|
|
30
|
+
this.connect();
|
|
31
|
+
}
|
|
32
|
+
connect() {
|
|
33
|
+
const sinceQuery = this.state.lastOffset > 0 ? `&since=${this.state.lastOffset}` : "";
|
|
34
|
+
const wsUrl = `${this.props.baseUrl}${sinceQuery}`;
|
|
35
|
+
const ws = new WebSocket(wsUrl, this.props.protocols);
|
|
36
|
+
ws.addEventListener("open", () => {
|
|
37
|
+
this.state.reconnectDelay = RECONNECT_DELAY;
|
|
38
|
+
process.stderr.write(`funnel: connected (${wsUrl})\n`);
|
|
39
|
+
});
|
|
40
|
+
ws.addEventListener("message", (event) => this.handleMessage(event));
|
|
41
|
+
ws.addEventListener("close", () => {
|
|
42
|
+
process.stderr.write(`funnel: disconnected, reconnecting in ${this.state.reconnectDelay}ms\n`);
|
|
43
|
+
setTimeout(() => this.connect(), this.state.reconnectDelay);
|
|
44
|
+
this.state.reconnectDelay = Math.min(this.state.reconnectDelay * 2, MAX_RECONNECT_DELAY);
|
|
45
|
+
});
|
|
46
|
+
ws.addEventListener("error", () => {});
|
|
47
|
+
}
|
|
48
|
+
async handleMessage(event) {
|
|
49
|
+
try {
|
|
50
|
+
const payload = JSON.parse(String(event.data));
|
|
51
|
+
const eventType = payload.meta?.event_type ?? "unknown";
|
|
52
|
+
if (typeof payload.offset === "number" && payload.offset > this.state.lastOffset) this.state.lastOffset = payload.offset;
|
|
53
|
+
process.stderr.write(`funnel: received event (${eventType})\n`);
|
|
54
|
+
await this.props.server.notification({
|
|
55
|
+
method: "notifications/claude/channel",
|
|
56
|
+
params: {
|
|
57
|
+
content: payload.content,
|
|
58
|
+
meta: payload.meta
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
process.stderr.write(`funnel: error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region lib/engine/mcp/read-channel-connectors.ts
|
|
68
|
+
const TOOL_CONNECTOR_TYPES = new Set([
|
|
69
|
+
"slack",
|
|
70
|
+
"gh",
|
|
71
|
+
"discord"
|
|
72
|
+
]);
|
|
73
|
+
const readChannelConnectors = (dir, channelId) => {
|
|
74
|
+
const settingsPath = join(dir, "settings.json");
|
|
75
|
+
if (!existsSync(settingsPath)) return null;
|
|
76
|
+
const raw = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
77
|
+
const parsed = settingsSchema.safeParse(raw);
|
|
78
|
+
if (!parsed.success) return null;
|
|
79
|
+
const channel = parsed.data.channels.find((c) => c.id === channelId);
|
|
80
|
+
if (!channel) return null;
|
|
81
|
+
const connectors = channel.connectors.filter((c) => TOOL_CONNECTOR_TYPES.has(c.type)).map((c) => ({
|
|
82
|
+
name: c.name,
|
|
83
|
+
type: c.type
|
|
84
|
+
}));
|
|
85
|
+
return {
|
|
86
|
+
channelName: channel.name,
|
|
87
|
+
connectors
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region lib/engine/mcp/read-gateway-token.ts
|
|
92
|
+
const readGatewayToken = (dir) => {
|
|
93
|
+
const fromEnv = process.env.FUNNEL_GATEWAY_TOKEN;
|
|
94
|
+
if (fromEnv && fromEnv.length > 0) return fromEnv;
|
|
95
|
+
const path = join(dir, "gateway.token");
|
|
96
|
+
if (!existsSync(path)) return null;
|
|
97
|
+
const value = readFileSync(path, "utf-8").trim();
|
|
98
|
+
return value.length > 0 ? value : null;
|
|
99
|
+
};
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region lib/engine/mcp/usage-hint-for-type.ts
|
|
102
|
+
const usageHintForType = (type) => {
|
|
103
|
+
if (type === "slack") return [
|
|
104
|
+
"Slack Web API.",
|
|
105
|
+
"To reply in the same thread: method=POST path=chat.postMessage body={ channel: meta.channel_id, text: \"...\", thread_ts: meta.thread_ts }",
|
|
106
|
+
"To react: method=POST path=reactions.add body={ channel: meta.channel_id, timestamp: meta.thread_ts, name: \"thumbsup\" }",
|
|
107
|
+
"Use meta fields from the incoming event: channel_id (Slack channel), thread_ts (thread anchor), user_id (sender)."
|
|
108
|
+
].join(" ");
|
|
109
|
+
if (type === "discord") return [
|
|
110
|
+
"Discord REST API.",
|
|
111
|
+
"To reply: method=POST path=/channels/<meta.channel_id>/messages body={ content: \"...\" }",
|
|
112
|
+
"Use meta fields: channel_id (Discord channel), user_id (sender), guild_id."
|
|
113
|
+
].join(" ");
|
|
114
|
+
if (type === "gh") return [
|
|
115
|
+
"GitHub REST via gh CLI.",
|
|
116
|
+
"To comment: method=POST path=repos/<meta.repository>/issues/<number>/comments body={ body: \"...\" }",
|
|
117
|
+
"Parse <number> from meta.subject_url. meta fields: repository (owner/repo), subject_type, subject_url, reason."
|
|
118
|
+
].join(" ");
|
|
119
|
+
return "Generic adapter call.";
|
|
120
|
+
};
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region lib/engine/mcp/channel-server.ts
|
|
123
|
+
const DEFAULT_FUNNEL_DIR = join(homedir(), ".funnel");
|
|
124
|
+
const BUILTIN_TOOL_NAMES = ["fnl_status", "fnl_debug"];
|
|
125
|
+
const isBuiltinTool = (name) => BUILTIN_TOOL_NAMES.includes(name);
|
|
126
|
+
const readAllChannels = (dir) => {
|
|
127
|
+
const settingsPath = join(dir, "settings.json");
|
|
128
|
+
if (!existsSync(settingsPath)) return [];
|
|
129
|
+
try {
|
|
130
|
+
const raw = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
131
|
+
const parsed = settingsSchema.safeParse(raw);
|
|
132
|
+
if (!parsed.success) return [];
|
|
133
|
+
return parsed.data.channels.map((c) => ({
|
|
134
|
+
id: c.id,
|
|
135
|
+
name: c.name
|
|
136
|
+
}));
|
|
137
|
+
} catch {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const startChannelServer = async (options = {}) => {
|
|
142
|
+
const dir = options.dir ?? DEFAULT_FUNNEL_DIR;
|
|
143
|
+
const gatewayBaseUrl = options.gatewayUrl ?? process.env.FUNNEL_GATEWAY_URL ?? `http://127.0.0.1:${resolveFunnelPort()}`;
|
|
144
|
+
const gatewayWsUrl = `${gatewayBaseUrl.replace(/^http/, "ws")}/ws`;
|
|
145
|
+
const channelId = options.channelId ?? process.env.FUNNEL_CHANNEL_ID;
|
|
146
|
+
const channel = channelId ? readChannelConnectors(dir, channelId) : null;
|
|
147
|
+
const token = options.token ?? readGatewayToken(dir);
|
|
148
|
+
const allChannels = readAllChannels(dir);
|
|
149
|
+
const currentChannelName = channel?.channelName ?? null;
|
|
150
|
+
const channelContext = allChannels.length > 0 ? [
|
|
151
|
+
"",
|
|
152
|
+
"Configured channels (use as the `channel` argument to fnl_debug):",
|
|
153
|
+
...allChannels.map((ch) => ` ${ch.name}${ch.name === currentChannelName ? " ← this session" : ""}`)
|
|
154
|
+
].join("\n") : "";
|
|
155
|
+
const server = new Server({
|
|
156
|
+
name: FUNNEL_MCP_NAME,
|
|
157
|
+
version: "1.0.0"
|
|
158
|
+
}, {
|
|
159
|
+
capabilities: {
|
|
160
|
+
experimental: { "claude/channel": {} },
|
|
161
|
+
tools: {}
|
|
162
|
+
},
|
|
163
|
+
instructions: [
|
|
164
|
+
`Events arrive as notifications (method: notifications/claude/channel) with two fields:`,
|
|
165
|
+
` content — the event payload as a JSON string (parse it to read the message)`,
|
|
166
|
+
` meta — key/value strings describing the event`,
|
|
167
|
+
"",
|
|
168
|
+
"meta fields by event_type:",
|
|
169
|
+
" slack: event_type=slack channel_id=C… thread_ts=1234.5678 user_id=U… mentioned=true|false",
|
|
170
|
+
" gh: event_type=gh repository=owner/repo subject_type=Issue|PullRequest subject_url=… reason=…",
|
|
171
|
+
" discord: event_type=discord channel_id=… user_id=… guild_id=… mentioned=true|false",
|
|
172
|
+
" schedule: event_type=schedule entry_id=…",
|
|
173
|
+
"",
|
|
174
|
+
"To reply to a Slack message in the same thread, call the connector tool with:",
|
|
175
|
+
` method: POST`,
|
|
176
|
+
` path: chat.postMessage`,
|
|
177
|
+
` body: { channel: meta.channel_id, text: "your reply", thread_ts: meta.thread_ts }`,
|
|
178
|
+
"",
|
|
179
|
+
"To comment on a GitHub issue/PR (extract from subject_url in meta):",
|
|
180
|
+
` method: POST`,
|
|
181
|
+
` path: repos/<meta.repository>/issues/<number>/comments (parse number from meta.subject_url)`,
|
|
182
|
+
` body: { body: "your reply" }`,
|
|
183
|
+
"",
|
|
184
|
+
"Built-in diagnostic tools — call proactively when events seem missing or delayed:",
|
|
185
|
+
" fnl_status — gateway running state, all listeners alive/dead, Claude WS clients",
|
|
186
|
+
" fnl_debug — per-channel diagnosis with last 10 events, rootCause, suggestedActions",
|
|
187
|
+
" omit channel arg to diagnose all channels; check summary.suggestedActions first",
|
|
188
|
+
channelContext
|
|
189
|
+
].join("\n")
|
|
190
|
+
});
|
|
191
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
192
|
+
const connectorTools = (channel?.connectors ?? []).map((c) => ({
|
|
193
|
+
name: c.name,
|
|
194
|
+
description: `Call the "${c.name}" (${c.type}) connector. ${usageHintForType(c.type)}`,
|
|
195
|
+
inputSchema: {
|
|
196
|
+
type: "object",
|
|
197
|
+
properties: {
|
|
198
|
+
method: {
|
|
199
|
+
type: "string",
|
|
200
|
+
description: "HTTP verb or API method (e.g. POST, chat.postMessage)"
|
|
201
|
+
},
|
|
202
|
+
path: {
|
|
203
|
+
type: "string",
|
|
204
|
+
description: "API path or method name (adapter-specific)"
|
|
205
|
+
},
|
|
206
|
+
body: {
|
|
207
|
+
type: "object",
|
|
208
|
+
description: "Request body / params (adapter-specific)"
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
required: ["method", "path"]
|
|
212
|
+
}
|
|
213
|
+
}));
|
|
214
|
+
const channelEnum = allChannels.length > 0 ? allChannels.map((ch) => ch.name) : void 0;
|
|
215
|
+
const builtinTools = [{
|
|
216
|
+
name: "fnl_status",
|
|
217
|
+
description: "Return the current funnel gateway status as JSON — gateway running state, listener alive/dead per channel, and connected Claude WS clients. Call this when you need to check whether the gateway is up or why events stopped arriving.",
|
|
218
|
+
inputSchema: {
|
|
219
|
+
type: "object",
|
|
220
|
+
properties: {}
|
|
221
|
+
}
|
|
222
|
+
}, {
|
|
223
|
+
name: "fnl_debug",
|
|
224
|
+
description: "Return a full channel diagnosis as JSON — gateway health, listener state, Claude WS connection, last 10 inbound events with outcome, connectionErrors (when listener is dead), and diagnosis.rootCause. Call this first when debugging missing events. Omit `channel` to diagnose all channels at once.",
|
|
225
|
+
inputSchema: {
|
|
226
|
+
type: "object",
|
|
227
|
+
properties: { channel: channelEnum ? {
|
|
228
|
+
type: "string",
|
|
229
|
+
description: `Channel name to inspect. One of: ${channelEnum.join(", ")}. Omit to get all channels.`,
|
|
230
|
+
enum: channelEnum
|
|
231
|
+
} : {
|
|
232
|
+
type: "string",
|
|
233
|
+
description: "Channel name to inspect. Omit to get all channels."
|
|
234
|
+
} }
|
|
235
|
+
}
|
|
236
|
+
}];
|
|
237
|
+
return { tools: [...connectorTools, ...builtinTools] };
|
|
238
|
+
});
|
|
239
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
240
|
+
const toolName = request.params.name;
|
|
241
|
+
if (isBuiltinTool(toolName)) return handleBuiltinTool(toolName, request.params.arguments, gatewayBaseUrl, token, allChannels);
|
|
242
|
+
if (!channel) throw new Error("FUNNEL_CHANNEL_ID is not set or channel not found in settings.json");
|
|
243
|
+
const args = request.params.arguments ?? {};
|
|
244
|
+
const method = typeof args.method === "string" ? args.method : "";
|
|
245
|
+
const path = typeof args.path === "string" ? args.path : "";
|
|
246
|
+
const body = args.body ?? {};
|
|
247
|
+
if (!method || !path) throw new Error("`method` and `path` are required");
|
|
248
|
+
const url = `${gatewayBaseUrl}/channels/${encodeURIComponent(channel.channelName)}/connectors/${encodeURIComponent(toolName)}/call`;
|
|
249
|
+
const headers = { "content-type": "application/json" };
|
|
250
|
+
if (token) headers.authorization = `Bearer ${token}`;
|
|
251
|
+
const res = await fetch(url, {
|
|
252
|
+
method: "POST",
|
|
253
|
+
headers,
|
|
254
|
+
body: JSON.stringify({
|
|
255
|
+
method,
|
|
256
|
+
path,
|
|
257
|
+
body
|
|
258
|
+
})
|
|
259
|
+
});
|
|
260
|
+
const text = await res.text();
|
|
261
|
+
if (!res.ok) throw new Error(`gateway call failed (${res.status}): ${text}`);
|
|
262
|
+
return { content: [{
|
|
263
|
+
type: "text",
|
|
264
|
+
text
|
|
265
|
+
}] };
|
|
266
|
+
});
|
|
267
|
+
const transport = new StdioServerTransport();
|
|
268
|
+
await server.connect(transport);
|
|
269
|
+
if (!channelId) return;
|
|
270
|
+
new FunnelChannelSubscriber({
|
|
271
|
+
server,
|
|
272
|
+
baseUrl: `${gatewayWsUrl}?channel=${encodeURIComponent(channelId)}`,
|
|
273
|
+
protocols: token ? [`funnel.token.${token}`] : void 0
|
|
274
|
+
}).start();
|
|
275
|
+
};
|
|
276
|
+
const handleBuiltinTool = async (name, args, gatewayBaseUrl, token, allChannels) => {
|
|
277
|
+
const headers = {};
|
|
278
|
+
if (token) headers.authorization = `Bearer ${token}`;
|
|
279
|
+
if (name === "fnl_status") {
|
|
280
|
+
const res = await fetch(`${gatewayBaseUrl}/status`, { headers }).catch(() => null);
|
|
281
|
+
if (!res) return { content: [{
|
|
282
|
+
type: "text",
|
|
283
|
+
text: JSON.stringify({
|
|
284
|
+
running: false,
|
|
285
|
+
error: "gateway unreachable",
|
|
286
|
+
hint: "run: fnl gateway start",
|
|
287
|
+
knownChannels: allChannels.map((ch) => ch.name)
|
|
288
|
+
})
|
|
289
|
+
}] };
|
|
290
|
+
const body = await res.json();
|
|
291
|
+
return { content: [{
|
|
292
|
+
type: "text",
|
|
293
|
+
text: JSON.stringify(body)
|
|
294
|
+
}] };
|
|
295
|
+
}
|
|
296
|
+
const channelArg = typeof args?.channel === "string" ? args.channel : null;
|
|
297
|
+
const url = channelArg ? `${gatewayBaseUrl}/debug?channel=${encodeURIComponent(channelArg)}` : `${gatewayBaseUrl}/debug`;
|
|
298
|
+
const res = await fetch(url, { headers }).catch(() => null);
|
|
299
|
+
if (!res) return { content: [{
|
|
300
|
+
type: "text",
|
|
301
|
+
text: JSON.stringify({
|
|
302
|
+
gateway: { running: false },
|
|
303
|
+
channels: allChannels.map((ch) => ({
|
|
304
|
+
id: ch.id,
|
|
305
|
+
name: ch.name,
|
|
306
|
+
diagnosis: {
|
|
307
|
+
status: "error",
|
|
308
|
+
message: "gateway is not running",
|
|
309
|
+
nextAction: "fnl gateway start",
|
|
310
|
+
rootCause: null
|
|
311
|
+
}
|
|
312
|
+
}))
|
|
313
|
+
})
|
|
314
|
+
}] };
|
|
315
|
+
const body = await res.json();
|
|
316
|
+
return { content: [{
|
|
317
|
+
type: "text",
|
|
318
|
+
text: JSON.stringify(body)
|
|
319
|
+
}] };
|
|
320
|
+
};
|
|
321
|
+
//#endregion
|
|
322
|
+
export { FUNNEL_MCP_ARGS, FUNNEL_MCP_COMMAND, FUNNEL_MCP_NAME, FileProcessGuard, FunnelClaude, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLocalConfigWriter, FunnelMcp, FunnelProfiles, FunnelTokenPrompter, LOCAL_CONFIG_FILENAME, MemoryFunnelTokenPrompter, NodeFunnelTokenPrompter, channelSpecSchema, connectorSpecSchema, funnelJsonSchema, localConfigSchema, profileSpecSchema, startChannelServer };
|
package/dist/{connector-diagnostic-log-OPpPi9V9.d.ts → connector-diagnostic-log-yTOojKUR.d.ts}
RENAMED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
+
//#region lib/engine/logger/logger.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Structured logger with three levels and an optional log-file path.
|
|
6
|
+
* Defaults to NodeFunnelLogger (appends to `<os.tmpdir()>/funnel/funnel.log`);
|
|
7
|
+
* MemoryFunnelLogger captures entries in memory and NoopFunnelLogger silences output.
|
|
8
|
+
*/
|
|
9
|
+
declare abstract class FunnelLogger {
|
|
10
|
+
abstract info(message: string, meta?: Record<string, unknown>): void;
|
|
11
|
+
abstract warn(message: string, meta?: Record<string, unknown>): void;
|
|
12
|
+
abstract error(message: string, meta?: Record<string, unknown>): void;
|
|
13
|
+
abstract readonly file: string | null;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
3
16
|
//#region lib/connectors/connector-listener.d.ts
|
|
4
17
|
type NotifyFn = (content: string, meta?: Record<string, string>) => Promise<void>;
|
|
5
18
|
/**
|
|
@@ -19,19 +32,6 @@ declare abstract class FunnelConnectorListener {
|
|
|
19
32
|
isAlive(): boolean;
|
|
20
33
|
}
|
|
21
34
|
//#endregion
|
|
22
|
-
//#region lib/engine/logger/logger.d.ts
|
|
23
|
-
/**
|
|
24
|
-
* Structured logger with three levels and an optional log-file path.
|
|
25
|
-
* Defaults to NodeFunnelLogger (appends to `<os.tmpdir()>/funnel/funnel.log`);
|
|
26
|
-
* MemoryFunnelLogger captures entries in memory and NoopFunnelLogger silences output.
|
|
27
|
-
*/
|
|
28
|
-
declare abstract class FunnelLogger {
|
|
29
|
-
abstract info(message: string, meta?: Record<string, unknown>): void;
|
|
30
|
-
abstract warn(message: string, meta?: Record<string, unknown>): void;
|
|
31
|
-
abstract error(message: string, meta?: Record<string, unknown>): void;
|
|
32
|
-
abstract readonly file: string | null;
|
|
33
|
-
}
|
|
34
|
-
//#endregion
|
|
35
35
|
//#region lib/gateway/connector-diagnostic-log.d.ts
|
|
36
36
|
/**
|
|
37
37
|
* Points in the listener's connection lifecycle. The single source of truth
|
|
@@ -205,4 +205,4 @@ declare abstract class ConnectorDiagnosticLog {
|
|
|
205
205
|
abstract close(): void;
|
|
206
206
|
}
|
|
207
207
|
//#endregion
|
|
208
|
-
export {
|
|
208
|
+
export { FunnelLogger as S, connectorConnectionEventSchema as _, ConnectorConnectionStatus as a, FunnelConnectorListener as b, ConnectorProcessedQuery as c, ConnectorRawEvent as d, ConnectorRawQuery as f, StoredRawEvent as g, StoredProcessedEvent as h, ConnectorConnectionRecord as i, ConnectorProcessedRecord as l, StoredConnectionEvent as m, ConnectorConnectionEvent as n, ConnectorDiagnosticLog as o, ConnectorRawRecord as p, ConnectorConnectionQuery as r, ConnectorProcessedEvent as s, CONNECTOR_CONNECTION_STATUSES as t, ConnectorQuery as u, connectorProcessedEventSchema as v, NotifyFn as x, connectorRawEventSchema as y };
|
|
@@ -16,12 +16,4 @@ var FunnelConnectorListener = class {
|
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
//#endregion
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Structured logger with three levels and an optional log-file path.
|
|
22
|
-
* Defaults to NodeFunnelLogger (appends to `<os.tmpdir()>/funnel/funnel.log`);
|
|
23
|
-
* MemoryFunnelLogger captures entries in memory and NoopFunnelLogger silences output.
|
|
24
|
-
*/
|
|
25
|
-
var FunnelLogger = class {};
|
|
26
|
-
//#endregion
|
|
27
|
-
export { FunnelConnectorListener as n, FunnelLogger as t };
|
|
19
|
+
export { FunnelConnectorListener as t };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as
|
|
3
|
-
import {
|
|
1
|
+
import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "../connector-diagnostic-log-yTOojKUR.js";
|
|
2
|
+
import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-DKgsVuMH.js";
|
|
3
|
+
import { n as discordConnectorSchema, t as DiscordConnectorConfig } from "../discord-connector-schema-R0Uu-3ns.js";
|
|
4
4
|
|
|
5
5
|
//#region lib/engine/http/http-client.d.ts
|
|
6
6
|
type HttpRequest = {
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as FunnelDiscordEventProcessor, r as FunnelDiscordAdapter, t as FunnelDiscordListener } from "../discord-listener-_jSE3HsQ.js";
|
|
2
|
+
import { t as discordConnectorSchema } from "../discord-connector-schema-CBDyGdOI.js";
|
|
2
3
|
export { FunnelDiscordAdapter, FunnelDiscordEventProcessor, FunnelDiscordListener, discordConnectorSchema };
|
package/dist/connectors/gh.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "../connector-diagnostic-log-yTOojKUR.js";
|
|
2
|
+
import { r as FunnelProcessRunner } from "../process-runner-DfniuWVU.js";
|
|
3
|
+
import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-DKgsVuMH.js";
|
|
4
|
+
import { n as ghConnectorSchema, t as GhConnectorConfig } from "../gh-connector-schema-eoTtHbY6.js";
|
|
4
5
|
|
|
5
6
|
//#region lib/connectors/gh-adapter.d.ts
|
|
6
7
|
type Deps$1 = {
|
package/dist/connectors/gh.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as ghConnectorSchema } from "../gh-connector-schema-o3Q1-ojL.js";
|
|
2
|
+
import { n as FunnelGhAdapter, t as FunnelGhListener } from "../gh-listener-DH-fClQm.js";
|
|
2
3
|
export { FunnelGhAdapter, FunnelGhListener, ghConnectorSchema };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as ScheduleEntry, c as scheduleEntrySchema, i as ScheduleConnectorConfig, l as ScheduleStateStore, n as ScheduleOnFired, o as scheduleCatchupPolicySchema, r as ScheduleCatchupPolicy, s as scheduleConnectorSchema, t as FunnelScheduleListener } from "../schedule-listener-CUyUFFR1.js";
|
|
2
2
|
|
|
3
3
|
//#region lib/connectors/match-cron.d.ts
|
|
4
4
|
declare const matchCron: (expr: string, date: Date) => boolean;
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as ScheduleStateStore, r as matchCron, t as FunnelScheduleListener } from "../schedule-listener-ePAjians.js";
|
|
2
|
+
import { n as scheduleConnectorSchema, r as scheduleEntrySchema, t as scheduleCatchupPolicySchema } from "../schedule-connector-schema-iCI61gzU.js";
|
|
2
3
|
export { FunnelScheduleListener, ScheduleStateStore, matchCron, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-
|
|
2
|
-
import { a as
|
|
1
|
+
import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-DKgsVuMH.js";
|
|
2
|
+
import { a as slackConnectorSchema, c as SlackProcessedEmit, d as SlackSkipReason, i as SlackConnectorConfig, l as SlackProcessedSkip, n as SlackOnAppCreated, o as FunnelSlackEventProcessor, r as SlackPreprocessEvent, s as SlackProcessed, t as FunnelSlackListener, u as SlackRawEvent } from "../slack-listener-Bv5xI9gC.js";
|
|
3
3
|
|
|
4
4
|
//#region lib/connectors/slack-adapter.d.ts
|
|
5
5
|
type SlackWebClientLike = {
|
package/dist/connectors/slack.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as FunnelSlackEventProcessor, r as FunnelSlackAdapter, t as FunnelSlackListener } from "../slack-listener-ClQuHhEF.js";
|
|
2
|
+
import { t as slackConnectorSchema } from "../slack-connector-schema-BCNWluHM.js";
|
|
2
3
|
export { FunnelSlackAdapter, FunnelSlackEventProcessor, FunnelSlackListener, slackConnectorSchema };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region lib/connectors/discord-connector-schema.ts
|
|
3
|
+
/**
|
|
4
|
+
* Like slack, a discord connector holds either a literal `botToken` or a
|
|
5
|
+
* `botTokenEnv` reference resolved from `process.env` at listener start. The
|
|
6
|
+
* reference form keeps the secret out of settings.json, but is only set through
|
|
7
|
+
* the engine API (`new Funnel(...)`); funnel.json and the `fnl` CLI produce
|
|
8
|
+
* literals.
|
|
9
|
+
*/
|
|
10
|
+
const discordConnectorSchema = z.object({
|
|
11
|
+
id: z.string(),
|
|
12
|
+
name: z.string(),
|
|
13
|
+
type: z.literal("discord"),
|
|
14
|
+
botToken: z.string().min(10).optional(),
|
|
15
|
+
/** Name of the env var holding the bot token, resolved at listener start. */
|
|
16
|
+
botTokenEnv: z.string().optional(),
|
|
17
|
+
createdAt: z.string().datetime().optional(),
|
|
18
|
+
updatedAt: z.string().datetime().optional()
|
|
19
|
+
});
|
|
20
|
+
//#endregion
|
|
21
|
+
export { discordConnectorSchema as t };
|