@datanovallc/openclaw-slack-router 0.1.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 (57) hide show
  1. package/README.md +224 -0
  2. package/dist/admin.d.ts +25 -0
  3. package/dist/admin.js +109 -0
  4. package/dist/admin.js.map +1 -0
  5. package/dist/app.d.ts +7 -0
  6. package/dist/app.js +18 -0
  7. package/dist/app.js.map +1 -0
  8. package/dist/bot.d.ts +15 -0
  9. package/dist/bot.js +42 -0
  10. package/dist/bot.js.map +1 -0
  11. package/dist/cli.d.ts +2 -0
  12. package/dist/cli.js +21 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/commands/init.d.ts +1 -0
  15. package/dist/commands/init.js +151 -0
  16. package/dist/commands/init.js.map +1 -0
  17. package/dist/config.d.ts +18 -0
  18. package/dist/config.js +41 -0
  19. package/dist/config.js.map +1 -0
  20. package/dist/context.d.ts +15 -0
  21. package/dist/context.js +36 -0
  22. package/dist/context.js.map +1 -0
  23. package/dist/events/app-mention.d.ts +3 -0
  24. package/dist/events/app-mention.js +66 -0
  25. package/dist/events/app-mention.js.map +1 -0
  26. package/dist/events/message.d.ts +3 -0
  27. package/dist/events/message.js +48 -0
  28. package/dist/events/message.js.map +1 -0
  29. package/dist/gateway/chat-client.d.ts +14 -0
  30. package/dist/gateway/chat-client.js +159 -0
  31. package/dist/gateway/chat-client.js.map +1 -0
  32. package/dist/handlers/reply.d.ts +4 -0
  33. package/dist/handlers/reply.js +4 -0
  34. package/dist/handlers/reply.js.map +1 -0
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +22 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/plugin.d.ts +84 -0
  39. package/dist/plugin.js +98 -0
  40. package/dist/plugin.js.map +1 -0
  41. package/dist/router.d.ts +11 -0
  42. package/dist/router.js +40 -0
  43. package/dist/router.js.map +1 -0
  44. package/dist/subagents/index.d.ts +7 -0
  45. package/dist/subagents/index.js +10 -0
  46. package/dist/subagents/index.js.map +1 -0
  47. package/dist/subagents/openclaw-gateway.d.ts +7 -0
  48. package/dist/subagents/openclaw-gateway.js +30 -0
  49. package/dist/subagents/openclaw-gateway.js.map +1 -0
  50. package/dist/subagents/types.d.ts +6 -0
  51. package/dist/subagents/types.js +2 -0
  52. package/dist/subagents/types.js.map +1 -0
  53. package/dist/types.d.ts +28 -0
  54. package/dist/types.js +2 -0
  55. package/dist/types.js.map +1 -0
  56. package/openclaw.plugin.json +17 -0
  57. package/package.json +57 -0
