@askexenow/exe-os 0.9.81 → 0.9.82

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "latest": "0.9.3",
3
+ "latest": "0.9.5",
4
4
  "stacks": {
5
5
  "0.9.0": {
6
6
  "version": "0.9.0",
@@ -11,7 +11,7 @@
11
11
  "id": "whatsapp_relink_required",
12
12
  "title": "WhatsApp QR re-link required for Baileys v7",
13
13
  "description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
14
- "requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp \u2192 Linked Devices after the update.",
14
+ "requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp Linked Devices after the update.",
15
15
  "expectedDowntimeMinutes": "2-5",
16
16
  "requiresConfirmation": true
17
17
  }
@@ -65,7 +65,7 @@
65
65
  "id": "whatsapp_relink_required",
66
66
  "title": "WhatsApp QR re-link required for Baileys v7",
67
67
  "description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
68
- "requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp \u2192 Linked Devices after the update.",
68
+ "requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp Linked Devices after the update.",
69
69
  "expectedDowntimeMinutes": "2-5",
70
70
  "requiresConfirmation": true
71
71
  }
@@ -119,7 +119,7 @@
119
119
  "id": "whatsapp_relink_required",
120
120
  "title": "WhatsApp QR re-link required for Baileys v7",
121
121
  "description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
122
- "requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp \u2192 Linked Devices after the update.",
122
+ "requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp Linked Devices after the update.",
123
123
  "expectedDowntimeMinutes": "2-5",
124
124
  "requiresConfirmation": true
125
125
  }
@@ -173,7 +173,7 @@
173
173
  "id": "whatsapp_relink_required",
174
174
  "title": "WhatsApp QR re-link required for Baileys v7",
175
175
  "description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
176
- "requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp \u2192 Linked Devices after the update.",
176
+ "requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp Linked Devices after the update.",
177
177
  "expectedDowntimeMinutes": "2-5",
178
178
  "requiresConfirmation": true
179
179
  }
@@ -228,6 +228,139 @@
228
228
  "sourceRef": "v0.9.3"
229
229
  }
230
230
  }
