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.
Files changed (184) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +150 -0
  4. package/dist/cli/health.d.ts +2 -0
  5. package/dist/cli/health.d.ts.map +1 -0
  6. package/dist/cli/health.js +2 -0
  7. package/dist/cli/health.js.map +1 -0
  8. package/dist/cli/index.d.ts +3 -0
  9. package/dist/cli/index.d.ts.map +1 -0
  10. package/dist/cli/index.js +98 -0
  11. package/dist/cli/index.js.map +1 -0
  12. package/dist/client.d.ts +22 -0
  13. package/dist/client.d.ts.map +1 -0
  14. package/dist/client.js +87 -0
  15. package/dist/client.js.map +1 -0
  16. package/dist/config/index.d.ts +17 -0
  17. package/dist/config/index.d.ts.map +1 -0
  18. package/dist/config/index.js +58 -0
  19. package/dist/config/index.js.map +1 -0
  20. package/dist/config/profiles.d.ts +18 -0
  21. package/dist/config/profiles.d.ts.map +1 -0
  22. package/dist/config/profiles.js +28 -0
  23. package/dist/config/profiles.js.map +1 -0
  24. package/dist/config/schema.d.ts +76 -0
  25. package/dist/config/schema.d.ts.map +1 -0
  26. package/dist/config/schema.js +29 -0
  27. package/dist/config/schema.js.map +1 -0
  28. package/dist/index.d.ts +7 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +5 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/routing/fuzzy.d.ts +16 -0
  33. package/dist/routing/fuzzy.d.ts.map +1 -0
  34. package/dist/routing/fuzzy.js +67 -0
  35. package/dist/routing/fuzzy.js.map +1 -0
  36. package/dist/routing/resolver.d.ts +31 -0
  37. package/dist/routing/resolver.d.ts.map +1 -0
  38. package/dist/routing/resolver.js +60 -0
  39. package/dist/routing/resolver.js.map +1 -0
  40. package/dist/security/audit.d.ts +9 -0
  41. package/dist/security/audit.d.ts.map +1 -0
  42. package/dist/security/audit.js +20 -0
  43. package/dist/security/audit.js.map +1 -0
  44. package/dist/security/permissions.d.ts +6 -0
  45. package/dist/security/permissions.d.ts.map +1 -0
  46. package/dist/security/permissions.js +15 -0
  47. package/dist/security/permissions.js.map +1 -0
  48. package/dist/security/rate-limiter.d.ts +16 -0
  49. package/dist/security/rate-limiter.d.ts.map +1 -0
  50. package/dist/security/rate-limiter.js +36 -0
  51. package/dist/security/rate-limiter.js.map +1 -0
  52. package/dist/security/sanitizer.d.ts +5 -0
  53. package/dist/security/sanitizer.d.ts.map +1 -0
  54. package/dist/security/sanitizer.js +26 -0
  55. package/dist/security/sanitizer.js.map +1 -0
  56. package/dist/security/token-validator.d.ts +9 -0
  57. package/dist/security/token-validator.d.ts.map +1 -0
  58. package/dist/security/token-validator.js +18 -0
  59. package/dist/security/token-validator.js.map +1 -0
  60. package/dist/server.d.ts +4 -0
  61. package/dist/server.d.ts.map +1 -0
  62. package/dist/server.js +64 -0
  63. package/dist/server.js.map +1 -0
  64. package/dist/tools/channels/create-channel.d.ts +3 -0
  65. package/dist/tools/channels/create-channel.d.ts.map +1 -0
  66. package/dist/tools/channels/create-channel.js +46 -0
  67. package/dist/tools/channels/create-channel.js.map +1 -0
  68. package/dist/tools/channels/delete-channel.d.ts +3 -0
  69. package/dist/tools/channels/delete-channel.d.ts.map +1 -0
  70. package/dist/tools/channels/delete-channel.js +20 -0
  71. package/dist/tools/channels/delete-channel.js.map +1 -0
  72. package/dist/tools/channels/edit-channel.d.ts +3 -0
  73. package/dist/tools/channels/edit-channel.d.ts.map +1 -0
  74. package/dist/tools/channels/edit-channel.js +32 -0
  75. package/dist/tools/channels/edit-channel.js.map +1 -0
  76. package/dist/tools/channels/get-channel.d.ts +3 -0
  77. package/dist/tools/channels/get-channel.d.ts.map +1 -0
  78. package/dist/tools/channels/get-channel.js +34 -0
  79. package/dist/tools/channels/get-channel.js.map +1 -0
  80. package/dist/tools/channels/index.d.ts +6 -0
  81. package/dist/tools/channels/index.d.ts.map +1 -0
  82. package/dist/tools/channels/index.js +6 -0
  83. package/dist/tools/channels/index.js.map +1 -0
  84. package/dist/tools/channels/list-channels.d.ts +3 -0
  85. package/dist/tools/channels/list-channels.d.ts.map +1 -0
  86. package/dist/tools/channels/list-channels.js +47 -0
  87. package/dist/tools/channels/list-channels.js.map +1 -0
  88. package/dist/tools/guilds/get-guild.d.ts +3 -0
  89. package/dist/tools/guilds/get-guild.d.ts.map +1 -0
  90. package/dist/tools/guilds/get-guild.js +30 -0
  91. package/dist/tools/guilds/get-guild.js.map +1 -0
  92. package/dist/tools/guilds/index.d.ts +3 -0
  93. package/dist/tools/guilds/index.d.ts.map +1 -0
  94. package/dist/tools/guilds/index.js +3 -0
  95. package/dist/tools/guilds/index.js.map +1 -0
  96. package/dist/tools/guilds/list-guilds.d.ts +3 -0
  97. package/dist/tools/guilds/list-guilds.d.ts.map +1 -0
  98. package/dist/tools/guilds/list-guilds.js +25 -0
  99. package/dist/tools/guilds/list-guilds.js.map +1 -0
  100. package/dist/tools/health-check.d.ts +3 -0
  101. package/dist/tools/health-check.d.ts.map +1 -0
  102. package/dist/tools/health-check.js +45 -0
  103. package/dist/tools/health-check.js.map +1 -0
  104. package/dist/tools/index.d.ts +3 -0
  105. package/dist/tools/index.d.ts.map +1 -0
  106. package/dist/tools/index.js +46 -0
  107. package/dist/tools/index.js.map +1 -0
  108. package/dist/tools/members/get-member.d.ts +3 -0
  109. package/dist/tools/members/get-member.d.ts.map +1 -0
  110. package/dist/tools/members/get-member.js +30 -0
  111. package/dist/tools/members/get-member.js.map +1 -0
  112. package/dist/tools/members/index.d.ts +3 -0
  113. package/dist/tools/members/index.d.ts.map +1 -0
  114. package/dist/tools/members/index.js +3 -0
  115. package/dist/tools/members/index.js.map +1 -0
  116. package/dist/tools/members/list-members.d.ts +3 -0
  117. package/dist/tools/members/list-members.d.ts.map +1 -0
  118. package/dist/tools/members/list-members.js +32 -0
  119. package/dist/tools/members/list-members.js.map +1 -0
  120. package/dist/tools/messaging/add-reaction.d.ts +3 -0
  121. package/dist/tools/messaging/add-reaction.d.ts.map +1 -0
  122. package/dist/tools/messaging/add-reaction.js +28 -0
  123. package/dist/tools/messaging/add-reaction.js.map +1 -0
  124. package/dist/tools/messaging/delete-message.d.ts +3 -0
  125. package/dist/tools/messaging/delete-message.d.ts.map +1 -0
  126. package/dist/tools/messaging/delete-message.js +28 -0
  127. package/dist/tools/messaging/delete-message.js.map +1 -0
  128. package/dist/tools/messaging/edit-message.d.ts +3 -0
  129. package/dist/tools/messaging/edit-message.d.ts.map +1 -0
  130. package/dist/tools/messaging/edit-message.js +33 -0
  131. package/dist/tools/messaging/edit-message.js.map +1 -0
  132. package/dist/tools/messaging/get-messages.d.ts +3 -0
  133. package/dist/tools/messaging/get-messages.d.ts.map +1 -0
  134. package/dist/tools/messaging/get-messages.js +48 -0
  135. package/dist/tools/messaging/get-messages.js.map +1 -0
  136. package/dist/tools/messaging/index.d.ts +6 -0
  137. package/dist/tools/messaging/index.d.ts.map +1 -0
  138. package/dist/tools/messaging/index.js +6 -0
  139. package/dist/tools/messaging/index.js.map +1 -0
  140. package/dist/tools/messaging/send-message.d.ts +3 -0
  141. package/dist/tools/messaging/send-message.d.ts.map +1 -0
  142. package/dist/tools/messaging/send-message.js +40 -0
  143. package/dist/tools/messaging/send-message.js.map +1 -0
  144. package/dist/tools/roles/index.d.ts +2 -0
  145. package/dist/tools/roles/index.d.ts.map +1 -0
  146. package/dist/tools/roles/index.js +2 -0
  147. package/dist/tools/roles/index.js.map +1 -0
  148. package/dist/tools/roles/list-roles.d.ts +3 -0
  149. package/dist/tools/roles/list-roles.d.ts.map +1 -0
  150. package/dist/tools/roles/list-roles.js +32 -0
  151. package/dist/tools/roles/list-roles.js.map +1 -0
  152. package/dist/tools/threads/create-thread.d.ts +3 -0
  153. package/dist/tools/threads/create-thread.d.ts.map +1 -0
  154. package/dist/tools/threads/create-thread.js +46 -0
  155. package/dist/tools/threads/create-thread.js.map +1 -0
  156. package/dist/tools/threads/index.d.ts +3 -0
  157. package/dist/tools/threads/index.d.ts.map +1 -0
  158. package/dist/tools/threads/index.js +3 -0
  159. package/dist/tools/threads/index.js.map +1 -0
  160. package/dist/tools/threads/list-threads.d.ts +3 -0
  161. package/dist/tools/threads/list-threads.d.ts.map +1 -0
  162. package/dist/tools/threads/list-threads.js +33 -0
  163. package/dist/tools/threads/list-threads.js.map +1 -0
  164. package/dist/tools/types.d.ts +29 -0
  165. package/dist/tools/types.d.ts.map +1 -0
  166. package/dist/tools/types.js +10 -0
  167. package/dist/tools/types.js.map +1 -0
  168. package/dist/transport/stdio.d.ts +3 -0
  169. package/dist/transport/stdio.d.ts.map +1 -0
  170. package/dist/transport/stdio.js +8 -0
  171. package/dist/transport/stdio.js.map +1 -0
  172. package/dist/utils/cache.d.ts +10 -0
  173. package/dist/utils/cache.d.ts.map +1 -0
  174. package/dist/utils/cache.js +27 -0
  175. package/dist/utils/cache.js.map +1 -0
  176. package/dist/utils/logger.d.ts +14 -0
  177. package/dist/utils/logger.d.ts.map +1 -0
  178. package/dist/utils/logger.js +32 -0
  179. package/dist/utils/logger.js.map +1 -0
  180. package/dist/utils/retry.d.ts +7 -0
  181. package/dist/utils/retry.d.ts.map +1 -0
  182. package/dist/utils/retry.js +27 -0
  183. package/dist/utils/retry.js.map +1 -0
  184. 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
+ [![CI](https://github.com/bookedsolidtech/discord-ops/actions/workflows/ci.yml/badge.svg)](https://github.com/bookedsolidtech/discord-ops/actions/workflows/ci.yml)
6
+ [![npm](https://img.shields.io/npm/v/discord-ops)](https://www.npmjs.com/package/discord-ops)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=health.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/cli/health.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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":"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"}
@@ -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