@helyx/bot 0.4.37 → 0.4.39

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 (74) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +22 -2
  3. package/dist/cloud-management-module.js +275 -206
  4. package/dist/command-registration-policy.d.ts +8 -0
  5. package/dist/command-registration-policy.js +39 -0
  6. package/dist/command-sync.d.ts +5 -1
  7. package/dist/command-sync.js +67 -3
  8. package/dist/control-server.d.ts +15 -3
  9. package/dist/control-server.js +34 -48
  10. package/dist/dashboard-commands.js +5 -1
  11. package/dist/developer-operations-module.d.ts +2 -0
  12. package/dist/developer-operations-module.js +65 -4
  13. package/dist/discord-rest.d.ts +5 -0
  14. package/dist/discord-rest.js +9 -0
  15. package/dist/events.js +4 -2
  16. package/dist/first-time-command-setup.d.ts +25 -0
  17. package/dist/first-time-command-setup.js +22 -0
  18. package/dist/guild-command-control-routes.d.ts +5 -0
  19. package/dist/guild-command-control-routes.js +191 -0
  20. package/dist/guild-command-discord-client.d.ts +21 -0
  21. package/dist/guild-command-discord-client.js +50 -0
  22. package/dist/guild-command-projection.d.ts +46 -0
  23. package/dist/guild-command-projection.js +254 -0
  24. package/dist/guild-command-read-service.d.ts +201 -0
  25. package/dist/guild-command-read-service.js +272 -0
  26. package/dist/guild-command-release.d.ts +19 -0
  27. package/dist/guild-command-release.js +47 -0
  28. package/dist/guild-command-worker.d.ts +49 -0
  29. package/dist/guild-command-worker.js +443 -0
  30. package/dist/guilds.d.ts +1 -1
  31. package/dist/guilds.js +41 -26
  32. package/dist/index.js +75 -4
  33. package/dist/interactions.d.ts +15 -4
  34. package/dist/interactions.js +285 -70
  35. package/dist/management-module.js +117 -41
  36. package/dist/module-logging.d.ts +1 -0
  37. package/dist/module-logging.js +5 -5
  38. package/dist/module-status-control.d.ts +19 -0
  39. package/dist/module-status-control.js +69 -0
  40. package/dist/modules.d.ts +2 -0
  41. package/dist/modules.js +21 -2
  42. package/dist/nexus-commands.d.ts +1 -12
  43. package/dist/nexus-commands.js +1 -99
  44. package/dist/oauth-module.js +134 -88
  45. package/dist/permissions-module.js +112 -39
  46. package/dist/privacy-module.d.ts +2 -0
  47. package/dist/privacy-module.js +60 -0
  48. package/dist/scheduled-task-runner.js +36 -1
  49. package/dist/shard-contracts.d.ts +24 -0
  50. package/dist/shard-contracts.js +20 -0
  51. package/dist/shard-router.d.ts +1 -0
  52. package/dist/shard-router.js +7 -0
  53. package/dist/shard-supervisor.d.ts +2 -1
  54. package/dist/shard-supervisor.js +94 -16
  55. package/dist/shard-worker.d.ts +5 -0
  56. package/dist/shard-worker.js +9 -1
  57. package/dist/shared-commands.d.ts +13 -0
  58. package/dist/shared-commands.js +163 -0
  59. package/dist/support-module.js +51 -51
  60. package/dist/trivia-data-provider.d.ts +26 -0
  61. package/dist/trivia-data-provider.js +51 -0
  62. package/documentation/README.md +32 -0
  63. package/documentation/examples/.env.example +117 -0
  64. package/documentation/guides/getting-started.md +51 -0
  65. package/documentation/guides/self-hosting.md +40 -0
  66. package/documentation/modules/building-a-module.md +37 -0
  67. package/documentation/modules/publishing-a-module.md +27 -0
  68. package/documentation/modules/testing-a-module.md +46 -0
  69. package/documentation/reference/environment.md +42 -0
  70. package/documentation/runbooks/backup-and-recovery.md +41 -0
  71. package/documentation/runbooks/installation-and-upgrades.md +48 -0
  72. package/package.json +7 -4
  73. package/dist/oauth-module.test.d.ts +0 -2
  74. package/dist/oauth-module.test.js +0 -172
