@mulmobridge/discord 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.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # @mulmobridge/discord
2
+
3
+ > **Experimental** — please test and [report issues](https://github.com/receptron/mulmoclaude/issues/new).
4
+
5
+ Discord bridge for [MulmoClaude](https://github.com/receptron/mulmoclaude). The bot responds to messages in channels it has access to.
6
+
7
+ ## Setup
8
+
9
+ ### 1. Create a Discord Application
10
+
11
+ 1. Go to [discord.com/developers/applications](https://discord.com/developers/applications) → **New Application**
12
+ 2. Name it (e.g. "MulmoClaude")
13
+
14
+ ### 2. Create the Bot
15
+
16
+ 1. **Bot** tab → **Add Bot**
17
+ 2. Copy the **Token** (this is your `DISCORD_BOT_TOKEN`)
18
+ 3. Enable **Message Content Intent** under Privileged Gateway Intents
19
+
20
+ ### 3. Invite the bot to your server
21
+
22
+ **OAuth2** → **URL Generator**:
23
+ - Scopes: `bot`
24
+ - Permissions: `Send Messages`, `Read Message History`
25
+
26
+ Copy the generated URL and open it in your browser to invite the bot.
27
+
28
+ ### 4. Run the bridge
29
+
30
+ ```bash
31
+ # With mock server (testing)
32
+ npx @mulmobridge/mock-server &
33
+ DISCORD_BOT_TOKEN=... \
34
+ MULMOCLAUDE_AUTH_TOKEN=mock-test-token \
35
+ npx @mulmobridge/discord
36
+
37
+ # With real MulmoClaude
38
+ DISCORD_BOT_TOKEN=... \
39
+ npx @mulmobridge/discord
40
+ ```
41
+
42
+ ## Environment Variables
43
+
44
+ | Variable | Required | Description |
45
+ |---|---|---|
46
+ | `DISCORD_BOT_TOKEN` | Yes | Bot token from Developer Portal |
47
+ | `DISCORD_ALLOWED_CHANNELS` | No | CSV of channel IDs to restrict (empty = all) |
48
+ | `MULMOCLAUDE_API_URL` | No | Default `http://localhost:3001` |
49
+ | `MULMOCLAUDE_AUTH_TOKEN` | No | Bearer token (auto-read from workspace) |
50
+
51
+ ## License
52
+
53
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
package/dist/index.js ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ // @mulmobridge/discord — Discord bridge for MulmoClaude.
3
+ //
4
+ // Required env vars:
5
+ // DISCORD_BOT_TOKEN — Bot token from Discord Developer Portal
6
+ //
7
+ // Optional:
8
+ // DISCORD_ALLOWED_CHANNELS — CSV of channel IDs (empty = allow all)
9
+ // MULMOCLAUDE_API_URL — default http://localhost:3001
10
+ // MULMOCLAUDE_AUTH_TOKEN — bearer token (or read from workspace)
11
+ import "dotenv/config";
12
+ import { Client, GatewayIntentBits, Partials } from "discord.js";
13
+ import { createBridgeClient } from "@mulmobridge/client";
14
+ const TRANSPORT_ID = "discord";
15
+ const MAX_DISCORD_LENGTH = 2000;
16
+ const token = process.env.DISCORD_BOT_TOKEN;
17
+ if (!token) {
18
+ console.error("DISCORD_BOT_TOKEN is required.\nSee README for setup instructions.");
19
+ process.exit(1);
20
+ }
21
+ const allowedChannels = new Set((process.env.DISCORD_ALLOWED_CHANNELS ?? "")
22
+ .split(",")
23
+ .map((s) => s.trim())
24
+ .filter(Boolean));
25
+ const allowAll = allowedChannels.size === 0;
26
+ const discord = new Client({
27
+ intents: [
28
+ GatewayIntentBits.Guilds,
29
+ GatewayIntentBits.GuildMessages,
30
+ GatewayIntentBits.DirectMessages,
31
+ GatewayIntentBits.MessageContent,
32
+ ],
33
+ // Partials.Channel is required to receive DM messageCreate events —
34
+ // DM channels are not cached by default in discord.js v14.
35
+ partials: [Partials.Channel],
36
+ });
37
+ const mulmo = createBridgeClient({ transportId: TRANSPORT_ID });
38
+ mulmo.onPush(async (ev) => {
39
+ try {
40
+ const channel = discord.channels.cache.get(ev.chatId) ??
41
+ (await discord.channels.fetch(ev.chatId).catch(() => null));
42
+ if (channel?.isTextBased() && "send" in channel) {
43
+ const sendable = channel;
44
+ await sendChunkedToSendable(sendable, ev.message);
45
+ }
46
+ else {
47
+ console.warn(`[discord] push: channel ${ev.chatId} not found or not text-based`);
48
+ }
49
+ }
50
+ catch (err) {
51
+ console.error(`[discord] push send failed: ${err}`);
52
+ }
53
+ });
54
+ discord.on("messageCreate", async (msg) => {
55
+ if (msg.author.bot)
56
+ return;
57
+ const channelId = msg.channelId;
58
+ const text = msg.content.trim();
59
+ if (!text)
60
+ return;
61
+ if (!allowAll && !allowedChannels.has(channelId))
62
+ return;
63
+ console.log(`[discord] message channel=${channelId} user=${msg.author.tag} len=${text.length}`);
64
+ try {
65
+ const ack = await mulmo.send(channelId, text);
66
+ if (ack.ok) {
67
+ await sendChunked(msg, ack.reply ?? "");
68
+ }
69
+ else {
70
+ const status = ack.status ? ` (${ack.status})` : "";
71
+ await msg.reply(`Error${status}: ${ack.error ?? "unknown"}`);
72
+ }
73
+ }
74
+ catch (err) {
75
+ console.error(`[discord] message handling failed: ${err}`);
76
+ }
77
+ });
78
+ async function sendChunked(msg, text) {
79
+ if (text.length === 0) {
80
+ await msg.reply("(empty reply)");
81
+ return;
82
+ }
83
+ for (let i = 0; i < text.length; i += MAX_DISCORD_LENGTH) {
84
+ const chunk = text.slice(i, i + MAX_DISCORD_LENGTH);
85
+ if (i === 0) {
86
+ await msg.reply(chunk);
87
+ }
88
+ else if ("send" in msg.channel) {
89
+ await msg.channel.send(chunk);
90
+ }
91
+ }
92
+ }
93
+ async function sendChunkedToSendable(channel, text) {
94
+ const content = text.length === 0 ? "(empty reply)" : text;
95
+ for (let i = 0; i < content.length; i += MAX_DISCORD_LENGTH) {
96
+ await channel.send(content.slice(i, i + MAX_DISCORD_LENGTH));
97
+ }
98
+ }
99
+ discord.once("ready", () => {
100
+ console.log("MulmoClaude Discord bridge");
101
+ console.log(`Logged in as ${discord.user?.tag}`);
102
+ console.log(`Channels: ${allowAll ? "(all)" : [...allowedChannels].join(", ")}`);
103
+ });
104
+ discord.login(token).catch((err) => {
105
+ console.error("Fatal:", err);
106
+ process.exit(1);
107
+ });
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@mulmobridge/discord",
3
+ "version": "0.1.0",
4
+ "description": "Discord bridge for MulmoBridge — connect a Discord bot to MulmoClaude",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "mulmobridge-discord": "./dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "prepack": "yarn build",
18
+ "typecheck": "tsc --noEmit",
19
+ "test": "echo no tests yet"
20
+ },
21
+ "license": "MIT",
22
+ "author": "Receptron Team",
23
+ "dependencies": {
24
+ "@mulmobridge/client": "^0.1.0",
25
+ "@mulmobridge/protocol": "^0.1.0",
26
+ "discord.js": "^14.26.3",
27
+ "dotenv": "^17.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^25.6.0",
31
+ "typescript": "^6.0.3"
32
+ }
33
+ }