discord-ops 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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +150 -0
- package/dist/cli/health.d.ts +2 -0
- package/dist/cli/health.d.ts.map +1 -0
- package/dist/cli/health.js +2 -0
- package/dist/cli/health.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +98 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client.d.ts +22 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +87 -0
- package/dist/client.js.map +1 -0
- package/dist/config/index.d.ts +17 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +58 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/profiles.d.ts +18 -0
- package/dist/config/profiles.d.ts.map +1 -0
- package/dist/config/profiles.js +28 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/config/schema.d.ts +76 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +29 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/routing/fuzzy.d.ts +16 -0
- package/dist/routing/fuzzy.d.ts.map +1 -0
- package/dist/routing/fuzzy.js +67 -0
- package/dist/routing/fuzzy.js.map +1 -0
- package/dist/routing/resolver.d.ts +31 -0
- package/dist/routing/resolver.d.ts.map +1 -0
- package/dist/routing/resolver.js +60 -0
- package/dist/routing/resolver.js.map +1 -0
- package/dist/security/audit.d.ts +9 -0
- package/dist/security/audit.d.ts.map +1 -0
- package/dist/security/audit.js +20 -0
- package/dist/security/audit.js.map +1 -0
- package/dist/security/permissions.d.ts +6 -0
- package/dist/security/permissions.d.ts.map +1 -0
- package/dist/security/permissions.js +15 -0
- package/dist/security/permissions.js.map +1 -0
- package/dist/security/rate-limiter.d.ts +16 -0
- package/dist/security/rate-limiter.d.ts.map +1 -0
- package/dist/security/rate-limiter.js +36 -0
- package/dist/security/rate-limiter.js.map +1 -0
- package/dist/security/sanitizer.d.ts +5 -0
- package/dist/security/sanitizer.d.ts.map +1 -0
- package/dist/security/sanitizer.js +26 -0
- package/dist/security/sanitizer.js.map +1 -0
- package/dist/security/token-validator.d.ts +9 -0
- package/dist/security/token-validator.d.ts.map +1 -0
- package/dist/security/token-validator.js +18 -0
- package/dist/security/token-validator.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +64 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/channels/create-channel.d.ts +3 -0
- package/dist/tools/channels/create-channel.d.ts.map +1 -0
- package/dist/tools/channels/create-channel.js +46 -0
- package/dist/tools/channels/create-channel.js.map +1 -0
- package/dist/tools/channels/delete-channel.d.ts +3 -0
- package/dist/tools/channels/delete-channel.d.ts.map +1 -0
- package/dist/tools/channels/delete-channel.js +20 -0
- package/dist/tools/channels/delete-channel.js.map +1 -0
- package/dist/tools/channels/edit-channel.d.ts +3 -0
- package/dist/tools/channels/edit-channel.d.ts.map +1 -0
- package/dist/tools/channels/edit-channel.js +32 -0
- package/dist/tools/channels/edit-channel.js.map +1 -0
- package/dist/tools/channels/get-channel.d.ts +3 -0
- package/dist/tools/channels/get-channel.d.ts.map +1 -0
- package/dist/tools/channels/get-channel.js +34 -0
- package/dist/tools/channels/get-channel.js.map +1 -0
- package/dist/tools/channels/index.d.ts +6 -0
- package/dist/tools/channels/index.d.ts.map +1 -0
- package/dist/tools/channels/index.js +6 -0
- package/dist/tools/channels/index.js.map +1 -0
- package/dist/tools/channels/list-channels.d.ts +3 -0
- package/dist/tools/channels/list-channels.d.ts.map +1 -0
- package/dist/tools/channels/list-channels.js +47 -0
- package/dist/tools/channels/list-channels.js.map +1 -0
- package/dist/tools/guilds/get-guild.d.ts +3 -0
- package/dist/tools/guilds/get-guild.d.ts.map +1 -0
- package/dist/tools/guilds/get-guild.js +30 -0
- package/dist/tools/guilds/get-guild.js.map +1 -0
- package/dist/tools/guilds/index.d.ts +3 -0
- package/dist/tools/guilds/index.d.ts.map +1 -0
- package/dist/tools/guilds/index.js +3 -0
- package/dist/tools/guilds/index.js.map +1 -0
- package/dist/tools/guilds/list-guilds.d.ts +3 -0
- package/dist/tools/guilds/list-guilds.d.ts.map +1 -0
- package/dist/tools/guilds/list-guilds.js +25 -0
- package/dist/tools/guilds/list-guilds.js.map +1 -0
- package/dist/tools/health-check.d.ts +3 -0
- package/dist/tools/health-check.d.ts.map +1 -0
- package/dist/tools/health-check.js +45 -0
- package/dist/tools/health-check.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +46 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/members/get-member.d.ts +3 -0
- package/dist/tools/members/get-member.d.ts.map +1 -0
- package/dist/tools/members/get-member.js +30 -0
- package/dist/tools/members/get-member.js.map +1 -0
- package/dist/tools/members/index.d.ts +3 -0
- package/dist/tools/members/index.d.ts.map +1 -0
- package/dist/tools/members/index.js +3 -0
- package/dist/tools/members/index.js.map +1 -0
- package/dist/tools/members/list-members.d.ts +3 -0
- package/dist/tools/members/list-members.d.ts.map +1 -0
- package/dist/tools/members/list-members.js +32 -0
- package/dist/tools/members/list-members.js.map +1 -0
- package/dist/tools/messaging/add-reaction.d.ts +3 -0
- package/dist/tools/messaging/add-reaction.d.ts.map +1 -0
- package/dist/tools/messaging/add-reaction.js +28 -0
- package/dist/tools/messaging/add-reaction.js.map +1 -0
- package/dist/tools/messaging/delete-message.d.ts +3 -0
- package/dist/tools/messaging/delete-message.d.ts.map +1 -0
- package/dist/tools/messaging/delete-message.js +28 -0
- package/dist/tools/messaging/delete-message.js.map +1 -0
- package/dist/tools/messaging/edit-message.d.ts +3 -0
- package/dist/tools/messaging/edit-message.d.ts.map +1 -0
- package/dist/tools/messaging/edit-message.js +33 -0
- package/dist/tools/messaging/edit-message.js.map +1 -0
- package/dist/tools/messaging/get-messages.d.ts +3 -0
- package/dist/tools/messaging/get-messages.d.ts.map +1 -0
- package/dist/tools/messaging/get-messages.js +48 -0
- package/dist/tools/messaging/get-messages.js.map +1 -0
- package/dist/tools/messaging/index.d.ts +6 -0
- package/dist/tools/messaging/index.d.ts.map +1 -0
- package/dist/tools/messaging/index.js +6 -0
- package/dist/tools/messaging/index.js.map +1 -0
- package/dist/tools/messaging/send-message.d.ts +3 -0
- package/dist/tools/messaging/send-message.d.ts.map +1 -0
- package/dist/tools/messaging/send-message.js +40 -0
- package/dist/tools/messaging/send-message.js.map +1 -0
- package/dist/tools/roles/index.d.ts +2 -0
- package/dist/tools/roles/index.d.ts.map +1 -0
- package/dist/tools/roles/index.js +2 -0
- package/dist/tools/roles/index.js.map +1 -0
- package/dist/tools/roles/list-roles.d.ts +3 -0
- package/dist/tools/roles/list-roles.d.ts.map +1 -0
- package/dist/tools/roles/list-roles.js +32 -0
- package/dist/tools/roles/list-roles.js.map +1 -0
- package/dist/tools/threads/create-thread.d.ts +3 -0
- package/dist/tools/threads/create-thread.d.ts.map +1 -0
- package/dist/tools/threads/create-thread.js +46 -0
- package/dist/tools/threads/create-thread.js.map +1 -0
- package/dist/tools/threads/index.d.ts +3 -0
- package/dist/tools/threads/index.d.ts.map +1 -0
- package/dist/tools/threads/index.js +3 -0
- package/dist/tools/threads/index.js.map +1 -0
- package/dist/tools/threads/list-threads.d.ts +3 -0
- package/dist/tools/threads/list-threads.d.ts.map +1 -0
- package/dist/tools/threads/list-threads.js +33 -0
- package/dist/tools/threads/list-threads.js.map +1 -0
- package/dist/tools/types.d.ts +29 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +10 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/transport/stdio.d.ts +3 -0
- package/dist/transport/stdio.d.ts.map +1 -0
- package/dist/transport/stdio.js +8 -0
- package/dist/transport/stdio.js.map +1 -0
- package/dist/utils/cache.d.ts +10 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +27 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +32 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/retry.d.ts +7 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +27 -0
- package/dist/utils/retry.js.map +1 -0
- package/package.json +80 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# discord-ops
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- 18 MCP tools: messaging, channels, guilds, members, roles, threads, health check
|
|
8
|
+
- Multi-guild project routing with channel aliases and notification types
|
|
9
|
+
- Per-project `.discord-ops.json` config overrides
|
|
10
|
+
- Lazy Discord login (tools enumerate before connection)
|
|
11
|
+
- Zod input validation on all tools
|
|
12
|
+
- Error sanitization (tokens, webhooks, snowflakes stripped)
|
|
13
|
+
- Audit logging to stderr
|
|
14
|
+
- Fuzzy name resolution (exact, normalized, substring)
|
|
15
|
+
- Stdio transport
|
|
16
|
+
- Full OSS scaffolding (MIT, CI/CD, changesets, dependabot)
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Booked Solid Technology
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# discord-ops
|
|
2
|
+
|
|
3
|
+
Agency-grade Discord MCP server with multi-guild project routing.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/bookedsolidtech/discord-ops/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/discord-ops)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **18 MCP tools** — messaging, channels, guilds, members, roles, threads, health check
|
|
12
|
+
- **Multi-guild project routing** — `send_message({ project: "my-app", channel: "builds" })` instead of raw channel IDs
|
|
13
|
+
- **Notification routing** — map notification types (ci_build, deploy, error) to channels per project
|
|
14
|
+
- **Lazy login** — tools enumerate before Discord connects; first tool call triggers login
|
|
15
|
+
- **Zod validation** — all inputs validated before execution
|
|
16
|
+
- **Error sanitization** — tokens, webhook URLs, and snowflake IDs stripped from error output
|
|
17
|
+
- **Audit logging** — every tool call logged to stderr
|
|
18
|
+
- **Fuzzy name resolution** — find channels/roles/members by name, normalized name, or substring
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Install
|
|
24
|
+
npm install -g discord-ops
|
|
25
|
+
|
|
26
|
+
# Set your bot token
|
|
27
|
+
export DISCORD_TOKEN="your-bot-token"
|
|
28
|
+
|
|
29
|
+
# Run health check
|
|
30
|
+
discord-ops health
|
|
31
|
+
|
|
32
|
+
# Start MCP server (stdio)
|
|
33
|
+
discord-ops
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Claude Code Integration
|
|
37
|
+
|
|
38
|
+
Add to your `.mcp.json`:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"discord": {
|
|
44
|
+
"command": "npx",
|
|
45
|
+
"args": ["-y", "discord-ops"],
|
|
46
|
+
"env": {
|
|
47
|
+
"DISCORD_TOKEN": "your-bot-token"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Project Routing
|
|
55
|
+
|
|
56
|
+
The killer feature: route messages by project name and channel alias instead of raw IDs.
|
|
57
|
+
|
|
58
|
+
### Global config (`~/.discord-ops.json`)
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"projects": {
|
|
63
|
+
"my-app": {
|
|
64
|
+
"guild_id": "123456789012345678",
|
|
65
|
+
"channels": {
|
|
66
|
+
"dev": "CHANNEL_ID",
|
|
67
|
+
"builds": "CHANNEL_ID",
|
|
68
|
+
"alerts": "CHANNEL_ID"
|
|
69
|
+
},
|
|
70
|
+
"default_channel": "dev"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"default_project": "my-app",
|
|
74
|
+
"notification_routing": {
|
|
75
|
+
"ci_build": "builds",
|
|
76
|
+
"error": "alerts",
|
|
77
|
+
"dev": "dev"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Per-project config (`.discord-ops.json` in repo root)
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"project": "my-app",
|
|
87
|
+
"notification_routing": {
|
|
88
|
+
"ci_build": "builds",
|
|
89
|
+
"deploy": "builds"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Usage
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
# By project + channel alias
|
|
98
|
+
send_message({ project: "my-app", channel: "builds", content: "Build passed!" })
|
|
99
|
+
|
|
100
|
+
# By notification type (auto-routed)
|
|
101
|
+
send_message({ notification_type: "ci_build", content: "CI green" })
|
|
102
|
+
|
|
103
|
+
# Direct channel ID (always works)
|
|
104
|
+
send_message({ channel_id: "123456789", content: "Hello" })
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Tools (v0.1.0)
|
|
108
|
+
|
|
109
|
+
| Tool | Description |
|
|
110
|
+
|------|-------------|
|
|
111
|
+
| `send_message` | Send a message with project routing |
|
|
112
|
+
| `get_messages` | Fetch recent messages |
|
|
113
|
+
| `edit_message` | Edit a bot message |
|
|
114
|
+
| `delete_message` | Delete a message |
|
|
115
|
+
| `add_reaction` | React to a message |
|
|
116
|
+
| `list_channels` | List guild channels |
|
|
117
|
+
| `get_channel` | Get channel details |
|
|
118
|
+
| `create_channel` | Create a channel |
|
|
119
|
+
| `edit_channel` | Edit channel properties |
|
|
120
|
+
| `delete_channel` | Delete a channel |
|
|
121
|
+
| `list_guilds` | List bot's guilds |
|
|
122
|
+
| `get_guild` | Get guild details |
|
|
123
|
+
| `list_members` | List guild members |
|
|
124
|
+
| `get_member` | Get member details |
|
|
125
|
+
| `list_roles` | List guild roles |
|
|
126
|
+
| `create_thread` | Create a thread |
|
|
127
|
+
| `list_threads` | List active threads |
|
|
128
|
+
| `health_check` | Bot status + permissions |
|
|
129
|
+
|
|
130
|
+
## Environment Variables
|
|
131
|
+
|
|
132
|
+
| Variable | Required | Description |
|
|
133
|
+
|----------|----------|-------------|
|
|
134
|
+
| `DISCORD_TOKEN` | Yes | Discord bot token |
|
|
135
|
+
| `DISCORD_OPS_CONFIG` | No | Path to global config (default: `~/.discord-ops.json`) |
|
|
136
|
+
| `DISCORD_OPS_LOG_LEVEL` | No | `debug`, `info`, `warn`, `error` (default: `info`) |
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
git clone https://github.com/bookedsolidtech/discord-ops.git
|
|
142
|
+
cd discord-ops
|
|
143
|
+
npm install
|
|
144
|
+
npm run build
|
|
145
|
+
npm test
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/cli/health.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/cli/health.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { loadConfig } from "../config/index.js";
|
|
3
|
+
import { DiscordClient } from "../client.js";
|
|
4
|
+
import { createServer } from "../server.js";
|
|
5
|
+
import { startStdioTransport } from "../transport/stdio.js";
|
|
6
|
+
import { logger, setLogLevel } from "../utils/logger.js";
|
|
7
|
+
async function main() {
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
// Handle --help
|
|
10
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
11
|
+
printUsage();
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
// Handle --version
|
|
15
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
16
|
+
console.log("discord-ops 0.1.0");
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
// Handle health subcommand
|
|
20
|
+
if (args[0] === "health") {
|
|
21
|
+
await runHealthCheck();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// Configure log level
|
|
25
|
+
const logLevel = process.env.DISCORD_OPS_LOG_LEVEL;
|
|
26
|
+
if (logLevel)
|
|
27
|
+
setLogLevel(logLevel);
|
|
28
|
+
// Load config (does NOT require Discord connection)
|
|
29
|
+
const config = loadConfig();
|
|
30
|
+
// Create lazy Discord client
|
|
31
|
+
const discord = new DiscordClient(config.token);
|
|
32
|
+
// Create MCP server with tool context
|
|
33
|
+
const server = createServer({ discord, config });
|
|
34
|
+
// Start stdio transport
|
|
35
|
+
await startStdioTransport(server);
|
|
36
|
+
// Graceful shutdown
|
|
37
|
+
const shutdown = async () => {
|
|
38
|
+
logger.info("Shutting down...");
|
|
39
|
+
await discord.destroy();
|
|
40
|
+
process.exit(0);
|
|
41
|
+
};
|
|
42
|
+
process.on("SIGINT", shutdown);
|
|
43
|
+
process.on("SIGTERM", shutdown);
|
|
44
|
+
}
|
|
45
|
+
async function runHealthCheck() {
|
|
46
|
+
try {
|
|
47
|
+
const config = loadConfig();
|
|
48
|
+
const discord = new DiscordClient(config.token);
|
|
49
|
+
console.log("Connecting to Discord...");
|
|
50
|
+
const client = await discord.getClient();
|
|
51
|
+
console.log(`\nBot: ${client.user?.tag}`);
|
|
52
|
+
console.log(`Guilds: ${client.guilds.cache.size}`);
|
|
53
|
+
for (const [id, guild] of client.guilds.cache) {
|
|
54
|
+
const me = await guild.members.fetchMe();
|
|
55
|
+
console.log(`\n ${guild.name} (${id})`);
|
|
56
|
+
console.log(` Members: ${guild.memberCount}`);
|
|
57
|
+
console.log(` Permissions: ${me.permissions.toArray().join(", ")}`);
|
|
58
|
+
}
|
|
59
|
+
console.log(`\nProjects configured: ${Object.keys(config.global.projects).length}`);
|
|
60
|
+
for (const [name, project] of Object.entries(config.global.projects)) {
|
|
61
|
+
console.log(` ${name}: guild=${project.guild_id}, channels=${Object.keys(project.channels).join(", ")}`);
|
|
62
|
+
}
|
|
63
|
+
console.log("\nHealth check passed.");
|
|
64
|
+
await discord.destroy();
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.error("Health check failed:", err instanceof Error ? err.message : err);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function printUsage() {
|
|
72
|
+
console.log(`
|
|
73
|
+
discord-ops - Agency-grade Discord MCP server
|
|
74
|
+
|
|
75
|
+
USAGE:
|
|
76
|
+
discord-ops Start MCP server (stdio transport)
|
|
77
|
+
discord-ops health Run health check + permission audit
|
|
78
|
+
discord-ops --help Show this help
|
|
79
|
+
discord-ops --version Show version
|
|
80
|
+
|
|
81
|
+
ENVIRONMENT:
|
|
82
|
+
DISCORD_TOKEN Discord bot token (required)
|
|
83
|
+
DISCORD_OPS_CONFIG Path to global config file (default: ~/.discord-ops.json)
|
|
84
|
+
DISCORD_OPS_LOG_LEVEL Log level: debug, info, warn, error (default: info)
|
|
85
|
+
|
|
86
|
+
CONFIG FILES:
|
|
87
|
+
~/.discord-ops.json Global project routing config
|
|
88
|
+
.discord-ops.json Per-project overrides (in repo root)
|
|
89
|
+
|
|
90
|
+
DOCUMENTATION:
|
|
91
|
+
https://github.com/bookedsolidtech/discord-ops
|
|
92
|
+
`);
|
|
93
|
+
}
|
|
94
|
+
main().catch((err) => {
|
|
95
|
+
logger.error("Fatal error", { error: err instanceof Error ? err.message : String(err) });
|
|
96
|
+
process.exit(1);
|
|
97
|
+
});
|
|
98
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGzD,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEnC,gBAAgB;IAChB,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,UAAU,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,mBAAmB;IACnB,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;QACjC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,2BAA2B;IAC3B,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QACzB,MAAM,cAAc,EAAE,CAAC;QACvB,OAAO;IACT,CAAC;IAED,sBAAsB;IACtB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,qBAA6C,CAAC;IAC3E,IAAI,QAAQ;QAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IAEpC,oDAAoD;IACpD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAE5B,6BAA6B;IAC7B,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEhD,sCAAsC;IACtC,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAEjD,wBAAwB;IACxB,MAAM,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAElC,oBAAoB;IACpB,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAChC,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAClC,CAAC;AAED,KAAK,UAAU,cAAc;IAC3B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAEhD,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC;QAEzC,OAAO,CAAC,GAAG,CAAC,UAAU,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QAEnD,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAC9C,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,EAAE,GAAG,CAAC,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;YACjD,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzE,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,0BAA0B,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACpF,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrE,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,WAAW,OAAO,CAAC,QAAQ,cAAc,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5G,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;QACtC,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAChF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,SAAS,UAAU;IACjB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;CAoBb,CAAC,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,MAAM,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACzF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Client, type Guild, type TextChannel } from "discord.js";
|
|
2
|
+
/**
|
|
3
|
+
* Lazy Discord client — tools enumerate before Discord connects.
|
|
4
|
+
* First tool call triggers login + cache warmup.
|
|
5
|
+
*/
|
|
6
|
+
export declare class DiscordClient {
|
|
7
|
+
private client;
|
|
8
|
+
private token;
|
|
9
|
+
private connecting;
|
|
10
|
+
private guildCache;
|
|
11
|
+
constructor(token: string);
|
|
12
|
+
/**
|
|
13
|
+
* Returns the underlying discord.js Client, connecting lazily on first call.
|
|
14
|
+
*/
|
|
15
|
+
getClient(): Promise<Client>;
|
|
16
|
+
private connect;
|
|
17
|
+
getGuild(guildId: string): Promise<Guild>;
|
|
18
|
+
getChannel(channelId: string): Promise<TextChannel>;
|
|
19
|
+
destroy(): Promise<void>;
|
|
20
|
+
get isConnected(): boolean;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,KAAK,KAAK,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAKrF;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,UAAU,CAA4B;gBAElC,KAAK,EAAE,MAAM;IAQzB;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;YASpB,OAAO;IAgCf,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAUzC,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IASnD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ9B,IAAI,WAAW,IAAI,OAAO,CAEzB;CACF"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Client, GatewayIntentBits } from "discord.js";
|
|
2
|
+
import { logger } from "./utils/logger.js";
|
|
3
|
+
import { validateTokenFormat } from "./security/token-validator.js";
|
|
4
|
+
import { TTLCache } from "./utils/cache.js";
|
|
5
|
+
/**
|
|
6
|
+
* Lazy Discord client — tools enumerate before Discord connects.
|
|
7
|
+
* First tool call triggers login + cache warmup.
|
|
8
|
+
*/
|
|
9
|
+
export class DiscordClient {
|
|
10
|
+
client = null;
|
|
11
|
+
token;
|
|
12
|
+
connecting = null;
|
|
13
|
+
guildCache = new TTLCache(300);
|
|
14
|
+
constructor(token) {
|
|
15
|
+
const validation = validateTokenFormat(token);
|
|
16
|
+
if (!validation.valid) {
|
|
17
|
+
throw new Error(`Invalid Discord token: ${validation.reason}`);
|
|
18
|
+
}
|
|
19
|
+
this.token = token;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Returns the underlying discord.js Client, connecting lazily on first call.
|
|
23
|
+
*/
|
|
24
|
+
async getClient() {
|
|
25
|
+
if (this.client?.isReady())
|
|
26
|
+
return this.client;
|
|
27
|
+
if (this.connecting)
|
|
28
|
+
return this.connecting;
|
|
29
|
+
this.connecting = this.connect();
|
|
30
|
+
return this.connecting;
|
|
31
|
+
}
|
|
32
|
+
async connect() {
|
|
33
|
+
logger.info("Connecting to Discord...");
|
|
34
|
+
const client = new Client({
|
|
35
|
+
intents: [
|
|
36
|
+
GatewayIntentBits.Guilds,
|
|
37
|
+
GatewayIntentBits.GuildMessages,
|
|
38
|
+
GatewayIntentBits.GuildMembers,
|
|
39
|
+
GatewayIntentBits.MessageContent,
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
await client.login(this.token);
|
|
43
|
+
await new Promise((resolve) => {
|
|
44
|
+
if (client.isReady()) {
|
|
45
|
+
resolve();
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
client.once("ready", () => resolve());
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
logger.info("Discord connected", {
|
|
52
|
+
user: client.user?.tag,
|
|
53
|
+
guilds: client.guilds.cache.size,
|
|
54
|
+
});
|
|
55
|
+
this.client = client;
|
|
56
|
+
this.connecting = null;
|
|
57
|
+
return client;
|
|
58
|
+
}
|
|
59
|
+
async getGuild(guildId) {
|
|
60
|
+
const cached = this.guildCache.get(guildId);
|
|
61
|
+
if (cached)
|
|
62
|
+
return cached;
|
|
63
|
+
const client = await this.getClient();
|
|
64
|
+
const guild = await client.guilds.fetch(guildId);
|
|
65
|
+
this.guildCache.set(guildId, guild);
|
|
66
|
+
return guild;
|
|
67
|
+
}
|
|
68
|
+
async getChannel(channelId) {
|
|
69
|
+
const client = await this.getClient();
|
|
70
|
+
const channel = await client.channels.fetch(channelId);
|
|
71
|
+
if (!channel || !channel.isTextBased()) {
|
|
72
|
+
throw new Error(`Channel ${channelId} not found or not a text channel`);
|
|
73
|
+
}
|
|
74
|
+
return channel;
|
|
75
|
+
}
|
|
76
|
+
async destroy() {
|
|
77
|
+
if (this.client) {
|
|
78
|
+
this.client.destroy();
|
|
79
|
+
this.client = null;
|
|
80
|
+
logger.info("Discord client destroyed");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
get isConnected() {
|
|
84
|
+
return this.client?.isReady() ?? false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type GlobalConfig, type PerProjectConfig } from "./schema.js";
|
|
2
|
+
export interface LoadedConfig {
|
|
3
|
+
global: GlobalConfig;
|
|
4
|
+
perProject?: PerProjectConfig;
|
|
5
|
+
token: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Loads config from environment + files.
|
|
9
|
+
*
|
|
10
|
+
* Resolution priority:
|
|
11
|
+
* 1. Per-project `.discord-ops.json` (cwd)
|
|
12
|
+
* 2. Global `~/.discord-ops.json` or DISCORD_OPS_CONFIG env var
|
|
13
|
+
* 3. Direct params always work regardless
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadConfig(): LoadedConfig;
|
|
16
|
+
export { type GlobalConfig, type PerProjectConfig } from "./schema.js";
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACtB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,IAAI,YAAY,CAUzC;AA0CD,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { resolve, join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
import { GlobalConfigSchema, PerProjectConfigSchema, } from "./schema.js";
|
|
6
|
+
/**
|
|
7
|
+
* Loads config from environment + files.
|
|
8
|
+
*
|
|
9
|
+
* Resolution priority:
|
|
10
|
+
* 1. Per-project `.discord-ops.json` (cwd)
|
|
11
|
+
* 2. Global `~/.discord-ops.json` or DISCORD_OPS_CONFIG env var
|
|
12
|
+
* 3. Direct params always work regardless
|
|
13
|
+
*/
|
|
14
|
+
export function loadConfig() {
|
|
15
|
+
const token = process.env.DISCORD_TOKEN;
|
|
16
|
+
if (!token) {
|
|
17
|
+
throw new Error("DISCORD_TOKEN environment variable is required");
|
|
18
|
+
}
|
|
19
|
+
const global = loadGlobalConfig();
|
|
20
|
+
const perProject = loadPerProjectConfig();
|
|
21
|
+
return { global, perProject, token };
|
|
22
|
+
}
|
|
23
|
+
function loadGlobalConfig() {
|
|
24
|
+
const configPath = process.env.DISCORD_OPS_CONFIG ?? resolve(homedir(), ".discord-ops.json");
|
|
25
|
+
if (!existsSync(configPath)) {
|
|
26
|
+
logger.debug("No global config found", { path: configPath });
|
|
27
|
+
return { projects: {} };
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
31
|
+
return GlobalConfigSchema.parse(raw);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
logger.warn("Failed to parse global config, using empty config", {
|
|
35
|
+
path: configPath,
|
|
36
|
+
error: err instanceof Error ? err.message : String(err),
|
|
37
|
+
});
|
|
38
|
+
return { projects: {} };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function loadPerProjectConfig() {
|
|
42
|
+
const configPath = join(process.cwd(), ".discord-ops.json");
|
|
43
|
+
if (!existsSync(configPath)) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
48
|
+
return PerProjectConfigSchema.parse(raw);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
logger.warn("Failed to parse per-project config", {
|
|
52
|
+
path: configPath,
|
|
53
|
+
error: err instanceof Error ? err.message : String(err),
|
|
54
|
+
});
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EACL,kBAAkB,EAClB,sBAAsB,GAGvB,MAAM,aAAa,CAAC;AAQrB;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACxC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,UAAU,GAAG,oBAAoB,EAAE,CAAC;IAE1C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;AACvC,CAAC;AAED,SAAS,gBAAgB;IACvB,MAAM,UAAU,GACd,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,mBAAmB,CAAC,CAAC;IAE5E,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QAC7D,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC1B,CAAC;IAED,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;QAC1D,OAAO,kBAAkB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,mDAAmD,EAAE;YAC/D,IAAI,EAAE,UAAU;YAChB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACxD,CAAC,CAAC;QACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB;IAC3B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,mBAAmB,CAAC,CAAC;IAE5D,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;QAC1D,OAAO,sBAAsB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,oCAAoC,EAAE;YAChD,IAAI,EAAE,UAAU;YAChB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACxD,CAAC,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { GlobalConfig, PerProjectConfig } from "./schema.js";
|
|
2
|
+
export interface ResolvedProject {
|
|
3
|
+
name: string;
|
|
4
|
+
guildId: string;
|
|
5
|
+
channels: Record<string, string>;
|
|
6
|
+
defaultChannel?: string;
|
|
7
|
+
notificationRouting?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolves a project from global + per-project config.
|
|
11
|
+
* Per-project notification_routing overrides global.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveProject(projectName: string, globalConfig: GlobalConfig, perProjectConfig?: PerProjectConfig): ResolvedProject | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Returns the default project name from config.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getDefaultProjectName(globalConfig: GlobalConfig, perProjectConfig?: PerProjectConfig): string | undefined;
|
|
18
|
+
//# sourceMappingURL=profiles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../src/config/profiles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAElE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,YAAY,EAC1B,gBAAgB,CAAC,EAAE,gBAAgB,GAClC,eAAe,GAAG,SAAS,CAiB7B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,YAAY,EAC1B,gBAAgB,CAAC,EAAE,gBAAgB,GAClC,MAAM,GAAG,SAAS,CAEpB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves a project from global + per-project config.
|
|
3
|
+
* Per-project notification_routing overrides global.
|
|
4
|
+
*/
|
|
5
|
+
export function resolveProject(projectName, globalConfig, perProjectConfig) {
|
|
6
|
+
const project = globalConfig.projects[projectName];
|
|
7
|
+
if (!project)
|
|
8
|
+
return undefined;
|
|
9
|
+
// Merge notification routing: per-project overrides global
|
|
10
|
+
const notificationRouting = {
|
|
11
|
+
...globalConfig.notification_routing,
|
|
12
|
+
...perProjectConfig?.notification_routing,
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
name: projectName,
|
|
16
|
+
guildId: project.guild_id,
|
|
17
|
+
channels: project.channels,
|
|
18
|
+
defaultChannel: project.default_channel,
|
|
19
|
+
notificationRouting,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns the default project name from config.
|
|
24
|
+
*/
|
|
25
|
+
export function getDefaultProjectName(globalConfig, perProjectConfig) {
|
|
26
|
+
return perProjectConfig?.project ?? globalConfig.default_project;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=profiles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profiles.js","sourceRoot":"","sources":["../../src/config/profiles.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,WAAmB,EACnB,YAA0B,EAC1B,gBAAmC;IAEnC,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACnD,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAE/B,2DAA2D;IAC3D,MAAM,mBAAmB,GAAG;QAC1B,GAAG,YAAY,CAAC,oBAAoB;QACpC,GAAG,gBAAgB,EAAE,oBAAoB;KAC1C,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,OAAO,CAAC,QAAQ;QACzB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,cAAc,EAAE,OAAO,CAAC,eAAe;QACvC,mBAAmB;KACpB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CACnC,YAA0B,EAC1B,gBAAmC;IAEnC,OAAO,gBAAgB,EAAE,OAAO,IAAI,YAAY,CAAC,eAAe,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const NotificationType: z.ZodEnum<["ci_build", "deploy", "release", "error", "announcement", "dev"]>;
|
|
3
|
+
export type NotificationType = z.infer<typeof NotificationType>;
|
|
4
|
+
export declare const ProjectConfigSchema: z.ZodObject<{
|
|
5
|
+
guild_id: z.ZodString;
|
|
6
|
+
channels: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
7
|
+
default_channel: z.ZodOptional<z.ZodString>;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
guild_id: string;
|
|
10
|
+
channels: Record<string, string>;
|
|
11
|
+
default_channel?: string | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
guild_id: string;
|
|
14
|
+
channels: Record<string, string>;
|
|
15
|
+
default_channel?: string | undefined;
|
|
16
|
+
}>;
|
|
17
|
+
export type ProjectConfig = z.infer<typeof ProjectConfigSchema>;
|
|
18
|
+
export declare const GlobalConfigSchema: z.ZodObject<{
|
|
19
|
+
projects: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
20
|
+
guild_id: z.ZodString;
|
|
21
|
+
channels: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
22
|
+
default_channel: z.ZodOptional<z.ZodString>;
|
|
23
|
+
}, "strip", z.ZodTypeAny, {
|
|
24
|
+
guild_id: string;
|
|
25
|
+
channels: Record<string, string>;
|
|
26
|
+
default_channel?: string | undefined;
|
|
27
|
+
}, {
|
|
28
|
+
guild_id: string;
|
|
29
|
+
channels: Record<string, string>;
|
|
30
|
+
default_channel?: string | undefined;
|
|
31
|
+
}>>;
|
|
32
|
+
default_project: z.ZodOptional<z.ZodString>;
|
|
33
|
+
notification_routing: z.ZodOptional<z.ZodRecord<z.ZodEnum<["ci_build", "deploy", "release", "error", "announcement", "dev"]>, z.ZodString>>;
|
|
34
|
+
}, "strip", z.ZodTypeAny, {
|
|
35
|
+
projects: Record<string, {
|
|
36
|
+
guild_id: string;
|
|
37
|
+
channels: Record<string, string>;
|
|
38
|
+
default_channel?: string | undefined;
|
|
39
|
+
}>;
|
|
40
|
+
default_project?: string | undefined;
|
|
41
|
+
notification_routing?: Partial<Record<"error" | "ci_build" | "deploy" | "release" | "announcement" | "dev", string>> | undefined;
|
|
42
|
+
}, {
|
|
43
|
+
projects: Record<string, {
|
|
44
|
+
guild_id: string;
|
|
45
|
+
channels: Record<string, string>;
|
|
46
|
+
default_channel?: string | undefined;
|
|
47
|
+
}>;
|
|
48
|
+
default_project?: string | undefined;
|
|
49
|
+
notification_routing?: Partial<Record<"error" | "ci_build" | "deploy" | "release" | "announcement" | "dev", string>> | undefined;
|
|
50
|
+
}>;
|
|
51
|
+
export type GlobalConfig = z.infer<typeof GlobalConfigSchema>;
|
|
52
|
+
export declare const PerProjectConfigSchema: z.ZodObject<{
|
|
53
|
+
project: z.ZodString;
|
|
54
|
+
notification_routing: z.ZodOptional<z.ZodRecord<z.ZodEnum<["ci_build", "deploy", "release", "error", "announcement", "dev"]>, z.ZodString>>;
|
|
55
|
+
}, "strip", z.ZodTypeAny, {
|
|
56
|
+
project: string;
|
|
57
|
+
notification_routing?: Partial<Record<"error" | "ci_build" | "deploy" | "release" | "announcement" | "dev", string>> | undefined;
|
|
58
|
+
}, {
|
|
59
|
+
project: string;
|
|
60
|
+
notification_routing?: Partial<Record<"error" | "ci_build" | "deploy" | "release" | "announcement" | "dev", string>> | undefined;
|
|
61
|
+
}>;
|
|
62
|
+
export type PerProjectConfig = z.infer<typeof PerProjectConfigSchema>;
|
|
63
|
+
export declare const EnvConfigSchema: z.ZodObject<{
|
|
64
|
+
DISCORD_TOKEN: z.ZodString;
|
|
65
|
+
DISCORD_OPS_CONFIG: z.ZodOptional<z.ZodString>;
|
|
66
|
+
DISCORD_OPS_LOG_LEVEL: z.ZodOptional<z.ZodEnum<["debug", "info", "warn", "error"]>>;
|
|
67
|
+
}, "strip", z.ZodTypeAny, {
|
|
68
|
+
DISCORD_TOKEN: string;
|
|
69
|
+
DISCORD_OPS_LOG_LEVEL?: "debug" | "info" | "warn" | "error" | undefined;
|
|
70
|
+
DISCORD_OPS_CONFIG?: string | undefined;
|
|
71
|
+
}, {
|
|
72
|
+
DISCORD_TOKEN: string;
|
|
73
|
+
DISCORD_OPS_LOG_LEVEL?: "debug" | "info" | "warn" | "error" | undefined;
|
|
74
|
+
DISCORD_OPS_CONFIG?: string | undefined;
|
|
75
|
+
}>;
|
|
76
|
+
//# sourceMappingURL=schema.d.ts.map
|