package/README.md ADDED
@@ -0,0 +1,224 @@
1
+ # openclaw-slack-router
2
+
3
+ A Slack router plugin for [openclaw](https://github.com/openclaw/openclaw). Connect your openclaw agent to Slack with per-channel context isolation — each channel is an independent project context, managed entirely through chat.
4
+
5
+ ## How it works
6
+
7
+ 1. Install via openclaw's plugin system
8
+ 2. Configure your Slack tokens (`openclaw config set ...` or `openclaw slack setup`)
9
+ 3. Start openclaw — the Slack bot starts automatically as a service
10
+ 4. Your main channel posts setup instructions on first start
11
+ 5. Say `new channel my-project` in the main channel to create a project channel
12
+ 6. Mention the bot in any project channel — it routes your message to openclaw and replies in-thread
13
+ 7. Each channel maintains its own context — `#project-a` and `#project-b` never bleed into each other
14
+
15
+ ## Install
16
+
17
+ ```
18
+ openclaw plugins install npm:@datanovallc/openclaw-slack-router
19
+ ```
20
+
21
+ Then restart your openclaw gateway:
22
+
23
+ ```
24
+ openclaw gateway restart
25
+ ```
26
+
27
+ ## Quick start
28
+
29
+ ### 1. Create a Slack app
30
+
31
+ In the [Slack app console](https://api.slack.com/apps):
32
+
33
+ - **Enable Socket Mode** — generate an App Token (`xapp-...`) under Settings → Basic Information
34
+ - **Add a Bot Token** (`xoxb-...`) under OAuth & Permissions → Install to Workspace
35
+ - **Required OAuth scopes:** `app_mentions:read`, `chat:write`, `channels:history`, `channels:join`, `channels:manage`, `groups:history`, `im:history`, `im:write`
36
+ - **Subscribe to bot events:** `app_mention`, `message.im`
37
+
38
+ ### 2. Run the setup wizard
39
+
40
+ ```
41
+ npx openclaw-slack-router init
42
+ ```
43
+
44
+ The wizard asks for your tokens, gateway URL, and a name for your main channel. It writes `.env` and `openclaw-slack-router.config.json`.
45
+
46
+ ### 3. Invite the bot to your main channel
47
+
48
+ Create the channel you named during setup, then invite your bot user to it. Copy the channel ID (right-click the channel → View channel details → copy the ID starting with `C...`), then add it to `openclaw-slack-router.config.json`:
49
+
50
+ ```json
51
+ {
52
+ "mainChannelId": "CXXXXXXXXX"
53
+ }
54
+ ```
55
+
56
+ ### 4. Start
57
+
58
+ ```
59
+ npx openclaw-slack-router start
60
+ ```
61
+
62
+ Or if installed locally:
63
+
64
+ ```
65
+ npm start
66
+ ```
67
+
68
+ On first start, the bot posts a welcome message in your main channel with instructions for creating project channels.
69
+
70
+ ## Managing channels via chat
71
+
72
+ Everything is done by talking to the bot in your main channel:
73
+
74
+ | Say this | What happens |
75
+ |----------|-------------|
76
+ | `new channel my-project` | Creates `#my-project` in Slack, joins it, registers it for routing |
77
+ | `list channels` | Shows all registered project channels |
78
+ | `remove channel my-project` | Unregisters the channel (Slack channel still exists) |
79
+ | `help` | Shows available commands |
80
+
81
+ ## Routing messages
82
+
83
+ In any project channel, mention the bot:
84
+
85
+ ```
86
+ @YourBot what's the status of the API migration?
87
+ ```
88
+
89
+ To route to a specific agent explicitly:
90
+
91
+ ```
92
+ @YourBot /research find recent papers on RAG architectures
93
+ @YourBot research: find recent papers on RAG architectures
94
+ ```
95
+
96
+ If no agent prefix is given, the message goes to the default agent (openclaw-gateway).
97
+
98
+ ## Configuration
99
+
100
+ `openclaw-slack-router.config.json` is created by `init` and managed by the bot. You can also edit it directly:
101
+
102
+ ```json
103
+ {
104
+ "botName": "Rook",
105
+ "mainChannelId": "CXXXXXXXXX",
106
+ "introPosted": false,
107
+ "defaultAgent": "default",
108
+ "agents": {
109
+ "default": {
110
+ "name": "openclaw-gateway",
111
+ "description": "Routes messages through the local openclaw gateway"
112
+ }
113
+ },
114
+ "channels": {
115
+ "C1234567890": {
116
+ "name": "my-project",
117
+ "historyLimit": 50
118
+ }
119
+ }
120
+ }
121
+ ```
122
+
123
+ | Field | Description |
124
+ |-------|-------------|
125
+ | `botName` | Display name used in logs and error messages |
126
+ | `mainChannelId` | Channel ID of your admin/control channel |
127
+ | `introPosted` | Set to `true` after the bot posts its first intro — prevents re-posting on restart |
128
+ | `defaultAgent` | Which agent handles unrouted messages |
129
+ | `agents` | Registered agent names and descriptions |
130
+ | `channels` | Active project channels; `historyLimit` controls how many messages are fetched for context |
131
+
132
+ **This file is gitignored** — your channel config and tokens stay local.
133
+
134
+ ## Environment variables
135
+
136
+ The plugin reads these from `~/.openclaw/.env` (openclaw's canonical env file). The setup wizard (`openclaw slack setup`) writes them there automatically.
137
+
138
+ | Variable | Required | Description |
139
+ |----------|----------|-------------|
140
+ | `SLACK_BOT_TOKEN` | Yes | Bot OAuth token (`xoxb-...`) |
141
+ | `SLACK_APP_TOKEN` | Yes | Socket Mode app token (`xapp-...`) |
142
+ | `OPENCLAW_GATEWAY_URL` | No | Gateway WebSocket URL (default: `ws://127.0.0.1:18789`) |
143
+ | `OPENCLAW_GATEWAY_TOKEN` | No | Gateway auth token if your gateway requires one |
144
+
145
+ ## Channel context model
146
+
147
+ Each Slack channel is an isolated project context:
148
+
149
+ - `#project-openclaw` only sees openclaw conversation history
150
+ - `#project-datanova` only sees datanova history
151
+ - No cross-project token burn, no context bleed
152
+
153
+ Context is built from **full channel history** (`conversations.history`), not individual thread replies. Threads are for sub-discussions — the context sent to openclaw is always the whole channel.
154
+
155
+ ## Adding a custom subagent
156
+
157
+ Implement the `SubagentDefinition` interface:
158
+
159
+ ```typescript
160
+ import type { SubagentDefinition, SubagentContext } from "openclaw-slack-router";
161
+
162
+ const myAgent: SubagentDefinition = {
163
+ name: "my-agent",
164
+ description: "Does something useful",
165
+ async handle(ctx: SubagentContext): Promise<string> {
166
+ // ctx.currentMessage — the current message text
167
+ // ctx.threadHistory — [{role, content}] full channel history
168
+ // ctx.channelId — Slack channel ID
169
+ // ctx.userId — Slack user ID who sent the message
170
+ // ctx.threadTs — thread timestamp to reply into
171
+ return "response text";
172
+ },
173
+ };
174
+ ```
175
+
176
+ Register it in `src/subagents/index.ts` alongside `openclaw-gateway`.
177
+
178
+ ## Development
179
+
180
+ ```
181
+ npm run dev # watch mode with tsx
182
+ npm test # run tests
183
+ npm run typecheck # type check without emitting
184
+ npm run build # compile to dist/
185
+ ```
186
+
187
+ ## Publishing to npm (maintainers)
188
+
189
+ ### 1. Create a Granular Access Token on npmjs.com
190
+
191
+ Go to [npmjs.com](https://www.npmjs.com) → your avatar → **Access Tokens** → **Generate New Token** → **Granular Access Token**.
192
+
193
+ Fill in the form:
194
+
195
+ | Field | Value |
196
+ |-------|-------|
197
+ | **Token name** | `GitHub Actions` |
198
+ | **Description** | Automated publishing via GitHub Actions |
199
+ | **Expiration** | Choose a date (npm requires one — 1 year is fine) |
200
+
201
+ Under **Packages and scopes → Permissions**, set it to **Read and write**. This is what allows the token to publish. Then under **Select packages**, choose **Only select packages and scopes** and add `@datanovallc/openclaw-slack-router` (or select the entire `@datanovallc` scope).
202
+
203
+ Leave **Organizations → Permissions** at no access — publishing doesn't need org-level permissions.
204
+
205
+ Click **Generate token** and copy it immediately (it's only shown once).
206
+
207
+ ### 2. Add the token to GitHub
208
+
209
+ In the GitHub repo → **Settings** → **Secrets and variables** → **Actions** → **New repository secret**:
210
+
211
+ - Name: `NPM_TOKEN`
212
+ - Value: the token you just copied
213
+
214
+ ### 3. Release a version
215
+
216
+ ```bash
217
+ # bump version
218
+ npm version patch # or minor / major
219
+
220
+ git push origin master --tags
221
+ ```
222
+
223
+ The tag push triggers the GitHub Actions workflow (`.github/workflows/publish.yml`) and publishes to npm automatically. The `--access public` flag is required for scoped packages (`@datanovallc/...`) on the free npm plan.
224
+
@@ -0,0 +1,25 @@
1
+ import type { SubagentConfig } from "./types.js";
2
+ export declare const INTRO_MESSAGE = "\uD83D\uDC4B *openclaw-slack-router is connected!*\n\nI'm your Slack-to-openclaw router. Each channel you create here becomes an isolated project context \u2014 messages in one channel never bleed into another.\n\n*Managing channels (type these here):*\n\u2022 `new channel <name>` \u2014 create a new project channel (e.g. `new channel my-project`)\n\u2022 `list channels` \u2014 show all active channels\n\u2022 `remove channel <name>` \u2014 deactivate a channel\n\nOnce a channel is created, mention me in it and I'll start routing your messages to openclaw.";
3
+ export declare function parseAdminCommand(text: string): {
4
+ type: "new";
5
+ name: string;
6
+ } | {
7
+ type: "remove";
8
+ name: string;
9
+ } | {
10
+ type: "list";
11
+ } | {
12
+ type: "help";
13
+ } | null;
14
+ export declare function handleAdminCommand(params: {
15
+ text: string;
16
+ client: any;
17
+ threadTs: string;
18
+ config: SubagentConfig;
19
+ configPath?: string;
20
+ }): Promise<string | null>;
21
+ export declare function postIntroIfNeeded(params: {
22
+ client: any;
23
+ config: SubagentConfig;
24
+ configPath?: string;
25
+ }): Promise<void>;
package/dist/admin.js ADDED
@@ -0,0 +1,109 @@
1
+ import { saveSubagentConfig } from "./config.js";
2
+ export const INTRO_MESSAGE = `👋 *openclaw-slack-router is connected!*
3
+
4
+ I'm your Slack-to-openclaw router. Each channel you create here becomes an isolated project context — messages in one channel never bleed into another.
5
+
6
+ *Managing channels (type these here):*
7
+ • \`new channel <name>\` — create a new project channel (e.g. \`new channel my-project\`)
8
+ • \`list channels\` — show all active channels
9
+ • \`remove channel <name>\` — deactivate a channel
10
+
11
+ Once a channel is created, mention me in it and I'll start routing your messages to openclaw.`;
12
+ // Matches: "new channel foo-bar", "new channel foo bar" (spaces become hyphens)
13
+ const NEW_CHANNEL_RE = /^new channel\s+(.+)$/i;
14
+ // Matches: "remove channel foo-bar"
15
+ const REMOVE_CHANNEL_RE = /^remove channel\s+(\S+)$/i;
16
+ // Matches: "list channels" or "list channel"
17
+ const LIST_CHANNELS_RE = /^list channels?$/i;
18
+ // Matches: "help"
19
+ const HELP_RE = /^help$/i;
20
+ export function parseAdminCommand(text) {
21
+ const trimmed = text.trim();
22
+ const newMatch = trimmed.match(NEW_CHANNEL_RE);
23
+ if (newMatch) {
24
+ const name = newMatch[1].trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
25
+ return { type: "new", name };
26
+ }
27
+ const removeMatch = trimmed.match(REMOVE_CHANNEL_RE);
28
+ if (removeMatch)
29
+ return { type: "remove", name: removeMatch[1].toLowerCase() };
30
+ if (LIST_CHANNELS_RE.test(trimmed))
31
+ return { type: "list" };
32
+ if (HELP_RE.test(trimmed))
33
+ return { type: "help" };
34
+ return null;
35
+ }
36
+ export async function handleAdminCommand(params) {
37
+ const { text, client, config, configPath } = params;
38
+ const cmd = parseAdminCommand(text);
39
+ if (!cmd)
40
+ return null;
41
+ if (cmd.type === "help") {
42
+ return INTRO_MESSAGE;
43
+ }
44
+ if (cmd.type === "list") {
45
+ const entries = Object.entries(config.channels);
46
+ if (entries.length === 0)
47
+ return "No project channels configured yet. Say `new channel <name>` to create one.";
48
+ const lines = entries.map(([id, ch]) => `• *#${ch.name}* (${id})`).join("\n");
49
+ return `*Active channels:*\n${lines}`;
50
+ }
51
+ if (cmd.type === "new") {
52
+ const channelName = cmd.name;
53
+ // Check if already registered by name
54
+ const existing = Object.values(config.channels).find((ch) => ch.name === channelName);
55
+ if (existing)
56
+ return `A channel named *#${channelName}* is already registered.`;
57
+ try {
58
+ const result = await client.conversations.create({ name: channelName });
59
+ const channelId = result.channel.id;
60
+ await client.conversations.join({ channel: channelId });
61
+ config.channels[channelId] = { name: channelName };
62
+ saveSubagentConfig(config, configPath);
63
+ return `✅ Created and joined *#${channelName}* (\`${channelId}\`). Mention me there to start routing messages to openclaw.`;
64
+ }
65
+ catch (err) {
66
+ const code = err?.data?.error ?? err?.message ?? String(err);
67
+ if (code === "name_taken")
68
+ return `A Slack channel named *#${channelName}* already exists. To register an existing channel, mention me in it directly.`;
69
+ return `Failed to create channel: ${code}`;
70
+ }
71
+ }
72
+ if (cmd.type === "remove") {
73
+ const channelName = cmd.name;
74
+ const entry = Object.entries(config.channels).find(([, ch]) => ch.name === channelName);
75
+ if (!entry)
76
+ return `No registered channel named *#${channelName}*. Say \`list channels\` to see what's active.`;
77
+ const [channelId] = entry;
78
+ delete config.channels[channelId];
79
+ saveSubagentConfig(config, configPath);
80
+ return `✅ Removed *#${channelName}* from routing. The Slack channel still exists but I'll no longer respond there.`;
81
+ }
82
+ return null;
83
+ }
84
+ export async function postIntroIfNeeded(params) {
85
+ const { client, config, configPath } = params;
86
+ if (!config.mainChannelId)
87
+ return;
88
+ // Track whether we've posted the intro via a sentinel in channels entry
89
+ const mainEntry = config.channels[config.mainChannelId];
90
+ if (mainEntry?.historyLimit === -1)
91
+ return; // sentinel: intro already posted
92
+ try {
93
+ await client.chat.postMessage({
94
+ channel: config.mainChannelId,
95
+ text: INTRO_MESSAGE,
96
+ mrkdwn: true,
97
+ });
98
+ // Mark intro as posted
99
+ if (!config.channels[config.mainChannelId]) {
100
+ config.channels[config.mainChannelId] = { name: "main" };
101
+ }
102
+ config.channels[config.mainChannelId].historyLimit = -1;
103
+ saveSubagentConfig(config, configPath);
104
+ }
105
+ catch {
106
+ // Non-fatal: channel may not exist yet or bot not invited
107
+ }
108
+ }
109
+ //# sourceMappingURL=admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"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"}
package/dist/app.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import SlackBolt from "@slack/bolt";
2
+ import type { AppConfig } from "./config.js";
3
+ import type { SubagentConfig } from "./types.js";
4
+ import type { SubagentRegistry } from "./subagents/index.js";
5
+ declare const App: typeof SlackBolt.App;
6
+ export declare function createApp(config: AppConfig, subagentConfig: SubagentConfig, botUserId: string, subagentRegistry: SubagentRegistry): InstanceType<typeof App>;
7
+ export {};
package/dist/app.js ADDED
@@ -0,0 +1,18 @@
1
+ import SlackBolt from "@slack/bolt";
2
+ import { registerAppMentionHandler } from "./events/app-mention.js";
3
+ import { registerMessageHandler } from "./events/message.js";
4
+ const slackBoltModule = SlackBolt;
5
+ const slackBolt = (slackBoltModule.App ? slackBoltModule : slackBoltModule.default) ??
6
+ slackBoltModule;
7
+ const { App } = slackBolt;
8
+ export function createApp(config, subagentConfig, botUserId, subagentRegistry) {
9
+ const app = new App({
10
+ token: config.SLACK_BOT_TOKEN,
11
+ appToken: config.SLACK_APP_TOKEN,
12
+ socketMode: true,
13
+ });
14
+ registerAppMentionHandler(app, subagentConfig, botUserId, subagentRegistry);
15
+ registerMessageHandler(app, subagentConfig, botUserId, subagentRegistry);
16
+ return app;
17
+ }
18
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,aAAa,CAAC;AAIpC,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,eAAe,GAAG,SAEvB,CAAC;AACF,MAAM,SAAS,GACb,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC;IACjE,eAAe,CAAC;AAClB,MAAM,EAAE,GAAG,EAAE,GAAG,SAAS,CAAC;AAE1B,MAAM,UAAU,SAAS,CACvB,MAAiB,EACjB,cAA8B,EAC9B,SAAiB,EACjB,gBAAkC;IAElC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC,eAAe;QAC7B,QAAQ,EAAE,MAAM,CAAC,eAAe;QAChC,UAAU,EAAE,IAAI;KACjB,CAAC,CAAC;IAEH,yBAAyB,CAAC,GAAG,EAAE,cAAc,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;IAC5E,sBAAsB,CAAC,GAAG,EAAE,cAAc,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;IAEzE,OAAO,GAAG,CAAC;AACb,CAAC"}
package/dist/bot.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export interface SlackBotOptions {
2
+ botToken: string;
3
+ appToken: string;
4
+ gatewayUrl: string;
5
+ gatewayToken?: string;
6
+ /** Path to openclaw-slack-router.config.json. Defaults to ./openclaw-slack-router.config.json */
7
+ configPath?: string;
8
+ logger?: {
9
+ info: (msg: string) => void;
10
+ warn: (msg: string) => void;
11
+ error: (msg: string) => void;
12
+ };
13
+ }
14
+ export declare function startSlackBot(options: SlackBotOptions): Promise<void>;
15
+ export declare function stopSlackBot(): Promise<void>;
package/dist/bot.js ADDED
@@ -0,0 +1,42 @@
1
+ import { loadSubagentConfig, DEFAULT_CONFIG_PATH } from "./config.js";
2
+ import { createApp } from "./app.js";
3
+ import { buildSubagentRegistry } from "./subagents/index.js";
4
+ import { postIntroIfNeeded } from "./admin.js";
5
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
+ let runningApp = null;
7
+ export async function startSlackBot(options) {
8
+ const { botToken, appToken, gatewayUrl, gatewayToken, configPath, logger } = options;
9
+ const log = logger ?? console;
10
+ const subagentConfig = loadSubagentConfig(configPath ?? DEFAULT_CONFIG_PATH);
11
+ // Resolve bot's own user ID at startup for assistant message tagging.
12
+ // Uses WebClient directly (before creating the Bolt app) since createApp needs botUserId.
13
+ const SlackBolt = await import("@slack/bolt");
14
+ const slackBoltModule = SlackBolt;
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ const slackBolt =
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ (slackBoltModule.App
19
+ ? slackBoltModule
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ : slackBoltModule.default) ?? slackBoltModule;
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ const { WebClient } = slackBolt;
24
+ const client = new WebClient(botToken);
25
+ const authResult = await client.auth.test();
26
+ const botUserId = authResult.user_id;
27
+ const subagentRegistry = buildSubagentRegistry({ gatewayUrl, gatewayToken });
28
+ // Inject tokens into config for the app factory (avoids separate env var requirement)
29
+ const appConfig = { SLACK_BOT_TOKEN: botToken, SLACK_APP_TOKEN: appToken };
30
+ const app = createApp(appConfig, subagentConfig, botUserId, subagentRegistry);
31
+ runningApp = app;
32
+ await app.start();
33
+ log.info(`${subagentConfig.botName} is running in Socket Mode (bot user: ${botUserId})`);
34
+ await postIntroIfNeeded({ client, config: subagentConfig, configPath });
35
+ }
36
+ export async function stopSlackBot() {
37
+ if (runningApp) {
38
+ await runningApp.stop();
39
+ runningApp = null;
40
+ }
41
+ }
42
+ //# sourceMappingURL=bot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bot.js","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAiB/C,8DAA8D;AAC9D,IAAI,UAAU,GAAQ,IAAI,CAAC;AAE3B,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAwB;IAC1D,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACrF,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,CAAC;IAE9B,MAAM,cAAc,GAAmB,kBAAkB,CAAC,UAAU,IAAI,mBAAmB,CAAC,CAAC;IAE7F,sEAAsE;IACtE,0FAA0F;IAC1F,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IAC9C,MAAM,eAAe,GAAG,SAEvB,CAAC;IACF,8DAA8D;IAC9D,MAAM,SAAS;IACb,8DAA8D;IAC9D,CAAE,eAAuB,CAAC,GAAG;QAC3B,CAAC,CAAC,eAAe;QACjB,8DAA8D;QAC9D,CAAC,CAAE,eAAuB,CAAC,OAAO,CAAC,IAAI,eAAe,CAAC;IAC3D,8DAA8D;IAC9D,MAAM,EAAE,SAAS,EAAE,GAAG,SAAgB,CAAC;IACvC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5C,MAAM,SAAS,GAAG,UAAU,CAAC,OAAiB,CAAC;IAE/C,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,CAAC;IAE7E,sFAAsF;IACtF,MAAM,SAAS,GAAG,EAAE,eAAe,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC;IAE3E,MAAM,GAAG,GAAG,SAAS,CAAC,SAAS,EAAE,cAAc,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;IAC9E,UAAU,GAAG,GAAG,CAAC;IACjB,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;IAClB,GAAG,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,OAAO,yCAAyC,SAAS,GAAG,CAAC,CAAC;IAEzF,MAAM,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,UAAU,CAAC,IAAI,EAAE,CAAC;QACxB,UAAU,GAAG,IAAI,CAAC;IACpB,CAAC;AACH,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { runInit } from "./commands/init.js";
4
+ import pkg from "../package.json" with { type: "json" };
5
+ const program = new Command();
6
+ program
7
+ .name("openclaw-slack-router")
8
+ .description("Slack router plugin for openclaw")
9
+ .version(pkg.version);
10
+ program
11
+ .command("init")
12
+ .description("Interactive setup wizard — configure credentials and main channel")
13
+ .action(runInit);
14
+ program
15
+ .command("start")
16
+ .description("Start the Slack router (same as npm start)")
17
+ .action(async () => {
18
+ await import("./index.js");
19
+ });
20
+ program.parse();
21
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAExD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,uBAAuB,CAAC;KAC7B,WAAW,CAAC,kCAAkC,CAAC;KAC/C,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAExB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,mEAAmE,CAAC;KAChF,MAAM,CAAC,OAAO,CAAC,CAAC;AAEnB,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,4CAA4C,CAAC;KACzD,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;AAC7B,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function runInit(): Promise<void>;
@@ -0,0 +1,151 @@
1
+ import { input, password, confirm, select } from "@inquirer/prompts";
2
+ import { writeFileSync, existsSync, readFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import path from "node:path";
5
+ import { saveSubagentConfig, DEFAULT_CONFIG_PATH } from "../config.js";
6
+ const OPENCLAW_ENV_PATH = path.join(homedir(), ".openclaw", ".env");
7
+ /** Parse a .env file into a key→value map (skips comments and blank lines). */
8
+ function parseEnvFile(filePath) {
9
+ if (!existsSync(filePath))
10
+ return {};
11
+ const lines = readFileSync(filePath, "utf-8").split("\n");
12
+ const result = {};
13
+ for (const line of lines) {
14
+ const trimmed = line.trim();
15
+ if (!trimmed || trimmed.startsWith("#"))
16
+ continue;
17
+ const idx = trimmed.indexOf("=");
18
+ if (idx === -1)
19
+ continue;
20
+ result[trimmed.slice(0, idx)] = trimmed.slice(idx + 1);
21
+ }
22
+ return result;
23
+ }
24
+ /** Append new KEY=VALUE pairs to ~/.openclaw/.env, skipping keys that already exist. */
25
+ function appendToOpenclawEnv(pairs) {
26
+ const existing = parseEnvFile(OPENCLAW_ENV_PATH);
27
+ const newLines = Object.entries(pairs)
28
+ .filter(([k]) => !existing[k])
29
+ .map(([k, v]) => `${k}=${v}`);
30
+ if (newLines.length === 0)
31
+ return;
32
+ const current = existsSync(OPENCLAW_ENV_PATH)
33
+ ? readFileSync(OPENCLAW_ENV_PATH, "utf-8").trimEnd()
34
+ : "";
35
+ const separator = current ? "\n" : "";
36
+ writeFileSync(OPENCLAW_ENV_PATH, current + separator + "\n" + newLines.join("\n") + "\n", "utf-8");
37
+ }
38
+ export async function runInit() {
39
+ console.log("\nopenclaw-slack-router setup\n");
40
+ if (existsSync(DEFAULT_CONFIG_PATH)) {
41
+ const overwrite = await confirm({
42
+ message: `${DEFAULT_CONFIG_PATH} already exists. Overwrite?`,
43
+ default: false,
44
+ });
45
+ if (!overwrite) {
46
+ console.log("Aborted.");
47
+ process.exit(0);
48
+ }
49
+ }
50
+ // --- Slack credentials ---
51
+ const existingEnv = parseEnvFile(OPENCLAW_ENV_PATH);
52
+ const hasExistingTokens = existingEnv["SLACK_BOT_TOKEN"]?.startsWith("xoxb-") &&
53
+ existingEnv["SLACK_APP_TOKEN"]?.startsWith("xapp-");
54
+ let botToken;
55
+ let appToken;
56
+ if (hasExistingTokens) {
57
+ console.log(`\nFound existing Slack tokens in ${OPENCLAW_ENV_PATH}`);
58
+ const tokenSource = await select({
59
+ message: "Use existing tokens or enter new ones?",
60
+ choices: [
61
+ { name: `Use existing (${existingEnv["SLACK_BOT_TOKEN"].slice(0, 14)}...)`, value: "existing" },
62
+ { name: "Enter new tokens manually", value: "manual" },
63
+ ],
64
+ });
65
+ if (tokenSource === "existing") {
66
+ botToken = existingEnv["SLACK_BOT_TOKEN"];
67
+ appToken = existingEnv["SLACK_APP_TOKEN"];
68
+ }
69
+ else {
70
+ botToken = await password({
71
+ message: "Bot token (xoxb-...):",
72
+ validate: (v) => v.startsWith("xoxb-") || "Must start with xoxb-",
73
+ });
74
+ appToken = await password({
75
+ message: "App token for Socket Mode (xapp-...):",
76
+ validate: (v) => v.startsWith("xapp-") || "Must start with xapp-",
77
+ });
78
+ }
79
+ }
80
+ else {
81
+ console.log(`\nSlack credentials (from https://api.slack.com/apps):`);
82
+ botToken = await password({
83
+ message: "Bot token (xoxb-...):",
84
+ validate: (v) => v.startsWith("xoxb-") || "Must start with xoxb-",
85
+ });
86
+ appToken = await password({
87
+ message: "App token for Socket Mode (xapp-...):",
88
+ validate: (v) => v.startsWith("xapp-") || "Must start with xapp-",
89
+ });
90
+ }
91
+ // --- Gateway ---
92
+ console.log("\nopenclaw gateway connection:");
93
+ const gatewayUrl = await input({
94
+ message: "Gateway WebSocket URL:",
95
+ default: existingEnv["OPENCLAW_GATEWAY_URL"] ?? "ws://127.0.0.1:18789",
96
+ });
97
+ const gatewayTokenRaw = await password({
98
+ message: "Gateway token (leave blank if none):",
99
+ });
100
+ // --- Bot identity ---
101
+ console.log("\nBot identity:");
102
+ const botName = await input({
103
+ message: "Bot name (shown in startup logs and error messages):",
104
+ default: "Rook",
105
+ });
106
+ // --- Main channel ---
107
+ console.log("\nMain channel setup:");
108
+ console.log("The main channel is your control panel — you create project channels by chatting here.");
109
+ const mainChannelName = await input({
110
+ message: "Main channel name (will be created in Slack):",
111
+ default: "rook-main",
112
+ validate: (v) => /^[a-z0-9-]{1,80}$/.test(v) || "Use lowercase letters, numbers, and hyphens only",
113
+ });
114
+ // --- Write tokens to ~/.openclaw/.env ---
115
+ const newEnvPairs = {
116
+ SLACK_BOT_TOKEN: botToken,
117
+ SLACK_APP_TOKEN: appToken,
118
+ OPENCLAW_GATEWAY_URL: gatewayUrl,
119
+ ...(gatewayTokenRaw ? { OPENCLAW_GATEWAY_TOKEN: gatewayTokenRaw } : {}),
120
+ };
121
+ appendToOpenclawEnv(newEnvPairs);
122
+ console.log(`\n✅ Tokens saved to ${OPENCLAW_ENV_PATH}`);
123
+ // --- Write config ---
124
+ const config = {
125
+ botName,
126
+ mainChannelId: null, // filled in after bot creates/joins the channel on first start
127
+ introPosted: false,
128
+ defaultAgent: "default",
129
+ agents: {
130
+ default: {
131
+ name: "openclaw-gateway",
132
+ description: "Routes messages through the local openclaw gateway",
133
+ },
134
+ },
135
+ channels: {},
136
+ };
137
+ saveSubagentConfig(config);
138
+ console.log(`✅ Written ${DEFAULT_CONFIG_PATH}`);
139
+ console.log(`
140
+ Next steps:
141
+ 1. Create a Slack channel named #${mainChannelName} and invite your bot to it
142
+ 2. Copy the channel ID (right-click → View channel details → copy ID starting with C...)
143
+ 3. Add it to ${DEFAULT_CONFIG_PATH}:
144
+ "mainChannelId": "CXXXXXXXXX"
145
+ 4. Restart openclaw: openclaw gateway restart
146
+
147
+ On first start, the bot will post setup instructions in #${mainChannelName}.
148
+ From there, say new channel <name> to create project channels.
149
+ `);
150
+ }
151
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGvE,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;AAEpE,+EAA+E;AAC/E,SAAS,YAAY,CAAC,QAAgB;IACpC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1D,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAClD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,SAAS;QACzB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,wFAAwF;AACxF,SAAS,mBAAmB,CAAC,KAA6B;IACxD,MAAM,QAAQ,GAAG,YAAY,CAAC,iBAAiB,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;SACnC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;SAC7B,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEhC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAElC,MAAM,OAAO,GAAG,UAAU,CAAC,iBAAiB,CAAC;QAC3C,CAAC,CAAC,YAAY,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC,OAAO,EAAE;QACpD,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACtC,aAAa,CACX,iBAAiB,EACjB,OAAO,GAAG,SAAS,GAAG,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,EACvD,OAAO,CACR,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO;IAC3B,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAE/C,IAAI,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC;YAC9B,OAAO,EAAE,GAAG,mBAAmB,6BAA6B;YAC5D,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,4BAA4B;IAC5B,MAAM,WAAW,GAAG,YAAY,CAAC,iBAAiB,CAAC,CAAC;IACpD,MAAM,iBAAiB,GACrB,WAAW,CAAC,iBAAiB,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC;QACnD,WAAW,CAAC,iBAAiB,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IAEtD,IAAI,QAAgB,CAAC;IACrB,IAAI,QAAgB,CAAC;IAErB,IAAI,iBAAiB,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,oCAAoC,iBAAiB,EAAE,CAAC,CAAC;QACrE,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC;YAC/B,OAAO,EAAE,wCAAwC;YACjD,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,iBAAiB,WAAW,CAAC,iBAAiB,CAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE;gBAChG,EAAE,IAAI,EAAE,2BAA2B,EAAE,KAAK,EAAE,QAAQ,EAAE;aACvD;SACF,CAAC,CAAC;QAEH,IAAI,WAAW,KAAK,UAAU,EAAE,CAAC;YAC/B,QAAQ,GAAG,WAAW,CAAC,iBAAiB,CAAE,CAAC;YAC3C,QAAQ,GAAG,WAAW,CAAC,iBAAiB,CAAE,CAAC;QAC7C,CAAC;aAAM,CAAC;YACN,QAAQ,GAAG,MAAM,QAAQ,CAAC;gBACxB,OAAO,EAAE,uBAAuB;gBAChC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,uBAAuB;aAClE,CAAC,CAAC;YACH,QAAQ,GAAG,MAAM,QAAQ,CAAC;gBACxB,OAAO,EAAE,uCAAuC;gBAChD,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,uBAAuB;aAClE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC;QACtE,QAAQ,GAAG,MAAM,QAAQ,CAAC;YACxB,OAAO,EAAE,uBAAuB;YAChC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,uBAAuB;SAClE,CAAC,CAAC;QACH,QAAQ,GAAG,MAAM,QAAQ,CAAC;YACxB,OAAO,EAAE,uCAAuC;YAChD,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,uBAAuB;SAClE,CAAC,CAAC;IACL,CAAC;IAED,kBAAkB;IAClB,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;IAE9C,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC;QAC7B,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,WAAW,CAAC,sBAAsB,CAAC,IAAI,sBAAsB;KACvE,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,MAAM,QAAQ,CAAC;QACrC,OAAO,EAAE,sCAAsC;KAChD,CAAC,CAAC;IAEH,uBAAuB;IACvB,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAE/B,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC;QAC1B,OAAO,EAAE,sDAAsD;QAC/D,OAAO,EAAE,MAAM;KAChB,CAAC,CAAC;IAEH,uBAAuB;IACvB,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC,wFAAwF,CAAC,CAAC;IAEtG,MAAM,eAAe,GAAG,MAAM,KAAK,CAAC;QAClC,OAAO,EAAE,+CAA+C;QACxD,OAAO,EAAE,WAAW;QACpB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,kDAAkD;KACnG,CAAC,CAAC;IAEH,2CAA2C;IAC3C,MAAM,WAAW,GAA2B;QAC1C,eAAe,EAAE,QAAQ;QACzB,eAAe,EAAE,QAAQ;QACzB,oBAAoB,EAAE,UAAU;QAChC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxE,CAAC;IAEF,mBAAmB,CAAC,WAAW,CAAC,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,uBAAuB,iBAAiB,EAAE,CAAC,CAAC;IAExD,uBAAuB;IACvB,MAAM,MAAM,GAAmB;QAC7B,OAAO;QACP,aAAa,EAAE,IAAI,EAAE,+DAA+D;QACpF,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,SAAS;QACvB,MAAM,EAAE;YACN,OAAO,EAAE;gBACP,IAAI,EAAE,kBAAkB;gBACxB,WAAW,EAAE,oDAAoD;aAClE;SACF;QACD,QAAQ,EAAE,EAAE;KACb,CAAC;IAEF,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3B,OAAO,CAAC,GAAG,CAAC,aAAa,mBAAmB,EAAE,CAAC,CAAC;IAEhD,OAAO,CAAC,GAAG,CAAC;;qCAEuB,eAAe;;iBAEnC,mBAAmB;;;;2DAIuB,eAAe;;CAEzE,CAAC,CAAC;AACH,CAAC"}