231
+ },
232
+ "0.9.4": {
233
+ "version": "0.9.4",
234
+ "releasedAt": "2026-05-12T00:00:00Z",
235
+ "notes": "Hygo customer stack release with exed refreshed to include npm @askexenow/exe-os@0.9.81 support/MCP parity fixes. Other service images remain pinned to the already-approved v0.9.3 artifacts.",
236
+ "breakingChanges": [
237
+ {
238
+ "id": "whatsapp_relink_required",
239
+ "title": "WhatsApp QR re-link required for Baileys v7",
240
+ "description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
241
+ "requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp → Linked Devices after the update.",
242
+ "expectedDowntimeMinutes": "2-5",
243
+ "requiresConfirmation": true
244
+ }
245
+ ],
246
+ "services": {
247
+ "crm": {
248
+ "env": "CRM_IMAGE_TAG",
249
+ "image": "registry.askexe.com/askexe/exe-crm:v0.9.3",
250
+ "healthUrl": "http://127.0.0.1:3000/healthz",
251
+ "deploymentScope": "customer"
252
+ },
253
+ "wiki": {
254
+ "env": "WIKI_IMAGE_TAG",
255
+ "image": "registry.askexe.com/askexe/exe-wiki:v0.9.3",
256
+ "healthUrl": "http://127.0.0.1:3001/api/ping",
257
+ "deploymentScope": "customer"
258
+ },
259
+ "exed": {
260
+ "env": "EXED_IMAGE_TAG",
261
+ "image": "registry.askexe.com/askexe/exed:v0.9.4",
262
+ "healthUrl": "http://127.0.0.1:8765/health",
263
+ "deploymentScope": "customer"
264
+ },
265
+ "gateway": {
266
+ "env": "GATEWAY_IMAGE_TAG",
267
+ "image": "registry.askexe.com/askexe/exe-gateway:v0.9.3",
268
+ "healthUrl": "http://127.0.0.1:3100/health",
269
+ "deploymentScope": "customer"
270
+ },
271
+ "monitorAgent": {
272
+ "env": "MONITOR_AGENT_IMAGE_TAG",
273
+ "image": "registry.askexe.com/askexe/exe-monitor-agent:v0.9.3",
274
+ "deploymentScope": "customer"
275
+ }
276
+ },
277
+ "releaseDescriptors": {
278
+ "exed": "AskExe/exe-os@stack-v0.9.4:stack.release.json",
279
+ "crm": "AskExe/exe-crm@0.9.3:stack.release.json",
280
+ "wiki": "AskExe/exe-wiki@0.9.3:stack.release.json",
281
+ "gateway": "AskExe/exe-gateway@0.9.3:stack.release.json",
282
+ "db": "AskExe/exe-db@0.9.3:stack.release.json",
283
+ "monitorAgent": "AskExe/exe-monitor@0.9.3:stack.release.json"
284
+ },
285
+ "componentVersions": {
286
+ "exe-os": {
287
+ "repo": "AskExe/exe-os",
288
+ "npmPackage": "@askexenow/exe-os",
289
+ "npmVersion": "0.9.81",
290
+ "image": "registry.askexe.com/askexe/exed:v0.9.4",
291
+ "imageVersion": "0.9.4",
292
+ "note": "Customer stack image tag is 0.9.4; bundled npm package is @askexenow/exe-os@0.9.81. Hygo pulls through registry.askexe.com, which proxies the AskExe-owned GHCR artifact server-side.",
293
+ "sourceRef": "stack-v0.9.4"
294
+ }
295
+ }
296
+ },
297
+ "0.9.5": {
298
+ "version": "0.9.5",
299
+ "releasedAt": "2026-05-12T00:00:00Z",
300
+ "notes": "Hygo cold-start fix: exed refreshed to include npm @askexenow/exe-os@0.9.82, fixing exe-os stack-update CLI routing and preserving support/MCP parity tools. Other services remain v0.9.3.",
301
+ "breakingChanges": [
302
+ {
303
+ "id": "whatsapp_relink_required",
304
+ "title": "WhatsApp QR re-link required for Baileys v7",
305
+ "description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
306
+ "requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp → Linked Devices after the update.",
307
+ "expectedDowntimeMinutes": "2-5",
308
+ "requiresConfirmation": true
309
+ }
310
+ ],
311
+ "services": {
312
+ "crm": {
313
+ "env": "CRM_IMAGE_TAG",
314
+ "image": "registry.askexe.com/askexe/exe-crm:v0.9.3",
315
+ "healthUrl": "http://127.0.0.1:3000/healthz",
316
+ "deploymentScope": "customer"
317
+ },
318
+ "wiki": {
319
+ "env": "WIKI_IMAGE_TAG",
320
+ "image": "registry.askexe.com/askexe/exe-wiki:v0.9.3",
321
+ "healthUrl": "http://127.0.0.1:3001/api/ping",
322
+ "deploymentScope": "customer"
323
+ },
324
+ "exed": {
325
+ "env": "EXED_IMAGE_TAG",
326
+ "image": "registry.askexe.com/askexe/exed:v0.9.5",
327
+ "healthUrl": "http://127.0.0.1:8765/health",
328
+ "deploymentScope": "customer"
329
+ },
330
+ "gateway": {
331
+ "env": "GATEWAY_IMAGE_TAG",
332
+ "image": "registry.askexe.com/askexe/exe-gateway:v0.9.3",
333
+ "healthUrl": "http://127.0.0.1:3100/health",
334
+ "deploymentScope": "customer"
335
+ },
336
+ "monitorAgent": {
337
+ "env": "MONITOR_AGENT_IMAGE_TAG",
338
+ "image": "registry.askexe.com/askexe/exe-monitor-agent:v0.9.3",
339
+ "deploymentScope": "customer"
340
+ }
341
+ },
342
+ "releaseDescriptors": {
343
+ "exed": "AskExe/exe-os@stack-v0.9.4:stack.release.json",
344
+ "crm": "AskExe/exe-crm@0.9.3:stack.release.json",
345
+ "wiki": "AskExe/exe-wiki@0.9.3:stack.release.json",
346
+ "gateway": "AskExe/exe-gateway@0.9.3:stack.release.json",
347
+ "db": "AskExe/exe-db@0.9.3:stack.release.json",
348
+ "monitorAgent": "AskExe/exe-monitor@0.9.3:stack.release.json"
349
+ },
350
+ "componentVersions": {
351
+ "exe-os": {
352
+ "repo": "AskExe/exe-os",
353
+ "npmPackage": "@askexenow/exe-os",
354
+ "npmVersion": "0.9.82",
355
+ "image": "registry.askexe.com/askexe/exed:v0.9.4",
356
+ "imageVersion": "0.9.5",
357
+ "note": "Customer stack image tag is 0.9.4; bundled npm package is @askexenow/exe-os@0.9.81. Hygo pulls through registry.askexe.com, which proxies the AskExe-owned GHCR artifact server-side.",
358
+ "sourceRef": "stack-v0.9.4",
359
+ "commit": "main@stack-v0.9.5"
360
+ }
361
+ },
362
+ "date": "2026-05-12",
363
+ "sourceRef": "stack-v0.9.5"
231
364
  }
