@helyx/bot 1.5.4 → 1.7.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 +19 -0
- package/README.md +5 -2
- package/command-root-catalogue.json +21 -5
- package/dist/cloud-connector.js +2 -0
- package/dist/cloud-control-dispatcher.js +20 -0
- package/dist/command-registration-policy.js +2 -0
- package/dist/control-directory-contracts.d.ts +50 -0
- package/dist/control-directory-contracts.js +2 -0
- package/dist/control-server-schemas.d.ts +3 -3
- package/dist/control-server.d.ts +10 -46
- package/dist/control-server.js +50 -3
- package/dist/creator-invite-service.d.ts +7 -0
- package/dist/creator-invite-service.js +17 -0
- package/dist/developer-operations-module.js +0 -55
- package/dist/diagnostic-command.js +1 -0
- package/dist/discord-guild-health.d.ts +2 -2
- package/dist/discord-guild-health.js +1 -0
- package/dist/events.d.ts +1 -0
- package/dist/events.js +2 -0
- package/dist/help-credentials.d.ts +25 -0
- package/dist/help-credentials.js +149 -0
- package/dist/help-module.d.ts +27 -0
- package/dist/help-module.js +288 -0
- package/dist/help-runtime.d.ts +3 -0
- package/dist/help-runtime.js +42 -0
- package/dist/help-transport.d.ts +53 -0
- package/dist/help-transport.js +267 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +10 -0
- package/dist/interactions.js +10 -6
- package/dist/process-info-module.d.ts +3 -0
- package/dist/process-info-module.js +97 -0
- package/dist/server-overview-control.d.ts +35 -0
- package/dist/server-overview-control.js +362 -0
- package/dist/server-overview-discord.d.ts +33 -0
- package/dist/server-overview-discord.js +212 -0
- package/dist/server-overview-profile.d.ts +8 -0
- package/dist/server-overview-profile.js +62 -0
- package/dist/server-overview-routes.d.ts +4 -0
- package/dist/server-overview-routes.js +41 -0
- package/dist/shard-contracts.d.ts +66 -0
- package/dist/shard-contracts.js +23 -0
- package/dist/shard-coordinator.d.ts +1 -1
- package/dist/shard-router.d.ts +9 -0
- package/dist/shard-router.js +6 -0
- package/dist/shard-supervisor.d.ts +1 -1
- package/dist/shard-supervisor.js +8 -0
- package/dist/shard-worker.d.ts +1 -1
- package/dist/shard-worker.js +8 -0
- package/dist/verification-service.d.ts +4 -0
- package/dist/verification-service.js +10 -0
- package/documentation/README.md +1 -0
- package/documentation/examples/.env.example +29 -85
- package/documentation/guides/help-command.md +62 -0
- package/documentation/reference/environment.md +7 -6
- package/documentation/runbooks/installation-and-upgrades.md +4 -1
- package/package.json +6 -6
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createHelpRuntimeCollection, HELP_DOCS_URL, } from "@helyx/core";
|
|
3
|
+
import { HELP_API_ORIGIN, parseHelpCredential, } from "./help-credentials.js";
|
|
4
|
+
export class HelpTransportError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code = "unavailable") {
|
|
7
|
+
super(`Help ${code.replaceAll("_", " ")}`);
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const responseSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
contractVersion: z.literal(1),
|
|
14
|
+
requestId: z.string().uuid(),
|
|
15
|
+
revision: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
16
|
+
mode: z.enum(["lexical", "hybrid"]),
|
|
17
|
+
kind: z.enum(["answer", "choices", "no_match", "invalid"]),
|
|
18
|
+
degraded: z
|
|
19
|
+
.enum(["provider_disabled", "provider_unavailable", "budget_exhausted"])
|
|
20
|
+
.optional(),
|
|
21
|
+
faq: z.unknown().optional(),
|
|
22
|
+
faqs: z.array(z.unknown()).max(3).optional(),
|
|
23
|
+
choiceToken: z.string().max(2048).optional(),
|
|
24
|
+
reason: z.string().optional(),
|
|
25
|
+
docsUrl: z.string().optional(),
|
|
26
|
+
})
|
|
27
|
+
.strict();
|
|
28
|
+
function parseResponse(value) {
|
|
29
|
+
const parsed = responseSchema.parse(value), base = {
|
|
30
|
+
mode: parsed.mode,
|
|
31
|
+
revision: parsed.revision,
|
|
32
|
+
...(parsed.degraded ? { degraded: parsed.degraded } : {}),
|
|
33
|
+
};
|
|
34
|
+
if (parsed.kind === "answer") {
|
|
35
|
+
const faqs = createHelpRuntimeCollection([
|
|
36
|
+
parsed.faq,
|
|
37
|
+
]).faqs;
|
|
38
|
+
return { ...base, kind: "answer", faq: faqs[0] };
|
|
39
|
+
}
|
|
40
|
+
if (parsed.kind === "choices") {
|
|
41
|
+
if (!parsed.faqs?.length || !parsed.choiceToken)
|
|
42
|
+
throw new HelpTransportError();
|
|
43
|
+
const input = parsed.faqs;
|
|
44
|
+
const valid = new Map(createHelpRuntimeCollection(input).faqs.map((faq) => [faq.id, faq]));
|
|
45
|
+
const faqs = input.map((faq) => valid.get(faq.id));
|
|
46
|
+
return { ...base, kind: "choices", faqs, choiceToken: parsed.choiceToken };
|
|
47
|
+
}
|
|
48
|
+
if (parsed.kind === "no_match" &&
|
|
49
|
+
["empty", "weak_match"].includes(parsed.reason ?? "") &&
|
|
50
|
+
parsed.docsUrl === HELP_DOCS_URL)
|
|
51
|
+
return {
|
|
52
|
+
...base,
|
|
53
|
+
kind: "no_match",
|
|
54
|
+
reason: parsed.reason,
|
|
55
|
+
docsUrl: HELP_DOCS_URL,
|
|
56
|
+
};
|
|
57
|
+
if (parsed.kind === "invalid" && parsed.reason === "too_long")
|
|
58
|
+
return { ...base, kind: "invalid", reason: "too_long" };
|
|
59
|
+
throw new HelpTransportError();
|
|
60
|
+
}
|
|
61
|
+
export class HttpHelpTransport {
|
|
62
|
+
options;
|
|
63
|
+
blocked = false;
|
|
64
|
+
blockedUntil = 0;
|
|
65
|
+
failures = 0;
|
|
66
|
+
origin;
|
|
67
|
+
controller = new AbortController();
|
|
68
|
+
constructor(options) {
|
|
69
|
+
this.options = options;
|
|
70
|
+
const url = new URL(options.baseUrl ?? HELP_API_ORIGIN);
|
|
71
|
+
if (url.username ||
|
|
72
|
+
url.password ||
|
|
73
|
+
url.pathname !== "/" ||
|
|
74
|
+
url.search ||
|
|
75
|
+
url.hash ||
|
|
76
|
+
(url.protocol !== "https:" &&
|
|
77
|
+
!(url.protocol === "http:" &&
|
|
78
|
+
["127.0.0.1", "localhost", "[::1]"].includes(url.hostname))) ||
|
|
79
|
+
(options.kind === "self-host" && url.origin !== HELP_API_ORIGIN))
|
|
80
|
+
throw new HelpTransportError();
|
|
81
|
+
this.origin = url.origin;
|
|
82
|
+
}
|
|
83
|
+
query(input) {
|
|
84
|
+
return this.request("query", {
|
|
85
|
+
question: input.question,
|
|
86
|
+
...(input.module ? { module: input.module } : {}),
|
|
87
|
+
}, input);
|
|
88
|
+
}
|
|
89
|
+
choose(input) {
|
|
90
|
+
return this.request("choose", { choiceToken: input.choiceToken, faqId: input.faqId }, input);
|
|
91
|
+
}
|
|
92
|
+
close() {
|
|
93
|
+
this.controller.abort();
|
|
94
|
+
}
|
|
95
|
+
async request(path, body, identity) {
|
|
96
|
+
if (this.blocked ||
|
|
97
|
+
Date.now() < this.blockedUntil ||
|
|
98
|
+
this.controller.signal.aborted)
|
|
99
|
+
throw new HelpTransportError();
|
|
100
|
+
try {
|
|
101
|
+
const response = await (this.options.fetch ?? fetch)(`${this.origin}/v1/help/${path}`, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
redirect: "error",
|
|
104
|
+
signal: AbortSignal.any([
|
|
105
|
+
this.controller.signal,
|
|
106
|
+
AbortSignal.timeout(6000),
|
|
107
|
+
]),
|
|
108
|
+
headers: {
|
|
109
|
+
Authorization: `Bearer ${this.options.token}`,
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
},
|
|
112
|
+
body: JSON.stringify({
|
|
113
|
+
contractVersion: 1,
|
|
114
|
+
locale: "en-GB",
|
|
115
|
+
botVersion: this.options.botVersion,
|
|
116
|
+
...body,
|
|
117
|
+
...(this.options.kind === "hosted"
|
|
118
|
+
? {
|
|
119
|
+
subject: {
|
|
120
|
+
userId: identity.userId,
|
|
121
|
+
...(identity.guildId ? { guildId: identity.guildId } : {}),
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
: {}),
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
if (response.status === 401 || response.status === 403)
|
|
128
|
+
this.blocked = true;
|
|
129
|
+
if (response.status === 429)
|
|
130
|
+
this.blockedUntil =
|
|
131
|
+
Date.now() +
|
|
132
|
+
Math.min(3600, Math.max(60, Number(response.headers.get("Retry-After")) || 60)) *
|
|
133
|
+
1000;
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
await response.body?.cancel();
|
|
136
|
+
if (response.status === 410)
|
|
137
|
+
throw new HelpTransportError("expired_choice");
|
|
138
|
+
throw new HelpTransportError();
|
|
139
|
+
}
|
|
140
|
+
const value = parseResponse(await boundedJson(response));
|
|
141
|
+
this.failures = 0;
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (error instanceof HelpTransportError &&
|
|
146
|
+
error.code === "expired_choice")
|
|
147
|
+
throw error;
|
|
148
|
+
if (++this.failures >= 3)
|
|
149
|
+
this.blockedUntil = Math.max(this.blockedUntil, Date.now() + 30000);
|
|
150
|
+
throw new HelpTransportError();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** Read per request so local disconnect/replacement takes effect without a restart. */
|
|
155
|
+
export class FileHelpTransport {
|
|
156
|
+
store;
|
|
157
|
+
botVersion;
|
|
158
|
+
fetcher;
|
|
159
|
+
current = null;
|
|
160
|
+
constructor(store, botVersion, fetcher) {
|
|
161
|
+
this.store = store;
|
|
162
|
+
this.botVersion = botVersion;
|
|
163
|
+
this.fetcher = fetcher;
|
|
164
|
+
}
|
|
165
|
+
async transport() {
|
|
166
|
+
const credential = await this.store.read();
|
|
167
|
+
if (!credential || Date.parse(credential.expiresAt) <= Date.now()) {
|
|
168
|
+
this.current?.transport.close();
|
|
169
|
+
this.current = null;
|
|
170
|
+
throw new HelpTransportError();
|
|
171
|
+
}
|
|
172
|
+
if (this.current?.token !== credential.token) {
|
|
173
|
+
this.current?.transport.close();
|
|
174
|
+
this.current = {
|
|
175
|
+
token: credential.token,
|
|
176
|
+
transport: new HttpHelpTransport({
|
|
177
|
+
token: credential.token,
|
|
178
|
+
kind: "self-host",
|
|
179
|
+
botVersion: this.botVersion,
|
|
180
|
+
...(this.fetcher ? { fetch: this.fetcher } : {}),
|
|
181
|
+
}),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return this.current.transport;
|
|
185
|
+
}
|
|
186
|
+
async query(input) {
|
|
187
|
+
return (await this.transport()).query(input);
|
|
188
|
+
}
|
|
189
|
+
async choose(input) {
|
|
190
|
+
return (await this.transport()).choose(input);
|
|
191
|
+
}
|
|
192
|
+
close() {
|
|
193
|
+
this.current?.transport.close();
|
|
194
|
+
this.current = null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export async function exchangeHelpEnrolment(code, fetcher = fetch) {
|
|
198
|
+
if (!/^hhe_[A-Za-z0-9_-]{43}$/u.test(code))
|
|
199
|
+
throw new HelpTransportError();
|
|
200
|
+
try {
|
|
201
|
+
const response = await fetcher(`${HELP_API_ORIGIN}/v1/help/enrolments/exchange`, {
|
|
202
|
+
method: "POST",
|
|
203
|
+
redirect: "error",
|
|
204
|
+
signal: AbortSignal.timeout(10000),
|
|
205
|
+
headers: { "Content-Type": "application/json" },
|
|
206
|
+
body: JSON.stringify({ code }),
|
|
207
|
+
});
|
|
208
|
+
if (!response.ok) {
|
|
209
|
+
await response.body?.cancel();
|
|
210
|
+
throw new HelpTransportError();
|
|
211
|
+
}
|
|
212
|
+
return parseHelpCredential(await boundedJson(response));
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
throw new HelpTransportError();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
export async function revokeHelpCredential(token, fetcher = fetch) {
|
|
219
|
+
if (!/^hhp_[A-Za-z0-9_-]{43}$/u.test(token))
|
|
220
|
+
throw new HelpTransportError();
|
|
221
|
+
try {
|
|
222
|
+
const response = await fetcher(`${HELP_API_ORIGIN}/v1/help/disconnect`, {
|
|
223
|
+
method: "POST",
|
|
224
|
+
redirect: "error",
|
|
225
|
+
signal: AbortSignal.timeout(10000),
|
|
226
|
+
headers: {
|
|
227
|
+
Authorization: `Bearer ${token}`,
|
|
228
|
+
"Content-Type": "application/json",
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify({ contractVersion: 1 }),
|
|
231
|
+
});
|
|
232
|
+
if (!response.ok) {
|
|
233
|
+
await response.body?.cancel();
|
|
234
|
+
throw new HelpTransportError();
|
|
235
|
+
}
|
|
236
|
+
z.object({ revoked: z.literal(true) })
|
|
237
|
+
.strict()
|
|
238
|
+
.parse(await boundedJson(response));
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
throw new HelpTransportError();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async function boundedJson(response) {
|
|
245
|
+
const max = 512 * 1024;
|
|
246
|
+
if (!response.body ||
|
|
247
|
+
Number(response.headers.get("Content-Length") ?? 0) > max)
|
|
248
|
+
throw new HelpTransportError();
|
|
249
|
+
const reader = response.body.getReader(), chunks = [];
|
|
250
|
+
let size = 0;
|
|
251
|
+
try {
|
|
252
|
+
while (true) {
|
|
253
|
+
const chunk = await reader.read();
|
|
254
|
+
if (chunk.done)
|
|
255
|
+
break;
|
|
256
|
+
const value = chunk.value;
|
|
257
|
+
if (!(value instanceof Uint8Array) || (size += value.byteLength) > max)
|
|
258
|
+
throw new HelpTransportError();
|
|
259
|
+
chunks.push(value);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
await reader.cancel().catch(() => undefined);
|
|
264
|
+
}
|
|
265
|
+
return JSON.parse(Buffer.concat(chunks, size).toString("utf8"));
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=help-transport.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type ModuleDefinition } from "@helyx/sdk";
|
|
2
2
|
import { GatewayIntentBits } from "discord.js";
|
|
3
3
|
import { type ShardHealthSnapshot } from "./shard-contracts.js";
|
|
4
|
+
export { HelpCredentialStore } from "./help-credentials.js";
|
|
5
|
+
export { exchangeHelpEnrolment, revokeHelpCredential, } from "./help-transport.js";
|
|
4
6
|
export { loadConfiguredModulePackages } from "./modules.js";
|
|
5
7
|
export { assertConfiguredModuleDatabaseCompatibility } from "./modules.js";
|
|
6
8
|
export { resolveModulePackageSpecifiers } from "./module-configuration.js";
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,10 @@ import { shardHealthSnapshotSchema, } from "./shard-contracts.js";
|
|
|
35
35
|
import { createSupportModule } from "./support-module.js";
|
|
36
36
|
import { createOAuthModule } from "./oauth-module.js";
|
|
37
37
|
import { createPrivacyModule } from "./privacy-module.js";
|
|
38
|
+
import { createProcessInfoModule } from "./process-info-module.js";
|
|
39
|
+
import { createRuntimeHelpModule } from "./help-runtime.js";
|
|
40
|
+
export { HelpCredentialStore } from "./help-credentials.js";
|
|
41
|
+
export { exchangeHelpEnrolment, revokeHelpCredential, } from "./help-transport.js";
|
|
38
42
|
import { installModerationPlatformServices } from "./moderation-platform-services.js";
|
|
39
43
|
import { EckoEvidenceReceiver } from "./ecko-evidence-receiver.js";
|
|
40
44
|
import { EckoTransportAuthenticator } from "./ecko-transport-auth.js";
|
|
@@ -361,6 +365,8 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
361
365
|
: undefined;
|
|
362
366
|
const supportModule = createSupportModule(cloudConnector);
|
|
363
367
|
const privacyModule = createPrivacyModule();
|
|
368
|
+
const processInfoModule = createProcessInfoModule("unsharded bot process");
|
|
369
|
+
const helpModule = createRuntimeHelpModule(environment);
|
|
364
370
|
const premiumEnvironment = requirePremiumProviderEnvironment(featureModules, environment.STRIPE_ENVIRONMENT);
|
|
365
371
|
const premiumActivationModule = !premiumEnvironment || !database
|
|
366
372
|
? undefined
|
|
@@ -380,6 +386,8 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
380
386
|
permissionsModule,
|
|
381
387
|
supportModule,
|
|
382
388
|
privacyModule,
|
|
389
|
+
processInfoModule,
|
|
390
|
+
helpModule,
|
|
383
391
|
...(premiumActivationModule ? [premiumActivationModule] : []),
|
|
384
392
|
...(oauthModule ? [oauthModule] : []),
|
|
385
393
|
...(cloudManagementModule ? [cloudManagementModule] : []),
|
|
@@ -458,6 +466,8 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
458
466
|
permissionsModule,
|
|
459
467
|
supportModule,
|
|
460
468
|
privacyModule,
|
|
469
|
+
processInfoModule,
|
|
470
|
+
helpModule,
|
|
461
471
|
...(premiumActivationModule ? [premiumActivationModule] : []),
|
|
462
472
|
...(oauthModule ? [oauthModule] : []),
|
|
463
473
|
...(cloudManagementModule ? [cloudManagementModule] : []),
|
package/dist/interactions.js
CHANGED
|
@@ -141,15 +141,19 @@ export function installInteractions(client, modules, registration, logger, servi
|
|
|
141
141
|
const subcommand = resolveCommandSubcommand(registered?.contribution, subcommandName, groupName);
|
|
142
142
|
const owner = resolveCommandOwner(registered, subcommandName, groupName);
|
|
143
143
|
const ownerModuleId = owner?.moduleId ?? registered?.moduleId;
|
|
144
|
+
const autocompleteOwner = subcommand ??
|
|
145
|
+
(!hasNestedCommands(registered?.contribution)
|
|
146
|
+
? registered?.contribution
|
|
147
|
+
: undefined);
|
|
144
148
|
if (!registered ||
|
|
145
|
-
!
|
|
149
|
+
!autocompleteOwner?.autocomplete ||
|
|
146
150
|
!ownerModuleId ||
|
|
147
151
|
!(await authorizeAutocomplete(interaction, ownerModuleId, resolveDirectCommandPath(registered.contribution, subcommand, groupName).permissionId, services, registration.selfAuthorizingGuildModuleIds?.has(ownerModuleId) ??
|
|
148
|
-
false,
|
|
152
|
+
false, autocompleteOwner.authorization === "resource-scoped", isInstallationIndependentModule(modulesById, ownerModuleId), autocompleteOwner.authorization, requiredCommandModule(modulesById, ownerModuleId)))) {
|
|
149
153
|
await response.respond([]);
|
|
150
154
|
return;
|
|
151
155
|
}
|
|
152
|
-
await
|
|
156
|
+
await autocompleteOwner.autocomplete(createAutocompleteContext(interaction, services, response.respond));
|
|
153
157
|
}
|
|
154
158
|
catch (error) {
|
|
155
159
|
logger.error({
|
|
@@ -217,15 +221,15 @@ export function installInteractions(client, modules, registration, logger, servi
|
|
|
217
221
|
const acknowledgement = subcommand?.acknowledgement ?? command.acknowledgement;
|
|
218
222
|
const authorization = subcommand?.authorization ?? command.authorization;
|
|
219
223
|
const rateLimit = subcommand?.rateLimit ?? command.rateLimit;
|
|
224
|
+
if (acknowledgement === "deferred-ephemeral") {
|
|
225
|
+
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
|
226
|
+
}
|
|
220
227
|
if (installationIndependentCommand &&
|
|
221
228
|
authorization === "public" &&
|
|
222
229
|
!rateLimit) {
|
|
223
230
|
await execute(createCommandContext(interaction, services));
|
|
224
231
|
return;
|
|
225
232
|
}
|
|
226
|
-
if (acknowledgement === "deferred-ephemeral") {
|
|
227
|
-
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
|
228
|
-
}
|
|
229
233
|
if (await authorizeCommand(interaction, ownerModuleId, permissionId, rateLimit, services, registration.selfAuthorizingGuildModuleIds?.has(ownerModuleId) ??
|
|
230
234
|
false, subcommand?.authorization === "resource-scoped", owner?.moduleName, installationIndependentCommand, authorization, requiredCommandModule(modulesById, ownerModuleId))) {
|
|
231
235
|
const context = createCommandContext(interaction, services);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { defineModule } from "@helyx/sdk";
|
|
2
|
+
import { INTERNAL_MODULE_MANIFEST_FIELDS } from "./internal-module-manifest.js";
|
|
3
|
+
const everyonePolicy = {
|
|
4
|
+
everyone: true,
|
|
5
|
+
requiredPermissions: [],
|
|
6
|
+
allowedRoleIds: [],
|
|
7
|
+
deniedRoleIds: [],
|
|
8
|
+
allowedUserIds: [],
|
|
9
|
+
deniedUserIds: [],
|
|
10
|
+
};
|
|
11
|
+
/** Local process metrics remain available without database or installation checks. */
|
|
12
|
+
export function createProcessInfoModule(processLabel) {
|
|
13
|
+
return defineModule({
|
|
14
|
+
manifest: {
|
|
15
|
+
...INTERNAL_MODULE_MANIFEST_FIELDS,
|
|
16
|
+
releaseStatus: "live",
|
|
17
|
+
id: "helyx.process-info",
|
|
18
|
+
name: "Helyx Process Information",
|
|
19
|
+
version: "0.1.0",
|
|
20
|
+
description: "Reports safe resource and uptime information for the running bot.",
|
|
21
|
+
entry: "./dist/process-info-module.js",
|
|
22
|
+
framework: "^0.1.0",
|
|
23
|
+
dependencies: [],
|
|
24
|
+
permissions: [],
|
|
25
|
+
contributions: { commands: ["proc"], events: [] },
|
|
26
|
+
},
|
|
27
|
+
defaultPermissions: {
|
|
28
|
+
module: everyonePolicy,
|
|
29
|
+
commands: { proc: everyonePolicy },
|
|
30
|
+
},
|
|
31
|
+
interactions: {
|
|
32
|
+
commands: [
|
|
33
|
+
{
|
|
34
|
+
name: "proc",
|
|
35
|
+
permissionId: "proc",
|
|
36
|
+
description: "Show safe process resource and uptime information",
|
|
37
|
+
acknowledgement: "immediate-response",
|
|
38
|
+
registrationPolicy: "platform-global",
|
|
39
|
+
contexts: ["guild", "bot_dm"],
|
|
40
|
+
integrationTypes: ["guild_install"],
|
|
41
|
+
authorization: "public",
|
|
42
|
+
async execute(context) {
|
|
43
|
+
const memory = process.memoryUsage();
|
|
44
|
+
const cpu = process.cpuUsage();
|
|
45
|
+
await context.reply({
|
|
46
|
+
ephemeral: true,
|
|
47
|
+
componentsV2: {
|
|
48
|
+
accentColor: 0x2563eb,
|
|
49
|
+
text: [
|
|
50
|
+
{
|
|
51
|
+
content: [
|
|
52
|
+
"## Process information",
|
|
53
|
+
`Runtime: ${processLabel}`,
|
|
54
|
+
`Uptime: ${formatDuration(process.uptime())}`,
|
|
55
|
+
`RSS memory: ${formatBytes(memory.rss)}`,
|
|
56
|
+
`Heap: ${formatBytes(memory.heapUsed)} used / ${formatBytes(memory.heapTotal)} allocated`,
|
|
57
|
+
`External memory: ${formatBytes(memory.external)}`,
|
|
58
|
+
`CPU time: ${formatCpu(cpu.user)} user / ${formatCpu(cpu.system)} system`,
|
|
59
|
+
`Node: ${safeInlineCode(process.version)}`,
|
|
60
|
+
`Platform: ${safeInlineCode(`${process.platform}/${process.arch}`)}`,
|
|
61
|
+
`PID: ${process.pid}`,
|
|
62
|
+
].join("\n"),
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
start: () => Promise.resolve(),
|
|
72
|
+
stop: () => Promise.resolve(),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function safeInlineCode(value) {
|
|
76
|
+
return `\`${value.replaceAll("`", "'").slice(0, 120)}\``;
|
|
77
|
+
}
|
|
78
|
+
function formatBytes(value) {
|
|
79
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MiB`;
|
|
80
|
+
}
|
|
81
|
+
function formatCpu(microseconds) {
|
|
82
|
+
return `${(microseconds / 1_000_000).toFixed(2)}s`;
|
|
83
|
+
}
|
|
84
|
+
function formatDuration(seconds) {
|
|
85
|
+
const total = Math.max(0, Math.floor(seconds));
|
|
86
|
+
const days = Math.floor(total / 86_400);
|
|
87
|
+
const hours = Math.floor((total % 86_400) / 3_600);
|
|
88
|
+
const minutes = Math.floor((total % 3_600) / 60);
|
|
89
|
+
const remainder = total % 60;
|
|
90
|
+
return [
|
|
91
|
+
...(days > 0 ? [`${days}d`] : []),
|
|
92
|
+
...(days > 0 || hours > 0 ? [`${hours}h`] : []),
|
|
93
|
+
`${minutes}m`,
|
|
94
|
+
`${remainder}s`,
|
|
95
|
+
].join(" ");
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=process-info-module.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type OverviewReadInput, type OverviewResponse, type OverviewRevokeInput } from "@helyx/control-protocol";
|
|
2
|
+
import { type GuildAccessPolicy, type ManagedResourceContext, type ModuleDefinition, type PermissionActor, type PermissionPolicyService, type ServiceAccess } from "@helyx/sdk";
|
|
3
|
+
import type { OverviewDiscordDirectory } from "./server-overview-discord.js";
|
|
4
|
+
export declare function createServerOverviewControl(deps: {
|
|
5
|
+
modules: ReadonlyMap<string, ModuleDefinition>;
|
|
6
|
+
services: ServiceAccess;
|
|
7
|
+
permissions: PermissionPolicyService;
|
|
8
|
+
resolveActor(guildId: string, actorUserId: string): Promise<PermissionActor>;
|
|
9
|
+
readGuildPolicy(guildId: string): Promise<GuildAccessPolicy>;
|
|
10
|
+
listModules(guildId: string): Promise<readonly {
|
|
11
|
+
moduleId: string;
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
}[]>;
|
|
14
|
+
requireAccessible(guildId: string, moduleId: string): Promise<void>;
|
|
15
|
+
directory?: OverviewDiscordDirectory | undefined;
|
|
16
|
+
auditInviteRevoke?(input: {
|
|
17
|
+
guildId: string;
|
|
18
|
+
actorUserId: string;
|
|
19
|
+
code: string;
|
|
20
|
+
outcome: "requested" | "revoked";
|
|
21
|
+
correlationId: string;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
context(guildId: string, actor: PermissionActor): ManagedResourceContext;
|
|
24
|
+
}): {
|
|
25
|
+
invalidate(guildId: string): void;
|
|
26
|
+
readServerOverview(input: OverviewReadInput & {
|
|
27
|
+
actorUserId: string;
|
|
28
|
+
}): Promise<OverviewResponse>;
|
|
29
|
+
revokeServerOverviewInvite(input: OverviewRevokeInput & {
|
|
30
|
+
actorUserId: string;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
revoked: true;
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=server-overview-control.d.ts.map
|