@@ -0,0 +1,48 @@
1
+ # Installation and upgrade runbook
2
+
3
+ ## First installation
4
+
5
+ 1. Generate the project with `helyx self-host init` and a reviewed build
6
+ manifest.
7
+ 2. Copy and complete `.env` with `helyx setup`.
8
+ 3. Install dependencies with `npm ci` if they were not installed during
9
+ generation.
10
+ 4. Run `npm run doctor` and resolve every error.
11
+ 5. Start the process and confirm database, Discord, and module health.
12
+ 6. Exercise one low-risk command in a test server before production use.
13
+
14
+ ## Upgrade
15
+
16
+ 1. Read the release notes and confirm the supported Node.js and npm versions.
17
+ 2. Create a current PostgreSQL backup and preserve the existing project
18
+ metadata and environment file.
19
+ 3. Stop the Helyx process cleanly.
20
+ 4. Preview the compatible release set:
21
+
22
+ ```sh
23
+ helyx self-host update --check
24
+ ```
25
+
26
+ 5. Apply it only after reviewing the complete change:
27
+
28
+ ```sh
29
+ helyx self-host update --confirm
30
+ npm ci
31
+ npm run doctor
32
+ ```
33
+
34
+ 6. Start Helyx and verify health, command availability, and one representative
35
+ workflow for every upgraded module.
36
+
37
+ The updater backs up managed project metadata under `.helyx/backups` and does
38
+ not change `.env` or customer database data. Database migrations can be
39
+ forward-only. Never edit migration history or downgrade individual Helyx
40
+ packages to simulate a rollback.
41
+
42
+ ## Failed validation
43
+
44
+ Leave the process stopped, retain sanitized logs, and determine whether the
45
+ failure is configuration, package integrity, database connectivity, or module
46
+ compatibility. If the updater restored managed files, rerun `npm ci` and
47
+ `npm run doctor` before restarting the previous version. Restore data only from
48
+ a tested backup and only when the incident specifically requires it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helyx/bot",
3
- "version": "0.4.37",
3
+ "version": "0.4.39",
4
4
  "description": "Production Discord runtime for the modular Helyx platform.",
5
5
  "keywords": [
6
6
  "helyx",
@@ -32,6 +32,7 @@
32
32
  "dist",
33
33
  "!dist/*.tsbuildinfo",
34
34
  "!dist/**/*.map",
35
+ "documentation",
35
36
  "CHANGELOG.md",
36
37
  "README.md",
37
38
  "LICENSE"
@@ -44,6 +45,7 @@
44
45
  },
45
46
  "scripts": {
46
47
  "build": "tsc -b",
48
+ "prepack": "node ../../scripts/public-documentation.mjs --sync-bot",
47
49
  "dev": "node --env-file-if-exists=../../.env --import tsx src/index.ts",
48
50
  "start": "node --env-file-if-exists=../../.env dist/index.js"
49
51
  },
@@ -51,17 +53,18 @@
51
53
  "@helyx/config": ">=0.5.0 <0.6.0",
52
54
  "@helyx/control-protocol": ">=0.4.0 <0.5.0",
53
55
  "@helyx/core": ">=0.1.0 <0.2.0",
54
- "@helyx/database": ">=0.8.0 <0.9.0",
56
+ "@helyx/database": ">=0.8.6 <0.9.0",
55
57
  "@helyx/framework": ">=0.1.0 <0.2.0",
56
58
  "@helyx/module-manifest": ">=0.7.0 <0.8.0",