232
365
  }
233
366
  }
package/dist/bin/cli.js CHANGED
@@ -20162,7 +20162,7 @@ function defaultStackPaths() {
20162
20162
  manifestRef: process.env.EXE_STACK_MANIFEST || "https://update.askexe.com/stack-manifest.json",
20163
20163
  auditUrl: process.env.EXE_STACK_AUDIT_URL || "https://update.askexe.com/v1/deploy-audits",
20164
20164
  imageCredentialsUrl: process.env.EXE_STACK_IMAGE_CREDENTIALS_URL || "https://update.askexe.com/v1/image-credentials",
20165
- manifestAuthToken: process.env.EXE_STACK_UPDATE_TOKEN,
20165
+ manifestAuthToken: process.env.EXE_STACK_UPDATE_TOKEN || process.env.EXE_LICENSE_KEY || loadLicense() || void 0,
20166
20166
  manifestPublicKey: loadDefaultPublicKey()
20167
20167
  };
20168
20168
  }
@@ -20177,6 +20177,7 @@ var ASKEXE_GHCR_IMAGE;
20177
20177
  var init_stack_update = __esm({
20178
20178
  "src/lib/stack-update.ts"() {
20179
20179
  "use strict";
20180
+ init_license();
20180
20181
  ASKEXE_GHCR_IMAGE = /^(?:ghcr\.io\/askexe|registry\.askexe\.com\/askexe)\/[a-z0-9._/-]+(?::[^:@$/{]+|@sha256:[a-f0-9]{64})$/i;
20181
20182
  }
20182
20183
  });
@@ -20310,8 +20311,8 @@ function printBreaking(changes) {
20310
20311
  if (c.expectedDowntimeMinutes) console.log(` Expected downtime: ${c.expectedDowntimeMinutes} minutes`);
20311
20312
  }
20312
20313
  }
