@helyx/bot 0.1.3 → 0.2.1

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.
@@ -0,0 +1,491 @@
1
+ import { defineModule, } from "@helyx/sdk";
2
+ import { CloudConnectorUserError, } from "./cloud-connector.js";
3
+ const CONNECT_MODAL_ID = "helyx.cloud.connect";
4
+ const CONNECT_CODE_FIELD_ID = "helyx.cloud.connect.code";
5
+ const DISCONNECT_MODAL_ID = "helyx.cloud.disconnect";
6
+ const DISCONNECT_CONFIRM_FIELD_ID = "helyx.cloud.disconnect.confirm";
7
+ const ROTATE_MODAL_ID = "helyx.cloud.rotate";
8
+ const ROTATE_CONFIRM_FIELD_ID = "helyx.cloud.rotate.confirm";
9
+ const SUPPORT_MODAL_ID = "helyx.cloud.support";
10
+ const SUPPORT_ENABLED_FIELD_ID = "helyx.cloud.support.enabled";
11
+ const SUPPORT_DATA_FIELD_ID = "helyx.cloud.support.data";
12
+ const DIAGNOSTICS_ENABLED_FIELD_ID = "helyx.cloud.support.diagnostics";
13
+ const VIOLET = 0x7c3aed;
14
+ const SUCCESS = 0x16a34a;
15
+ const DANGER = 0xdc2626;
16
+ export function createCloudManagementModule(input) {
17
+ const requireOwner = async (context) => {
18
+ if (await isDeploymentOwner(input.client, context.userId, input.configuredOwnerUserIds))
19
+ return true;
20
+ await context.reply({
21
+ ephemeral: true,
22
+ componentsV2: {
23
+ accentColor: DANGER,
24
+ text: [
25
+ {
26
+ content: "## Deployment owner required\nOnly the Discord application owner, an application team member, or a user listed in `HELYX_OWNER_USER_IDS` can change Cloud Connect.",
27
+ },
28
+ ],
29
+ },
30
+ });
31
+ return false;
32
+ };
33
+ return defineModule({
34
+ manifest: {
35
+ manifestVersion: "0.1",
36
+ releaseStatus: "live",
37
+ id: "helyx.cloud-connect",
38
+ name: "Helyx Cloud Connect",
39
+ version: "0.1.0",
40
+ description: "Links this self-hosted Helyx deployment to the hosted dashboard.",
41
+ entry: "./dist/cloud-management-module.js",
42
+ framework: "^0.1.0",
43
+ dependencies: [],
44
+ permissions: [],
45
+ contributions: { commands: ["helyx-cloud"], events: [] },
46
+ },
47
+ defaultPermissions: {
48
+ module: restrictedPolicy(),
49
+ commands: {
50
+ "helyx-cloud.connect": restrictedPolicy(),
51
+ "helyx-cloud.status": restrictedPolicy(),
52
+ "helyx-cloud.disconnect": restrictedPolicy(),
53
+ "helyx-cloud.rotate": restrictedPolicy(),
54
+ "helyx-cloud.support": restrictedPolicy(),
55
+ },
56
+ },
57
+ interactions: {
58
+ commands: [
59
+ {
60
+ name: "helyx-cloud",
61
+ description: "Manage this self-hosted bot's Helyx Cloud connection",
62
+ registration: "global",
63
+ contexts: ["guild", "bot_dm"],
64
+ integrationTypes: ["guild_install"],
65
+ subcommands: [
66
+ {
67
+ name: "connect",
68
+ description: "Connect this deployment using a one-use code",
69
+ rateLimit: { scope: "user", limit: 5, windowSeconds: 60 },
70
+ async execute(context) {
71
+ if (!(await requireOwner(context)))
72
+ return;
73
+ await context.showModal({
74
+ customId: CONNECT_MODAL_ID,
75
+ title: "Connect to Helyx Cloud",
76
+ text: [
77
+ {
78
+ content: "Create a short-lived connection code in `app.helyx.gg`, then paste it below. The code is used once and is not stored.",
79
+ },
80
+ ],
81
+ fields: [
82
+ {
83
+ customId: CONNECT_CODE_FIELD_ID,
84
+ label: "Cloud Connect code",
85
+ description: "Five groups of four letters and numbers.",
86
+ style: "short",
87
+ required: true,
88
+ minLength: 24,
89
+ maxLength: 24,
90
+ placeholder: "ABCD-EFGH-JKLM-NPQR-STUV",
91
+ },
92
+ ],
93
+ });
94
+ },
95
+ },
96
+ {
97
+ name: "status",
98
+ description: "Show this deployment's Cloud Connect status",
99
+ rateLimit: { scope: "user", limit: 20, windowSeconds: 60 },
100
+ async execute(context) {
101
+ if (!(await requireOwner(context)))
102
+ return;
103
+ await context.reply(statusResponse(input.connector.status()));
104
+ },
105
+ },
106
+ {
107
+ name: "disconnect",
108
+ description: "Revoke this deployment's Cloud Connect identity",
109
+ rateLimit: { scope: "user", limit: 5, windowSeconds: 60 },
110
+ async execute(context) {
111
+ if (!(await requireOwner(context)))
112
+ return;
113
+ await context.showModal({
114
+ customId: DISCONNECT_MODAL_ID,
115
+ title: "Disconnect Helyx Cloud",
116
+ text: [
117
+ {
118
+ content: "This revokes the saved connector identity. A new dashboard code will be required to reconnect.",
119
+ },
120
+ ],
121
+ fields: [
122
+ {
123
+ type: "string-select",
124
+ customId: DISCONNECT_CONFIRM_FIELD_ID,
125
+ label: "Confirm disconnection",
126
+ required: true,
127
+ minValues: 1,
128
+ maxValues: 1,
129
+ options: [
130
+ {
131
+ label: "Keep Cloud Connect",
132
+ value: "keep",
133
+ default: true,
134
+ },
135
+ {
136
+ label: "Disconnect and revoke",
137
+ value: "disconnect",
138
+ },
139
+ ],
140
+ },
141
+ ],
142
+ });
143
+ },
144
+ },
145
+ {
146
+ name: "rotate",
147
+ description: "Replace the saved Cloud Connect credential",
148
+ rateLimit: { scope: "user", limit: 3, windowSeconds: 60 },
149
+ async execute(context) {
150
+ if (!(await requireOwner(context)))
151
+ return;
152
+ await context.showModal({
153
+ customId: ROTATE_MODAL_ID,
154
+ title: "Rotate Cloud credential",
155
+ text: [
156
+ {
157
+ content: "Rotation replaces the encrypted connector key and immediately reconnects the bot. Local Discord features keep running.",
158
+ },
159
+ ],
160
+ fields: [
161
+ {
162
+ type: "string-select",
163
+ customId: ROTATE_CONFIRM_FIELD_ID,
164
+ label: "Confirm credential rotation",
165
+ required: true,
166
+ minValues: 1,
167
+ maxValues: 1,
168
+ options: [
169
+ {
170
+ label: "Cancel",
171
+ value: "cancel",
172
+ default: true,
173
+ },
174
+ {
175
+ label: "Rotate credential",
176
+ value: "rotate",
177
+ },
178
+ ],
179
+ },
180
+ ],
181
+ });
182
+ },
183
+ },
184
+ {
185
+ name: "support",
186
+ description: "Choose what Helyx support may inspect",
187
+ rateLimit: { scope: "user", limit: 5, windowSeconds: 60 },
188
+ async execute(context) {
189
+ if (!(await requireOwner(context)))
190
+ return;
191
+ const consent = await input.connector.getSupportConsent();
192
+ await context.showModal({
193
+ customId: SUPPORT_MODAL_ID,
194
+ title: "Cloud support access",
195
+ text: [
196
+ {
197
+ content: "Support inspection is off by default. Submitted message content, secrets and arbitrary logs are never included.",
198
+ },
199
+ ],
200
+ fields: [
201
+ {
202
+ type: "string-select",
203
+ customId: SUPPORT_ENABLED_FIELD_ID,
204
+ label: "Allow bounded support inspection",
205
+ required: true,
206
+ minValues: 1,
207
+ maxValues: 1,
208
+ options: [
209
+ {
210
+ label: "No",
211
+ value: "no",
212
+ default: !consent.supportInspectionEnabled,
213
+ },
214
+ {
215
+ label: "Yes",
216
+ value: "yes",
217
+ default: consent.supportInspectionEnabled,
218
+ },
219
+ ],
220
+ },
221
+ {
222
+ type: "string-select",
223
+ customId: SUPPORT_DATA_FIELD_ID,
224
+ label: "Allowed support data",
225
+ description: "Select only the bounded metadata support may inspect.",
226
+ required: false,
227
+ minValues: 0,
228
+ maxValues: 5,
229
+ options: [
230
+ ["Module inventory", "module_inventory"],
231
+ ["Redacted configuration", "configuration"],
232
+ ["Permission summaries", "permissions"],
233
+ ["Logging policy", "logging"],
234
+ ["Connector health", "health"],
235
+ ].map(([label, value]) => ({
236
+ label,
237
+ value,
238
+ default: consent.supportDataClasses.includes(value),
239
+ })),
240
+ },
241
+ {
242
+ type: "string-select",
243
+ customId: DIAGNOSTICS_ENABLED_FIELD_ID,
244
+ label: "Allow bounded diagnostics",
245
+ required: true,
246
+ minValues: 1,
247
+ maxValues: 1,
248
+ options: [
249
+ {
250
+ label: "No",
251
+ value: "no",
252
+ default: !consent.diagnosticsEnabled,
253
+ },
254
+ {
255
+ label: "Yes",
256
+ value: "yes",
257
+ default: consent.diagnosticsEnabled,
258
+ },
259
+ ],
260
+ },
261
+ ],
262
+ });
263
+ },
264
+ },
265
+ ],
266
+ },
267
+ ],
268
+ modals: [
269
+ {
270
+ customId: CONNECT_MODAL_ID,
271
+ async execute(context) {
272
+ if (!(await requireOwner(context)))
273
+ return;
274
+ await context.deferReply?.(true);
275
+ try {
276
+ const status = await input.connector.enrol(context.getField(CONNECT_CODE_FIELD_ID), "discord");
277
+ await context.reply({
278
+ ephemeral: true,
279
+ componentsV2: {
280
+ accentColor: SUCCESS,
281
+ text: [
282
+ {
283
+ content: `## Cloud Connect saved\nInstallation \`${status.installationId ?? "pending"}\` has been enrolled. The bot is establishing its secure outbound connection now.`,
284
+ },
285
+ ],
286
+ },
287
+ });
288
+ }
289
+ catch (error) {
290
+ await context.reply(connectorErrorResponse(error));
291
+ }
292
+ },
293
+ },
294
+ {
295
+ customId: DISCONNECT_MODAL_ID,
296
+ async execute(context) {
297
+ if (!(await requireOwner(context)))
298
+ return;
299
+ const confirmation = context.getStringSelectValues(DISCONNECT_CONFIRM_FIELD_ID)[0];
300
+ if (confirmation !== "disconnect") {
301
+ await context.reply({
302
+ ephemeral: true,
303
+ componentsV2: {
304
+ accentColor: VIOLET,
305
+ text: [
306
+ {
307
+ content: "## Cloud Connect kept\nNo connection or credential changes were made.",
308
+ },
309
+ ],
310
+ },
311
+ });
312
+ return;
313
+ }
314
+ await context.deferReply?.(true);
315
+ try {
316
+ await input.connector.disconnect();
317
+ await context.reply({
318
+ ephemeral: true,
319
+ componentsV2: {
320
+ accentColor: SUCCESS,
321
+ text: [
322
+ {
323
+ content: "## Cloud Connect disconnected\nThe local connector identity has been revoked. This bot continues to work locally.",
324
+ },
325
+ ],
326
+ },
327
+ });
328
+ }
329
+ catch (error) {
330
+ await context.reply(connectorErrorResponse(error));
331
+ }
332
+ },
333
+ },
334
+ {
335
+ customId: ROTATE_MODAL_ID,
336
+ async execute(context) {
337
+ if (!(await requireOwner(context)))
338
+ return;
339
+ if (context.getStringSelectValues(ROTATE_CONFIRM_FIELD_ID)[0] !==
340
+ "rotate") {
341
+ await context.reply({
342
+ ephemeral: true,
343
+ componentsV2: {
344
+ accentColor: VIOLET,
345
+ text: [
346
+ {
347
+ content: "## Rotation cancelled\nThe connector credential was not changed.",
348
+ },
349
+ ],
350
+ },
351
+ });
352
+ return;
353
+ }
354
+ await context.deferReply?.(true);
355
+ try {
356
+ await input.connector.rotate();
357
+ await context.reply({
358
+ ephemeral: true,
359
+ componentsV2: {
360
+ accentColor: SUCCESS,
361
+ text: [
362
+ {
363
+ content: "## Credential rotated\nThe previous key was revoked and Cloud Connect is reconnecting with its new encrypted identity.",
364
+ },
365
+ ],
366
+ },
367
+ });
368
+ }
369
+ catch (error) {
370
+ await context.reply(connectorErrorResponse(error));
371
+ }
372
+ },
373
+ },
374
+ {
375
+ customId: SUPPORT_MODAL_ID,
376
+ async execute(context) {
377
+ if (!(await requireOwner(context)))
378
+ return;
379
+ await context.deferReply?.(true);
380
+ try {
381
+ const enabled = context.getStringSelectValues(SUPPORT_ENABLED_FIELD_ID)[0] ===
382
+ "yes";
383
+ const updated = await input.connector.updateSupportConsent({
384
+ supportInspectionEnabled: enabled,
385
+ supportDataClasses: [
386
+ ...context.getStringSelectValues(SUPPORT_DATA_FIELD_ID),
387
+ ],
388
+ diagnosticsEnabled: context.getStringSelectValues(DIAGNOSTICS_ENABLED_FIELD_ID)[0] === "yes",
389
+ });
390
+ await context.reply({
391
+ ephemeral: true,
392
+ componentsV2: {
393
+ accentColor: SUCCESS,
394
+ text: [
395
+ {
396
+ content: [
397
+ "## Support consent saved",
398
+ `**Inspection:** ${updated.supportInspectionEnabled ? "Allowed" : "Off"}`,
399
+ `**Allowed data:** ${updated.supportDataClasses.length ? updated.supportDataClasses.join(", ") : "None"}`,
400
+ `**Diagnostics:** ${updated.diagnosticsEnabled ? "Allowed" : "Off"}`,
401
+ ].join("\n"),
402
+ },
403
+ ],
404
+ },
405
+ });
406
+ }
407
+ catch (error) {
408
+ await context.reply(connectorErrorResponse(error));
409
+ }
410
+ },
411
+ },
412
+ ],
413
+ },
414
+ async start() { },
415
+ async stop() { },
416
+ });
417
+ }
418
+ async function isDeploymentOwner(client, userId, configuredOwnerUserIds) {
419
+ if (configuredOwnerUserIds.includes(userId))
420
+ return true;
421
+ const application = await client.application?.fetch();
422
+ const owner = application?.owner;
423
+ if (!owner)
424
+ return false;
425
+ if ("members" in owner)
426
+ return owner.members.has(userId);
427
+ return owner.id === userId;
428
+ }
429
+ function statusResponse(status) {
430
+ const labels = {
431
+ disabled: "Disabled by deployment configuration",
432
+ unpaired: "Ready for a connection code",
433
+ connecting: "Connecting securely",
434
+ online: "Connected",
435
+ offline: "Temporarily offline; local operation continues",
436
+ revoked: "Disconnected and revoked",
437
+ stopped: "Stopped",
438
+ };
439
+ return {
440
+ ephemeral: true,
441
+ componentsV2: {
442
+ accentColor: status.state === "online"
443
+ ? SUCCESS
444
+ : status.state === "revoked"
445
+ ? DANGER
446
+ : VIOLET,
447
+ text: [
448
+ {
449
+ content: [
450
+ "## Helyx Cloud Connect",
451
+ `**Status:** ${labels[status.state]}`,
452
+ status.installationId
453
+ ? `**Installation:** \`${status.installationId}\``
454
+ : undefined,
455
+ status.lastConnectedAt
456
+ ? `**Last connected:** <t:${Math.floor(status.lastConnectedAt.getTime() / 1_000)}:R>`
457
+ : undefined,
458
+ ]
459
+ .filter(Boolean)
460
+ .join("\n"),
461
+ },
462
+ ],
463
+ },
464
+ };
465
+ }
466
+ function connectorErrorResponse(error) {
467
+ return {
468
+ ephemeral: true,
469
+ componentsV2: {
470
+ accentColor: DANGER,
471
+ text: [
472
+ {
473
+ content: `## Cloud Connect could not complete that request\n${error instanceof CloudConnectorUserError
474
+ ? error.message
475
+ : "Cloud Connect is temporarily unavailable. The local bot continues to work normally."}`,
476
+ },
477
+ ],
478
+ },
479
+ };
480
+ }
481
+ function restrictedPolicy() {
482
+ return {
483
+ everyone: false,
484
+ requiredPermissions: ["MANAGE_GUILD"],
485
+ allowedRoleIds: [],
486
+ deniedRoleIds: [],
487
+ allowedUserIds: [],
488
+ deniedUserIds: [],
489
+ };
490
+ }
491
+ //# sourceMappingURL=cloud-management-module.js.map
@@ -34,6 +34,7 @@ interface DashboardModuleDefinition {
34
34
  category: string;
35
35
  icon: string;
36
36
  availability: "available";
37
+ releaseStatus: "planned" | "testing" | "beta" | "live";
37
38
  requiredPermissions: string[];
38
39
  settings: readonly unknown[];
39
40
  commands: DashboardCommandDefinition[];
@@ -575,6 +575,7 @@ function mapDashboardModule(module) {
575
575
  category: dashboard.category[0].toUpperCase() + dashboard.category.slice(1),
576
576
  icon: dashboard.icon,
577
577
  availability: "available",
578
+ releaseStatus: module.manifest.releaseStatus,
578
579
  requiredPermissions: [...module.manifest.permissions],
579
580
  settings: dashboard.settings,
580
581
  commands: dashboardCommands(module),
package/dist/index.js CHANGED
@@ -6,8 +6,11 @@ import { createPlatformServices } from "@helyx/platform";
6
6
  import { HELYX_SERVICE_NAMES } from "@helyx/sdk";
7
7
  import { Client, Events, GatewayIntentBits, Partials } from "discord.js";
8
8
  import pino from "pino";
9
+ import { createRequire } from "node:module";
9
10
  import { join } from "node:path";
10
11
  import { pathToFileURL } from "node:url";
12
+ import { CloudConnectorController } from "./cloud-connector.js";
13
+ import { createCloudManagementModule } from "./cloud-management-module.js";
11
14
  import { installInteractions } from "./interactions.js";
12
15
  import { installGuildTracking } from "./guilds.js";
13
16
  import { loadConfiguredModulePackages } from "./modules.js";
@@ -66,9 +69,29 @@ export async function startBot(processEnvironment = process.env) {
66
69
  allowedMentions: { parse: [] },
67
70
  });
68
71
  const guildResources = new DiscordGuildResourceDirectory(client);
72
+ const cloudConnector = new CloudConnectorController({
73
+ environment,
74
+ database,
75
+ client,
76
+ modules: modulePackages,
77
+ coreVersion: readBotVersion(),
78
+ logger: bootstrapLogger,
79
+ });
69
80
  const permissionsModule = createPermissionsModule(featureModules);
70
81
  const managementModule = createManagementModule(featureModules, guildResources);
71
- const modules = [managementModule, permissionsModule, ...featureModules];
82
+ const cloudManagementModule = environment.HELYX_CLOUD_CONNECT_ENABLED
83
+ ? createCloudManagementModule({
84
+ connector: cloudConnector,
85
+ client,
86
+ configuredOwnerUserIds: environment.HELYX_OWNER_USER_IDS,
87
+ })
88
+ : undefined;
89
+ const modules = [
90
+ managementModule,
91
+ permissionsModule,
92
+ ...(cloudManagementModule ? [cloudManagementModule] : []),
93
+ ...featureModules,
94
+ ];
72
95
  const platformServices = new Map(createPlatformServices(database, modules));
73
96
  if (featureModules.some(({ manifest }) => manifest.id === "helyx.logging")) {
74
97
  platformServices.set(HELYX_SERVICE_NAMES.moduleLogging, new DiscordModuleLoggingService(client, database, modules, bootstrapLogger));
@@ -79,7 +102,12 @@ export async function startBot(processEnvironment = process.env) {
79
102
  logger: bootstrapLogger,
80
103
  services: platformServices,
81
104
  });
82
- const prepareGuilds = installGuildTracking(client, database, modules, bootstrapLogger, new Set([managementModule.manifest.id, permissionsModule.manifest.id]));
105
+ const enabledCoreModuleIds = new Set([
106
+ managementModule.manifest.id,
107
+ permissionsModule.manifest.id,
108
+ ...(cloudManagementModule ? [cloudManagementModule.manifest.id] : []),
109
+ ]);
110
+ const prepareGuilds = installGuildTracking(client, database, modules, bootstrapLogger, enabledCoreModuleIds);
83
111
  const commandsReady = installInteractions(client, modules, {
84
112
  ...(environment.DISCORD_INTERNAL_GUILD_ID
85
113
  ? { internalGuildId: environment.DISCORD_INTERNAL_GUILD_ID }
@@ -87,6 +115,8 @@ export async function startBot(processEnvironment = process.env) {
87
115
  prepare: prepareGuilds,
88
116
  }, bootstrapLogger, serviceAccess);
89
117
  installModuleEvents(client, modules, bootstrapLogger, serviceAccess);
118
+ const controlService = new BotControlService(database, modules, guildResources, serviceAccess);
119
+ cloudConnector.setControlHandler(controlService);
90
120
  let controlServer;
91
121
  let shuttingDown = false;
92
122
  async function shutdown(reason = "programmatic") {
@@ -96,6 +126,10 @@ export async function startBot(processEnvironment = process.env) {
96
126
  process.off("SIGINT", onSigint);
97
127
  process.off("SIGTERM", onSigterm);
98
128
  bootstrapLogger.info({ event: "bot.shutdown", reason }, "Bot shutdown requested");
129
+ await cloudConnector.stop().catch((error) => {
130
+ process.exitCode = 1;
131
+ bootstrapLogger.error({ event: "cloud.stop_failed", error: safeError(error) }, "Cloud Connect shutdown failed");
132
+ });
99
133
  if (controlServer)
100
134
  await controlServer.close().catch((error) => {
101
135
  process.exitCode = 1;
@@ -122,9 +156,10 @@ export async function startBot(processEnvironment = process.env) {
122
156
  await runtime.start();
123
157
  await client.login(environment.DISCORD_TOKEN);
124
158
  await commandsReady;
159
+ await cloudConnector.start();
125
160
  if (environment.HELYX_BOT_CONTROL_SECRET) {
126
161
  controlServer = await createBotControlServer({
127
- service: new BotControlService(database, modules, guildResources, serviceAccess),
162
+ service: controlService,
128
163
  secret: environment.HELYX_BOT_CONTROL_SECRET,
129
164
  logger: { level: environment.LOG_LEVEL },
130
165
  });
@@ -142,6 +177,7 @@ export async function startBot(processEnvironment = process.env) {
142
177
  }
143
178
  }
144
179
  catch (error) {
180
+ await cloudConnector.stop().catch(() => undefined);
145
181
  if (controlServer)
146
182
  await controlServer.close().catch(() => undefined);
147
183
  try {
@@ -162,6 +198,14 @@ export async function startBot(processEnvironment = process.env) {
162
198
  throw error;
163
199
  }
164
200
  }
201
+ function readBotVersion() {
202
+ const metadata = createRequire(import.meta.url)("../package.json");
203
+ if (typeof metadata !== "object" ||
204
+ metadata === null ||
205
+ typeof metadata.version !== "string")
206
+ throw new Error("Bot package version is unavailable");
207
+ return metadata.version;
208
+ }
165
209
  export function gatewayIntentsForModules(modules) {
166
210
  const intents = [GatewayIntentBits.Guilds];
167
211
  if (modules.some((module) => module.events?.messageReactionAdd?.length)) {
@@ -28,6 +28,7 @@ export function createManagementModule(configurableModules, resources) {
28
28
  return defineModule({
29
29
  manifest: {
30
30
  manifestVersion: "0.1",
31
+ releaseStatus: "live",
31
32
  id: "helyx.management",
32
33
  name: "Helyx management",
33
34
  version: "0.1.0",
@@ -23,6 +23,7 @@ export function createPermissionsModule(configurableModules) {
23
23
  return defineModule({
24
24
  manifest: {
25
25
  manifestVersion: "0.1",
26
+ releaseStatus: "live",
26
27
  id: "helyx.permissions",
27
28
  name: "Permissions",
28
29
  version: "0.1.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helyx/bot",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Production Discord runtime for the modular Helyx platform.",
5
5
  "keywords": [
6
6
  "helyx",
@@ -48,16 +48,21 @@
48
48
  "start": "node --env-file-if-exists=../../.env dist/index.js"
49
49
  },
50
50
  "dependencies": {
51
- "@helyx/config": "0.1.2",
52
- "@helyx/core": "0.1.2",
53
- "@helyx/database": "0.1.1",
54
- "@helyx/framework": "0.1.2",
55
- "@helyx/module-manifest": "0.1.1",
56
- "@helyx/platform": "0.1.2",
57
- "@helyx/sdk": "0.2.2",
51
+ "@helyx/config": "0.2.1",
52
+ "@helyx/control-protocol": "0.1.1",
53
+ "@helyx/core": "0.1.4",
54
+ "@helyx/database": "0.2.0",
55
+ "@helyx/framework": "0.1.4",
56
+ "@helyx/module-manifest": "0.3.0",
57
+ "@helyx/platform": "0.1.4",
58
+ "@helyx/sdk": "0.2.4",
58
59
  "discord.js": "14.27.0",
59
60
  "fastify": "5.10.0",
60
61
  "pino": "10.3.1",
62
+ "ws": "8.21.1",
61
63
  "zod": "4.4.3"
64
+ },
65
+ "devDependencies": {
66
+ "@types/ws": "8.18.1"
62
67
  }
63
68
  }