57
- "@helyx/platform": ">=0.6.0 <0.7.0",
58
- "@helyx/sdk": ">=0.8.0 <0.9.0",
59
+ "@helyx/platform": ">=0.6.2 <0.7.0",
60
+ "@helyx/sdk": ">=0.8.4 <0.9.0",
59
61
  "@helyx/status-client": ">=0.1.0 <0.2.0",
60
62
  "@helyx/verification-contracts": ">=0.3.0 <0.4.0",
61
63
  "discord.js": "14.27.0",
62
64
  "fastify": "5.10.0",
63
65
  "pino": "10.3.1",
64
66
  "redis": "6.1.0",
67
+ "undici": "6.28.0",
65
68
  "ws": "8.21.1",
66
69
  "zod": "4.4.3"
67
70
  },
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=oauth-module.test.d.ts.map
@@ -1,172 +0,0 @@
1
- import { HELYX_SERVICE_NAMES, } from "@helyx/sdk";
2
- import { describe, expect, it, vi } from "vitest";
3
- import { createOAuthModule } from "./oauth-module.js";
4
- describe("global OAuth command", () => {
5
- it("is global, guild-scoped and available without management permissions", () => {
6
- const module = createOAuthModule();
7
- const command = module.interactions?.commands?.[0];
8
- const revoke = command?.subcommands?.[0];
9
- expect(command).toMatchObject({
10
- name: "oauth",
11
- registration: "global",
12
- contexts: ["guild"],
13
- integrationTypes: ["guild_install"],
14
- });
15
- expect(revoke).toMatchObject({
16
- name: "revoke",
17
- rateLimit: { scope: "user", limit: 5, windowSeconds: 60 },
18
- });
19
- expect(module.manifest.permissions).toEqual([]);
20
- expect(module.defaultPermissions?.commands["oauth.revoke"]).toMatchObject({
21
- everyone: true,
22
- requiredPermissions: [],
23
- });
24
- });
25
- it("does not open a modal when the acting user has no connections", async () => {
26
- const oauth = oauthService({ connections: [] });
27
- const showModal = vi.fn();
28
- const replies = [];
29
- const command = createOAuthModule().interactions?.commands?.[0]?.subcommands?.[0];
30
- await command?.execute(commandContext(oauth, {
31
- showModal,
32
- reply: (response) => {
33
- replies.push(response);
34
- return Promise.resolve();
35
- },
36
- }));
37
- expect(showModal).not.toHaveBeenCalled();
38
- expect(replies).toEqual([expect.objectContaining({ ephemeral: true })]);
39
- expect(replies[0]?.componentsV2?.text?.[0]?.content).toContain("do not have any OAuth services");
40
- });
41
- it("uses opaque selections and requires explicit removal confirmation", async () => {
42
- const oauth = oauthService({
43
- connections: [
44
- {
45
- selectionRef: "opaque-selection-reference-123",
46
- provider: "youtube",
47
- displayName: "Example creator",
48
- connectedAt: new Date().toISOString(),
49
- },
50
- ],
51
- });
52
- const showModal = vi.fn();
53
- const command = createOAuthModule().interactions?.commands?.[0]?.subcommands?.[0];
54
- await command?.execute(commandContext(oauth, { showModal }));
55
- const modal = showModal.mock.calls[0]?.[0];
56
- expect(modal?.title).toBe("Revoke OAuth access");
57
- const service = modal?.items?.find((item) => "customId" in item && item.customId.endsWith("service"));
58
- const confirmation = modal?.items?.find((item) => "customId" in item && item.customId.endsWith("confirm"));
59
- expect(service).toMatchObject({
60
- type: "string-select",
61
- required: true,
62
- options: [
63
- expect.objectContaining({ value: "opaque-selection-reference-123" }),
64
- ],
65
- });
66
- expect(confirmation).toMatchObject({
67
- type: "string-select",
68
- required: true,
69
- options: [expect.objectContaining({ value: "remove_access" })],
70
- });
71
- });
72
- it("revokes only for the acting user and returns an ephemeral advisory", async () => {
73
- const revoke = vi.fn().mockResolvedValue({
74
- provider: "twitch",
75
- outcome: "completed",
76
- providerRevocation: "failed",
77
- });
78
- const oauth = oauthService({ connections: [], revoke });
79
- const replies = [];
80
- const modal = createOAuthModule().interactions?.modals?.[0];
81
- await modal?.execute(modalContext(oauth, {
82
- reply: (response) => {
83
- replies.push(response);
84
- return Promise.resolve();
85
- },
86
- }));
87
- expect(revoke).toHaveBeenCalledWith({
88
- discordUserId: "12345678901234567",
89
- selectionRef: "opaque-selection-reference-123",
90
- confirmation: "remove_access",
91
- comment: "No longer needed",
92
- correlationId: "interaction-1",
93
- });
94
- expect(replies[0]).toMatchObject({ ephemeral: true });
95
- expect(replies[0]?.componentsV2?.text?.[0]?.content).toContain("connected-app settings");
96
- });
97
- });
98
- function oauthService(input) {
99
- return {
100
- connectionStatus: () => Promise.resolve([]),
101
- listConnections: () => Promise.resolve(input.connections),
102
- createAuthorization: () => Promise.resolve({
103
- authorizationUrl: "https://example.com",
104
- expiresAt: "",
105
- }),
106
- verifyEligibility: () => Promise.resolve({
107
- provider: "youtube",
108
- status: "unknown",
109
- connected: false,
110
- checkedAt: new Date().toISOString(),
111
- }),
112
- revoke: input.revoke ??
113
- (() => Promise.resolve({
114
- provider: "youtube",
115
- outcome: "completed",
116
- providerRevocation: "completed",
117
- })),
118
- };
119
- }
120
- function services(oauth) {
121
- return {
122
- has: (name) => name === HELYX_SERVICE_NAMES.oauth,
123
- get: (name) => {
124
- if (name !== HELYX_SERVICE_NAMES.oauth)
125
- throw new Error("unknown service");
126
- return oauth;
127
- },
128
- };
129
- }
130
- function commandContext(oauth, overrides = {}) {
131
- return {
132
- ...interactionBase(oauth),
133
- showModal: () => Promise.resolve(),
134
- getStringOption: () => null,
135
- getBooleanOption: () => null,
136
- getUserOption: () => null,
137
- getChannelOption: () => null,
138
- getRoleOption: () => null,
139
- ...overrides,
140
- };
141
- }
142
- function modalContext(oauth, overrides = {}) {
143
- return {
144
- ...interactionBase(oauth),
145
- getField: (customId) => customId.endsWith("comment") ? "No longer needed" : "",
146
- getStringSelectValues: (customId) => customId.endsWith("service")
147
- ? ["opaque-selection-reference-123"]
148
- : ["remove_access"],
149
- getSelectedUserIds: () => [],
150
- getSelectedRoleIds: () => [],
151
- getSelectedMentionables: () => ({ userIds: [], roleIds: [] }),
152
- getSelectedChannelIds: () => [],
153
- ...overrides,
154
- };
155
- }
156
- function interactionBase(oauth) {
157
- return {
158
- interactionId: "interaction-1",
159
- userId: "12345678901234567",
160
- userDisplayName: "Member",
161
- userRoleIds: [],
162
- isAdministrator: false,
163
- serverId: "12345678901234568",
164
- services: services(oauth),
165
- reply: () => Promise.resolve(),
166
- sendToChannel: () => Promise.resolve({ messageId: "message-1" }),
167
- deleteChannelMessage: () => Promise.resolve(),
168
- editChannelMessage: () => Promise.resolve(),
169
- addChannelMessageReaction: () => Promise.resolve(),
170
- };
171
- }
172
- //# sourceMappingURL=oauth-module.test.js.map