20313
- async function main7() {
20314
- const opts = parseArgs4(process.argv.slice(2));
20314
+ async function main7(args2 = process.argv.slice(2)) {
20315
+ const opts = parseArgs4(args2);
20315
20316
  if (opts.rollback) {
20316
20317
  if (!opts.yes) {
20317
20318
  console.error("Refusing to rollback without --yes.");
@@ -20354,7 +20355,7 @@ var init_stack_update2 = __esm({
20354
20355
  "use strict";
20355
20356
  init_is_main();
20356
20357
  init_stack_update();
20357
- if (isMainModule(import.meta.url)) {
20358
+ if (isMainModule(import.meta.url) && (process.argv[1] ?? "").includes("stack-update")) {
20358
20359
  main7().catch((err) => {
20359
20360
  console.error(err instanceof Error ? err.message : String(err));
20360
20361
  process.exit(1);
@@ -20558,7 +20559,7 @@ var init_registry_proxy2 = __esm({
20558
20559
  "use strict";
20559
20560
  init_is_main();
20560
20561
  init_registry_proxy();
20561
- if (isMainModule(import.meta.url)) {
20562
+ if (isMainModule(import.meta.url) && (process.argv[1] ?? "").includes("registry-proxy")) {
20562
20563
  main8().catch((err) => {
20563
20564
  console.error(err instanceof Error ? err.message : String(err));
20564
20565
  process.exit(1);
@@ -36351,7 +36352,7 @@ ID: ${result.id}`);
36351
36352
  await runUpdate2(args.slice(1));
36352
36353
  } else if (args[0] === "stack-update") {
36353
36354
  const { runStackUpdateCli } = await Promise.resolve().then(() => (init_stack_update2(), stack_update_exports));
36354
- await runStackUpdateCli();
36355
+ await runStackUpdateCli(args.slice(1));
36355
36356
  } else if (args[0] === "registry-proxy") {
36356
36357
  const { main: runRegistryProxy2 } = await Promise.resolve().then(() => (init_registry_proxy2(), registry_proxy_exports));
36357
36358
  await runRegistryProxy2(args.slice(1));
@@ -8098,11 +8098,11 @@ __export(signal_exports, {
8098
8098
  SignalAdapter: () => SignalAdapter
8099
8099
  });
8100
8100
  import { randomUUID as randomUUID5 } from "crypto";
8101
- var DEFAULT_TIMEOUT_MS, POLL_INTERVAL_MS, SignalAdapter;
8101
+ var DEFAULT_TIMEOUT_MS2, POLL_INTERVAL_MS, SignalAdapter;
8102
8102
  var init_signal = __esm({
8103
8103
  "src/gateway/adapters/signal.ts"() {
8104
8104
  "use strict";
8105
- DEFAULT_TIMEOUT_MS = 1e4;
8105
+ DEFAULT_TIMEOUT_MS2 = 1e4;
8106
8106
  POLL_INTERVAL_MS = 2e3;
8107
8107
  SignalAdapter = class {
8108
8108
  platform = "signal";
@@ -8163,7 +8163,7 @@ var init_signal = __esm({
8163
8163
  method: "POST",
8164
8164
  headers: { "Content-Type": "application/json" },
8165
8165
  body: JSON.stringify(body),
8166
- signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
8166
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2)
8167
8167
  });
8168
8168
  if (!res.ok) {
8169
8169
  const errText = await res.text().catch(() => "");
@@ -8181,7 +8181,7 @@ var init_signal = __esm({
8181
8181
  recipient: isGroup ? void 0 : channelId,
8182
8182
  group_id: isGroup ? channelId.slice("group:".length) : void 0
8183
8183
  }),
8184
- signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
8184
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2)
8185
8185
  });
8186
8186
  } catch {
8187
8187
  }
@@ -8192,7 +8192,7 @@ var init_signal = __esm({
8192
8192
  try {
8193
8193
  const res = await fetch(`${this.baseUrl}/v1/about`, {
8194
8194
  method: "GET",
8195
- signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
8195
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2)
8196
8196
  });
8197
8197
  this._connected = res.ok;
8198
8198
  return {
@@ -8223,7 +8223,7 @@ var init_signal = __esm({
8223
8223
  `${this.baseUrl}/v1/receive/${encodeURIComponent(this.account)}`,
8224
8224
  {
8225
8225
  method: "GET",
8226
- signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
8226
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2)
8227
8227
  }
8228
8228
  );
8229
8229
  if (!res.ok) {
@@ -8366,7 +8366,7 @@ var init_signal = __esm({
8366
8366
  if (!this.messageHandler || !this.account) return;
8367
8367
  const res = await fetch(
8368
8368
  `${this.baseUrl}/v1/contacts/${encodeURIComponent(this.account)}`,
8369
- { signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS) }
8369
+ { signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2) }
8370
8370
  );
8371
8371
  if (!res.ok) return;
8372
8372
  const contacts = await res.json();
@@ -8401,7 +8401,7 @@ var init_signal = __esm({
8401
8401
  if (!this.messageHandler || !this.account) return;
8402
8402
  const res = await fetch(
8403
8403
  `${this.baseUrl}/v1/groups/${encodeURIComponent(this.account)}`,
8404
- { signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS) }
8404
+ { signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2) }
8405
8405
  );
8406
8406
  if (!res.ok) return;
8407
8407
  const groups = await res.json();
@@ -8619,12 +8619,12 @@ var init_discord = __esm({
8619
8619
  messageHandler = null;
8620
8620
  connected = false;
8621
8621
  async connect(config2) {
8622
- const { Client, GatewayIntentBits } = await import("discord.js");
8622
+ const { Client: Client2, GatewayIntentBits } = await import("discord.js");
8623
8623
  const token = config2.credentials.bot_token ?? config2.credentials.token;
8624
8624
  if (!token) {
8625
8625
  throw new Error("Discord requires bot_token in credentials");
8626
8626
  }
8627
- const discordClient = new Client({
8627
+ const discordClient = new Client2({
8628
8628
  intents: [
8629
8629
  GatewayIntentBits.Guilds,
8630
8630
  GatewayIntentBits.GuildMessages,
@@ -13843,6 +13843,91 @@ function toIsoTimestamp(value) {
13843
13843
  return UNKNOWN_TIMESTAMP;
13844
13844
  }
13845
13845
 
13846
+ // src/gateway/read-only-sql.ts
13847
+ import { Client } from "pg";
13848
+ var DEFAULT_MAX_ROWS = 100;
13849
+ var HARD_MAX_ROWS = 1e3;
13850
+ var DEFAULT_TIMEOUT_MS = 1e4;
13851
+ var BLOCKED_SQL = /\b(insert|update|delete|merge|upsert|drop|alter|create|truncate|grant|revoke|copy|call|do|listen|notify|vacuum|analyze|comment|security|set\s+role|reset|begin|commit|rollback|savepoint|lock)\b/i;
13852
+ var ReadOnlySqlValidationError = class extends Error {
13853
+ constructor(message) {
13854
+ super(message);
13855
+ this.name = "ReadOnlySqlValidationError";
13856
+ }
13857
+ };
13858
+ var ReadOnlySqlRunner = class {
13859
+ constructor(config2 = {}) {
13860
+ this.config = config2;
13861
+ }
13862
+ async run(sql, maxRows) {
13863
+ const databaseUrl = this.config.databaseUrl ?? process.env.EXE_GATEWAY_SQL_DATABASE_URL ?? process.env.EXE_COMPANY_BRAIN_SQL_DATABASE_URL ?? process.env.DATABASE_URL;
13864
+ if (!databaseUrl) {
13865
+ throw new Error("Read-only SQL is not configured. Set EXE_GATEWAY_SQL_DATABASE_URL or DATABASE_URL.");
13866
+ }
13867
+ const cleaned = validateSql(sql);
13868
+ const limit = clampRows(maxRows ?? this.config.maxRows ?? DEFAULT_MAX_ROWS);
13869
+ const timeoutMs = this.config.statementTimeoutMs ?? DEFAULT_TIMEOUT_MS;
13870
+ const wrappedSql = `SELECT * FROM (${cleaned}) AS exe_readonly_sql LIMIT ${limit + 1}`;
13871
+ const client = new Client({ connectionString: databaseUrl });
13872
+ const start = Date.now();
13873
+ try {
13874
+ await client.connect();
13875
+ await client.query("BEGIN READ ONLY");
13876
+ await client.query("SET LOCAL transaction_read_only = on");
13877
+ await client.query("SET LOCAL statement_timeout = $1", [timeoutMs]);
13878
+ const result = await client.query(wrappedSql);
13879
+ await client.query("COMMIT");
13880
+ const rows = result.rows.slice(0, limit);
13881
+ return {
13882
+ columns: result.fields.map((field) => field.name),
13883
+ rows,
13884
+ row_count: rows.length,
13885
+ max_rows: limit,
13886
+ truncated: result.rows.length > limit,
13887
+ execution_ms: Date.now() - start
13888
+ };
13889
+ } catch (err) {
13890
+ try {
13891
+ await client.query("ROLLBACK");
13892
+ } catch {
13893
+ }
13894
+ throw err;
13895
+ } finally {
13896
+ await client.end().catch(() => void 0);
13897
+ }
13898
+ }
13899
+ };
13900
+ function validateSql(sql) {
13901
+ const trimmed = sql.trim().replace(/;\s*$/, "");
13902
+ if (!trimmed) throw new ReadOnlySqlValidationError("sql is required");
13903
+ if (!/^(select|with)\b/i.test(trimmed)) {
13904
+ throw new ReadOnlySqlValidationError("Only SELECT/WITH read-only SQL is allowed");
13905
+ }
13906
+ if (hasMultipleStatements(trimmed)) {
13907
+ throw new ReadOnlySqlValidationError("Multiple SQL statements are not allowed");
13908
+ }
13909
+ if (BLOCKED_SQL.test(trimmed)) {
13910
+ throw new ReadOnlySqlValidationError("SQL contains a blocked write/admin keyword");
13911
+ }
13912
+ return trimmed;
13913
+ }
13914
+ function hasMultipleStatements(sql) {
13915
+ let single = false;
13916
+ let double = false;
13917
+ for (let i = 0; i < sql.length; i += 1) {
13918
+ const ch = sql[i];
13919
+ const prev = sql[i - 1];
13920
+ if (ch === "'" && !double && prev !== "\\") single = !single;
13921
+ if (ch === '"' && !single && prev !== "\\") double = !double;
13922
+ if (ch === ";" && !single && !double) return true;
13923
+ }
13924
+ return false;
13925
+ }
13926
+ function clampRows(value) {
13927
+ if (!Number.isFinite(value) || value <= 0) return DEFAULT_MAX_ROWS;
13928
+ return Math.min(Math.floor(value), HARD_MAX_ROWS);
13929
+ }
13930
+
13846
13931
  // src/gateway/webhook-server.ts
13847
13932
  var DEFAULT_HOST = process.env.EXE_WEBHOOK_HOST || "127.0.0.1";
13848
13933
  var BODY_SIZE_LIMIT = 1048576;
@@ -13859,6 +13944,7 @@ var WebhookServer = class {
13859
13944
  server = null;
13860
13945
  handlers = /* @__PURE__ */ new Map();
13861
13946
  startedAt = 0;
13947
+ readOnlySqlRunner = new ReadOnlySqlRunner();
13862
13948
  /** Register a handler for a platform: POST /webhook/:platform */
13863
13949
  onPlatform(platform, handler) {
13864
13950
  this.handlers.set(platform, handler);
@@ -13905,6 +13991,10 @@ var WebhookServer = class {
13905
13991
  await this.handleQuery(req, res);
13906
13992
  return;
13907
13993
  }
13994
+ if (method === "POST" && isRoute(url, "/query/sql")) {
13995
+ await this.handleReadOnlySql(req, res);
13996
+ return;
13997
+ }
13908
13998
  if (method === "GET" && url.startsWith("/webhook/whatsapp")) {
13909
13999
  this.handleWhatsAppVerification(req, res);
13910
14000
  return;
@@ -13947,6 +14037,29 @@ var WebhookServer = class {
13947
14037
  sendJson(res, 503, { error: "Database unavailable" });
13948
14038
  }
13949
14039
  }
14040
+ async handleReadOnlySql(req, res) {
14041
+ if ((this.config.authToken || this.config.queryAuthToken) && !this.verifyQueryAuth(req)) {
14042
+ sendJson(res, 401, { error: "Unauthorized" });
14043
+ return;
14044
+ }
14045
+ try {
14046
+ const body = await readBody(req);
14047
+ const sql = typeof body.sql === "string" ? body.sql : "";
14048
+ const maxRows = typeof body.max_rows === "number" ? body.max_rows : void 0;
14049
+ const result = await this.readOnlySqlRunner.run(sql, maxRows);
14050
+ sendJson(res, 200, result);
14051
+ } catch (err) {
14052
+ if (err instanceof ReadOnlySqlValidationError) {
14053
+ sendJson(res, 400, { error: err.message });
14054
+ return;
14055
+ }
14056
+ console.error(
14057
+ "[webhook-server] Read-only SQL error:",
14058
+ err instanceof Error ? err.message : err
14059
+ );
14060
+ sendJson(res, 503, { error: err instanceof Error ? err.message : "Database unavailable" });
14061
+ }
14062
+ }
13950
14063
  handleWhatsAppVerification(req, res) {
13951
14064
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
13952
14065
  const mode = url.searchParams.get("hub.mode");
@@ -196,7 +196,7 @@ async function main(args = process.argv.slice(2)) {
196
196
  }
197
197
  await runRegistryProxy(registryProxyOptionsFromEnv());
198
198
  }
199
- if (isMainModule(import.meta.url)) {
199
+ if (isMainModule(import.meta.url) && (process.argv[1] ?? "").includes("registry-proxy")) {
200
200
  main().catch((err) => {
201
201
  console.error(err instanceof Error ? err.message : String(err));
202
202
  process.exit(1);