@helyx/bot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # Helyx bot
2
+
3
+ The Helyx Discord runtime validates its environment, verifies and migrates PostgreSQL, validates explicitly configured module packages, applies their migrations, composes platform services, synchronises guild installations and commands, logs in with the non-privileged Guilds intent, and shuts down Discord, modules, and PostgreSQL safely.
4
+
5
+ Each command declares `registration: "global"` for every installed Discord server or `registration: "guild"` for the internal server. Set `DISCORD_INTERNAL_GUILD_ID` whenever a loaded module contains guild-only commands. Global and guild commands are synchronized independently.
6
+
7
+ Registration uses Discord's bulk-overwrite operation, so both scopes are synchronized to exactly the commands supplied by the active Helyx modules. Duplicate command names and component IDs fail before the bot logs in.
8
+
9
+ The built-in global `/helyx` and `/permissions` commands are core infrastructure. `/helyx` gives server owners, administrators and members with Manage Server permission an ephemeral module-management flow. It lists installed feature packages, enables or disables them, stages schema-defined settings, uses native Discord channel and role selectors, validates the complete configuration, and saves through the same versioned configuration and audit services as the dashboard. Dependency rules prevent a required module being disabled or a dependent module being enabled out of order.
10
+
11
+ `/permissions` uses an owner/administrator-protected select and modal flow with native Discord selectors to manage inherited module policies and command overrides. Feature packages require an explicit allowlist: hosted distributions carry a reviewed `hosted-modules.json` catalogue, generated self-host projects use `HELYX_MODULE_CATALOGUE_PATH`, and `HELYX_MODULE_PACKAGES` remains an intentional comma-separated override. The sources are merged and de-duplicated. A core distribution has no catalogue and runs core-only when both self-host selectors are empty.
12
+
13
+ Newly discovered feature modules are disabled for each Discord server by default. Global commands remain registered so Discord can present the installed Helyx command catalogue, but attempts to use a disabled module receive an ephemeral explanation directing the member to `/helyx` or the dashboard. Package upgrades preserve each server's existing enabled or disabled state.
14
+
15
+ Gateway intents are derived from installed event contributions. The bot always requests Guilds, adds non-privileged reaction access only for reaction modules, and adds the privileged Server Members intent only when an installed module handles member joins. Deployments containing such a module must enable the matching intent in the Discord Developer Portal before startup.
16
+
17
+ When `HELYX_BOT_CONTROL_SECRET` is configured, the bot also starts a private authenticated HTTP control server on `HELYX_BOT_CONTROL_PORT`. The dashboard API uses this service to obtain real Discord roles and channels and to apply validated module, configuration and permission changes. Only `/health` is unauthenticated; all `/v1` routes require the shared bearer secret. Keep this endpoint on a private service network and do not assign it a public domain.
18
+
19
+ For packaged operation use `helyx start`. `helyx migrate` applies core database migrations without Discord, and read-only `helyx doctor` verifies environment, PostgreSQL migration history, and configured package metadata.
@@ -0,0 +1,198 @@
1
+ import { type DatabasePool } from "@helyx/database";
2
+ import { type DashboardActionResult, type MessageResponse, type ModuleDefinition, type ServiceAccess } from "@helyx/sdk";
3
+ import { type Client } from "discord.js";
4
+ import { type FastifyInstance } from "fastify";
5
+ interface DashboardPermissionPolicy {
6
+ preset: "everyone" | "moderators" | "administrators" | "custom";
7
+ allowedRoleIds: string[];
8
+ deniedRoleIds: string[];
9
+ }
10
+ interface DashboardCommandDefinition {
11
+ id: string;
12
+ name: string;
13
+ description: string;
14
+ defaultPreset: DashboardPermissionPolicy["preset"];
15
+ }
16
+ interface DashboardLogEventDefinition {
17
+ id: string;
18
+ name: string;
19
+ description: string;
20
+ defaultEnabled: boolean;
21
+ }
22
+ interface DashboardActionDefinition {
23
+ id: string;
24
+ label: string;
25
+ description: string;
26
+ buttonLabel: string;
27
+ style: "primary" | "secondary" | "danger";
28
+ }
29
+ interface DashboardModuleDefinition {
30
+ id: string;
31
+ name: string;
32
+ version: string;
33
+ summary: string;
34
+ category: string;
35
+ icon: string;
36
+ availability: "available";
37
+ requiredPermissions: string[];
38
+ settings: readonly unknown[];
39
+ commands: DashboardCommandDefinition[];
40
+ logEvents: DashboardLogEventDefinition[];
41
+ actions: DashboardActionDefinition[];
42
+ }
43
+ interface DashboardLoggingPolicy {
44
+ eventId: string;
45
+ enabled: boolean;
46
+ version: number;
47
+ }
48
+ interface DashboardPermissionState {
49
+ version: number;
50
+ module: DashboardPermissionPolicy;
51
+ commands: Array<{
52
+ commandId: string;
53
+ version: number;
54
+ enabled: boolean;
55
+ policy: DashboardPermissionPolicy;
56
+ }>;
57
+ }
58
+ export interface BotControlGuildResources {
59
+ channels: Array<{
60
+ id: string;
61
+ name: string;
62
+ type: "guild_text" | "guild_announcement";
63
+ }>;
64
+ roles: Array<{
65
+ id: string;
66
+ name: string;
67
+ colour: string;
68
+ }>;
69
+ }
70
+ export interface BotControlGuildResourceDirectory {
71
+ getGuildResources(guildId: string): Promise<BotControlGuildResources>;
72
+ sendToChannel(channelId: string, response: MessageResponse): Promise<{
73
+ messageId: string;
74
+ }>;
75
+ deleteChannelMessage(channelId: string, messageId: string): Promise<void>;
76
+ }
77
+ export declare class DiscordGuildResourceDirectory implements BotControlGuildResourceDirectory {
78
+ private readonly client;
79
+ constructor(client: Client);
80
+ getGuildResources(guildId: string): Promise<BotControlGuildResources>;
81
+ sendToChannel(channelId: string, response: MessageResponse): Promise<{
82
+ messageId: string;
83
+ }>;
84
+ deleteChannelMessage(channelId: string, messageId: string): Promise<void>;
85
+ }
86
+ export interface BotControlState {
87
+ channels: BotControlGuildResources["channels"];
88
+ roles: BotControlGuildResources["roles"];
89
+ modules: DashboardModuleDefinition[];
90
+ moduleStatuses: Record<string, "enabled" | "disabled" | "unavailable">;
91
+ configurations: Record<string, {
92
+ version: number;
93
+ value: Record<string, unknown>;
94
+ } | undefined>;
95
+ permissions: Record<string, DashboardPermissionState>;
96
+ logging: Record<string, DashboardLoggingPolicy[]>;
97
+ }
98
+ export interface BotControlHandler {
99
+ getGuildState(guildId: string): Promise<BotControlState>;
100
+ setModuleStatus(input: {
101
+ guildId: string;
102
+ moduleId: string;
103
+ enabled: boolean;
104
+ actorUserId: string;
105
+ correlationId: string;
106
+ }): Promise<BotControlState>;
107
+ updateConfiguration(input: {
108
+ guildId: string;
109
+ moduleId: string;
110
+ value: Record<string, unknown>;
111
+ expectedVersion: number | null;
112
+ actorUserId: string;
113
+ correlationId: string;
114
+ }): Promise<{
115
+ value: Record<string, unknown>;
116
+ version: number;
117
+ }>;
118
+ updatePermissions(input: {
119
+ guildId: string;
120
+ moduleId: string;
121
+ state: DashboardPermissionState;
122
+ actorUserId: string;
123
+ correlationId: string;
124
+ }): Promise<DashboardPermissionState>;
125
+ updateLoggingPolicy(input: {
126
+ guildId: string;
127
+ moduleId: string;
128
+ eventId: string;
129
+ enabled: boolean;
130
+ expectedVersion: number;
131
+ actorUserId: string;
132
+ correlationId: string;
133
+ }): Promise<DashboardLoggingPolicy>;
134
+ executeDashboardAction(input: {
135
+ guildId: string;
136
+ moduleId: string;
137
+ actionId: string;
138
+ actorUserId: string;
139
+ correlationId: string;
140
+ }): Promise<DashboardActionResult>;
141
+ }
142
+ export declare class BotControlService implements BotControlHandler {
143
+ #private;
144
+ private readonly database;
145
+ private readonly resources;
146
+ constructor(database: DatabasePool, modules: readonly ModuleDefinition[], resources: BotControlGuildResourceDirectory, services: ServiceAccess);
147
+ getGuildState(guildId: string): Promise<BotControlState>;
148
+ setModuleStatus(input: {
149
+ guildId: string;
150
+ moduleId: string;
151
+ enabled: boolean;
152
+ actorUserId: string;
153
+ correlationId: string;
154
+ }): Promise<BotControlState>;
155
+ updateConfiguration(input: {
156
+ guildId: string;
157
+ moduleId: string;
158
+ value: Record<string, unknown>;
159
+ expectedVersion: number | null;
160
+ actorUserId: string;
161
+ correlationId: string;
162
+ }): Promise<{
163
+ value: Record<string, unknown>;
164
+ version: number;
165
+ }>;
166
+ updatePermissions(input: {
167
+ guildId: string;
168
+ moduleId: string;
169
+ state: DashboardPermissionState;
170
+ actorUserId: string;
171
+ correlationId: string;
172
+ }): Promise<DashboardPermissionState>;
173
+ updateLoggingPolicy(input: {
174
+ guildId: string;
175
+ moduleId: string;
176
+ eventId: string;
177
+ enabled: boolean;
178
+ expectedVersion: number;
179
+ actorUserId: string;
180
+ correlationId: string;
181
+ }): Promise<DashboardLoggingPolicy>;
182
+ executeDashboardAction(input: {
183
+ guildId: string;
184
+ moduleId: string;
185
+ actionId: string;
186
+ actorUserId: string;
187
+ correlationId: string;
188
+ }): Promise<DashboardActionResult>;
189
+ }
190
+ export declare function createBotControlServer(input: {
191
+ service: BotControlHandler;
192
+ secret: string;
193
+ logger?: boolean | {
194
+ level: string;
195
+ };
196
+ }): Promise<FastifyInstance>;
197
+ export {};
198
+ //# sourceMappingURL=control-server.d.ts.map