shardwire 1.4.0 → 1.4.5

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/dist/index.js CHANGED
@@ -22,7 +22,11 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  BridgeCapabilityError: () => BridgeCapabilityError,
24
24
  connectBotBridge: () => connectBotBridge,
25
- createBotBridge: () => createBotBridge
25
+ createBotBridge: () => createBotBridge,
26
+ createThreadThenSendMessage: () => createThreadThenSendMessage,
27
+ deferThenEditInteractionReply: () => deferThenEditInteractionReply,
28
+ deferUpdateThenEditInteractionReply: () => deferUpdateThenEditInteractionReply,
29
+ getShardwireCatalog: () => getShardwireCatalog
26
30
  });
27
31
  module.exports = __toCommonJS(index_exports);
28
32
 
@@ -104,6 +108,17 @@ var EVENT_REQUIRED_INTENTS = {
104
108
  channelUpdate: ["Guilds"],
105
109
  channelDelete: ["Guilds"]
106
110
  };
111
+ var SUBSCRIPTION_FILTER_KEYS = [
112
+ "guildId",
113
+ "channelId",
114
+ "userId",
115
+ "commandName",
116
+ "customId",
117
+ "interactionKind",
118
+ "channelType",
119
+ "parentChannelId",
120
+ "threadId"
121
+ ];
107
122
  function getAvailableEvents(intents) {
108
123
  const enabled = new Set(intents);
109
124
  return BOT_EVENT_NAMES.filter(
@@ -2311,14 +2326,16 @@ function createBotBridgeWithRuntime(options, runtime) {
2311
2326
 
2312
2327
  // src/discord/types.ts
2313
2328
  var BridgeCapabilityError = class extends Error {
2314
- constructor(kind, name, message) {
2329
+ constructor(kind, name, message, details) {
2315
2330
  super(message ?? `Capability "${name}" is not available for ${kind}.`);
2316
2331
  this.kind = kind;
2317
2332
  this.name = name;
2333
+ this.details = details;
2318
2334
  this.name = "BridgeCapabilityError";
2319
2335
  }
2320
2336
  kind;
2321
2337
  name;
2338
+ details;
2322
2339
  };
2323
2340
 
2324
2341
  // src/bridge/transport/socket.ts
@@ -2327,6 +2344,172 @@ function createNodeWebSocket(url) {
2327
2344
  return new import_ws2.WebSocket(url);
2328
2345
  }
2329
2346
 
2347
+ // src/dx/explain-capability.ts
2348
+ var DENIED_REMEDIATION = "Add the required gateway intents on `createBotBridge({ intents })` and/or widen `server.secrets[].allow.events` or `allow.actions` for this secret.";
2349
+ function isBotEventName(name) {
2350
+ return BOT_EVENT_NAMES.includes(name);
2351
+ }
2352
+ function isBotActionName(name) {
2353
+ return BOT_ACTION_NAMES.includes(name);
2354
+ }
2355
+ function buildBridgeCapabilityErrorDetails(kind, name) {
2356
+ const base = {
2357
+ reasonCode: "not_in_capabilities",
2358
+ kind,
2359
+ name,
2360
+ remediation: DENIED_REMEDIATION
2361
+ };
2362
+ if (kind === "event" && isBotEventName(name)) {
2363
+ return { ...base, requiredIntents: EVENT_REQUIRED_INTENTS[name] };
2364
+ }
2365
+ return base;
2366
+ }
2367
+ function explainCapability(connected, capabilities, query) {
2368
+ const { kind, name } = query;
2369
+ const known = kind === "event" ? isBotEventName(name) : isBotActionName(name);
2370
+ if (!known) {
2371
+ return {
2372
+ kind,
2373
+ name,
2374
+ known: false,
2375
+ reasonCode: "unknown_name",
2376
+ remediation: "Use a built-in event or action name from `app.catalog()`."
2377
+ };
2378
+ }
2379
+ if (!connected || !capabilities) {
2380
+ const base = {
2381
+ kind,
2382
+ name,
2383
+ known: true,
2384
+ reasonCode: "not_negotiated",
2385
+ remediation: "Call `await app.ready()` (or `await app.preflight()`) after connecting to see negotiated bridge access."
2386
+ };
2387
+ if (kind === "event") {
2388
+ return {
2389
+ ...base,
2390
+ requiredIntents: EVENT_REQUIRED_INTENTS[name]
2391
+ };
2392
+ }
2393
+ return base;
2394
+ }
2395
+ if (kind === "event") {
2396
+ const ev = name;
2397
+ const allowedByBridge2 = capabilities.events.includes(ev);
2398
+ return {
2399
+ kind: "event",
2400
+ name: ev,
2401
+ known: true,
2402
+ requiredIntents: EVENT_REQUIRED_INTENTS[ev],
2403
+ allowedByBridge: allowedByBridge2,
2404
+ reasonCode: allowedByBridge2 ? "allowed" : "denied_by_bridge",
2405
+ ...!allowedByBridge2 ? { remediation: DENIED_REMEDIATION } : {}
2406
+ };
2407
+ }
2408
+ const act = name;
2409
+ const allowedByBridge = capabilities.actions.includes(act);
2410
+ return {
2411
+ kind: "action",
2412
+ name: act,
2413
+ known: true,
2414
+ allowedByBridge,
2415
+ reasonCode: allowedByBridge ? "allowed" : "denied_by_bridge",
2416
+ ...!allowedByBridge ? { remediation: DENIED_REMEDIATION } : {}
2417
+ };
2418
+ }
2419
+
2420
+ // src/dx/preflight.ts
2421
+ function parseAppBridgeUrl(url) {
2422
+ try {
2423
+ return new URL(url);
2424
+ } catch {
2425
+ return null;
2426
+ }
2427
+ }
2428
+ function buildPreflightReport(input) {
2429
+ const issues = [];
2430
+ const { connected, capabilities, appUrl, desired, subscriptionCapabilityMessage, subscriptionCapabilityRemediation } = input;
2431
+ if (subscriptionCapabilityMessage) {
2432
+ issues.push({
2433
+ severity: "error",
2434
+ code: "subscription_event_not_negotiated",
2435
+ message: subscriptionCapabilityMessage,
2436
+ remediation: subscriptionCapabilityRemediation ?? "Remove `app.on` handlers for disallowed events or widen bot intents / secret scope."
2437
+ });
2438
+ }
2439
+ const parsed = parseAppBridgeUrl(appUrl);
2440
+ if (parsed?.protocol === "ws:") {
2441
+ const host = parsed.hostname;
2442
+ const isLoopback = host === "localhost" || host === "127.0.0.1" || host === "::1";
2443
+ if (isLoopback) {
2444
+ issues.push({
2445
+ severity: "warning",
2446
+ code: "insecure_transport_local",
2447
+ message: "Using ws:// to a loopback host is fine for development.",
2448
+ remediation: "Use wss:// behind TLS for production deployments."
2449
+ });
2450
+ }
2451
+ }
2452
+ if (!connected || !capabilities) {
2453
+ issues.push({
2454
+ severity: "warning",
2455
+ code: "not_connected",
2456
+ message: "Preflight ran before the app finished authenticating.",
2457
+ remediation: "Ensure the bridge URL and secret are correct; `preflight()` awaits authentication internally."
2458
+ });
2459
+ return {
2460
+ ok: issues.every((i) => i.severity !== "error"),
2461
+ connected,
2462
+ capabilities,
2463
+ issues
2464
+ };
2465
+ }
2466
+ const allowedEvents = new Set(capabilities.events);
2467
+ const allowedActions = new Set(capabilities.actions);
2468
+ if (desired?.events?.length) {
2469
+ for (const ev of desired.events) {
2470
+ if (!allowedEvents.has(ev)) {
2471
+ issues.push({
2472
+ severity: "error",
2473
+ code: "desired_event_not_allowed",
2474
+ message: `Negotiated capabilities do not include event "${ev}".`,
2475
+ remediation: "Add gateway intents on the bot bridge and/or include this event in the scoped secret `allow.events`."
2476
+ });
2477
+ }
2478
+ }
2479
+ }
2480
+ if (desired?.actions?.length) {
2481
+ for (const act of desired.actions) {
2482
+ if (!allowedActions.has(act)) {
2483
+ issues.push({
2484
+ severity: "error",
2485
+ code: "desired_action_not_allowed",
2486
+ message: `Negotiated capabilities do not include action "${act}".`,
2487
+ remediation: "Widen `server.secrets[].allow.actions` or use a full-access secret for this app."
2488
+ });
2489
+ }
2490
+ }
2491
+ }
2492
+ const hasError = issues.some((i) => i.severity === "error");
2493
+ return {
2494
+ ok: !hasError,
2495
+ connected,
2496
+ capabilities: { events: [...capabilities.events], actions: [...capabilities.actions] },
2497
+ issues
2498
+ };
2499
+ }
2500
+
2501
+ // src/dx/shardwire-catalog.ts
2502
+ function getShardwireCatalog() {
2503
+ return {
2504
+ events: BOT_EVENT_NAMES.map((name) => ({
2505
+ name,
2506
+ requiredIntents: EVENT_REQUIRED_INTENTS[name]
2507
+ })),
2508
+ actions: [...BOT_ACTION_NAMES],
2509
+ subscriptionFilters: [...SUBSCRIPTION_FILTER_KEYS]
2510
+ };
2511
+ }
2512
+
2330
2513
  // src/utils/backoff.ts
2331
2514
  function getBackoffDelay(attempt, config) {
2332
2515
  const base = Math.min(config.maxDelayMs, config.initialDelayMs * 2 ** attempt);
@@ -2453,7 +2636,12 @@ function connectBotBridge(options) {
2453
2636
  const allowedEvents = new Set(currentCapabilities.events);
2454
2637
  const invalidEvents = [...eventHandlers.keys()].filter((eventName) => !allowedEvents.has(eventName));
2455
2638
  const firstInvalid = invalidEvents[0];
2456
- capabilityError = firstInvalid !== void 0 ? new BridgeCapabilityError("event", firstInvalid, `Event "${firstInvalid}" is not available for this app.`) : null;
2639
+ capabilityError = firstInvalid !== void 0 ? new BridgeCapabilityError(
2640
+ "event",
2641
+ firstInvalid,
2642
+ `Event "${firstInvalid}" is not available for this app.`,
2643
+ buildBridgeCapabilityErrorDetails("event", firstInvalid)
2644
+ ) : null;
2457
2645
  for (const [eventName, handlers] of eventHandlers.entries()) {
2458
2646
  if (!allowedEvents.has(eventName)) {
2459
2647
  continue;
@@ -2626,7 +2814,12 @@ function connectBotBridge(options) {
2626
2814
  ts: Date.now(),
2627
2815
  error: {
2628
2816
  code: "FORBIDDEN",
2629
- message: `Action "${name}" is not available for this app.`
2817
+ message: `Action "${name}" is not available for this app.`,
2818
+ details: {
2819
+ reasonCode: "action_not_in_capabilities",
2820
+ action: name,
2821
+ remediation: "Add gateway intents on the bot bridge and/or include this action in the scoped secret `allow.actions`."
2822
+ }
2630
2823
  }
2631
2824
  };
2632
2825
  }
@@ -2773,9 +2966,49 @@ function connectBotBridge(options) {
2773
2966
  actions: [...currentCapabilities.actions]
2774
2967
  };
2775
2968
  },
2969
+ catalog() {
2970
+ return getShardwireCatalog();
2971
+ },
2972
+ explainCapability(query) {
2973
+ const connected = Boolean(socket && socket.readyState === 1 && isAuthed);
2974
+ const caps = connected ? { events: [...currentCapabilities.events], actions: [...currentCapabilities.actions] } : null;
2975
+ return explainCapability(connected, caps, query);
2976
+ },
2977
+ async preflight(desired) {
2978
+ try {
2979
+ await connect();
2980
+ } catch (error) {
2981
+ return {
2982
+ ok: false,
2983
+ connected: false,
2984
+ capabilities: null,
2985
+ issues: [
2986
+ {
2987
+ severity: "error",
2988
+ code: "connection_failed",
2989
+ message: error instanceof Error ? error.message : "Failed to connect or authenticate.",
2990
+ remediation: "Verify `url` and `secret`, and ensure the bot bridge is running and reachable."
2991
+ }
2992
+ ]
2993
+ };
2994
+ }
2995
+ return buildPreflightReport({
2996
+ connected: Boolean(socket && socket.readyState === 1 && isAuthed),
2997
+ capabilities: isAuthed ? { events: [...currentCapabilities.events], actions: [...currentCapabilities.actions] } : null,
2998
+ appUrl: options.url,
2999
+ ...desired !== void 0 ? { desired } : {},
3000
+ subscriptionCapabilityMessage: capabilityError?.message ?? null,
3001
+ subscriptionCapabilityRemediation: capabilityError?.details?.remediation ?? null
3002
+ });
3003
+ },
2776
3004
  on(name, handler, filter) {
2777
3005
  if (currentCapabilities.events.length > 0 && !currentCapabilities.events.includes(name)) {
2778
- throw new BridgeCapabilityError("event", name, `Event "${name}" is not available for this app.`);
3006
+ throw new BridgeCapabilityError(
3007
+ "event",
3008
+ name,
3009
+ `Event "${name}" is not available for this app.`,
3010
+ buildBridgeCapabilityErrorDetails("event", name)
3011
+ );
2779
3012
  }
2780
3013
  const casted = handler;
2781
3014
  const subscription = filter ? { name, filter } : { name };
@@ -2826,10 +3059,54 @@ function connectBotBridge(options) {
2826
3059
  }
2827
3060
  };
2828
3061
  }
3062
+
3063
+ // src/workflows/interaction.ts
3064
+ async function deferThenEditInteractionReply(app, args, options) {
3065
+ const { interactionId, defer, edit } = args;
3066
+ const deferResult = await app.actions.deferInteraction({ interactionId, ...defer }, options);
3067
+ if (!deferResult.ok) {
3068
+ return deferResult;
3069
+ }
3070
+ return app.actions.editInteractionReply(edit, options);
3071
+ }
3072
+ async function deferUpdateThenEditInteractionReply(app, args, options) {
3073
+ const { interactionId, edit } = args;
3074
+ const deferResult = await app.actions.deferUpdateInteraction({ interactionId }, options);
3075
+ if (!deferResult.ok) {
3076
+ return deferResult;
3077
+ }
3078
+ return app.actions.editInteractionReply(edit, options);
3079
+ }
3080
+
3081
+ // src/workflows/thread.ts
3082
+ async function createThreadThenSendMessage(app, args, options) {
3083
+ const threadResult = await app.actions.createThread(args.thread, options);
3084
+ if (!threadResult.ok) {
3085
+ return {
3086
+ threadResult,
3087
+ messageResult: {
3088
+ ok: false,
3089
+ requestId: options?.requestId ?? "unknown",
3090
+ ts: Date.now(),
3091
+ error: {
3092
+ code: "INVALID_REQUEST",
3093
+ message: "Skipped send because createThread failed.",
3094
+ details: { skipped: true }
3095
+ }
3096
+ }
3097
+ };
3098
+ }
3099
+ const messageResult = await app.actions.sendMessage({ ...args.message, channelId: threadResult.data.id }, options);
3100
+ return { threadResult, messageResult };
3101
+ }
2829
3102
  // Annotate the CommonJS export names for ESM import in node:
2830
3103
  0 && (module.exports = {
2831
3104
  BridgeCapabilityError,
2832
3105
  connectBotBridge,
2833
- createBotBridge
3106
+ createBotBridge,
3107
+ createThreadThenSendMessage,
3108
+ deferThenEditInteractionReply,
3109
+ deferUpdateThenEditInteractionReply,
3110
+ getShardwireCatalog
2834
3111
  });
2835
3112
  //# sourceMappingURL=index.js.map