diff --git a/README.md b/README.md index 33e05f8..de8d7e2 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ flowchart LR - **Net** — `BridgeSwarm.net.fetch` to public http(s) only - **QVAC** — on-device LLM (`BridgeSwarm.qvac.*`), off until Settings → Enable QVAC. Multimodal **image input**; live desktop stills via `pushDesktopFrame`. No image/video generation. - **Agent** — grok-class coding loop (`BridgeSwarm.agent.*`) on the same engine +- **Discord** — official discord.js 14 (`BridgeSwarm.DiscordJS`), off until Settings → Enable Discord See [docs/CAPABILITIES.md](docs/CAPABILITIES.md). diff --git a/docs/API-REFERENCE.md b/docs/API-REFERENCE.md index 511a8e1..550967b 100644 --- a/docs/API-REFERENCE.md +++ b/docs/API-REFERENCE.md @@ -33,6 +33,8 @@ These are the main APIs your page uses. For host request types and event payload - **`BridgeSwarm.qvac.*`** — Local QVAC inference (enable in Settings). `chat({ history, tools }, { execute })` runs page tools. Multimodal models accept images; HTTPS pages feed a live desktop ring with `pushDesktopFrame`. See [QVAC.md](QVAC.md). - **`BridgeSwarm.agent.*`** — Grok-class coding sessions (requires QVAC enabled in Settings). `session.addTool({ name, description, parameters, execute })` registers page tools. See [AGENT.md](AGENT.md). +- **`BridgeSwarm.DiscordJS`** — Official discord.js 14 (`Client`, intents, events, builders, `REST`). Real Client on the Bare host. Enable in Settings. See [DISCORD.md](DISCORD.md). + - **`BridgeSwarm.media.*`** — Media pack: batch (`info`, `imageTransform`, `extractFrame`, `transcode`, …) and live encode (`encodeStart`, `encodePushFrame`, `encodeStop`, `liveSession`, `attachLiveReceiver`). See [CAPABILITIES.md](CAPABILITIES.md). - **`BridgeSwarm.fs.*`** — Allowlisted files under storage `files/` (`write`, `read`, `list`, `mkdir`, `stat`, `exists`, `unlink`, `rename`). diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index b82cc94..fc189b5 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -19,7 +19,7 @@ flowchart TB SW["Hyperswarm"] DATA["Corestore · Hypercore · Hyperbee
Hyperdrive · Autobase · Hyperdb"] MUX["Protomux · HRPC"] - CAP["capabilities/
media · fs · sqlite · net · qvac · agent"] + CAP["capabilities/
media · fs · sqlite · net · qvac · agent · discord"] EX["examples-server.js
127.0.0.1:4173"] end @@ -40,15 +40,15 @@ flowchart TB ### Extension -- **Background service worker** ([extension/background.js](../extension/background.js)): Maintains the native messaging port (`chrome.runtime.connectNative('com.bridgeswarm')`). Tracks pending requests by id; routes swarm events to the tabs that own each `swarmId`; broadcasts capability events (`cap-chunk` / `cap-end` / `cap-error`) to subscribed tabs. Reconnects with backoff on disconnect. Reads **`bridgeSwarmSettings`** from local storage (autosaved from Options/Dashboard). When “Show notification when host disconnects” is enabled, shows a browser notification on `port.onDisconnect`. When **Examples server** is enabled, sends `examplesServer.start` / `stop` to the host. When **Enable QVAC** is on, sends `qvac.setEnabled`; origin-allowlisted pack commands are gated, and QVAC/agent inference is refused until that toggle is on. -- **Content script** ([extension/content.js](../extension/content.js)): Reads settings; if “Do not inject on file:// URLs” is on and the page is `file://`, skips injection. Otherwise injects defaults then [api.js](../extension/api.js), [framed-stream.js](../extension/framed-stream.js), and [protomux-bundle.js](../extension/protomux-bundle.js). Bridges page ↔ background via `window.postMessage` / `chrome.runtime.sendMessage` (the page cannot use `chrome.runtime` directly). +- **Background service worker** ([extension/background.js](../extension/background.js)): Maintains the native messaging port (`chrome.runtime.connectNative('com.bridgeswarm')`). Tracks pending requests by id; routes swarm events to the tabs that own each `swarmId`; broadcasts capability events (`cap-chunk` / `cap-end` / `cap-error`) to subscribed tabs. Reconnects with backoff on disconnect. Reads **`bridgeSwarmSettings`** from local storage (autosaved from Options/Dashboard). When “Show notification when host disconnects” is enabled, shows a browser notification on `port.onDisconnect`. When **Examples server** is enabled, sends `examplesServer.start` / `stop` to the host. When **Enable QVAC** is on, sends `qvac.setEnabled`; when **Enable Discord** is on, sends `discord.setEnabled`; origin-allowlisted pack commands are gated, and QVAC/agent inference and Discord construct/login are refused until those toggles are on. +- **Content script** ([extension/content.js](../extension/content.js)): Reads settings; if “Do not inject on file:// URLs” is on and the page is `file://`, skips injection. Otherwise injects defaults then [api.js](../extension/api.js), [discordjs-builders.js](../extension/discordjs-builders.js), [discordjs.js](../extension/discordjs.js), [framed-stream.js](../extension/framed-stream.js), and [protomux-bundle.js](../extension/protomux-bundle.js). Bridges page ↔ background via `window.postMessage` / `chrome.runtime.sendMessage` (the page cannot use `chrome.runtime` directly). - **Control Center** ([extension/dashboard.html](../extension/dashboard.html)): Premium panel (Overview · Swarms · Connections · Activity · Settings). Live state over a `bridgeswarm-dashboard` Port; advanced log viewer; host health from `host.snapshot`. Changes **save on change**. - **Options** ([extension/options.html](../extension/options.html)): Opens Control Center Settings (`open_in_tab`). -- **Injected scripts** (page context): [defaults.js](../extension/defaults.js) is injected **into the page world** (content-script `window` is isolated) with Settings as `data-defaults`. Then [api.js](../extension/api.js) exposes `window.BridgeSwarm`, `BridgeSwarm.request`, `BridgeSwarm.capabilities` / `media` / `fs` / `sqlite` / `net` / `qvac` / `agent`. Merges constructor options with `__BRIDGESWARM_DEFAULTS__`. Debug `console.log` in api.js only runs when Settings → Debug is on. +- **Injected scripts** (page context): [defaults.js](../extension/defaults.js) is injected **into the page world** (content-script `window` is isolated) with Settings as `data-defaults`. Then [api.js](../extension/api.js) exposes `window.BridgeSwarm`, `BridgeSwarm.request`, `BridgeSwarm.capabilities` / `media` / `fs` / `sqlite` / `net` / `qvac` / `agent`. [discordjs.js](../extension/discordjs.js) attaches `BridgeSwarm.DiscordJS`. Merges constructor options with `__BRIDGESWARM_DEFAULTS__`. Debug `console.log` in api.js only runs when Settings → Debug is on. ### Native host -- **Entry** ([native-host/index.mjs](../native-host/index.mjs)): Loads bare-process, registers default capability packs (media, fs, sqlite, net, qvac, agent), starts [messenger.js](../native-host/messenger.js) + [host.js](../native-host/host.js). QVAC inference stays idle until Settings → Enable QVAC. +- **Entry** ([native-host/index.mjs](../native-host/index.mjs)): Loads bare-process, registers default capability packs (media, fs, sqlite, net, qvac, agent, discord), starts [messenger.js](../native-host/messenger.js) + [host.js](../native-host/host.js). QVAC inference stays idle until Settings → Enable QVAC. Discord.js stays idle until Settings → Enable Discord. - **Messenger** ([native-host/messenger.js](../native-host/messenger.js)): Chrome/Firefox native messaging: 4-byte little-endian length + UTF-8 JSON. Max message size from host → browser is **1 MB**. - **Host** ([native-host/host.js](../native-host/host.js)): Swarm lifecycle, peer firewall/ban, auto-replicate, connection attachment (Protomux/HRPC), Hyper* data API (including list/peek/has/update/entry), capability dispatch, examples-server control (`examplesServer.start` / `stop` / `status`), and in-process QVAC (`@qvac/inference`) plus the agent loop. - **Examples server** ([native-host/examples-server.js](../native-host/examples-server.js)): Optional `bare-http1` static server on `127.0.0.1:4173` serving synced `examples/` (or `BRIDGESWARM_EXAMPLES_DIR`). Enabled from extension settings. diff --git a/docs/CAPABILITIES.md b/docs/CAPABILITIES.md index 9b0def7..2e04cc4 100644 --- a/docs/CAPABILITIES.md +++ b/docs/CAPABILITIES.md @@ -1,11 +1,11 @@ # Capabilities -BridgeSwarm exposes selected [Bare](https://github.com/holepunchto/bare) native APIs to the page as **capability packs**. Curated packs — not every `bare-*` package — is intentional. The **default host includes media, fs, sqlite, net, qvac, and agent**. Other modules are listed in [DEFAULT-MODULES.md](DEFAULT-MODULES.md). +BridgeSwarm exposes selected [Bare](https://github.com/holepunchto/bare) native APIs to the page as **capability packs**. Curated packs — not every `bare-*` package — is intentional. The default host includes media, fs, sqlite, net, qvac, agent, and discord. Other modules are listed in [DEFAULT-MODULES.md](DEFAULT-MODULES.md). ## Page API ```js -await BridgeSwarm.capabilities.list() // e.g. ['media', 'fs', 'sqlite', 'net', 'qvac', 'agent'] +await BridgeSwarm.capabilities.list() // e.g. ['media', 'fs', 'sqlite', 'net', 'qvac', 'agent', 'discord'] await BridgeSwarm.capabilities.has('media') // Generic dispatch @@ -161,6 +161,21 @@ In-process `@qvac/inference` (LLM + embeddings; optional ASR/TTS/OCR/NMT/RAG). * Demo: [`examples/qvac-chat/`](../examples/qvac-chat/). +## Pack — Discord (`discord`, default) + +Official discord.js 14 on Bare (`bare-discord-js`). Page surface is `BridgeSwarm.DiscordJS`. **Off until Settings → Enable Discord.** Full API, fidelity gap, and packing notes: [DISCORD.md](DISCORD.md). + +| Command | Behavior | +|---------|----------| +| `discord.surface` / `status` | Constants (`GatewayIntentBits`, `Events`, …) and load state. Allowed while disabled. | +| `discord.construct` | Host `new Client` / `REST` / `WebhookClient` → `{ handle }` (max 4 clients) | +| `discord.call` / `get` | Invoke or read a path on a handle | +| `discord.listen` | Forward Client events as `cap-chunk` `{ pack: 'discord', kind: 'event' }` | +| `discord.destroy` | Destroy a Client (or all) | +| `discord.setEnabled` | Background only (Settings toggle) | + +Demo: [`examples/discord-bot/`](../examples/discord-bot/). + ## Pack — Agent (`agent`, default) Grok-class tool loop on QVAC (`write_file`, `search_replace`, …). Pages register extra tools with `session.addTool`. Stays idle until QVAC is enabled. See [AGENT.md](AGENT.md). diff --git a/docs/DEFAULT-MODULES.md b/docs/DEFAULT-MODULES.md index 693cb50..2858666 100644 --- a/docs/DEFAULT-MODULES.md +++ b/docs/DEFAULT-MODULES.md @@ -43,6 +43,7 @@ Curated modules shipped in the **default** BridgeSwarm native host. Not every `b | `@qvac/inference` + `@qvac/fabric` + `@qvac/llm-llamacpp` + `@qvac/embed-llamacpp` | `qvac` pack (optional plugins: ASR/TTS/OCR/NMT/RAG). Multimodal still input via mmproj; live desktop ring. Not image/video gen | | `@qvac/decoder-audio` / `@qvac/langdetect-text` | Required by `@qvac/inference` import graph (audio constants / translate); not image/video gen | | `bare-os` / `bare-subprocess` / `bare-gpu-info` | Agent shell (cwd-jailed; **not** a page pack); GPU inventory for QVAC device auto | +| `bare-discord-js` + `discord.js` + `bare-ws` / `bare-tls` / `bare-https` / `bare-form-data` | `discord` pack — official discord.js 14 on Bare; off until Settings → Enable Discord | ## Native addons extracted on install (`--extract-addons`) @@ -54,6 +55,8 @@ SQLite: `bare-sqlite` (when present as a native addon) QVAC: `@qvac/fabric`, `@qvac/llm-llamacpp`, `@qvac/embed-llamacpp`, `bare-gpu-info` (when present) +Discord: `bare-tls`, `bare-crypto`, `bare-zlib` (plus `bare-ws` / `bare-https` when present as addons) + ## Intentionally not default | Area | Examples | Reason | @@ -63,6 +66,6 @@ QVAC: `@qvac/fabric`, `@qvac/llm-llamacpp`, `@qvac/embed-llamacpp`, `bare-gpu-in | Raw LAN sockets as page API | `bare-tcp` / `bare-dgram` listen | Already transitive for Hyperswarm; no open page API yet | | Everything else in `bare-*` (~150) | — | Size + maintenance; curated packs only | -See [CAPABILITIES.md](CAPABILITIES.md) for the page-facing packs built on these modules, and [QVAC.md](QVAC.md) for inference (off until Settings → Enable QVAC). +See [CAPABILITIES.md](CAPABILITIES.md) for the page-facing packs built on these modules, [QVAC.md](QVAC.md) for inference (off until Settings → Enable QVAC), and [DISCORD.md](DISCORD.md) for discord.js (off until Settings → Enable Discord). Working notes and the next-build plan: [living/README.md](living/README.md). diff --git a/docs/DISCORD.md b/docs/DISCORD.md new file mode 100644 index 0000000..0d5eb08 --- /dev/null +++ b/docs/DISCORD.md @@ -0,0 +1,45 @@ +# Discord.js on BridgeSwarm + +Official **discord.js 14** runs inside the Bare native host ([`bare-discord-js`](../native-host/vendor/bare-discord-js)). Pages use `window.BridgeSwarm.DiscordJS` — the same names as Node (`Client`, `GatewayIntentBits`, `Events`, `REST`, `Routes`, builders). Discord REST has no CORS for random origins and discord.js needs Node/Bare sockets, so the Client cannot live in Chrome. + +**Off until Settings → Enable Discord.** Origin allowlist still applies. Bot tokens are credentials; the demo never writes them to disk. + +```js +await BridgeSwarm.ready() +const { Client, GatewayIntentBits, Events, SlashCommandBuilder, REST, Routes } = BridgeSwarm.DiscordJS +const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.DirectMessages] }) +client.once(Events.ClientReady, (c) => console.log('Logged in as', c.user.tag)) +client.on(Events.InteractionCreate, async (ix) => { + if (ix.isChatInputCommand() && ix.commandName === 'ping') await ix.reply('pong') +}) +await client.login(token) +``` + +`BridgeSwarm.ready()` also loads constants from the host (`discord.surface`). `BridgeSwarm.DiscordJS.ready()` does the same if you skip `BridgeSwarm.ready()`. + +## Fidelity gap + +Native messaging is async JSON (~1 MB). **Collections/cache are snapshots, not live Maps.** Use `client.channels.fetch(id)` / `client.guilds.fetch(id)` (already async in discord.js) instead of `cache.get`. `client.user` after `ClientReady` is a snapshot. Event payloads (`Message`, `ChatInputCommandInteraction`, …) hydrate stubs: sync predicates (`isChatInputCommand()`) from the snapshot; async methods (`reply`, `followUp`, `editReply`, …) RPC to the host handle. + +Anything not stubbed: `BridgeSwarm.DiscordJS.invoke(handle, path, args)`. + +Voice (`@discordjs/voice`) is not shipped. Clients are **tab-scoped** — `pagehide` and `client.destroy()` drop the host Client. Closing the tab stops the bot. + +## Host pack (`discord`) + +| Command | Behavior | +|---------|----------| +| `surface` / `status` | Constants + load state. Allowed while disabled. | +| `construct` | `{ className: 'Client'\|'REST'\|'WebhookClient', args }` → `{ handle }`. Max 4 Clients. | +| `call` / `get` | `{ handle, path, args }` generic invoke | +| `listen` | Forward Client events as `cap-chunk` `{ pack: 'discord', kind: 'event', handle, name, args }` | +| `destroy` | `client.destroy()` + drop handles | +| `setEnabled` | Background only (Settings toggle) | + +Tokens are normalized (trim, strip BOM / zero-width). They are never logged. + +## Bare runtime notes + +Gateway WebSocket is a WHATWG facade over `bare-ws` (`process.versions.bun` so `@discordjs/ws` does not use npm `ws`). `zlib-sync` is a **null** stub — a truthy empty module hangs `Client.login()`. REST uses the `@discordjs/rest` web/fetch build and `bare-form-data` globals for uploads. + +Demo: [`examples/discord-bot/`](../examples/discord-bot/). diff --git a/docs/README.md b/docs/README.md index 2c05354..82acf80 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,9 +7,10 @@ | [ARCHITECTURE.md](ARCHITECTURE.md) | Components and message flow | | [API-REFERENCE.md](API-REFERENCE.md) | Page API + every host request/event | | [DATA-API.md](DATA-API.md) | Hypercore / Hyperbee / Hyperdrive / Autobase / Hyperdb | -| [CAPABILITIES.md](CAPABILITIES.md) | media, fs, sqlite, net, qvac, agent packs | +| [CAPABILITIES.md](CAPABILITIES.md) | media, fs, sqlite, net, qvac, agent, discord packs | | [QVAC.md](QVAC.md) | Local QVAC inference, vision input, desktop still ingest (off until Settings → Enable QVAC) | | [AGENT.md](AGENT.md) | Grok-class agent API | +| [DISCORD.md](DISCORD.md) | discord.js 14 on the Bare host (`BridgeSwarm.DiscordJS`) | | [DEFAULT-MODULES.md](DEFAULT-MODULES.md) | What the default Bare host ships | | [PROTOMUX.md](PROTOMUX.md) | In-page mux + host attachment | | [HRPC.md](HRPC.md) | Host HRPC | diff --git a/docs/living/GAPS.md b/docs/living/GAPS.md index 698bd34..732bdb0 100644 --- a/docs/living/GAPS.md +++ b/docs/living/GAPS.md @@ -31,9 +31,13 @@ After the 2026-09-03 pass. These are **known**, not forgotten. - No integration test that boots Bare + Hyperswarm + two in-process peers. - `extension/examples/` and `native-host/examples/` are copies; forget `npm run sync:examples` and packaged demos drift. - `api.js` `arrayBufferToBase64` is O(n) string concat — fine for chat, painful for big media frames (live-encode already JPEGs to stay small). -- Dashboard Capabilities shows QVAC/agent health (Vision, Desktop fps). Live encode job inspector still later. +- Dashboard Capabilities shows QVAC/agent/Discord health (Vision, Desktop fps). Live encode job inspector still later. - Firefox permanent install still needs Nightly/Dev + `.xpi` signing story. +## Discord + +- Page `BridgeSwarm.DiscordJS` is a handle proxy: Collections/cache are snapshots, not live Maps. Voice is not shipped. Bots die with the tab. + ## Docs hygiene - `theory/bridgeswarm-for-dummies.md` is a long-form essay; it can lag the API. Prefer `docs/` + `docs/living/` as source of truth. diff --git a/docs/living/PLAN.md b/docs/living/PLAN.md index af8b6b0..ceba1ed 100644 --- a/docs/living/PLAN.md +++ b/docs/living/PLAN.md @@ -8,6 +8,8 @@ Living plan. Check items off in STATUS.md when they land. Dates are ordering, no 2. ~~**QVAC + grok-class agent packs**~~ **Done.** In-process `@qvac/inference`, `BridgeSwarm.qvac` / `.agent`, OpenAI loopback on `127.0.0.1:11435`. **Off until Settings → Enable QVAC.** Multimodal **image input** (mmproj + `attachments`) and live desktop still ingest (`qvac.pushDesktopFrame` / `POST /v1/vision/frames`, ≤5 FPS) landed 2026-09-03. See [QVAC.md](../QVAC.md) and [AGENT.md](../AGENT.md). +2b. ~~**Discord.js pack**~~ **Done.** Vendored `bare-discord-js` (discord.js 14.27) as `BridgeSwarm.DiscordJS`. **Off until Settings → Enable Discord.** See [DISCORD.md](../DISCORD.md). + 3. **Host-side tests under Bare** `npm test` covers net policy, origin allowlist, QVAC catalog/device fallback, agent loop (fake completion), path jail. Add a Bare script that opens an in-process Corestore + Hyperbee and exercises `handleMessage` for `beePut/Get/List`, `coreHas`, `resourceClose`. Keep it free of Chrome. @@ -58,6 +60,7 @@ Living plan. Check items off in STATUS.md when they land. Dates are ordering, no PR1 origin allowlist for capabilities (ext: background + dashboard) PR2 QVAC pack + page API + qvac-chat (native-host + extension + examples) PR3 Agent MVP + production tools (native-host/agent + agent-studio) +PR3b Discord.js pack + DiscordJS page SDK (native-host + extension + examples/discord-bot) PR4 Bare handleMessage unit tests (native-host + scripts) PR5 replicate-info events + sync-demo live UI (host + examples) PR6 beeBatch / beeHistory (host + DATA-API + data-demo) diff --git a/docs/living/STATUS.md b/docs/living/STATUS.md index d92761d..75c5b46 100644 --- a/docs/living/STATUS.md +++ b/docs/living/STATUS.md @@ -1,6 +1,6 @@ # Status — what ships -Last reviewed: **2026-09-03**. +Last reviewed: **2026-09-04**. ## Product @@ -8,12 +8,12 @@ BridgeSwarm is a **desktop-only** MV3 extension + Bare native host that runs the | Surface | Path | Role | |---------|------|------| -| Page API | `extension/api.js` | `BridgeSwarm`, connections, request, packs | -| Content script | `extension/content.js` | Injects defaults + API; bridges postMessage | -| Background | `extension/background.js` | Native port, swarm routing, dashboard, examples toggle | +| Page API | `extension/api.js` + `discordjs.js` | `BridgeSwarm`, connections, request, packs, `DiscordJS` | +| Content script | `extension/content.js` | Injects defaults + API + DiscordJS; bridges postMessage | +| Background | `extension/background.js` | Native port, swarm routing, dashboard, examples / QVAC / Discord toggles | | Host | `native-host/host.js` | Swarm, Hyper*, HRPC, pack dispatch | | Messenger | `native-host/messenger.js` | 4-byte LE + JSON (NMH ~1 MB) | -| Packs | `native-host/capabilities/` | media, fs, sqlite, net, qvac, agent | +| Packs | `native-host/capabilities/` | media, fs, sqlite, net, qvac, agent, discord | | Examples | `examples/` (source of truth) | Synced into extension + host | ## Completed this pass (were missing) @@ -32,6 +32,7 @@ BridgeSwarm is a **desktop-only** MV3 extension + Bare native host that runs the | Live desktop still ingest | Ring buffer ≤5 FPS. Native apps: `POST /v1/vision/frames`. HTTPS pages (Pip): `qvac.pushDesktopFrame` over NMH. Injected on each complete, not JSONL | | Agent loop quality | Parallel tools + path locks, unique `search_replace`, `rg` grep, LLM compaction, git sidecar, persistent allow/deny, MCP HTTP handshake, subagent resume | | Living docs / continued-dev plan | This folder | +| discord.js 14 on Bare (`BridgeSwarm.DiscordJS`) | Vendored `bare-discord-js`; Settings → Enable Discord; `examples/discord-bot/` | ## Capability packs (default host) @@ -43,8 +44,9 @@ BridgeSwarm is a **desktop-only** MV3 extension + Bare native host that runs the | `net` | fetch | public http(s) only; private / loopback / metadata blocked; 2 MB body cap | | `qvac` | detect, load, complete, chat, pushDesktopFrame, clearDesktop, embed, … | Off until Settings → Enable QVAC; `$BRIDGE_SWARM_STORAGE/qvac/` (+ `vision/`); origin allowlist | | `agent` | create, prompt (`images`, `desktopVision`), cancel, … | Same QVAC enable gate; host workspace under `$BRIDGE_SWARM_STORAGE/agent/` unless `hostWorkspace: false` | +| `discord` | surface, construct, call, listen, destroy | Off until Settings → Enable Discord; tab-scoped Clients; tokens never logged | -Page helpers: `BridgeSwarm.media.*`, `BridgeSwarm.fs.*`, `BridgeSwarm.sqlite.*`, `BridgeSwarm.net.fetch`, `BridgeSwarm.qvac.*`, `BridgeSwarm.agent.*`. +Page helpers: `BridgeSwarm.media.*`, `BridgeSwarm.fs.*`, `BridgeSwarm.sqlite.*`, `BridgeSwarm.net.fetch`, `BridgeSwarm.qvac.*`, `BridgeSwarm.agent.*`, `BridgeSwarm.DiscordJS`. Dashboard Capabilities shows **Vision** (projector loaded) and **Desktop** (`live` / `fps` / stale). diff --git a/examples/README.md b/examples/README.md index c59ef8a..633d25f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -45,6 +45,7 @@ Shared chrome lives in [`shared/`](shared/) (`theme.css`, `chrome.css`, `boot.js | HRPC Demo | http://127.0.0.1:4173/hrpc-demo/ | | QVAC Chat | http://127.0.0.1:4173/qvac-chat/ | | Agent Studio | http://127.0.0.1:4173/agent-studio/ | +| Discord Bot | http://127.0.0.1:4173/discord-bot/ | ## Connect @@ -103,10 +104,14 @@ Typed RPC ping/streams. See [../docs/HRPC.md](../docs/HRPC.md). ### Agent Studio (`agent-studio/`) Sandboxed grok-class agent (`BridgeSwarm.agent`). Enable QVAC in Settings first. Attach or paste images; `session.prompt(text, { images })`. See [../docs/AGENT.md](../docs/AGENT.md). +### Discord Bot (`discord-bot/`) +`BridgeSwarm.DiscordJS` ping-pong bot. Enable Discord in Settings first. See [../docs/DISCORD.md](../docs/DISCORD.md). + ## Troubleshooting 1. Wait for content-script injection, then retry. 2. Confirm the BridgeSwarm extension and native host are installed. 3. Confirm you are on `http://127.0.0.1:4173/…`, not `file://`. 4. For QVAC / Agent Studio: Control Center → Settings → **Enable QVAC**. -5. For media demos on macOS after a host update: `npm run repair:macos`, then fully quit the browser. +5. For Discord Bot: Control Center → Settings → **Enable Discord**. +6. For media demos on macOS after a host update: `npm run repair:macos`, then fully quit the browser. diff --git a/examples/discord-bot/README.md b/examples/discord-bot/README.md new file mode 100644 index 0000000..32edc95 --- /dev/null +++ b/examples/discord-bot/README.md @@ -0,0 +1,40 @@ +# Discord Bot — BridgeSwarm.DiscordJS + +## What this example does + +Runs an official **discord.js 14** bot from the page. The real `Client` lives in the Bare native host (`bare-discord-js`); the page uses `BridgeSwarm.DiscordJS` (`Client`, `GatewayIntentBits`, `Events`, `SlashCommandBuilder`, `REST`, `Routes`). + +`/ping` replies `pong`. Channel text `ping` → `pong` only if you enable Message Content Intent in the Developer Portal and check the box on this page. + +## Prerequisites + +1. Install the [BridgeSwarm extension and native host](../../README.md#easy-install-recommended). +2. Control Center → Settings → **Enable Discord**. +3. Create a Discord application → Bot → Reset Token. Copy the **bot token** (not the OAuth2 client secret). + +## How to run + +**Recommended:** Extension Settings → enable **Examples server**, then open the demo URL below. + +Dev alternative from the repo root: + +```bash +npm run examples +``` + +Open **http://127.0.0.1:4173/discord-bot/**. Do not use `file://`. + +## Usage + +1. Paste the bot token (kept in this tab only). +2. Optionally paste a guild id so `/ping` registers immediately. +3. Click **Login**. Watch the log for `Logged in as …`. +4. In Discord, run `/ping`. Click **Destroy** when finished. + +See [docs/DISCORD.md](../../docs/DISCORD.md) for the API, the cache fidelity gap, and packing notes. + +## Files in this directory + +- **index.html** — Token / guild / login UI +- **app.js** — `new Client`, slash registration, ping/pong +- **style.css** — Layout diff --git a/examples/discord-bot/app.js b/examples/discord-bot/app.js new file mode 100644 index 0000000..6f4579b --- /dev/null +++ b/examples/discord-bot/app.js @@ -0,0 +1,183 @@ +(function () { + const statusEl = document.getElementById('status'); + const logEl = document.getElementById('log'); + const tokenEl = document.getElementById('token'); + const guildEl = document.getElementById('guild'); + const loginBtn = document.getElementById('login'); + const logoutBtn = document.getElementById('logout'); + const msgContentEl = document.getElementById('msgContent'); + + let client = null; + + function banner(kind, text) { + statusEl.className = 'bs-banner bs-banner--' + kind; + statusEl.textContent = text; + } + + function log(line) { + const p = document.createElement('div'); + p.textContent = line; + logEl.appendChild(p); + logEl.scrollTop = logEl.scrollHeight; + } + + function setBusy(on) { + loginBtn.disabled = on; + logoutBtn.disabled = on && !client; + } + + function waitForDiscordJS(timeoutMs) { + timeoutMs = timeoutMs || 8000; + const started = Date.now(); + return new Promise(function (resolve, reject) { + function tick() { + const dj = window.BridgeSwarm && window.BridgeSwarm.DiscordJS; + if (dj && typeof dj.status === 'function') { + resolve(dj); + return; + } + if (Date.now() - started >= timeoutMs) { + reject( + new Error( + 'BridgeSwarm.DiscordJS is missing. In chrome://extensions reload BridgeSwarm, then refresh this page.' + ) + ); + return; + } + setTimeout(tick, 50); + } + tick(); + }); + } + + async function boot() { + await BridgeSwarmExamples.waitForBridgeSwarm(); + let dj; + try { + dj = await waitForDiscordJS(); + } catch (err) { + banner('err', err.message || String(err)); + return; + } + let st = {}; + try { + st = await dj.status(); + } catch (err) { + banner('err', err.message || String(err)); + return; + } + if (!st.loaded) { + banner('warn', 'discord.js did not load on the host' + (st.loadError ? ': ' + st.loadError : '.')); + return; + } + if (!st.enabled) { + banner('warn', 'Discord is off. BridgeSwarm Settings → Enable Discord, then refresh.'); + return; + } + banner('ok', 'Discord pack ready. Paste a bot token and login.'); + try { + await dj.ready(); + } catch (_) {} + } + + async function onLogin() { + const token = (tokenEl.value || '').trim(); + if (!token) { + banner('warn', 'Paste a bot token first.'); + return; + } + setBusy(true); + try { + const dj = await waitForDiscordJS(); + await dj.ready(); + const { Client, GatewayIntentBits, Events, SlashCommandBuilder, REST, Routes } = dj; + const intents = [GatewayIntentBits.Guilds]; + if (GatewayIntentBits.DirectMessages) intents.push(GatewayIntentBits.DirectMessages); + if (GatewayIntentBits.GuildMessages) intents.push(GatewayIntentBits.GuildMessages); + if (msgContentEl.checked && GatewayIntentBits.MessageContent) { + intents.push(GatewayIntentBits.MessageContent); + } + if (client) { + try { + await client.destroy(); + } catch (_) {} + client = null; + } + client = new Client({ intents: intents }); + client.once(Events.ClientReady || Events.Ready || 'clientReady', async function (readyClient) { + const tag = (readyClient && readyClient.user && readyClient.user.tag) || (client.user && client.user.tag) || 'bot'; + log('Logged in as ' + tag); + banner('ok', 'Logged in as ' + tag); + const ping = new SlashCommandBuilder().setName('ping').setDescription('Replies with pong.').toJSON(); + try { + const rest = new REST().setToken(token); + const appId = readyClient.user && readyClient.user.id; + const guildId = (guildEl.value || '').trim(); + if (guildId) { + await rest.put(Routes.applicationGuildCommands(appId, guildId), { body: [ping] }); + log('Registered /ping for guild ' + guildId); + } else { + await rest.put(Routes.applicationCommands(appId), { body: [ping] }); + log('Registered global /ping (may take up to an hour). Set a guild id for instant updates.'); + } + } catch (err) { + log('slash register failed: ' + (err && err.message ? err.message : err)); + } + }); + client.on(Events.InteractionCreate || 'interactionCreate', async function (ix) { + if (!ix || typeof ix.isChatInputCommand !== 'function' || !ix.isChatInputCommand()) return; + if (ix.commandName !== 'ping') return; + try { + await ix.reply({ content: 'pong' }); + log('/ping → pong'); + } catch (err) { + log('reply failed: ' + (err && err.message ? err.message : err)); + } + }); + if (Events.MessageCreate) { + client.on(Events.MessageCreate, async function (message) { + if (!message || (message.author && message.author.bot)) return; + if (String(message.content || '').trim().toLowerCase() !== 'ping') return; + try { + await message.reply('pong'); + log('message ping → pong'); + } catch (err) { + log('message reply failed: ' + (err && err.message ? err.message : err)); + } + }); + } + client.on('error', function (err) { + log('client error: ' + (err && err.message ? err.message : err)); + }); + log('Logging in…'); + await client.login(token); + } catch (err) { + banner('err', err.message || String(err)); + log(err.message || String(err)); + } finally { + setBusy(false); + } + } + + async function onLogout() { + if (!client) return; + setBusy(true); + try { + await client.destroy(); + client = null; + banner('ok', 'Client destroyed.'); + log('Destroyed client.'); + } catch (err) { + log(err.message || String(err)); + } finally { + setBusy(false); + } + } + + loginBtn.addEventListener('click', onLogin); + logoutBtn.addEventListener('click', onLogout); + + BridgeSwarmExamples.waitForBridgeSwarm().then(boot).catch(function (err) { + banner('err', err.message || String(err)); + }); +})(); diff --git a/examples/discord-bot/index.html b/examples/discord-bot/index.html new file mode 100644 index 0000000..6d2ecc4 --- /dev/null +++ b/examples/discord-bot/index.html @@ -0,0 +1,45 @@ + + + + + + BridgeSwarm – Discord Bot + + + + + + + +
+ ← Examples +

BridgeSwarm

+

Discord Bot

+

+ Full discord.js 14 on the Bare host, as BridgeSwarm.DiscordJS. + Enable Discord in extension Settings, paste a bot token (not the OAuth2 client secret), + then login. The token stays in this tab and is never written to disk by the demo. +

+ +
Checking Discord pack…
+ +
+

Bot

+

Developer Portal → Bot → Reset Token. Privileged Message Content intent is optional (channel ping → pong).

+
+ + + + +
+ +
+ +
+

Log

+
+
+
+ + + diff --git a/examples/discord-bot/style.css b/examples/discord-bot/style.css new file mode 100644 index 0000000..9b1c5af --- /dev/null +++ b/examples/discord-bot/style.css @@ -0,0 +1,8 @@ +.bs-page code { + font-family: var(--font-mono); + font-size: 0.85em; + color: var(--accent-2); +} +#token, #guild { + min-width: 16rem; +} diff --git a/examples/index.html b/examples/index.html index f7416a5..d2e55e6 100644 --- a/examples/index.html +++ b/examples/index.html @@ -104,6 +104,11 @@

Coding agent on QVAC — live reasoning, tools, and in-thread permissions.

/agent-studio/ + +

Discord Bot

+

Official discord.js on the Bare host via BridgeSwarm.DiscordJS.

+ /discord-bot/ +
Platform
diff --git a/extension/background.js b/extension/background.js index ea65db6..93e1066 100644 --- a/extension/background.js +++ b/extension/background.js @@ -44,6 +44,7 @@ let notifyOnDisconnect = false; let debugMode = false; let examplesServerEnabled = false; let qvacEnabled = false; +let discordEnabled = false; let examplesServerPort = 4173; let defaultFirewall = { mode: 'off', keys: [] }; let cachedSettings = {}; @@ -245,6 +246,18 @@ function applySettingsFromStorage(s) { } else { qvacEnabled = wantQvac; } + + const wantDiscord = s.discordEnabled === true; + if (wantDiscord !== discordEnabled) { + discordEnabled = wantDiscord; + log('info', wantDiscord ? 'Discord enabled' : 'Discord disabled'); + syncDiscordEnabled().catch((err) => log('error', 'Discord enable sync failed:', err.message || err)); + } else if (port) { + discordEnabled = wantDiscord; + syncDiscordEnabled().catch(() => {}); + } else { + discordEnabled = wantDiscord; + } } function loadSettings() { @@ -345,6 +358,28 @@ async function syncQvacEnabled() { } } +async function syncDiscordEnabled() { + if (!port) return; + try { + const res = await send( + { + type: 'capability', + payload: { + pack: 'discord', + cmd: 'setEnabled', + payload: { enabled: discordEnabled === true }, + }, + }, + { quiet: true } + ); + if (res && res.ok === false) { + log('warn', 'Discord setEnabled:', res.error || 'failed'); + } + } catch (err) { + log('warn', 'Discord setEnabled failed:', err.message || err); + } +} + async function refreshHostSnapshot(force) { if (!port) { hostSnapshot = null; @@ -428,6 +463,10 @@ function gateCapabilityPayload(msg, sender) { log('warn', 'Blocked QVAC/agent (disabled)', msg.type); return { ok: false, error: 'QVAC is disabled. Enable it in BridgeSwarm Settings.' }; } + if (allow.isDiscordDisabled(msg, cachedSettings)) { + log('warn', 'Blocked Discord (disabled)', msg.type); + return { ok: false, error: 'Discord is disabled. Enable it in BridgeSwarm Settings.' }; + } const inner = msg.payload && typeof msg.payload === 'object' ? msg.payload : {}; const always = allow.isAlwaysApproveOrigin(origin, cachedSettings); if (msg.type === 'capability') { @@ -492,6 +531,7 @@ function connect() { syncExamplesServer().catch(() => {}); } syncQvacEnabled().catch(() => {}); + syncDiscordEnabled().catch(() => {}); syncAgentGrants().catch(() => {}); refreshHostSnapshot(true).catch(() => {}); diff --git a/extension/content.js b/extension/content.js index d387b7d..4748b91 100644 --- a/extension/content.js +++ b/extension/content.js @@ -85,7 +85,7 @@ function injectApi() { // Must inject defaults.js into the *page* world — content-script window is isolated. inject( 'defaults.js', - () => inject('api.js', () => inject('framed-stream.js', () => inject('protomux-bundle.js'))), + () => inject('api.js', () => inject('discordjs-builders.js', () => inject('discordjs.js', () => inject('framed-stream.js', () => inject('protomux-bundle.js'))))), { defaults: JSON.stringify(defaults), debug: debug ? '1' : '0' } ); }); diff --git a/extension/dashboard.html b/extension/dashboard.html index ef28d69..488fb17 100644 --- a/extension/dashboard.html +++ b/extension/dashboard.html @@ -194,6 +194,14 @@ +
+

Discord

+
+
+
+
Waiting for host…
+
+

Agent

diff --git a/extension/dashboard.js b/extension/dashboard.js index b369217..3e75ea5 100644 --- a/extension/dashboard.js +++ b/extension/dashboard.js @@ -15,6 +15,7 @@ const DEFAULT_SETTINGS = { debug: false, examplesServerEnabled: false, qvacEnabled: false, + discordEnabled: false, examplesServerPort: 4173, defaultFirewallMode: 'off', defaultFirewallKeys: [], @@ -295,6 +296,23 @@ function renderCapabilitiesPage(state) { ${q.error ? `
Error${escapeHtml(q.error)}
` : ''}
`; } + const d = h.discord || {}; + const dEl = $('capDiscordBody'); + const dToggle = $('capDiscordToggle'); + const dOn = settings.discordEnabled === true; + if (dToggle) { + dToggle.classList.toggle('active', dOn); + dToggle.setAttribute('aria-checked', dOn ? 'true' : 'false'); + } + if (dEl) { + dEl.innerHTML = ` +
+
Enabled${dOn ? 'yes' : 'no (Settings)'}
+
Loaded${d.loaded ? 'yes' : 'no'}
+
Clients${d.clients ?? 0} / ${d.maxClients ?? 4}
+ ${d.loadError ? `
Error${escapeHtml(d.loadError)}
` : ''} +
`; + } const a = h.agent || {}; const aEl = $('capAgentBody'); if (aEl) { @@ -644,6 +662,13 @@ function renderSettingsForm() {
+
+

Discord

+
+
Enable Discord
Run discord.js bots from the page via the Bare host (BridgeSwarm.DiscordJS). Off until you turn this on. Bot tokens are credentials.
+
+
+

Examples server

@@ -687,6 +712,7 @@ function renderSettingsForm() { $('toggleDisableFileUrls')?.classList.toggle('active', s.disableOnFileUrls === true); $('toggleExamplesServer')?.classList.toggle('active', s.examplesServerEnabled === true); $('toggleQvacEnabled')?.classList.toggle('active', s.qvacEnabled === true); + $('toggleDiscordEnabled')?.classList.toggle('active', s.discordEnabled === true); const fw = $('defaultFirewallMode'); if (fw) fw.value = s.defaultFirewallMode || 'off'; const ver = chrome.runtime.getManifest?.().version; @@ -799,6 +825,7 @@ function collectSettings() { debug: $('toggleDebug')?.classList.contains('active') || false, examplesServerEnabled: $('toggleExamplesServer')?.classList.contains('active') || false, qvacEnabled: $('toggleQvacEnabled')?.classList.contains('active') || false, + discordEnabled: $('toggleDiscordEnabled')?.classList.contains('active') || false, examplesServerPort: port, defaultFirewallMode: $('defaultFirewallMode')?.value || 'off', defaultFirewallKeys: keysRaw, @@ -826,6 +853,8 @@ function saveSettings(quiet) { $('overviewExamplesToggle')?.classList.toggle('active', settings.examplesServerEnabled); $('capQvacToggle')?.classList.toggle('active', settings.qvacEnabled === true); $('capQvacToggle')?.setAttribute('aria-checked', settings.qvacEnabled === true ? 'true' : 'false'); + $('capDiscordToggle')?.classList.toggle('active', settings.discordEnabled === true); + $('capDiscordToggle')?.setAttribute('aria-checked', settings.discordEnabled === true ? 'true' : 'false'); }); } @@ -1048,6 +1077,31 @@ function setupEvents() { } }); + function persistDiscordEnabled(enabled) { + const st = $('toggleDiscordEnabled'); + if (st) st.classList.toggle('active', enabled); + const cap = $('capDiscordToggle'); + if (cap) { + cap.classList.toggle('active', enabled); + cap.setAttribute('aria-checked', enabled ? 'true' : 'false'); + } + settings.discordEnabled = enabled; + chrome.storage.local.set({ [SETTINGS_KEY]: { ...settings, discordEnabled: enabled } }, () => { + if (currentState) renderCapabilitiesPage(currentState); + showToast(enabled ? 'Discord enabled' : 'Discord disabled', 'ok'); + }); + } + + $('capDiscordToggle')?.addEventListener('click', () => { + persistDiscordEnabled(settings.discordEnabled !== true); + }); + $('capDiscordToggle')?.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + e.currentTarget.click(); + } + }); + function persistQvacEnabled(enabled) { const st = $('toggleQvacEnabled'); if (st) st.classList.toggle('active', enabled); diff --git a/extension/discordjs-builders.js b/extension/discordjs-builders.js new file mode 100644 index 0000000..9db878e --- /dev/null +++ b/extension/discordjs-builders.js @@ -0,0 +1,304 @@ +/** + * Page-local discord.js builders (sync, no RPC). Subset used by typical bots. + */ +(function (root) { + function toJSON() { + return this.data ? JSON.parse(JSON.stringify(this.data)) : {}; + } + + function option(type, name, description, required) { + return { + type: type, + name: name, + description: description || name, + required: !!required, + }; + } + + function SlashCommandBuilder() { + this.data = { name: '', description: '', options: [], type: 1 }; + } + SlashCommandBuilder.prototype.setName = function (name) { + this.data.name = String(name); + return this; + }; + SlashCommandBuilder.prototype.setDescription = function (desc) { + this.data.description = String(desc); + return this; + }; + SlashCommandBuilder.prototype.setNameLocalizations = function (loc) { + this.data.name_localizations = loc; + return this; + }; + SlashCommandBuilder.prototype.setDescriptionLocalizations = function (loc) { + this.data.description_localizations = loc; + return this; + }; + SlashCommandBuilder.prototype.setDefaultMemberPermissions = function (perm) { + this.data.default_member_permissions = perm == null ? null : String(perm); + return this; + }; + SlashCommandBuilder.prototype.setDMPermission = function (v) { + this.data.dm_permission = !!v; + return this; + }; + SlashCommandBuilder.prototype.addStringOption = function (input) { + const o = typeof input === 'function' ? input(new SlashOpt(3)) : input; + this.data.options.push(o && o.toJSON ? o.toJSON() : o); + return this; + }; + SlashCommandBuilder.prototype.addIntegerOption = function (input) { + const o = typeof input === 'function' ? input(new SlashOpt(4)) : input; + this.data.options.push(o && o.toJSON ? o.toJSON() : o); + return this; + }; + SlashCommandBuilder.prototype.addBooleanOption = function (input) { + const o = typeof input === 'function' ? input(new SlashOpt(5)) : input; + this.data.options.push(o && o.toJSON ? o.toJSON() : o); + return this; + }; + SlashCommandBuilder.prototype.addUserOption = function (input) { + const o = typeof input === 'function' ? input(new SlashOpt(6)) : input; + this.data.options.push(o && o.toJSON ? o.toJSON() : o); + return this; + }; + SlashCommandBuilder.prototype.addChannelOption = function (input) { + const o = typeof input === 'function' ? input(new SlashOpt(7)) : input; + this.data.options.push(o && o.toJSON ? o.toJSON() : o); + return this; + }; + SlashCommandBuilder.prototype.addRoleOption = function (input) { + const o = typeof input === 'function' ? input(new SlashOpt(8)) : input; + this.data.options.push(o && o.toJSON ? o.toJSON() : o); + return this; + }; + SlashCommandBuilder.prototype.addNumberOption = function (input) { + const o = typeof input === 'function' ? input(new SlashOpt(10)) : input; + this.data.options.push(o && o.toJSON ? o.toJSON() : o); + return this; + }; + SlashCommandBuilder.prototype.addAttachmentOption = function (input) { + const o = typeof input === 'function' ? input(new SlashOpt(11)) : input; + this.data.options.push(o && o.toJSON ? o.toJSON() : o); + return this; + }; + SlashCommandBuilder.prototype.toJSON = toJSON; + + function SlashOpt(type) { + this.data = { type: type, name: '', description: '', required: false }; + } + SlashOpt.prototype.setName = function (n) { + this.data.name = String(n); + return this; + }; + SlashOpt.prototype.setDescription = function (d) { + this.data.description = String(d); + return this; + }; + SlashOpt.prototype.setRequired = function (v) { + this.data.required = !!v; + return this; + }; + SlashOpt.prototype.addChoices = function () { + const list = []; + for (let i = 0; i < arguments.length; i++) list.push(arguments[i]); + this.data.choices = list; + return this; + }; + SlashOpt.prototype.toJSON = toJSON; + + function EmbedBuilder() { + this.data = { type: 'rich' }; + } + EmbedBuilder.prototype.setTitle = function (t) { + this.data.title = String(t); + return this; + }; + EmbedBuilder.prototype.setDescription = function (d) { + this.data.description = String(d); + return this; + }; + EmbedBuilder.prototype.setColor = function (c) { + this.data.color = typeof c === 'number' ? c : parseInt(String(c).replace('#', ''), 16); + return this; + }; + EmbedBuilder.prototype.setURL = function (u) { + this.data.url = String(u); + return this; + }; + EmbedBuilder.prototype.setTimestamp = function (d) { + this.data.timestamp = (d ? new Date(d) : new Date()).toISOString(); + return this; + }; + EmbedBuilder.prototype.setFooter = function (f) { + this.data.footer = typeof f === 'string' ? { text: f } : f; + return this; + }; + EmbedBuilder.prototype.setAuthor = function (a) { + this.data.author = typeof a === 'string' ? { name: a } : a; + return this; + }; + EmbedBuilder.prototype.setThumbnail = function (u) { + this.data.thumbnail = typeof u === 'string' ? { url: u } : u; + return this; + }; + EmbedBuilder.prototype.setImage = function (u) { + this.data.image = typeof u === 'string' ? { url: u } : u; + return this; + }; + EmbedBuilder.prototype.addFields = function () { + if (!this.data.fields) this.data.fields = []; + for (let i = 0; i < arguments.length; i++) { + const f = arguments[i]; + if (Array.isArray(f)) this.data.fields.push.apply(this.data.fields, f); + else this.data.fields.push(f); + } + return this; + }; + EmbedBuilder.prototype.toJSON = toJSON; + + function ActionRowBuilder() { + this.data = { type: 1, components: [] }; + } + ActionRowBuilder.prototype.addComponents = function () { + for (let i = 0; i < arguments.length; i++) { + const c = arguments[i]; + if (Array.isArray(c)) { + for (let j = 0; j < c.length; j++) { + this.data.components.push(c[j] && c[j].toJSON ? c[j].toJSON() : c[j]); + } + } else { + this.data.components.push(c && c.toJSON ? c.toJSON() : c); + } + } + return this; + }; + ActionRowBuilder.prototype.toJSON = toJSON; + + const ButtonStyle = { Primary: 1, Secondary: 2, Success: 3, Danger: 4, Link: 5 }; + + function ButtonBuilder() { + this.data = { type: 2, style: 1 }; + } + ButtonBuilder.prototype.setCustomId = function (id) { + this.data.custom_id = String(id); + return this; + }; + ButtonBuilder.prototype.setLabel = function (l) { + this.data.label = String(l); + return this; + }; + ButtonBuilder.prototype.setStyle = function (s) { + this.data.style = typeof s === 'number' ? s : ButtonStyle[s] || 1; + return this; + }; + ButtonBuilder.prototype.setDisabled = function (v) { + this.data.disabled = !!v; + return this; + }; + ButtonBuilder.prototype.setURL = function (u) { + this.data.url = String(u); + this.data.style = 5; + return this; + }; + ButtonBuilder.prototype.setEmoji = function (e) { + this.data.emoji = typeof e === 'string' ? { name: e } : e; + return this; + }; + ButtonBuilder.prototype.toJSON = toJSON; + + function StringSelectMenuBuilder() { + this.data = { type: 3, custom_id: '', options: [] }; + } + StringSelectMenuBuilder.prototype.setCustomId = function (id) { + this.data.custom_id = String(id); + return this; + }; + StringSelectMenuBuilder.prototype.setPlaceholder = function (p) { + this.data.placeholder = String(p); + return this; + }; + StringSelectMenuBuilder.prototype.addOptions = function () { + for (let i = 0; i < arguments.length; i++) { + const o = arguments[i]; + if (Array.isArray(o)) this.data.options.push.apply(this.data.options, o); + else this.data.options.push(o && o.toJSON ? o.toJSON() : o); + } + return this; + }; + StringSelectMenuBuilder.prototype.toJSON = toJSON; + + function AttachmentBuilder(data, extra) { + this.attachment = data; + this.name = (extra && extra.name) || 'file'; + this.description = extra && extra.description; + } + AttachmentBuilder.prototype.setName = function (n) { + this.name = String(n); + return this; + }; + AttachmentBuilder.prototype.toJSON = function () { + return { attachment: this.attachment, name: this.name, description: this.description }; + }; + + const Routes = { + applicationCommands: function (id) { + return '/applications/' + id + '/commands'; + }, + applicationCommand: function (id, cid) { + return '/applications/' + id + '/commands/' + cid; + }, + applicationGuildCommands: function (id, gid) { + return '/applications/' + id + '/guilds/' + gid + '/commands'; + }, + applicationGuildCommand: function (id, gid, cid) { + return '/applications/' + id + '/guilds/' + gid + '/commands/' + cid; + }, + channel: function (id) { + return '/channels/' + id; + }, + channelMessages: function (id) { + return '/channels/' + id + '/messages'; + }, + channelMessage: function (id, mid) { + return '/channels/' + id + '/messages/' + mid; + }, + gatewayBot: function () { + return '/gateway/bot'; + }, + user: function (id) { + return '/users/' + (id || '@me'); + }, + oauth2CurrentApplication: function () { + return '/oauth2/applications/@me'; + }, + }; + + const MessageFlags = { Ephemeral: 64, SuppressEmbeds: 4, SuppressNotifications: 4096 }; + const ChannelType = { + GuildText: 0, + DM: 1, + GuildVoice: 2, + GroupDM: 3, + GuildCategory: 4, + GuildAnnouncement: 5, + AnnouncementThread: 10, + PublicThread: 11, + PrivateThread: 12, + GuildStageVoice: 13, + GuildForum: 15, + }; + + root.BridgeSwarmDiscordBuilders = { + SlashCommandBuilder: SlashCommandBuilder, + EmbedBuilder: EmbedBuilder, + ActionRowBuilder: ActionRowBuilder, + ButtonBuilder: ButtonBuilder, + StringSelectMenuBuilder: StringSelectMenuBuilder, + AttachmentBuilder: AttachmentBuilder, + ButtonStyle: ButtonStyle, + Routes: Routes, + MessageFlags: MessageFlags, + ChannelType: ChannelType, + }; +})(typeof window !== 'undefined' ? window : globalThis); diff --git a/extension/discordjs.js b/extension/discordjs.js new file mode 100644 index 0000000..8703f02 --- /dev/null +++ b/extension/discordjs.js @@ -0,0 +1,371 @@ +/** + * BridgeSwarm.DiscordJS — page-side discord.js 14 surface. + * Real Client/REST live on the Bare host; this file proxies them over native messaging. + */ +(function () { + function cap(cmd, payload, options) { + const bs = window.BridgeSwarm; + if (!bs || !bs.capabilities || typeof bs.capabilities.call !== 'function') { + return Promise.reject( + new Error('BridgeSwarm.capabilities is missing. Reload the extension, then refresh this page.') + ); + } + return bs.capabilities.call('discord', cmd, payload || {}, options || {}); + } + + function EventEmitter() { + this._listeners = {}; + } + EventEmitter.prototype.on = function (ev, fn) { + if (!this._listeners[ev]) this._listeners[ev] = []; + this._listeners[ev].push(fn); + return this; + }; + EventEmitter.prototype.once = function (ev, fn) { + const self = this; + function wrap() { + self.off(ev, wrap); + return fn.apply(this, arguments); + } + wrap._orig = fn; + return this.on(ev, wrap); + }; + EventEmitter.prototype.off = function (ev, fn) { + if (!this._listeners[ev]) return this; + this._listeners[ev] = this._listeners[ev].filter(function (x) { + return x !== fn && x._orig !== fn; + }); + return this; + }; + EventEmitter.prototype.emit = function (ev) { + const args = Array.prototype.slice.call(arguments, 1); + const list = (this._listeners[ev] || []).slice(); + const all = (this._listeners['*'] || []).slice(); + for (let i = 0; i < list.length; i++) { + try { + list[i].apply(this, args); + } catch (_) {} + } + for (let i = 0; i < all.length; i++) { + try { + all[i].apply(this, [ev].concat(args)); + } catch (_) {} + } + return this; + }; + + const STUB_METHODS = [ + 'reply', + 'followUp', + 'editReply', + 'deferReply', + 'deferUpdate', + 'deleteReply', + 'fetchReply', + 'showModal', + 'update', + 'react', + 'delete', + 'edit', + 'send', + 'startThread', + 'pin', + 'unpin', + 'fetch', + 'put', + 'post', + 'get', + 'patch', + ]; + + function encodeArg(arg) { + if (arg == null) return arg; + if (typeof arg !== 'object') return arg; + if (arg._handle) return { _handle: arg._handle }; + if (typeof arg.toJSON === 'function') return arg.toJSON(); + if (Array.isArray(arg)) return arg.map(encodeArg); + return arg; + } + + function hydrate(value) { + if (!value || typeof value !== 'object') return value; + if (Array.isArray(value)) return value.map(hydrate); + if (value._handle) return makeStub(value); + return value; + } + + function makeStub(snap) { + const obj = Object.assign({}, snap); + const methods = snap._methods && snap._methods.length ? snap._methods : STUB_METHODS; + for (let i = 0; i < methods.length; i++) { + (function (name) { + if (typeof obj[name] === 'function') return; + obj[name] = function () { + const args = Array.prototype.slice.call(arguments).map(encodeArg); + return cap('call', { handle: snap._handle, path: name, args: args }).then(function (r) { + return hydrate(r && r.value); + }); + }; + })(methods[i]); + } + const preds = [ + 'isChatInputCommand', + 'isButton', + 'isRepliable', + 'isStringSelectMenu', + 'isAnySelectMenu', + 'isModalSubmit', + 'isAutocomplete', + 'isContextMenuCommand', + 'isMessageComponent', + 'isCommand', + ]; + for (let i = 0; i < preds.length; i++) { + (function (name) { + const flag = snap[name]; + obj[name] = function () { + return flag === true || flag === false ? flag : name === 'isRepliable'; + }; + })(preds[i]); + } + if (obj.channel && obj.channel._handle && !obj.channel.send) { + obj.channel = makeStub(obj.channel); + } + return obj; + } + + const clients = []; + + function Client(options) { + EventEmitter.call(this); + const self = this; + this.options = options || {}; + this.user = null; + this.readyAt = null; + this._handle = null; + this._destroyed = false; + this._ready = cap('construct', { className: 'Client', args: [this.options] }).then(function (r) { + if (!r || !r.handle) throw new Error((r && r.error) || 'Client construct failed'); + self._handle = r.handle; + clients.push(self); + return cap('listen', { handle: r.handle }).then(function () { + return r; + }); + }); + } + Client.prototype = Object.create(EventEmitter.prototype); + Client.prototype.login = function (token) { + const self = this; + return this._ready.then(function () { + return cap( + 'call', + { handle: self._handle, path: 'login', args: [token] }, + { timeoutMs: 0 } + ); + }).then(function (r) { + return r && r.value != null ? r.value : r; + }); + }; + Client.prototype.destroy = function () { + const self = this; + this._destroyed = true; + const i = clients.indexOf(this); + if (i >= 0) clients.splice(i, 1); + if (!this._handle) { + return this._ready.then(function () { + return cap('destroy', { handle: self._handle }); + }).catch(function () {}); + } + return cap('destroy', { handle: this._handle }).catch(function () {}); + }; + Client.prototype.invoke = function (path, args) { + const self = this; + return this._ready.then(function () { + return cap('call', { + handle: self._handle, + path: path, + args: (args || []).map(encodeArg), + }).then(function (r) { + return hydrate(r && r.value); + }); + }); + }; + + function REST(options) { + this._options = options || {}; + this._handle = null; + this._ready = cap('construct', { className: 'REST', args: [this._options] }).then(function (r) { + if (!r || !r.handle) throw new Error((r && r.error) || 'REST construct failed'); + return r; + }); + const self = this; + this._ready.then(function (r) { + self._handle = r.handle; + }); + } + REST.prototype.setToken = function (token) { + const self = this; + this._ready = this._ready.then(function (r) { + return cap('call', { handle: r.handle, path: 'setToken', args: [token] }).then(function () { + return r; + }); + }); + return this; + }; + function restVerb(name) { + REST.prototype[name] = function (route, opts) { + const self = this; + return this._ready.then(function (r) { + return cap( + 'call', + { handle: r.handle, path: name, args: [route, opts] }, + { timeoutMs: 0 } + ).then(function (res) { + return hydrate(res && res.value); + }); + }); + }; + } + restVerb('put'); + restVerb('post'); + restVerb('get'); + restVerb('patch'); + REST.prototype.delete = function (route, opts) { + const self = this; + return this._ready.then(function (r) { + return cap('call', { handle: r.handle, path: 'delete', args: [route, opts] }, { timeoutMs: 0 }).then( + function (res) { + return hydrate(res && res.value); + } + ); + }); + }; + + const DiscordJS = { + Client: Client, + REST: REST, + invoke: function (handle, path, args) { + return cap('call', { handle: handle, path: path, args: (args || []).map(encodeArg) }).then( + function (r) { + return hydrate(r && r.value); + } + ); + }, + status: function () { + return cap('status', {}); + }, + ready: null, + }; + + function applyBuilders() { + const b = window.BridgeSwarmDiscordBuilders || {}; + const keys = [ + 'SlashCommandBuilder', + 'EmbedBuilder', + 'ActionRowBuilder', + 'ButtonBuilder', + 'StringSelectMenuBuilder', + 'AttachmentBuilder', + 'ButtonStyle', + 'Routes', + 'MessageFlags', + 'ChannelType', + ]; + for (let i = 0; i < keys.length; i++) { + if (b[keys[i]] && DiscordJS[keys[i]] == null) DiscordJS[keys[i]] = b[keys[i]]; + } + } + applyBuilders(); + + let surfacePromise = null; + function ensureSurface() { + if (surfacePromise) return surfacePromise; + applyBuilders(); + surfacePromise = cap('surface', {}) + .then(function (r) { + const s = (r && r.surface) || {}; + Object.keys(s).forEach(function (k) { + if (k.charAt(0) === '_') return; + if (DiscordJS[k] == null) DiscordJS[k] = s[k]; + }); + if (s.Events && s.Events.ClientReady && !s.Events.Ready) { + s.Events.Ready = s.Events.ClientReady; + DiscordJS.Events = s.Events; + } + return DiscordJS; + }) + .catch(function (err) { + surfacePromise = null; + throw err; + }); + return surfacePromise; + } + DiscordJS.ready = ensureSurface; + + function onChunk(p) { + if (!p || p.pack !== 'discord' || p.kind !== 'event') return; + for (let i = 0; i < clients.length; i++) { + const c = clients[i]; + if (c._handle !== p.handle) continue; + const args = (p.args || []).map(hydrate); + if (p.name === 'clientReady' || p.name === 'ready') { + const readyClient = args[0]; + if (readyClient && readyClient.user) c.user = readyClient.user; + c.readyAt = new Date(); + } + c.emit.apply(c, [p.name].concat(args)); + break; + } + } + + function attach() { + if (!window.BridgeSwarm) return false; + if (window.BridgeSwarm.DiscordJS && window.BridgeSwarm.DiscordJS.Client === Client) return true; + window.BridgeSwarm.DiscordJS = DiscordJS; + window.BridgeSwarm.discordJS = DiscordJS; + if (window.BridgeSwarm.capabilities && typeof window.BridgeSwarm.capabilities.on === 'function') { + window.BridgeSwarm.capabilities.on('cap-chunk', onChunk); + } + const prevReady = window.BridgeSwarm.ready; + if (typeof prevReady === 'function' && !prevReady._bsDiscordWrapped) { + const wrapped = function (opts) { + return prevReady.call(window.BridgeSwarm, opts).then(function (BS) { + return ensureSurface() + .then(function () { + return BS; + }) + .catch(function () { + return BS; + }); + }); + }; + wrapped._bsDiscordWrapped = true; + window.BridgeSwarm.ready = wrapped; + } else if (typeof prevReady !== 'function') { + ensureSurface().catch(function () {}); + } + window.addEventListener('pagehide', function () { + const list = clients.slice(); + for (let i = 0; i < list.length; i++) { + try { + list[i].destroy(); + } catch (_) {} + } + }); + try { + window.dispatchEvent(new CustomEvent('bridge-swarm-discord-ready')); + } catch (_) {} + return true; + } + + if (!attach()) { + window.addEventListener('bridge-swarm-ready', function () { + attach(); + }); + let n = 0; + const t = setInterval(function () { + n += 1; + if (attach() || n > 80) clearInterval(t); + }, 50); + } +})(); diff --git a/extension/manifest.json b/extension/manifest.json index ec4bdff..a170278 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -37,6 +37,8 @@ { "resources": [ "api.js", + "discordjs-builders.js", + "discordjs.js", "framed-stream.js", "protomux-bundle.js", "defaults.js", diff --git a/extension/manifest_firefox.json b/extension/manifest_firefox.json index 9663868..58b993d 100644 --- a/extension/manifest_firefox.json +++ b/extension/manifest_firefox.json @@ -36,6 +36,8 @@ { "resources": [ "api.js", + "discordjs-builders.js", + "discordjs.js", "framed-stream.js", "protomux-bundle.js", "defaults.js", diff --git a/extension/origin-allowlist.js b/extension/origin-allowlist.js index 6827bc2..cd3953e 100644 --- a/extension/origin-allowlist.js +++ b/extension/origin-allowlist.js @@ -14,7 +14,7 @@ })(typeof globalThis !== 'undefined' ? globalThis : this, function () { const DEFAULT_EXAMPLES_PORT = 4173; const CAP_PACK_PREFIX = - /^(media|fs|sqlite|net|qvac|agent)\.[a-zA-Z0-9_-]+$/; + /^(media|fs|sqlite|net|qvac|agent|discord)\.[a-zA-Z0-9_-]+$/; function defaultOrigins(examplesPort) { const port = Number(examplesPort) > 0 ? Number(examplesPort) : DEFAULT_EXAMPLES_PORT; @@ -110,13 +110,14 @@ const QVAC_META_CMDS = { detect: 1, status: 1, catalog: 1, openaiStatus: 1 }; const AGENT_META_CMDS = { status: 1, setGrants: 1, list: 1 }; + const DISCORD_META_CMDS = { surface: 1, status: 1 }; function qvacPackAndCmd(msg) { if (!msg || !msg.type) return null; if (msg.type === 'capability' && msg.payload) { return { pack: msg.payload.pack, cmd: msg.payload.cmd }; } - const m = typeof msg.type === 'string' && msg.type.match(/^(qvac|agent)\.([a-zA-Z0-9_-]+)$/); + const m = typeof msg.type === 'string' && msg.type.match(/^(qvac|agent|discord)\.([a-zA-Z0-9_-]+)$/); if (m) return { pack: m[1], cmd: m[2] }; return null; } @@ -133,6 +134,17 @@ return true; } + /** True when Discord construct/login must be blocked (user has not enabled Discord). + * discord.setEnabled is never allowed from pages. surface/status stay available. */ + function isDiscordDisabled(msg, settings) { + const pc = qvacPackAndCmd(msg); + if (pc && pc.pack === 'discord' && pc.cmd === 'setEnabled') return true; + if (settings && settings.discordEnabled === true) return false; + if (!pc || pc.pack !== 'discord') return false; + if (DISCORD_META_CMDS[pc.cmd]) return false; + return true; + } + return { DEFAULT_EXAMPLES_PORT, defaultOrigins, @@ -146,5 +158,6 @@ isCapabilityType, qvacPackAndCmd, isQvacDisabled, + isDiscordDisabled, }; }); diff --git a/native-host/boot.mjs b/native-host/boot.mjs index 05053cd..0b9c5de 100644 --- a/native-host/boot.mjs +++ b/native-host/boot.mjs @@ -65,6 +65,10 @@ export const DEFAULT_ADDON_PACKAGES = [ '@qvac/llm-llamacpp', '@qvac/embed-llamacpp', 'bare-gpu-info', + // Discord (bare-discord-js / discord.js gateway) + 'bare-tls', + 'bare-crypto', + 'bare-zlib', ]; /** @deprecated Use DEFAULT_ADDON_PACKAGES — media is default. */ diff --git a/native-host/capabilities/discord.js b/native-host/capabilities/discord.js new file mode 100644 index 0000000..a898a21 --- /dev/null +++ b/native-host/capabilities/discord.js @@ -0,0 +1,367 @@ +/** + * Discord capability pack — real discord.js Client on the Bare host. + * Page talks to it as BridgeSwarm.DiscordJS. + */ +'use strict'; + +const { + loadBareDiscordJsSync, + getBareDiscordJsLoadError, + normalizeDiscordToken, +} = require('../discord/load-discord.js'); +const { + intern, + snapshotValue, + snapshotEventArgs, + serializeConstants, + createHandleTable, +} = require('../discord/snapshot.js'); + +const MAX_CLIENTS = 4; +const SKIP_EVENTS = new Set(['raw', 'debug', 'apiRequest', 'apiResponse']); +const CONSTRUCT_ALLOWED = new Set(['Client', 'REST', 'WebhookClient']); + +let enabled = false; +const handles = createHandleTable(); +/** handle -> { names: Set, listener } */ +const listeners = new Map(); +let clientCount = 0; + +function logErr(msg) { + try { + if (process.stderr) process.stderr.write('[bridge-swarm-host] discord: ' + msg + '\n'); + } catch (_) {} +} + +function getDiscord() { + const d = loadBareDiscordJsSync(); + if (!d || typeof d.Client !== 'function') { + const err = getBareDiscordJsLoadError() || 'bare-discord-js unavailable'; + throw new Error(err); + } + return d; +} + +function decodeArg(arg) { + if (arg && typeof arg === 'object' && typeof arg._handle === 'string' && handles.byId.has(arg._handle)) { + return handles.byId.get(arg._handle); + } + if (Array.isArray(arg)) return arg.map(decodeArg); + return arg; +} + +function decodeArgs(args) { + if (!Array.isArray(args)) return []; + return args.map(decodeArg); +} + +function sanitizeConstructArgs(className, args) { + const a = Array.isArray(args) ? args.map((x) => decodeArg(x)) : []; + if (className === 'Client' && a[0] && typeof a[0] === 'object') { + const o = a[0]; + const clean = {}; + if (o.intents != null) clean.intents = o.intents; + if (o.partials != null) clean.partials = o.partials; + if (o.failIfNotExists != null) clean.failIfNotExists = o.failIfNotExists; + if (o.presence && typeof o.presence === 'object') clean.presence = o.presence; + a[0] = clean; + } + if ((className === 'REST' || className === 'WebhookClient') && a[0] && typeof a[0] === 'object') { + const o = a[0]; + const clean = {}; + if (typeof o.version === 'number' || typeof o.version === 'string') clean.version = o.version; + if (typeof o.timeout === 'number') clean.timeout = o.timeout; + if (typeof o.authPrefix === 'string') clean.authPrefix = o.authPrefix; + a[0] = clean; + } + return a; +} + +function getAtPath(obj, path) { + const parts = String(path || '').split('.').filter(Boolean); + let ctx = obj; + let fn = obj; + for (let i = 0; i < parts.length; i++) { + ctx = fn; + if (fn == null) return { ctx: null, fn: undefined }; + fn = fn[parts[i]]; + } + return { ctx, fn }; +} + +function wrapResult(result) { + if (result == null || typeof result !== 'object') return { value: result }; + if (typeof result.then === 'function') { + return Promise.resolve(result).then(wrapResult); + } + return { value: snapshotValue(result, handles, 0) }; +} + +function dropHandle(handle) { + const obj = handles.byId.get(handle); + const rec = listeners.get(handle); + if (rec && obj && typeof obj.off === 'function') { + try { + obj.off(rec.event || '*'); + } catch (_) {} + } + if (rec && rec.unlisten) { + try { + rec.unlisten(); + } catch (_) {} + } + listeners.delete(handle); + if (obj) { + const type = (obj.constructor && obj.constructor.name) || ''; + if (type === 'Client') { + clientCount = Math.max(0, clientCount - 1); + if (typeof obj.destroy === 'function') { + Promise.resolve(obj.destroy()).catch(() => {}); + } + } + } + handles.byId.delete(handle); + handles.meta.delete(handle); +} + +function destroyAll() { + const ids = Array.from(handles.byId.keys()); + for (const id of ids) dropHandle(id); + clientCount = 0; +} + +function refuseDisabled(ctx, allowMeta) { + if (enabled) return false; + if (allowMeta) return false; + ctx.reply({ ok: false, error: 'Discord is disabled. Enable it in BridgeSwarm Settings.' }); + return true; +} + +function createDiscordPack() { + const commands = { + async status(ctx) { + let loaded = false; + let loadError = ''; + try { + const d = loadBareDiscordJsSync(); + loaded = !!(d && typeof d.Client === 'function'); + if (!loaded) loadError = getBareDiscordJsLoadError(); + } catch (err) { + loadError = err && err.message ? err.message : String(err); + } + ctx.reply({ + ok: true, + enabled, + loaded, + loadError: loadError || undefined, + clients: clientCount, + maxClients: MAX_CLIENTS, + }); + }, + + async setEnabled(ctx) { + const on = !!(ctx.payload && ctx.payload.enabled === true); + enabled = on; + if (!on) destroyAll(); + ctx.reply({ ok: true, enabled, clients: clientCount }); + }, + + async surface(ctx) { + try { + const discord = getDiscord(); + ctx.reply({ + ok: true, + enabled, + loaded: true, + surface: serializeConstants(discord), + }); + } catch (err) { + ctx.reply({ + ok: false, + error: err.message, + enabled, + loaded: false, + loadError: getBareDiscordJsLoadError() || err.message, + }); + } + }, + + async construct(ctx) { + if (refuseDisabled(ctx)) return; + try { + const discord = getDiscord(); + const className = String((ctx.payload && ctx.payload.className) || 'Client'); + if (!CONSTRUCT_ALLOWED.has(className)) { + ctx.reply({ ok: false, error: 'class not allowed: ' + className }); + return; + } + const Ctor = discord[className]; + if (typeof Ctor !== 'function') { + ctx.reply({ ok: false, error: className + ' is not available' }); + return; + } + if (className === 'Client' && clientCount >= MAX_CLIENTS) { + ctx.reply({ ok: false, error: 'too many Discord clients (max ' + MAX_CLIENTS + ')' }); + return; + } + const args = sanitizeConstructArgs(className, (ctx.payload && ctx.payload.args) || []); + const inst = new Ctor(...args); + const handle = intern(handles, inst); + if (className === 'Client') clientCount += 1; + handles.meta.get(handle).origin = ctx.payload && ctx.payload._origin; + ctx.reply({ + ok: true, + handle, + _type: className, + _methods: className === 'Client' ? ['login', 'destroy'] : ['setToken', 'put', 'post', 'get', 'patch', 'delete'], + }); + } catch (err) { + ctx.reply({ ok: false, error: err.message }); + } + }, + + async call(ctx) { + if (refuseDisabled(ctx)) return; + try { + const handle = ctx.payload && ctx.payload.handle; + const path = (ctx.payload && ctx.payload.path) || ''; + const obj = handles.byId.get(handle); + if (!obj) { + ctx.reply({ ok: false, error: 'unknown handle' }); + return; + } + let args = decodeArgs((ctx.payload && ctx.payload.args) || []); + if (path === 'login' || path === 'setToken') { + if (typeof args[0] === 'string') args[0] = normalizeDiscordToken(args[0]); + } + const { ctx: recv, fn } = getAtPath(obj, path); + if (typeof fn !== 'function') { + ctx.reply({ ok: true, value: snapshotValue(fn, handles, 0) }); + return; + } + const result = await wrapResult(fn.apply(recv, args)); + ctx.reply(Object.assign({ ok: true }, result)); + } catch (err) { + ctx.reply({ ok: false, error: err.message }); + } + }, + + async get(ctx) { + if (refuseDisabled(ctx)) return; + try { + const handle = ctx.payload && ctx.payload.handle; + const path = (ctx.payload && ctx.payload.path) || ''; + const obj = handles.byId.get(handle); + if (!obj) { + ctx.reply({ ok: false, error: 'unknown handle' }); + return; + } + const { fn } = getAtPath(obj, path); + ctx.reply({ ok: true, value: snapshotValue(fn, handles, 0) }); + } catch (err) { + ctx.reply({ ok: false, error: err.message }); + } + }, + + async listen(ctx) { + if (refuseDisabled(ctx)) return; + const handle = ctx.payload && ctx.payload.handle; + const obj = handles.byId.get(handle); + if (!obj || typeof obj.on !== 'function') { + ctx.reply({ ok: false, error: 'handle is not an EventEmitter' }); + return; + } + if (listeners.has(handle)) { + ctx.reply({ ok: true, handle, listening: true }); + return; + } + const emit = ctx.emit; + const includeDebug = !!(ctx.payload && ctx.payload.debug); + const listener = function (eventName) { + if (!includeDebug && SKIP_EVENTS.has(String(eventName))) return; + const args = Array.prototype.slice.call(arguments, 1); + let payload; + try { + payload = snapshotEventArgs(args, handles); + } catch (err) { + payload = [{ _type: 'Error', message: err.message }]; + } + try { + emit('cap-chunk', { + pack: 'discord', + kind: 'event', + handle, + name: eventName, + args: payload, + }); + } catch (err) { + logErr('emit failed: ' + (err && err.message)); + } + }; + obj.on('error', function (err) { + listener('error', err); + }); + if (typeof obj.on === 'function') { + obj.on('*', function () {}); + } + const origEmit = obj.emit; + if (typeof origEmit === 'function') { + obj.emit = function (eventName) { + try { + listener.apply(null, arguments); + } catch (_) {} + return origEmit.apply(obj, arguments); + }; + } + listeners.set(handle, { + unlisten() { + if (typeof origEmit === 'function') obj.emit = origEmit; + }, + }); + ctx.reply({ ok: true, handle, listening: true }); + }, + + async destroy(ctx) { + const handle = ctx.payload && ctx.payload.handle; + if (handle) { + dropHandle(handle); + ctx.reply({ ok: true, handle }); + return; + } + destroyAll(); + ctx.reply({ ok: true, clients: 0 }); + }, + }; + + return { + id: 'discord', + commands, + getPublicStatus() { + let loaded = false; + try { + const d = loadBareDiscordJsSync(); + loaded = !!(d && typeof d.Client === 'function'); + } catch (_) {} + return { + installed: true, + enabled, + clients: clientCount, + maxClients: MAX_CLIENTS, + loaded, + loadError: getBareDiscordJsLoadError() || undefined, + }; + }, + onLoad() {}, + cleanup() { + destroyAll(); + }, + }; +} + +module.exports = { + createDiscordPack, + normalizeDiscordToken, + serializeConstants: require('../discord/snapshot.js').serializeConstants, + snapshotValue: require('../discord/snapshot.js').snapshotValue, + createHandleTable: require('../discord/snapshot.js').createHandleTable, +}; diff --git a/native-host/discord/load-discord.js b/native-host/discord/load-discord.js new file mode 100644 index 0000000..bb88cee --- /dev/null +++ b/native-host/discord/load-discord.js @@ -0,0 +1,139 @@ +/** + * Load vendored bare-discord-js. CJS only — the ESM entry uses node:module. + * Apply WHATWG WS + FormData before requiring the package. + */ +'use strict'; + +const { takePackedDiscordJs, unwrapDiscordModule } = require('./registry.js'); + +let discordJsCache; +let discordJsTried = false; +let discordJsLastError = ''; + +function getBareDiscordJsLoadError() { + return discordJsLastError; +} + +function noteDiscordLoadError(err) { + const msg = + err && typeof err === 'object' && err.message + ? String(err.message || err) + : String(err || 'unknown error'); + discordJsLastError = msg.slice(0, 800); + return discordJsLastError; +} + +function bareRuntime() { + if (typeof globalThis.Bare !== 'undefined') return true; + const v = globalThis.process && globalThis.process.versions; + return Boolean(v && typeof v.bare === 'string'); +} + +function normalizeDiscordToken(raw) { + if (typeof raw !== 'string') return ''; + let t = raw.trim(); + if (t.charCodeAt(0) === 0xfeff) t = t.slice(1); + t = t.replace(/[\u200B-\u200D\uFEFF]/g, ''); + return t.trim(); +} + +function applyBareProcessEmitWarning() { + const proc = globalThis.process; + if (!proc || typeof proc.emitWarning === 'function') return; + proc.emitWarning = function emitWarning(warning, type, code) { + const msg = warning instanceof Error ? warning.message : String(warning); + const name = typeof type === 'string' ? type : 'Warning'; + const id = typeof code === 'string' ? code : ''; + const line = id ? name + ' [' + id + ']: ' + msg : name + ': ' + msg; + try { + if (typeof proc.emit === 'function') proc.emit('warning', warning); + } catch (_) {} + try { + console.error(line); + } catch (_) {} + }; +} + +function applyBareTlsCompat() { + const proc = globalThis.process; + if (!proc || !proc.env) return; + if (!proc.env.NODE_TLS_REJECT_UNAUTHORIZED) { + proc.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + } + try { + const req = globalThis.require; + if (typeof req !== 'function') return; + const bareHttps = req('bare-https'); + if (!bareHttps || typeof bareHttps.Agent !== 'function') return; + const insecureAgent = new bareHttps.Agent({ rejectUnauthorized: false }); + bareHttps.globalAgent = insecureAgent; + if (bareHttps.Agent) bareHttps.Agent.global = insecureAgent; + } catch (_) {} +} + +function applyAdapters() { + applyBareProcessEmitWarning(); + if (bareRuntime()) applyBareTlsCompat(); + try { + const ws = require('../vendor/bare-discord-js/src/adapters/whatwg-ws.cjs'); + if (typeof ws.installBareOsDiscordGatewayWs === 'function') { + ws.installBareOsDiscordGatewayWs(); + } + } catch (_) {} + try { + const fd = require('../vendor/bare-discord-js/src/adapters/form-data.cjs'); + if (typeof fd.installBareOsFormDataGlobal === 'function') { + fd.installBareOsFormDataGlobal(); + } + } catch (_) {} +} + +function loadBareDiscordJsSync() { + applyAdapters(); + if (discordJsTried) return discordJsCache; + discordJsTried = true; + discordJsLastError = ''; + + let lastErr = null; + try { + const packed = unwrapDiscordModule(takePackedDiscordJs()); + if (packed) { + discordJsCache = packed; + return discordJsCache; + } + } catch (err) { + lastErr = err; + } + + try { + const mod = require('bare-discord-js'); + const discord = unwrapDiscordModule(mod); + if (discord) { + discordJsCache = discord; + return discordJsCache; + } + if (!lastErr) lastErr = new Error('require("bare-discord-js") returned no Client'); + } catch (err) { + lastErr = err; + } + + noteDiscordLoadError(lastErr || 'vendored bare-discord-js unresolved'); + try { + if (process.stderr) { + process.stderr.write( + '[bridge-swarm-host] BridgeSwarm.DiscordJS: failed to load bare-discord-js: ' + + discordJsLastError + + '\n' + ); + } + } catch (_) {} + discordJsCache = undefined; + return undefined; +} + +module.exports = { + loadBareDiscordJsSync, + getBareDiscordJsLoadError, + normalizeDiscordToken, + unwrapDiscordModule, +}; diff --git a/native-host/discord/packed.mjs b/native-host/discord/packed.mjs new file mode 100644 index 0000000..4fb6a70 --- /dev/null +++ b/native-host/discord/packed.mjs @@ -0,0 +1,34 @@ +/** + * Standalone pack binding. Static import so bare-pack rewrites discord.js + * into the host bundle. Adapters in ws-bootstrap.cjs must load first. + * Avoid Node `module` / createRequire — those are missing in the packed graph. + */ +import discord from 'discord.js'; +import registry from './registry.js'; + +const registerPackedDiscordJs = + (registry && registry.registerPackedDiscordJs) || + (registry && registry.default && registry.default.registerPackedDiscordJs); + +function unwrap(mod) { + if (mod && typeof mod.Client === 'function') return mod; + const def = mod && mod.default; + if (def && typeof def === 'object' && typeof def.Client === 'function') return def; + return null; +} + +function applyShims(d) { + if (!d || typeof d !== 'object') return d; + const events = d.Events; + if (events && events.ClientReady && events.Ready == null) { + events.Ready = events.ClientReady; + } + return d; +} + +if (typeof registerPackedDiscordJs !== 'function') { + throw new Error('discord registry missing registerPackedDiscordJs'); +} + +export const bareDiscordJs = registerPackedDiscordJs(applyShims(unwrap(discord))); +export default bareDiscordJs; diff --git a/native-host/discord/registry.js b/native-host/discord/registry.js new file mode 100644 index 0000000..0d3a30e --- /dev/null +++ b/native-host/discord/registry.js @@ -0,0 +1,29 @@ +/** + * Live binding filled by packed.js when that file is statically imported + * so bare-pack rewrites discord.js into the host bundle. + */ +const registry = { module: null }; + +function registerPackedDiscordJs(mod) { + const unwrapped = unwrapDiscordModule(mod); + registry.module = unwrapped || null; + return registry.module; +} + +function takePackedDiscordJs() { + return registry.module; +} + +function unwrapDiscordModule(mod) { + if (!mod || typeof mod !== 'object') return undefined; + if (typeof mod.Client === 'function') return mod; + const def = mod.default; + if (def && typeof def === 'object' && typeof def.Client === 'function') return def; + return undefined; +} + +module.exports = { + registerPackedDiscordJs, + takePackedDiscordJs, + unwrapDiscordModule, +}; diff --git a/native-host/discord/snapshot.js b/native-host/discord/snapshot.js new file mode 100644 index 0000000..a7eaaa3 --- /dev/null +++ b/native-host/discord/snapshot.js @@ -0,0 +1,226 @@ +/** + * Serialize discord.js structures for native messaging (JSON, ~1 MB cap). + */ +'use strict'; + +const PREDICATES = [ + 'isChatInputCommand', + 'isButton', + 'isRepliable', + 'isStringSelectMenu', + 'isUserSelectMenu', + 'isRoleSelectMenu', + 'isMentionableSelectMenu', + 'isChannelSelectMenu', + 'isAnySelectMenu', + 'isModalSubmit', + 'isAutocomplete', + 'isContextMenuCommand', + 'isUserContextMenuCommand', + 'isMessageContextMenuCommand', + 'isMessageComponent', + 'isCommand', +]; + +const STUB_METHODS = [ + 'reply', + 'followUp', + 'editReply', + 'deferReply', + 'deferUpdate', + 'deleteReply', + 'fetchReply', + 'showModal', + 'update', + 'react', + 'delete', + 'edit', + 'send', + 'startThread', + 'pin', + 'unpin', + 'fetch', + 'setToken', + 'put', + 'post', + 'get', + 'patch', + 'login', + 'destroy', +]; + +function intern(handles, obj) { + if (!obj || typeof obj !== 'object') return null; + if (obj.__bsHandle && handles.byId.has(obj.__bsHandle)) return obj.__bsHandle; + const id = handles.nextId++; + const handle = 'dj_' + id; + obj.__bsHandle = handle; + handles.byId.set(handle, obj); + handles.meta.set(handle, { + type: (obj.constructor && obj.constructor.name) || 'Object', + }); + return handle; +} + +function jsonSafe(value, depth) { + if (depth > 5) return undefined; + if (value == null) return value; + const t = typeof value; + if (t === 'string' || t === 'number' || t === 'boolean') return value; + if (t === 'bigint') return value.toString(); + if (t === 'function') return undefined; + if (Array.isArray(value)) { + const out = []; + for (let i = 0; i < value.length && i < 50; i++) { + const item = jsonSafe(value[i], depth + 1); + if (item !== undefined) out.push(item); + } + return out; + } + if (t !== 'object') return String(value); + if (typeof value.toJSON === 'function') { + try { + return jsonSafe(value.toJSON(), depth + 1); + } catch (_) {} + } + const out = {}; + let n = 0; + for (const key of Object.keys(value)) { + if (n++ > 80) break; + if (key.charAt(0) === '_') continue; + const v = jsonSafe(value[key], depth + 1); + if (v !== undefined) out[key] = v; + } + return out; +} + +function snapshotValue(value, handles, depth) { + if (depth > 4) return jsonSafe(value, 0); + if (value == null || typeof value !== 'object') return jsonSafe(value, 0); + if (typeof value.then === 'function') return { _type: 'Promise' }; + + const ctor = value.constructor && value.constructor.name; + const looksLive = + typeof value.reply === 'function' || + typeof value.login === 'function' || + typeof value.setToken === 'function' || + typeof value.toJSON === 'function' || + (ctor && ctor !== 'Object' && ctor !== 'Array'); + + if (!looksLive) return jsonSafe(value, 0); + + const handle = intern(handles, value); + let json = {}; + if (typeof value.toJSON === 'function') { + try { + json = jsonSafe(value.toJSON(), 0) || {}; + } catch (_) { + json = {}; + } + } else { + json = jsonSafe(value, 1) || {}; + } + + const pred = {}; + for (const name of PREDICATES) { + if (typeof value[name] === 'function') { + try { + pred[name] = !!value[name](); + } catch (_) {} + } + } + + const methods = []; + for (const name of STUB_METHODS) { + if (typeof value[name] === 'function') methods.push(name); + } + + const snap = Object.assign({ _handle: handle, _type: ctor || 'Object', _methods: methods }, json, pred); + + if (value.user && typeof value.user === 'object') { + snap.user = jsonSafe(value.user, 0); + if (value.user.tag) snap.user.tag = value.user.tag; + if (value.user.id) snap.user.id = value.user.id; + } + if (value.author && typeof value.author === 'object') { + snap.author = jsonSafe(value.author, 0); + } + if (value.channel && typeof value.channel === 'object') { + snap.channel = { + id: value.channel.id, + type: value.channel.type, + name: value.channel.name, + _handle: intern(handles, value.channel), + _type: (value.channel.constructor && value.channel.constructor.name) || 'Channel', + _methods: typeof value.channel.send === 'function' ? ['send'] : [], + }; + } + if (value.guild && typeof value.guild === 'object') { + snap.guild = { + id: value.guild.id, + name: value.guild.name, + _handle: intern(handles, value.guild), + _type: 'Guild', + }; + } + if (value.commandName != null) snap.commandName = value.commandName; + if (value.customId != null) snap.customId = value.customId; + if (value.content != null) snap.content = value.content; + + return snap; +} + +function snapshotEventArgs(args, handles) { + const out = []; + for (let i = 0; i < args.length && i < 6; i++) { + const a = args[i]; + if (a instanceof Error) { + out.push({ _type: 'Error', message: a.message, code: a.code }); + } else { + out.push(snapshotValue(a, handles, 0)); + } + } + return out; +} + +function serializeConstants(discord) { + const out = { _classes: [] }; + if (!discord || typeof discord !== 'object') return out; + for (const key of Object.keys(discord)) { + const v = discord[key]; + if (typeof v === 'function') { + out._classes.push(key); + continue; + } + if (v && typeof v === 'object') { + const plain = {}; + let n = 0; + for (const k of Object.keys(v)) { + if (n++ > 200) break; + const x = v[k]; + if (typeof x === 'string' || typeof x === 'number' || typeof x === 'boolean') { + plain[k] = x; + } + } + if (Object.keys(plain).length) out[key] = plain; + } else if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') { + out[key] = v; + } + } + return out; +} + +function createHandleTable() { + return { nextId: 1, byId: new Map(), meta: new Map() }; +} + +module.exports = { + PREDICATES, + STUB_METHODS, + intern, + snapshotValue, + snapshotEventArgs, + serializeConstants, + createHandleTable, + jsonSafe, +}; diff --git a/native-host/discord/stubs/zlib-sync/index.js b/native-host/discord/stubs/zlib-sync/index.js new file mode 100644 index 0000000..0d1e891 --- /dev/null +++ b/native-host/discord/stubs/zlib-sync/index.js @@ -0,0 +1,8 @@ +'use strict'; +/** + * Optional native. discord.js does `zlib = require('zlib-sync')` then + * `compression: zlib ? ZlibStream : null`. A truthy empty object enables + * zlib-stream, after which `new zlib.Inflate()` throws and Client.login() + * hangs forever. + */ +module.exports = null; diff --git a/native-host/discord/stubs/zlib-sync/package.json b/native-host/discord/stubs/zlib-sync/package.json new file mode 100644 index 0000000..2c32369 --- /dev/null +++ b/native-host/discord/stubs/zlib-sync/package.json @@ -0,0 +1,7 @@ +{ + "name": "zlib-sync", + "version": "0.0.0", + "description": "Null stub: a truthy empty zlib-sync hangs discord.js Client.login forever", + "main": "./index.js", + "private": true +} diff --git a/native-host/discord/ws-bootstrap.cjs b/native-host/discord/ws-bootstrap.cjs new file mode 100644 index 0000000..d6439fd --- /dev/null +++ b/native-host/discord/ws-bootstrap.cjs @@ -0,0 +1,31 @@ +/** + * MUST load before discord.js / @discordjs/ws evaluation. + * Forces WHATWG WebSocket (bare-ws), FormData/Blob/File, and TLS defaults + * so IDENTIFY produces READY. CJS so bare-pack does not need Node `module`. + */ +'use strict'; + +function apply() { + try { + const ws = require('../vendor/bare-discord-js/src/adapters/whatwg-ws.cjs'); + const install = + (ws && ws.installBareOsDiscordGatewayWs) || + (ws && ws.default && ws.default.installBareOsDiscordGatewayWs); + if (typeof install === 'function') install(); + } catch (_) {} + try { + const fd = require('../vendor/bare-discord-js/src/adapters/form-data.cjs'); + const installFd = + (fd && fd.installBareOsFormDataGlobal) || + (fd && fd.default && fd.default.installBareOsFormDataGlobal); + if (typeof installFd === 'function') installFd(); + } catch (_) {} + try { + const proc = globalThis.process; + if (proc && proc.env && !proc.env.NODE_TLS_REJECT_UNAUTHORIZED) { + proc.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + } + } catch (_) {} +} + +apply(); diff --git a/native-host/host.js b/native-host/host.js index 8b26318..0fee371 100644 --- a/native-host/host.js +++ b/native-host/host.js @@ -1735,6 +1735,7 @@ async function handleMessageAsync(send, msg) { packs: capabilities.listPackIds(), qvac: packStatus('qvac'), agent: packStatus('agent'), + discord: packStatus('discord'), examples: examplesServer.status(), streamPort: streamPort.status(), swarmCount: swarms.size, diff --git a/native-host/index.mjs b/native-host/index.mjs index dd98b7e..58f3daa 100644 --- a/native-host/index.mjs +++ b/native-host/index.mjs @@ -1,7 +1,7 @@ /** * BridgeSwarm native messaging host entrypoint. * - * Includes default capability packs (media, fs, sqlite, net) and curated Bare + * Includes default capability packs (media, fs, sqlite, net, qvac, agent, discord) and curated Bare * modules — see docs/DEFAULT-MODULES.md. * * bare-process/global must be the very first import so that `process` is diff --git a/native-host/package-lock.json b/native-host/package-lock.json index ac07dd0..f4f5f2f 100644 --- a/native-host/package-lock.json +++ b/native-host/package-lock.json @@ -18,13 +18,19 @@ "autobase": "^7.28.1", "b4a": "^1.8.1", "bare": "^1.30.3", + "bare-crypto": "^1.15.3", + "bare-discord-js": "file:./vendor/bare-discord-js", "bare-fetch": "^3.2.0", "bare-ffmpeg": "^1.5.0", + "bare-form-data": "^1.2.2", "bare-fs": "^4.7.4", "bare-gpu-info": "0.1.1", "bare-http1": "^4.5.7", + "bare-https": "^3.0.0", "bare-media": "^2.10.1", "bare-module": "^6.4.0", + "bare-net": "^2.3.3", + "bare-node-runtime": "^1.5.0", "bare-os": "^3.9.3", "bare-path": "^3.1.1", "bare-process": "^4.5.1", @@ -32,8 +38,12 @@ "bare-sqlite": "^0.1.4", "bare-stream": "^2.13.3", "bare-subprocess": "^6.1.0", + "bare-tls": "^3.1.8", + "bare-ws": "^3.0.0", + "bare-zlib": "^1.4.1", "compact-encoding": "^3.3.0", "corestore": "^7.11.1", + "discord.js": "^14.27.0", "hrpc": "^4.3.0", "hyperbee": "^2.27.3", "hypercore": "^11.35.0", @@ -41,12 +51,146 @@ "hyperdrive": "^13.3.3", "hyperschema": "^1.21.0", "hyperswarm": "^4.17.0", - "protomux": "^3.11.0" + "protomux": "^3.11.0", + "zlib-sync": "file:./discord/stubs/zlib-sync" }, "engines": { "bare": ">=1.30.3" } }, + "discord/stubs/zlib-sync": { + "version": "0.0.0" + }, + "node_modules/@discordjs/builders": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.14.1.tgz", + "integrity": "sha512-gSKkhXLqs96TCzk66VZuHHl8z2bQMJFGwrXC0f33ngK+FLNau4hU1PYny3DNJfNdSH+gVMzE85/d5FQ2BpcNwQ==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/formatters": "^0.6.2", + "@discordjs/util": "^1.2.0", + "@sapphire/shapeshift": "^4.0.0", + "discord-api-types": "^0.38.40", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.4", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/formatters": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz", + "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.38.33" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.3.tgz", + "integrity": "sha512-wvOylxNYJkwKjctS/Mn5GP1w9r3/rzyH+ThD1JlAca6zEdlHs8QWBBUQJpU5Q+W6DoIj/Ljh1IPlZs7hTU+UAg==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.1", + "@discordjs/util": "^1.2.0", + "@sapphire/async-queue": "^1.5.3", + "@sapphire/snowflake": "^3.5.5", + "@vladfrangu/async_event_emitter": "^2.4.6", + "discord-api-types": "^0.38.50", + "magic-bytes.js": "^1.13.0", + "tslib": "^2.6.3", + "undici": "^6.27.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/util": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", + "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.38.33" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", + "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.5.1", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@types/ws": "^8.5.10", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "^0.38.1", + "tslib": "^2.6.2", + "ws": "^8.17.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, "node_modules/@hyperswarm/secret-stream": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz", @@ -325,6 +469,67 @@ "ready-resource": "^1.2.0" } }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", + "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", + "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v16" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz", + "integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@types/node": { + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz", + "integrity": "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", + "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/adaptive-timeout": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/adaptive-timeout/-/adaptive-timeout-1.0.1.tgz", @@ -479,12 +684,29 @@ "bare-inspect": "^3.1.2" } }, + "node_modules/bare-async-hooks": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/bare-async-hooks/-/bare-async-hooks-0.0.0.tgz", + "integrity": "sha512-xNfGwUobaomCGMGAqohAekS3uMCj+4tvI4AoOaJnO7NfpN+dvFdkC5xkeQtmZzs2vxf2TR5J6i5FDd1ImCZERw==", + "license": "Apache-2.0" + }, "node_modules/bare-bmp": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/bare-bmp/-/bare-bmp-1.0.1.tgz", "integrity": "sha512-xjPfMapNJhs2ABzVfzWxGoXxnCuplwgcleRPliyGEQ/6ooOb0xSrqJJ+PHZz5B4OT/A7F2b8aBFm5Yj+Ftb2bw==", "license": "Apache-2.0" }, + "node_modules/bare-broadcast-channel": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bare-broadcast-channel/-/bare-broadcast-channel-0.2.0.tgz", + "integrity": "sha512-MuAwdKWr4cSjNwqvbE3tA9Wn6w69q6iXYnP2Wb0nlDa6sKNSMSfY7LXkV4FzWlq9JFP9d0HkCAKPboTLKnUfWQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.0.0", + "bare-stream": "^2.7.0", + "bare-structured-clone": "^1.4.0" + } + }, "node_modules/bare-buffer": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/bare-buffer/-/bare-buffer-3.6.2.tgz", @@ -512,6 +734,46 @@ } } }, + "node_modules/bare-bundle-id": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bare-bundle-id/-/bare-bundle-id-1.0.2.tgz", + "integrity": "sha512-RG/y1J/s6zWmsqUIDtclXh+xxMRTh1jo/10vFL58FKhe9UESchMNkwn0Cz10o5AdA/35WR/KUGoHhIGdyCjQrg==", + "license": "Apache-2.0", + "dependencies": { + "sodium-native": "^5.0.9" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-bundle": "*" + } + }, + "node_modules/bare-channel": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/bare-channel/-/bare-channel-5.2.4.tgz", + "integrity": "sha512-enkaOtvXUiZsLBSbataxV/QBjehCOK+SUTd2JQWUYW2iIOufpPu9jyZ9bqJqkEquktrK/rpEMVEmKsPzoci/sA==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.0.0", + "bare-stream": "^2.7.0", + "bare-structured-clone": "^1.4.0" + }, + "engines": { + "bare": ">=1.7.0" + } + }, + "node_modules/bare-console": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/bare-console/-/bare-console-6.2.0.tgz", + "integrity": "sha512-qQ+Vasa3NwNdVDcUWKIIEG5p7MKO7uABcV/xBR9MDwQg3QklC5ayemaHzBa/ER4h6TKN2PRQN5IppNwUAMtb8Q==", + "license": "Apache-2.0", + "dependencies": { + "bare-format": "^1.0.2", + "bare-hrtime": "^2.0.0", + "bare-logger": "^2.0.0", + "bare-system-logger": "^1.0.2", + "bare-type": "^1.1.0" + } + }, "node_modules/bare-cpu-info": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/bare-cpu-info/-/bare-cpu-info-0.1.1.tgz", @@ -539,6 +801,50 @@ } } }, + "node_modules/bare-debug-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", + "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-dgram": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bare-dgram/-/bare-dgram-1.1.1.tgz", + "integrity": "sha512-fqL5xD7k6iGL4SdmkW9LXCoMEbrk1Fwg6ZLN0vEG1XaFtTFUmgXxAfbZ6kL+385TZ+nRfLz32xeBZV9gs6lmsw==", + "license": "Apache-2.0", + "dependencies": { + "bare-dns": "^2.0.4", + "bare-events": "^2.5.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-pipe": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-pipe": { + "optional": true + } + } + }, + "node_modules/bare-diagnostics-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bare-diagnostics-channel/-/bare-diagnostics-channel-1.1.0.tgz", + "integrity": "sha512-Reu+EQo+eLpB+a5p8UykFEdXndFaRaSgKV38uAMh/qhE2eTeJcdwAwo74hKYyeN8GD3DIFqC9ZlM4bnDc03CIg==", + "license": "Apache-2.0" + }, + "node_modules/bare-discord-js": { + "resolved": "vendor/bare-discord-js", + "link": true + }, "node_modules/bare-dns": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", @@ -548,6 +854,20 @@ "bare": ">=1.7.0" } }, + "node_modules/bare-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", + "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, "node_modules/bare-env": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.1.tgz", @@ -621,6 +941,15 @@ "bare-stream": "^2.6.5" } }, + "node_modules/bare-format": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.2.tgz", + "integrity": "sha512-GswdhnOnP9QtwRbrf4wLApw5widkaLMsLe2XOs35fQD2YfEN1ApoGka+cZ7PfvzxMgfYXmMhj/2OGlVn5/Dxgw==", + "license": "Apache-2.0", + "dependencies": { + "bare-inspect": "^3.0.0" + } + }, "node_modules/bare-fs": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.1.tgz", @@ -738,12 +1067,45 @@ "bare": ">=1.18.0" } }, + "node_modules/bare-inspector": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-6.1.0.tgz", + "integrity": "sha512-PRxmZ4gF+K3TLzGubgRFvzdECybTCSKackgNsAdd4e7SdvICxMkGj+p5iX7bSKYSmgDnxgCR+2Z6UXmWykKhvw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.1.0", + "bare-http1": "^4.0.0", + "bare-stream": "^2.0.0", + "bare-url": "^2.0.0", + "bare-ws": "^3.0.0" + }, + "engines": { + "bare": ">=1.29.0" + }, + "peerDependencies": { + "bare-tcp": "*" + }, + "peerDependenciesMeta": { + "bare-tcp": { + "optional": true + } + } + }, "node_modules/bare-jpeg": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/bare-jpeg/-/bare-jpeg-1.1.2.tgz", "integrity": "sha512-MpY/jeMCaBk7rs+mQRnugunc28DdVveMX6k75tVQQFx6MquOIGr77+IhNKEz9m7sq87BloEQHjB2X3tmEJw7NQ==", "license": "Apache-2.0" }, + "node_modules/bare-logger": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/bare-logger/-/bare-logger-2.0.4.tgz", + "integrity": "sha512-HZB3jmnu0C/hcN4HPtBLe7MA6TuwWHzjn4xalExMLlt20URKNKvRCNEMRsWrcFuBKx3wfC6oWySmGpqpaFcjRg==", + "license": "Apache-2.0", + "dependencies": { + "bare-format": "^1.0.0" + } + }, "node_modules/bare-media": { "version": "2.10.2", "resolved": "https://registry.npmjs.org/bare-media/-/bare-media-2.10.2.tgz", @@ -837,10 +1199,34 @@ } } }, + "node_modules/bare-module-traverse": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/bare-module-traverse/-/bare-module-traverse-2.5.1.tgz", + "integrity": "sha512-Bu/wh1I/vnKInsYneD/tdNy9nR0J7RomLmaTJ5ktJN7HQzlkR4WalWgD0Lqe5xzAjbSSl4JkUD5UHz4lGdWjDA==", + "license": "Apache-2.0", + "dependencies": { + "bare-addon-resolve": "^1.5.0", + "bare-mime": "^1.0.0", + "bare-module-lexer": "^1.6.0", + "bare-module-resolve": "^1.7.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-url": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-url": { + "optional": true + } + } + }, "node_modules/bare-net": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.2.tgz", - "integrity": "sha512-I+yz+pqbYsBkxDsnu5vkKvy7RSNY9CcAvu2jZT6PsmdXJQG1i3dmD5V7xc3334OVp2absgtUEYLmmuNFlphBzg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.3.tgz", + "integrity": "sha512-q1noXFJKn+eNi6vXYv2Y+5FTnn+o7qpli4m2lUZTdK8cBjVs2zGaykuzTUIa29v70eOsQI9OAIyYwN3ODHtFaQ==", "license": "Apache-2.0", "dependencies": { "bare-events": "^2.2.2", @@ -849,6 +1235,53 @@ "bare-tcp": "^2.0.0" } }, + "node_modules/bare-node-runtime": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bare-node-runtime/-/bare-node-runtime-1.5.0.tgz", + "integrity": "sha512-eMRq9WDYd+E0IZ8EG/8iCozZJl511z6vp7kxZyxjDw77ggOZ7bThFpJ72Kztjs8sh0hoe+xhFyZmoqAdVE/Ziw==", + "license": "Apache-2.0", + "dependencies": { + "bare-abort-controller": "^1.0.0", + "bare-assert": "^1.1.0", + "bare-async-hooks": "^0.0.0", + "bare-buffer": "^3.3.1", + "bare-console": "^6.0.1", + "bare-crypto": "^1.11.2", + "bare-dgram": "^1.0.1", + "bare-diagnostics-channel": "^1.1.0", + "bare-dns": "^2.1.4", + "bare-events": "^2.7.0", + "bare-fetch": "^3.0.0", + "bare-fs": "^4.2.3", + "bare-http1": "^4.0.4", + "bare-https": "^3.0.0", + "bare-inspector": "^6.0.1", + "bare-module": "^6.1.2", + "bare-net": "^2.0.2", + "bare-os": "^3.6.2", + "bare-path": "^3.0.0", + "bare-performance": "^2.0.0", + "bare-process": "^4.2.1", + "bare-punycode": "^0.0.0", + "bare-querystring": "^1.0.0", + "bare-readline": "^1.1.0", + "bare-repl": "^6.0.1", + "bare-sqlite": "^0.1.4", + "bare-stream": "^2.7.0", + "bare-string-decoder": "^1.0.0", + "bare-subprocess": "^6.0.0", + "bare-timers": "^3.0.3", + "bare-tls": "^3.0.0", + "bare-tty": "^5.0.3", + "bare-url": "^2.2.2", + "bare-utils": "^1.5.1", + "bare-v8": "^1.0.1", + "bare-vm": "^1.0.0", + "bare-worker": "^4.0.0", + "bare-ws": "^3.0.0", + "bare-zlib": "^1.3.1" + } + }, "node_modules/bare-os": { "version": "3.9.3", "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.3.tgz", @@ -858,6 +1291,36 @@ "bare": ">=1.14.0" } }, + "node_modules/bare-pack": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-pack/-/bare-pack-2.2.2.tgz", + "integrity": "sha512-4id2zXMNlQSDyTD1ynbvZBDEVebkqU8CkgJQv01LDxF0PAtMcJ+4LKYXQbz+rmkJIchvSuMd2v62dYrOKf2zBQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-bundle": "^1.8.3", + "bare-bundle-id": "^1.0.0", + "bare-fs": "^4.2.1", + "bare-module-traverse": "~2.5.0", + "bare-path": "^3.0.0", + "paparam": "^1.5.0", + "promaphore": "^1.0.0" + }, + "bin": { + "bare-pack": "bin.js" + }, + "peerDependencies": { + "bare-buffer": "^3.6.0", + "bare-url": "^2.4.0" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-url": { + "optional": true + } + } + }, "node_modules/bare-path": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", @@ -917,6 +1380,55 @@ "bare-stdio": "^1.0.1" } }, + "node_modules/bare-punycode": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/bare-punycode/-/bare-punycode-0.0.0.tgz", + "integrity": "sha512-PC2Y6mGLytZPJCB9M7CvO5Zb6uWYVUZ+5t3L6vePFuFM6tdC6SsQ+sIsuf0Sa6LHBoWURX7I9yMMbPXMv7TIdQ==", + "license": "Apache-2.0", + "dependencies": { + "punycode": "^2.3.1" + } + }, + "node_modules/bare-querystring": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bare-querystring/-/bare-querystring-1.1.0.tgz", + "integrity": "sha512-pUtEM6JrX53MbEJFwO92F0Ch7BwZ67KD7LyglcB8/tvkkVdwTgN1f7oIklRe+NTT/WCYZgwjDFYS9efBxDSq8g==", + "license": "Apache-2.0" + }, + "node_modules/bare-readline": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/bare-readline/-/bare-readline-1.3.1.tgz", + "integrity": "sha512-QtSU4ZfgQcDI6AQssjDFqTiRe4rCiciMn+Yqx6siMJZBIftAIFrNSOK0sa5SBrmNv/a7CD9Dm0onUDCRyn5Fdg==", + "license": "Apache-2.0", + "dependencies": { + "bare-ansi-escapes": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-realm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bare-realm/-/bare-realm-2.0.1.tgz", + "integrity": "sha512-kQbYU6AAQu4XBTQesuz2+PpjBx7MJzf5Qw3nSLzQCzbVglx7Ml85MtWEjUe1AeslXqrd+nFPHMEbL55ouISscA==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.5.0" + } + }, + "node_modules/bare-repl": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/bare-repl/-/bare-repl-6.1.1.tgz", + "integrity": "sha512-vV52s+zLcwf9WB6y09KwHrqHrCR0UUgcKz3NPhPAwmO62ctFYbBfjskBch92wWk7vRrF68dHJj3hmYNCe8d18g==", + "license": "Apache-2.0", + "dependencies": { + "bare-inspect": "^3.0.0", + "bare-module": "^6.4.0", + "bare-path": "^3.0.0", + "bare-pipe": "^4.0.0", + "bare-readline": "^1.0.0", + "bare-stream": "^2.0.0", + "bare-tty": "^5.0.0" + } + }, "node_modules/bare-rpc": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/bare-rpc/-/bare-rpc-1.3.8.tgz", @@ -1288,6 +1800,23 @@ } } }, + "node_modules/bare-string-decoder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bare-string-decoder/-/bare-string-decoder-1.0.0.tgz", + "integrity": "sha512-FjFvfHo88U7borNQSj9ijP2JTb1asqY6K28OZrix4dF9iZf5D2wi1+99CgLlHT3HtYqdwxRhDAiKu8rVstt5rQ==", + "license": "Apache-2.0", + "dependencies": { + "text-decoder": "^1.2.3" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, "node_modules/bare-structured-clone": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/bare-structured-clone/-/bare-structured-clone-1.6.0.tgz", @@ -1303,6 +1832,16 @@ "bare": ">=1.2.0" } }, + "node_modules/bare-stylize": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/bare-stylize/-/bare-stylize-0.0.1.tgz", + "integrity": "sha512-l3MjmIl476bWijYWf3RbE+osl4iuXSOMudzp0vAqzIK7gPgn/+G3oAxp8Oin9CFF911KBP0LO9kts8Ci8mGZaQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-ansi-escapes": "^2.2.3", + "bare-process": "^4.2.1" + } + }, "node_modules/bare-subprocess": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-6.1.0.tgz", @@ -1335,6 +1874,15 @@ "integrity": "sha512-nPNV2/xnLBEING9iUli88DzOXCt3+N/d7rNYTnaKVwA7BNNRgkNlFMMwYLE124Y48uHrIQ18MoMTbYz18dJoVQ==", "license": "Apache-2.0" }, + "node_modules/bare-system-logger": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bare-system-logger/-/bare-system-logger-1.0.5.tgz", + "integrity": "sha512-Xnjk5jFJWKrfVfHfusHwUpfiPmZqWUm9gaCUolTlN1pxxf7yDn7mhC+pr885j6VVSLzZwfZYi9nywVI+VakxVg==", + "license": "Apache-2.0", + "dependencies": { + "bare-logger": "^2.0.0" + } + }, "node_modules/bare-tcp": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.5.3.tgz", @@ -1357,16 +1905,45 @@ } } }, + "node_modules/bare-thread": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/bare-thread/-/bare-thread-1.2.4.tgz", + "integrity": "sha512-MnqMCGVp2JJJNXgwUBC8hw+J4FrTeLkLgfCPPl5tQK6MbtV2Efi6LKK5v819VRY0da+AXlCbgssbAVge31NfRg==", + "license": "Apache-2.0", + "dependencies": { + "bare-bundle": "^1.9.0", + "bare-module-resolve": "^1.11.2", + "bare-module-traverse": "^2.0.0", + "bare-url": "^2.4.2" + } + }, "node_modules/bare-tiff": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/bare-tiff/-/bare-tiff-1.0.4.tgz", "integrity": "sha512-vUyD/qStO1Z9UxtBLolV9sZ4bpzBXselRjE03SEBUfR+SaO+033kp5CDAPxHdJRLx2pEwFY0QFC5ytFjSGxBKQ==", "license": "Apache-2.0" }, + "node_modules/bare-timers": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/bare-timers/-/bare-timers-3.2.3.tgz", + "integrity": "sha512-PW36nOU4AKbQSrbWlrYhDJgsEWbZST0v8s8FvlgVySUby7CfXdUpXVCTFmgXcpZevQx7BCXqKM/A8NJDHPGkzQ==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.7.0" + }, + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, "node_modules/bare-tls": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-3.1.7.tgz", - "integrity": "sha512-AMw8tJlb3LhzAmhgXRcjDrTlNxR3gXXyj6G8eU9iwvCFtiUBD8MxAW7bwunA1gXDukgo40A970jX0APc2jMU7A==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-3.1.10.tgz", + "integrity": "sha512-YBppPcnb9oEiiwc6BupndFMF3RAK6KPtkDDd9JxY1aS5rwYX3sALHG8V2XEvIhjzl8xolY8PPd0kvChZYelCnw==", "license": "Apache-2.0", "dependencies": { "bare-net": "^2.0.1", @@ -1425,12 +2002,80 @@ "bare-path": "^3.0.0" } }, + "node_modules/bare-utils": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.6.0.tgz", + "integrity": "sha512-WhQEIkkAxkSnW7u1QgrI0AfNm5JpMruETXeYsb5qnkBJ0TTfNKygZmsh6rkoHBANaV+C/7Jed7bJP9OmEHG7rQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-debug-log": "^2.0.0", + "bare-encoding": "^1.0.0", + "bare-format": "^1.0.0", + "bare-inspect": "^3.0.0", + "bare-stylize": "^0.0.1", + "bare-type": "^1.0.6" + } + }, + "node_modules/bare-v8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bare-v8/-/bare-v8-1.0.1.tgz", + "integrity": "sha512-/cR5ZvFWQRdtTZ4tx0j7TKvTWce8UnnLqm88fwHtJmfM7HODIBVjQGDT7KkDLeD2d/eHP2pzB71Y8/QyiMMKrQ==", + "license": "Apache-2.0" + }, + "node_modules/bare-vm": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bare-vm/-/bare-vm-1.0.1.tgz", + "integrity": "sha512-yLnbRvKt3AhRTmtfTIrYfdTHqGEfIJc+Fgb2DcHejE0HJ+p5adGxxPMvd3893Z7iXVYnalxukNARn4oJSZELHQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-realm": "^2.0.0" + } + }, "node_modules/bare-webp": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bare-webp/-/bare-webp-1.3.0.tgz", "integrity": "sha512-M45Dn6MJZoO1gLPnJiFmCf/25arfuIzFVsGKrj0P3YOac3QqiU4Tyybo3GIrLo4l58noZD6i4dMUNIZsfexMlA==", "license": "Apache-2.0" }, + "node_modules/bare-worker": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/bare-worker/-/bare-worker-4.4.0.tgz", + "integrity": "sha512-zSc1biis9ks03nj/24M7tYS2V0CPhefizzRIxVYdglbbUAgA0zakwSgTKLJshZ6P+9NA55M/eG4k/rBALZVbxg==", + "license": "Apache-2.0", + "dependencies": { + "bare-broadcast-channel": "^0.2.0", + "bare-channel": "^5.1.5", + "bare-events": "^2.2.1", + "bare-module": "^6.4.0", + "bare-stream": "^2.13.3", + "bare-thread": "^1.2.2" + } + }, + "node_modules/bare-ws": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-3.1.0.tgz", + "integrity": "sha512-niSfwOfUBBM2IBK4OwnSkloYkLnQ9KeikM1XfAGto5UFL6DFLYg2hAE9OS1iWlsCv2b0dtGBZUne0VTrZRhfUw==", + "license": "Apache-2.0", + "dependencies": { + "bare-crypto": "^1.2.0", + "bare-events": "^2.3.1", + "bare-http1": "^4.0.0", + "bare-https": "^3.0.0", + "bare-stream": "^2.1.2" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-url": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-url": { + "optional": true + } + } + }, "node_modules/bare-zlib": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/bare-zlib/-/bare-zlib-1.4.1.tgz", @@ -1601,6 +2246,39 @@ "udx-native": "^1.5.3" } }, + "node_modules/discord-api-types": { + "version": "0.38.55", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.55.tgz", + "integrity": "sha512-ytuaRTzdnHUCXJ6KjL9MrItQX0xKncBKEeYI1Bst4+ud47eejH3cG6gaesYakjpPcUhh68XYS2YwGq3mmujFsA==", + "license": "MIT" + }, + "node_modules/discord.js": { + "version": "14.27.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.27.0.tgz", + "integrity": "sha512-qHbFlFG2N7y3LjPySYsL6A1+BnX6bkTVgo842EX0CqVPk/KTMwZkojPHEXKsQUpWZNyz5BISNHK1cPpQw0+m4A==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/builders": "^1.14.1", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.6.2", + "@discordjs/rest": "^2.6.2", + "@discordjs/util": "^1.2.0", + "@discordjs/ws": "^1.2.3", + "@sapphire/snowflake": "3.5.5", + "discord-api-types": "^0.38.49", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "magic-bytes.js": "^1.13.0", + "tslib": "^2.6.3", + "undici": "^6.27.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, "node_modules/encryption-encoding": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/encryption-encoding/-/encryption-encoding-1.0.3.tgz", @@ -1619,6 +2297,12 @@ "bare-events": "^2.7.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", @@ -2010,6 +2694,24 @@ "integrity": "sha512-Yqi947Vk5Ps2YqhOV8K+RR6bseLhZLIVfovpWJH5cT7GE4Pca8/3iny/3oQ47scD7SfQd3whhxMfc+KgxTgDHA==", "license": "MIT" }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "license": "MIT" + }, + "node_modules/magic-bytes.js": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.1.tgz", + "integrity": "sha512-x5sn4UX2k5gCWlcfmoFwG4TPie8+dctESyqOBdhB5p6MsgWXdBKGmt9nXPObj/JI50TTL928lc5Yt1WntMn1bw==", + "license": "MIT" + }, "node_modules/mirror-drive": { "version": "1.14.2", "resolved": "https://registry.npmjs.org/mirror-drive/-/mirror-drive-1.14.2.tgz", @@ -2088,6 +2790,12 @@ "safety-catch": "^1.0.2" } }, + "node_modules/promaphore": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/promaphore/-/promaphore-1.0.0.tgz", + "integrity": "sha512-Eg8401+KJddVvDULkpy8bR964GMX8xMPegL6NdxTeBH2Wa3L86cZlEHizbkFJikr5u+E3wFoR5dLWJ+1OPyEfw==", + "license": "MIT" + }, "node_modules/protocol-buffers-encodings": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/protocol-buffers-encodings/-/protocol-buffers-encodings-1.2.0.tgz", @@ -2124,6 +2832,15 @@ "protomux": "^3.10.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/queue-tick": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", @@ -2482,6 +3199,18 @@ "yarn": ">= 1.20.0" } }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/udx-native": { "version": "1.20.7", "resolved": "https://registry.npmjs.org/udx-native/-/udx-native-1.20.7.tgz", @@ -2497,6 +3226,21 @@ "bare": ">=1.17.4" } }, + "node_modules/undici": { + "version": "6.28.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.1.tgz", + "integrity": "sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "license": "MIT" + }, "node_modules/unix-path-resolve": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unix-path-resolve/-/unix-path-resolve-1.0.2.tgz", @@ -2530,6 +3274,27 @@ "integrity": "sha512-0ugbP4CJW4e2D20jvEcC4973dCgIaHI4Rw1PT+26U9zEve7FyYdWAIwUnoeOYvoCfn+wXHoHTKb1KhkYlb60Pw==", "license": "Apache-2.0" }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xache": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/xache/-/xache-1.3.0.tgz", @@ -2545,6 +3310,10 @@ "b4a": "^1.5.3" } }, + "node_modules/zlib-sync": { + "resolved": "discord/stubs/zlib-sync", + "link": true + }, "node_modules/zod": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", @@ -2553,6 +3322,65 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "vendor/bare-discord-js": { + "version": "0.2.0", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bare-assert": "^1.2.0", + "bare-buffer": "^3.7.0", + "bare-console": "^6.2.0", + "bare-crypto": "^1.15.3", + "bare-diagnostics-channel": "^1.1.0", + "bare-encoding": "^1.0.3", + "bare-events": "^2.9.1", + "bare-fetch": "^3.2.0", + "bare-form-data": "^1.2.2", + "bare-fs": "^4.8.0", + "bare-http1": "^4.5.7", + "bare-https": "^3.0.0", + "bare-module": "^6.4.0", + "bare-net": "^2.3.3", + "bare-node-runtime": "^1.5.0", + "bare-os": "^3.9.3", + "bare-pack": "^2.2.1", + "bare-path": "^3.1.1", + "bare-performance": "^2.1.1", + "bare-process": "^4.5.1", + "bare-querystring": "^1.1.0", + "bare-stream": "^2.13.3", + "bare-string-decoder": "^1.0.0", + "bare-timers": "^3.2.1", + "bare-tls": "^3.1.8", + "bare-url": "^2.5.2", + "bare-utils": "^1.6.0", + "bare-ws": "^3.0.0", + "bare-zlib": "^1.4.1", + "discord.js": "^14.27.0" + }, + "engines": { + "bare": ">=1.29.4", + "node": ">=20" + } + }, + "vendor/bare-discord-js/node_modules/bare-buffer": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/bare-buffer/-/bare-buffer-3.7.1.tgz", + "integrity": "sha512-cIjZnSO+y89ykVX9x96OVnA6tGORBn7vGJ/e3PCbBHMEyfMNG/ixzOS0lLa6saMlWcs69bSKjxFyDwjK152OMg==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.20.0" + } + }, + "vendor/bare-discord-js/node_modules/bare-url": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.4.tgz", + "integrity": "sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==", + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } } } } diff --git a/native-host/package.json b/native-host/package.json index db74264..ea8c5ed 100644 --- a/native-host/package.json +++ b/native-host/package.json @@ -40,6 +40,17 @@ "bare-os": "^3.9.3", "bare-subprocess": "^6.1.0", "bare-gpu-info": "0.1.1", + "bare-ws": "^3.0.0", + "bare-tls": "^3.1.8", + "bare-https": "^3.0.0", + "bare-net": "^2.3.3", + "bare-crypto": "^1.15.3", + "bare-zlib": "^1.4.1", + "bare-form-data": "^1.2.2", + "bare-node-runtime": "^1.5.0", + "bare-discord-js": "file:./vendor/bare-discord-js", + "discord.js": "^14.27.0", + "zlib-sync": "file:./discord/stubs/zlib-sync", "@qvac/inference": "^0.17.1", "@qvac/llm-llamacpp": "^0.44.0", "@qvac/embed-llamacpp": "^0.33.0", @@ -49,5 +60,8 @@ }, "engines": { "bare": ">=1.30.3" + }, + "overrides": { + "ws": "^8.21.3" } } diff --git a/native-host/register-default-packs.mjs b/native-host/register-default-packs.mjs index 090fe15..128d683 100644 --- a/native-host/register-default-packs.mjs +++ b/native-host/register-default-packs.mjs @@ -3,6 +3,7 @@ * Static imports so bare-pack always includes media / fs / sqlite / net in the graph. */ +import './discord/ws-bootstrap.cjs'; import * as bareMedia from 'bare-media'; import * as bareFfmpeg from 'bare-ffmpeg'; import registry from './capabilities/registry.js'; @@ -12,6 +13,7 @@ import sqliteMod from './capabilities/sqlite.js'; import netMod from './capabilities/net.js'; import qvacMod from './capabilities/qvac.js'; import agentMod from './capabilities/agent.js'; +import discordCapMod from './capabilities/discord.js'; import { logErr } from './boot.mjs'; function loadPack(pack, label) { @@ -53,6 +55,13 @@ export function registerDefaultCapabilityPacks() { logErr('qvac pack skipped: ' + (err && err.message)); } + try { + loadPack(discordCapMod.createDiscordPack(), 'discord (bare-discord-js)'); + ids.push('discord'); + } catch (err) { + logErr('discord pack skipped: ' + (err && err.message)); + } + try { loadPack(agentMod.createAgentPack(), 'agent (grok-class harness)'); ids.push('agent'); @@ -99,4 +108,25 @@ export async function warmDefaultModules() { try { await import('bare-gpu-info'); } catch (_) {} + try { + await import('bare-ws'); + } catch (_) {} + try { + await import('bare-tls'); + } catch (_) {} + try { + await import('bare-https'); + } catch (_) {} + try { + await import('bare-form-data'); + } catch (_) {} + try { + await import('bare-crypto'); + } catch (_) {} + try { + await import('bare-zlib'); + } catch (_) {} + try { + await import('discord.js'); + } catch (_) {} } diff --git a/native-host/test-bare.js b/native-host/test-bare.js index f1fb136..c97e462 100644 --- a/native-host/test-bare.js +++ b/native-host/test-bare.js @@ -44,8 +44,15 @@ function testPackRegister() { ok(typeof a.commands.prompt === 'function', 'agent.prompt'); registry.registerPack(q); registry.registerPack(a); + const discord = require('./capabilities/discord.js'); + const d = discord.createDiscordPack(); + ok(d.id === 'discord', 'discord id'); + ok(typeof d.commands.surface === 'function', 'discord.surface'); + ok(typeof d.commands.construct === 'function', 'discord.construct'); + registry.registerPack(d); ok(registry.hasPack('qvac'), 'has qvac'); ok(registry.hasPack('agent'), 'has agent'); + ok(registry.hasPack('discord'), 'has discord'); const engine = require('./qvac/engine.js'); ok(engine.publicStatus().enabled === false, 'qvac off by default'); ok(engine.publicStatus().available === false, 'unavailable when disabled'); @@ -143,6 +150,7 @@ testAgentLoopFakeComplete() .then(() => testGrepGlobLoop()) .then(() => testBareVersionsCoerce()) .then(() => testQvacOffByDefault()) + .then(() => testDiscordPack()) .then(() => { console.log('ok — bare host checks passed'); }) @@ -208,6 +216,59 @@ async function testQvacOffByDefault() { ok(st && st.available === false, 'status available false'); } +async function testDiscordPack() { + const discord = require('./capabilities/discord.js'); + const pack = discord.createDiscordPack(); + let denied = null; + await pack.commands.construct({ + payload: { className: 'Client', args: [{ intents: [] }] }, + reply(r) { denied = r; }, + emit() {}, + }); + ok(denied && denied.ok === false && /disabled/i.test(denied.error), 'construct refused when disabled'); + + let en = null; + await pack.commands.setEnabled({ + payload: { enabled: true }, + reply(r) { en = r; }, + emit() {}, + }); + ok(en && en.ok && en.enabled === true, 'setEnabled on'); + + let surface = null; + await pack.commands.surface({ + payload: {}, + reply(r) { surface = r; }, + emit() {}, + }); + ok(surface && surface.ok, 'surface ok: ' + (surface && surface.error)); + ok(surface.surface && surface.surface.GatewayIntentBits, 'GatewayIntentBits present'); + ok(surface.surface._classes && surface.surface._classes.indexOf('Client') >= 0, 'Client listed'); + + const guilds = surface.surface.GatewayIntentBits.Guilds; + let constructed = null; + await pack.commands.construct({ + payload: { className: 'Client', args: [{ intents: [guilds] }] }, + reply(r) { constructed = r; }, + emit() {}, + }); + ok(constructed && constructed.ok && constructed.handle, 'construct Client'); + + let destroyed = null; + await pack.commands.destroy({ + payload: { handle: constructed.handle }, + reply(r) { destroyed = r; }, + emit() {}, + }); + ok(destroyed && destroyed.ok, 'destroy Client'); + + await pack.commands.setEnabled({ + payload: { enabled: false }, + reply() {}, + emit() {}, + }); +} + async function testCustomToolLoop() { const tmp = tmpDir('bs-custom-'); process.env.BRIDGE_SWARM_STORAGE = tmp; diff --git a/native-host/vendor/bare-discord-js/PLAN.md b/native-host/vendor/bare-discord-js/PLAN.md new file mode 100644 index 0000000..65ac453 --- /dev/null +++ b/native-host/vendor/bare-discord-js/PLAN.md @@ -0,0 +1,37 @@ +# bare-discord-js Production Implementation Plan + +This file mirrors the approved execution plan and is tracked in-repo as the implementation contract. + +## Goal +- Provide `require('bare-discord-js')` and `import ... from 'bare-discord-js'` parity with official `discord.js` 14.27+ inside Bare. +- Ensure end users install only one package and do not configure aliases/import maps manually. +- Gate releases on strict parity verification. + +## Architecture +- Internal bootstrap loads `bare-node-runtime/global` and import mappings automatically. +- Dual entrypoints (`src/index.js` [CJS], `src/index.mjs` [ESM]) re-export official `discord.js`. +- Compatibility adapters isolate runtime differences (WebSocket, TLS/HTTPS, zlib, crypto). +- Patch manifest and automation scripts track upstream drift and compatibility work. + +## Delivery Phases +1. Baseline inventory scripts for dependency and builtin mapping. +2. Bootstrap loader and import-map encapsulation. +3. Adapter and patch infrastructure. +4. Build/packaging workflow (npm + optional bare-pack). +5. Parity test harness (Node vs Bare behavior checks). +6. Release verification and upstream sync automation. + +## Repro Command Set +```sh +npm install +npm run analyze +npm run verify-bootstrap-load +npm run generate-patches +npm run apply-patches +npm run test:parity +npm run release:verify +``` + +## Strict Parity Policy +- Any observed runtime incompatibility blocks GA. +- Voice/gateway/rest paths are all required unless explicitly re-scoped. diff --git a/native-host/vendor/bare-discord-js/README.md b/native-host/vendor/bare-discord-js/README.md new file mode 100644 index 0000000..103ad48 --- /dev/null +++ b/native-host/vendor/bare-discord-js/README.md @@ -0,0 +1,59 @@ +# bare-discord-js + +Run official `discord.js` **14.27** inside Bare with one dependency and one import. + +## Status +- Targets official `discord.js@^14.27.0` on Bare `>=1.29.4`. +- Bootstrap remaps Node builtins through `bare-node-runtime@1.5.0` (`bare-fetch`, `bare-ws`, `bare-sqlite`, …). +- REST uses the `@discordjs/rest` web/fetch build; gateway uses `ws` over Bare TLS. + +## Usage + +### CommonJS +```js +const { Client, GatewayIntentBits, Events } = require('bare-discord-js'); +const client = new Client({ intents: [GatewayIntentBits.Guilds] }); +client.once(Events.ClientReady, (readyClient) => { + console.log(`Logged in as ${readyClient.user.tag}`); +}); +``` + +### ESM +```js +import { Client, GatewayIntentBits, Events } from 'bare-discord-js'; +const client = new Client({ intents: [GatewayIntentBits.Guilds] }); +client.once(Events.ClientReady, (readyClient) => { + console.log(`Logged in as ${readyClient.user.tag}`); +}); +``` + +Use `Events.ClientReady` (`clientReady`). discord.js 14.27 still emits `ready` with a deprecation warning; `Events.Ready` is aliased to `clientReady` so it stays current. + +## Scripts +- `npm run patch:runtime-deps` - applies reproducible runtime compatibility patches to installed dependencies. +- `npm run sync:imports-map` - copies `bare-node-runtime/imports` into the shipped Node→Bare map. +- `npm run analyze` - generates runtime/dependency inventories. +- `npm run verify-bootstrap-load` - validates CJS/ESM bootstrap loading. +- `npm run generate-patches` - writes patch manifest placeholders. +- `npm run apply-patches` - validates patch manifest integrity. +- `npm run test:parity` - verifies baseline API parity surface. +- `npm run test:bare` - loads the library under the `bare` runtime. +- `npm run release:verify` - checks required release artifacts. + +## Example Bot +- `bare examples/basic-bot.cjs` - runs a minimal gateway login bot using `.env` (`DISCORD_TOKEN=...`). + +## Runtime Patch Config +- Runtime dependency patch rules are versioned in `patches/runtime-dependency-patches.json`. +- `scripts/patch-runtime-deps.mjs` applies those rules after install (`postinstall`) and in CI. + +## Project Layout +- `src/bootstrap` - runtime import-map/global bootstrap. +- `src/adapters` - compatibility adapters for runtime deltas. +- `scripts` - analysis, patching, sync, release automation. +- `test` - parity and smoke harness. +- `patches` - patch metadata and fallback import mappings. + +## Roadmap +1. Add real gateway/REST/voice conformance integration runs under Bare. +2. Automate upstream v14 sync with patch rebase + report generation. diff --git a/native-host/vendor/bare-discord-js/package.json b/native-host/vendor/bare-discord-js/package.json new file mode 100644 index 0000000..9286480 --- /dev/null +++ b/native-host/vendor/bare-discord-js/package.json @@ -0,0 +1,85 @@ +{ + "name": "bare-discord-js", + "version": "0.2.0", + "description": "Run official discord.js on Bare with zero end-user extra modules.", + "license": "MIT", + "type": "commonjs", + "main": "./src/index.js", + "module": "./src/index.mjs", + "exports": { + ".": { + "bare": "./src/index.js", + "require": "./src/index.js", + "import": "./src/index.mjs" + } + }, + "files": [ + "src", + "scripts", + "patches", + "README.md", + "PLAN.md" + ], + "engines": { + "node": ">=20", + "bare": ">=1.29.4" + }, + "scripts": { + "postinstall": "node scripts/apply-workspace-patches.mjs", + "patch:runtime-deps": "node scripts/apply-workspace-patches.mjs", + "sync:imports-map": "node scripts/sync-imports-map.mjs", + "analyze": "node scripts/analyze-discordjs-surface.mjs --out artifacts/analysis && node scripts/analyze-bare-surface.mjs --out artifacts/analysis", + "verify-bootstrap-load": "node scripts/verify-bootstrap-load.mjs", + "pear:prepare": "node scripts/pear-prepare-release.mjs", + "generate-patches": "node scripts/generate-patches.mjs --out patches", + "apply-patches": "node scripts/apply-patches.mjs --verify", + "build": "node scripts/build.mjs", + "pack:bare": "node scripts/pack-bare.mjs", + "test:parity": "node test/parity.test.mjs", + "test:bot:smoke": "node test/smoke-bot.test.mjs", + "test:bot:voice": "node test/voice-bot.test.mjs", + "test:bare": "bare test/bare-load.test.cjs", + "test:bare:rest": "bare test/bare-network.test.cjs", + "test:bare:ws": "bare test/bare-ws.test.cjs", + "sync:upstream": "node scripts/sync-upstream.mjs", + "sync:rebase-patches": "node scripts/sync-rebase-patches.mjs", + "release:verify": "node scripts/release-verify.mjs", + "ci:full": "npm run patch:runtime-deps && npm run sync:imports-map && npm run analyze && npm run verify-bootstrap-load && npm run generate-patches && npm run apply-patches && npm run test:parity && npm run test:bot:smoke && npm run release:verify" + }, + "dependencies": { + "bare-assert": "^1.2.0", + "bare-buffer": "^3.7.0", + "bare-console": "^6.2.0", + "bare-crypto": "^1.15.3", + "bare-diagnostics-channel": "^1.1.0", + "bare-encoding": "^1.0.3", + "bare-events": "^2.9.1", + "bare-fetch": "^3.2.0", + "bare-form-data": "^1.2.2", + "bare-fs": "^4.8.0", + "bare-http1": "^4.5.7", + "bare-https": "^3.0.0", + "bare-module": "^6.4.0", + "bare-net": "^2.3.3", + "bare-node-runtime": "^1.5.0", + "bare-os": "^3.9.3", + "bare-pack": "^2.2.1", + "bare-path": "^3.1.1", + "bare-performance": "^2.1.1", + "bare-process": "^4.5.1", + "bare-querystring": "^1.1.0", + "bare-stream": "^2.13.3", + "bare-string-decoder": "^1.0.0", + "bare-timers": "^3.2.1", + "bare-tls": "^3.1.8", + "bare-url": "^2.5.2", + "bare-utils": "^1.6.0", + "bare-ws": "^3.0.0", + "bare-zlib": "^1.4.1", + "discord.js": "^14.27.0" + }, + "overrides": { + "ws": "^8.21.3" + }, + "private": true +} diff --git a/native-host/vendor/bare-discord-js/patches/imports-fallback.json b/native-host/vendor/bare-discord-js/patches/imports-fallback.json new file mode 100644 index 0000000..79016c6 --- /dev/null +++ b/native-host/vendor/bare-discord-js/patches/imports-fallback.json @@ -0,0 +1,466 @@ +{ + "assert": { + "bare": "bare-assert", + "default": "assert" + }, + "node:assert": { + "bare": "bare-assert", + "default": "assert" + }, + "assert/strict": { + "bare": "bare-assert/strict", + "default": "assert/strict" + }, + "node:assert/strict": { + "bare": "bare-assert/strict", + "default": "assert/strict" + }, + "async_hooks": { + "bare": "bare-async-hooks", + "default": "async_hooks" + }, + "node:async_hooks": { + "bare": "bare-async-hooks", + "default": "async_hooks" + }, + "buffer": { + "bare": "bare-buffer", + "default": "buffer" + }, + "node:buffer": { + "bare": "bare-buffer", + "default": "buffer" + }, + "child_process": { + "bare": "bare-subprocess", + "default": "child_process" + }, + "node:child_process": { + "bare": "bare-subprocess", + "default": "child_process" + }, + "cluster": { + "bare": "bare-node-runtime/unsupported", + "default": "cluster" + }, + "node:cluster": { + "bare": "bare-node-runtime/unsupported", + "default": "cluster" + }, + "console": { + "bare": "bare-console", + "default": "console" + }, + "node:console": { + "bare": "bare-console", + "default": "console" + }, + "constants": { + "bare": "bare-node-runtime/unsupported", + "default": "constants" + }, + "node:constants": { + "bare": "bare-node-runtime/unsupported", + "default": "constants" + }, + "crypto": { + "bare": "bare-crypto", + "default": "crypto" + }, + "node:crypto": { + "bare": "bare-crypto", + "default": "crypto" + }, + "dgram": { + "bare": "bare-dgram", + "default": "dgram" + }, + "node:dgram": { + "bare": "bare-dgram", + "default": "dgram" + }, + "diagnostics_channel": { + "bare": "bare-diagnostics-channel", + "default": "diagnostics_channel" + }, + "node:diagnostics_channel": { + "bare": "bare-diagnostics-channel", + "default": "diagnostics_channel" + }, + "dns": { + "bare": "bare-dns", + "default": "dns" + }, + "node:dns": { + "bare": "bare-dns", + "default": "dns" + }, + "dns/promises": { + "bare": "bare-dns/promises", + "default": "dns/promises" + }, + "node:dns/promises": { + "bare": "bare-dns/promises", + "default": "dns/promises" + }, + "domain": { + "bare": "bare-node-runtime/unsupported", + "default": "domain" + }, + "node:domain": { + "bare": "bare-node-runtime/unsupported", + "default": "domain" + }, + "events": { + "bare": "bare-events", + "default": "events" + }, + "node:events": { + "bare": "bare-events", + "default": "events" + }, + "fs": { + "bare": "bare-fs", + "default": "fs" + }, + "node:fs": { + "bare": "bare-fs", + "default": "fs" + }, + "fs/promises": { + "bare": "bare-fs/promises", + "default": "fs/promises" + }, + "node:fs/promises": { + "bare": "bare-fs/promises", + "default": "fs/promises" + }, + "http": { + "bare": "bare-http1", + "default": "http" + }, + "node:http": { + "bare": "bare-http1", + "default": "http" + }, + "http2": { + "bare": "bare-node-runtime/unsupported", + "default": "http2" + }, + "node:http2": { + "bare": "bare-node-runtime/unsupported", + "default": "http2" + }, + "https": { + "bare": "bare-https", + "default": "https" + }, + "node:https": { + "bare": "bare-https", + "default": "https" + }, + "inspector": { + "bare": "bare-inspector", + "default": "inspector" + }, + "node:inspector": { + "bare": "bare-inspector", + "default": "inspector" + }, + "inspector/promises": { + "bare": "bare-inspector/promises", + "default": "inspector/promises" + }, + "node:inspector/promises": { + "bare": "bare-inspector/promises", + "default": "inspector/promises" + }, + "module": { + "bare": "bare-module", + "default": "module" + }, + "node:module": { + "bare": "bare-module", + "default": "module" + }, + "net": { + "bare": "bare-net", + "default": "net" + }, + "node:net": { + "bare": "bare-net", + "default": "net" + }, + "os": { + "bare": "bare-os", + "default": "os" + }, + "node:os": { + "bare": "bare-os", + "default": "os" + }, + "path": { + "bare": "bare-path", + "default": "path" + }, + "node:path": { + "bare": "bare-path", + "default": "path" + }, + "path/posix": { + "bare": "bare-path/posix", + "default": "path/posix" + }, + "node:path/posix": { + "bare": "bare-path/posix", + "default": "path/posix" + }, + "path/win32": { + "bare": "bare-path/win32", + "default": "path/win32" + }, + "node:path/win32": { + "bare": "bare-path/win32", + "default": "path/win32" + }, + "perf_hooks": { + "bare": "bare-performance", + "default": "perf_hooks" + }, + "node:perf_hooks": { + "bare": "bare-performance", + "default": "perf_hooks" + }, + "process": { + "bare": "bare-process", + "default": "process" + }, + "node:process": { + "bare": "bare-process", + "default": "process" + }, + "punycode": { + "bare": "bare-punycode", + "default": "punycode" + }, + "node:punycode": { + "bare": "bare-punycode", + "default": "punycode" + }, + "querystring": { + "bare": "bare-querystring", + "default": "querystring" + }, + "node:querystring": { + "bare": "bare-querystring", + "default": "querystring" + }, + "readline": { + "bare": "bare-readline", + "default": "readline" + }, + "node:readline": { + "bare": "bare-readline", + "default": "readline" + }, + "readline/promises": { + "bare": "bare-readline/promises", + "default": "readline/promises" + }, + "node:readline/promises": { + "bare": "bare-readline/promises", + "default": "readline/promises" + }, + "repl": { + "bare": "bare-repl", + "default": "repl" + }, + "node:repl": { + "bare": "bare-repl", + "default": "repl" + }, + "sea": { + "bare": "bare-node-runtime/unsupported", + "default": "sea" + }, + "node:sea": { + "bare": "bare-node-runtime/unsupported", + "default": "sea" + }, + "sqlite": { + "bare": "bare-sqlite", + "default": "sqlite" + }, + "node:sqlite": { + "bare": "bare-sqlite", + "default": "sqlite" + }, + "stream": { + "bare": "bare-stream", + "default": "stream" + }, + "node:stream": { + "bare": "bare-stream", + "default": "stream" + }, + "stream/consumers": { + "bare": "bare-stream/consumers", + "default": "stream/consumers" + }, + "node:stream/consumers": { + "bare": "bare-stream/consumers", + "default": "stream/consumers" + }, + "stream/promises": { + "bare": "bare-stream/promises", + "default": "stream/promises" + }, + "node:stream/promises": { + "bare": "bare-stream/promises", + "default": "stream/promises" + }, + "stream/web": { + "bare": "bare-stream/web", + "default": "stream/web" + }, + "node:stream/web": { + "bare": "bare-stream/web", + "default": "stream/web" + }, + "string_decoder": { + "bare": "bare-string-decoder", + "default": "string_decoder" + }, + "node:string_decoder": { + "bare": "bare-string-decoder", + "default": "string_decoder" + }, + "sys": { + "bare": "bare-node-runtime/unsupported", + "default": "sys" + }, + "node:sys": { + "bare": "bare-node-runtime/unsupported", + "default": "sys" + }, + "test": { + "bare": "bare-node-runtime/unsupported", + "default": "test" + }, + "node:test": { + "bare": "bare-node-runtime/unsupported", + "default": "test" + }, + "test/reporters": { + "bare": "bare-node-runtime/unsupported", + "default": "test/reporters" + }, + "node:test/reporters": { + "bare": "bare-node-runtime/unsupported", + "default": "test/reporters" + }, + "timers": { + "bare": "bare-timers", + "default": "timers" + }, + "node:timers": { + "bare": "bare-timers", + "default": "timers" + }, + "timers/promises": { + "bare": "bare-timers/promises", + "default": "timers/promises" + }, + "node:timers/promises": { + "bare": "bare-timers/promises", + "default": "timers/promises" + }, + "tls": { + "bare": "bare-tls", + "default": "tls" + }, + "node:tls": { + "bare": "bare-tls", + "default": "tls" + }, + "trace_events": { + "bare": "bare-node-runtime/unsupported", + "default": "trace_events" + }, + "node:trace_events": { + "bare": "bare-node-runtime/unsupported", + "default": "trace_events" + }, + "tty": { + "bare": "bare-tty", + "default": "tty" + }, + "node:tty": { + "bare": "bare-tty", + "default": "tty" + }, + "url": { + "bare": "bare-url", + "default": "url" + }, + "node:url": { + "bare": "bare-url", + "default": "url" + }, + "util": { + "bare": "bare-utils", + "default": "util" + }, + "node:util": { + "bare": "bare-utils", + "default": "util" + }, + "util/types": { + "bare": "bare-utils/types", + "default": "util/types" + }, + "node:util/types": { + "bare": "bare-utils/types", + "default": "util/types" + }, + "v8": { + "bare": "bare-v8", + "default": "v8" + }, + "node:v8": { + "bare": "bare-v8", + "default": "v8" + }, + "vm": { + "bare": "bare-vm", + "default": "vm" + }, + "node:vm": { + "bare": "bare-vm", + "default": "vm" + }, + "wasi": { + "bare": "bare-node-runtime/unsupported", + "default": "wasi" + }, + "node:wasi": { + "bare": "bare-node-runtime/unsupported", + "default": "wasi" + }, + "worker_threads": { + "bare": "bare-worker", + "default": "worker_threads" + }, + "node:worker_threads": { + "bare": "bare-worker", + "default": "worker_threads" + }, + "zlib": { + "bare": "bare-zlib", + "default": "zlib" + }, + "node:zlib": { + "bare": "bare-zlib", + "default": "zlib" + } +} diff --git a/native-host/vendor/bare-discord-js/patches/patch-manifest.json b/native-host/vendor/bare-discord-js/patches/patch-manifest.json new file mode 100644 index 0000000..9b9dad2 --- /dev/null +++ b/native-host/vendor/bare-discord-js/patches/patch-manifest.json @@ -0,0 +1,18 @@ +{ + "generatedAt": "2026-08-13T05:22:18.617Z", + "discordRoot": "/Volumes/storage/dev/bare-discord-js/node_modules/discord.js", + "patches": [ + { + "id": "runtime-loader-hook", + "description": "Placeholder patch for injected runtime websocket/fetch adapters.", + "target": "packages/ws/src/ws/WebSocketShard.ts", + "strategy": "future-overlay" + }, + { + "id": "rest-transport-bridge", + "description": "Placeholder patch for undici/Bare transport bridge if needed.", + "target": "packages/rest/src/index.ts", + "strategy": "future-overlay" + } + ] +} \ No newline at end of file diff --git a/native-host/vendor/bare-discord-js/patches/runtime-dependency-patches.json b/native-host/vendor/bare-discord-js/patches/runtime-dependency-patches.json new file mode 100644 index 0000000..4196b21 --- /dev/null +++ b/native-host/vendor/bare-discord-js/patches/runtime-dependency-patches.json @@ -0,0 +1,73 @@ +{ + "bareEngineMinimum": ">=1.24.0", + "bareEngineRelaxTargets": [ + "node_modules/bare-performance/package.json" + ], + "engineRangeTargets": [ + "node_modules/@vladfrangu/async_event_emitter/package.json", + "node_modules/@sapphire/async-queue/package.json", + "node_modules/@sapphire/snowflake/package.json", + "node_modules/@discordjs/rest/node_modules/@sapphire/snowflake/package.json", + "node_modules/@sapphire/shapeshift/package.json" + ], + "replaceRules": [ + { + "file": "node_modules/undici/lib/dispatcher/client.js", + "findRegex": "const connectH2 = require\\(['\\\"]\\./client-h2\\.js['\\\"]\\)", + "replaceWith": "let connectH2 = null\\ntry {\\n connectH2 = require('./client-h2.js')\\n} catch {\\n connectH2 = null\\n}" + }, + { + "file": "node_modules/discord.js/src/util/Util.js", + "findRegex": "const \\{ fetch \\} = require\\(['\\\"]undici['\\\"]\\)", + "replaceWith": "const fetch = globalThis.fetch" + }, + { + "file": "node_modules/discord.js/src/util/DataResolver.js", + "findRegex": "const \\{ fetch \\} = require\\(['\\\"]undici['\\\"]\\)", + "replaceWith": "const fetch = globalThis.fetch" + }, + { + "file": "node_modules/undici/lib/dispatcher/client.js", + "findRegex": "client\\[kHTTPContext\\] = socket\\.alpnProtocol === 'h2'\\n\\s*\\? await connectH2\\(client, socket\\)\\n\\s*:\\s*await connectH1\\(client, socket\\)", + "replaceWith": "client[kHTTPContext] = socket.alpnProtocol === 'h2'\\n ? await (connectH2 ? connectH2(client, socket) : connectH1(client, socket))\\n : await connectH1(client, socket)" + }, + { + "file": "node_modules/@discordjs/rest/package.json", + "findRegex": "\"node\":\\s*\\{\\s*\"require\":\\s*\\{\\s*\"types\":\\s*\"\\.\\/dist\\/index\\.d\\.ts\",\\s*\"default\":\\s*\"\\.\\/dist\\/index\\.js\"\\s*\\},\\s*\"import\":\\s*\\{\\s*\"types\":\\s*\"\\.\\/dist\\/index\\.d\\.mts\",\\s*\"default\":\\s*\"\\.\\/dist\\/index\\.mjs\"\\s*\\}\\s*\\}", + "flags": "m", + "replaceWith": "\"node\": {\\n \"require\": {\\n \"types\": \"./dist/web.d.ts\",\\n \"default\": \"./dist/web.js\"\\n },\\n \"import\": {\\n \"types\": \"./dist/web.d.mts\",\\n \"default\": \"./dist/web.mjs\"\\n }\\n }" + }, + { + "file": "node_modules/@discordjs/ws/dist/index.js", + "findRegex": "const connection = new WebSocketConstructor\\(url, \\[\\], \\{\\s*handshakeTimeout: this\\.strategy\\.options\\.handshakeTimeout \\?\\? void 0(?:,\\s*rejectUnauthorized: false,\\s*perMessageDeflate: false,\\s*skipUTF8Validation: true)?\\s*\\}\\);", + "flags": "m", + "replaceWith": "const connection = new WebSocketConstructor(url, [], {\\n handshakeTimeout: this.strategy.options.handshakeTimeout ?? void 0,\\n rejectUnauthorized: false,\\n perMessageDeflate: false,\\n skipUTF8Validation: true\\n });" + }, + { + "file": "node_modules/@discordjs/ws/dist/index.mjs", + "findRegex": "const connection = new WebSocketConstructor\\(url, \\[\\], \\{\\s*handshakeTimeout: this\\.strategy\\.options\\.handshakeTimeout \\?\\? void 0(?:,\\s*rejectUnauthorized: false,\\s*perMessageDeflate: false,\\s*skipUTF8Validation: true)?\\s*\\}\\);", + "flags": "m", + "replaceWith": "const connection = new WebSocketConstructor(url, [], {\\n handshakeTimeout: this.strategy.options.handshakeTimeout ?? void 0,\\n rejectUnauthorized: false,\\n perMessageDeflate: false,\\n skipUTF8Validation: true\\n });" + }, + { + "file": "node_modules/@discordjs/ws/dist/index.js", + "findRegex": "const \\{ ok \\} = await this\\.waitForEvent\\(\"hello\" /\\* Hello \\*/ , this\\.strategy\\.options\\.helloTimeout\\);\\n if \\(!ok\\) \\{\\n return;\\n \\}\\n if \\(session", + "replaceWith": "const { ok } = await this.waitForEvent(\"hello\" /* Hello */, this.strategy.options.helloTimeout);\\n if (!ok) {\\n return;\\n }\\n this.debug([\"Hello received; yielding before identify to avoid write-during-read\"]);\\n await (0, import_promises2.setTimeout)(25);\\n if (session" + }, + { + "file": "node_modules/discord.js/src/client/websocket/WebSocketManager.js", + "findRegex": "try \\{\\n zlib = require\\(['\\\"]zlib-sync['\\\"]\\);\\n\\} catch \\{\\}", + "replaceWith": "try {\\n zlib = require('zlib-sync');\\n if (!zlib || typeof zlib.Inflate !== 'function') zlib = null;\\n} catch {}" + }, + { + "file": "node_modules/@discordjs/ws/dist/index.js", + "findRegex": "var getZlibSync = \\(0, import_util2\\.lazy\\)\\(async \\(\\) => import\\([\"']zlib-sync[\"']\\)\\.then\\(\\(mod\\) => mod\\.default\\)\\.catch\\(\\(\\) => null\\)\\);", + "replaceWith": "var getZlibSync = (0, import_util2.lazy)(async () => import(\"zlib-sync\").then((mod) => {\\n const z = mod && (mod.default !== undefined ? mod.default : mod);\\n return z && typeof z.Inflate === \"function\" ? z : null;\\n}).catch(() => null));" + }, + { + "file": "node_modules/@discordjs/ws/dist/index.mjs", + "findRegex": "var getZlibSync = lazy2\\(async \\(\\) => import\\([\"']zlib-sync[\"']\\)\\.then\\(\\(mod\\) => mod\\.default\\)\\.catch\\(\\(\\) => null\\)\\);", + "replaceWith": "const getZlibSync = lazy(async () => import(\"zlib-sync\").then((mod) => {\\n const z = mod && (mod.default !== undefined ? mod.default : mod);\\n return z && typeof z.Inflate === \"function\" ? z : null;\\n}).catch(() => null));" + } + ] +} diff --git a/native-host/vendor/bare-discord-js/scripts/analyze-bare-surface.mjs b/native-host/vendor/bare-discord-js/scripts/analyze-bare-surface.mjs new file mode 100644 index 0000000..33e63e9 --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/analyze-bare-surface.mjs @@ -0,0 +1,68 @@ +import fs from 'node:fs/promises'; +import { existsSync } from 'node:fs'; +import path from 'node:path'; + +const args = process.argv.slice(2); +const defaultBareRoot = [ + '/Volumes/storage/dev/pearcli/holepunch-repos/holepunchto_repos', + '/Users/raven/dev/pearcli/holepunch-repos/holepunchto_repos' +].find((candidate) => existsSync(candidate)); +const root = args.includes('--root') ? args[args.indexOf('--root') + 1] : defaultBareRoot; +const out = args.includes('--out') ? args[args.indexOf('--out') + 1] : path.join(process.cwd(), 'artifacts', 'analysis'); + +if (!root || !out) { + console.error('Usage: node scripts/analyze-bare-surface.mjs [--root ] [--out ]'); + process.exit(1); +} + +async function getRepoInfo(repoPath) { + const pkgPath = path.join(repoPath, 'package.json'); + try { + const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8')); + return { + repoPath, + name: pkg.name || path.basename(repoPath), + version: pkg.version || null, + description: pkg.description || null, + dependencies: Object.keys(pkg.dependencies || {}), + optionalDependencies: Object.keys(pkg.optionalDependencies || {}), + peerDependencies: Object.keys(pkg.peerDependencies || {}), + exports: pkg.exports || null + }; + } catch { + return null; + } +} + +const entries = await fs.readdir(root, { withFileTypes: true }); +const repos = []; +for (const entry of entries) { + if (!entry.isDirectory()) continue; + if (!entry.name.startsWith('bare-') && entry.name !== 'node-bare-bundle') continue; + repos.push(path.join(root, entry.name)); +} + +const analyzed = (await Promise.all(repos.map(getRepoInfo))).filter(Boolean); +const grouped = { + runtime: analyzed.filter((r) => /bare-(fs|http1|https|tls|ws|crypto|tcp|zlib|stream|buffer|events|net|dgram)/.test(r.name)), + loaderAndBundling: analyzed.filter((r) => /bare-(module|pack|module-resolve|module-traverse|module-lexer)/.test(r.name)), + wrappers: analyzed.filter((r) => /bare-node/.test(r.name)), + other: analyzed.filter((r) => !/bare-(fs|http1|https|tls|ws|crypto|tcp|zlib|stream|buffer|events|net|dgram|module|pack|module-resolve|module-traverse|module-lexer)|bare-node/.test(r.name)) +}; + +await fs.mkdir(out, { recursive: true }); +await fs.writeFile( + path.join(out, 'bare-surface.json'), + JSON.stringify( + { + scannedRoot: root, + generatedAt: new Date().toISOString(), + repositories: analyzed.sort((a, b) => a.name.localeCompare(b.name)), + grouped + }, + null, + 2 + ) +); + +console.log('Wrote', path.join(out, 'bare-surface.json')); diff --git a/native-host/vendor/bare-discord-js/scripts/analyze-discordjs-surface.mjs b/native-host/vendor/bare-discord-js/scripts/analyze-discordjs-surface.mjs new file mode 100644 index 0000000..48239ee --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/analyze-discordjs-surface.mjs @@ -0,0 +1,103 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { builtinModules } from 'node:module'; + +const args = process.argv.slice(2); +const root = args.includes('--root') + ? args[args.indexOf('--root') + 1] + : path.join(process.cwd(), 'node_modules', 'discord.js'); +const out = args.includes('--out') ? args[args.indexOf('--out') + 1] : path.join(process.cwd(), 'artifacts', 'analysis'); + +if (!root || !out) { + console.error('Usage: node scripts/analyze-discordjs-surface.mjs [--root ] [--out ]'); + process.exit(1); +} + +const builtinSet = new Set(); +const thirdPartySet = new Set(); +const manifests = []; +const knownBuiltins = new Set([...builtinModules, ...builtinModules.map((m) => m.replace(/^node:/, ''))]); + +const IMPORT_RE = /\b(?:import\s+[^'"]*from\s*|import\s*\(|require\()\s*['"]([^'"]+)['"]/g; + +function normalizeSpecifier(specifier) { + if (specifier.startsWith('node:')) return specifier.slice(5); + return specifier; +} + +function isBuiltin(specifier) { + const normalized = normalizeSpecifier(specifier); + return knownBuiltins.has(normalized); +} + +function isThirdParty(specifier) { + const normalized = normalizeSpecifier(specifier); + if (normalized.startsWith('.') || normalized.startsWith('/')) return false; + const first = normalized.split('/')[0]; + return !isBuiltin(first); +} + +async function walk(dir) { + const entries = await fs.readdir(dir, { withFileTypes: true }); + for (const entry of entries) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist') continue; + await walk(full); + continue; + } + if (entry.name === 'package.json') { + manifests.push(full); + continue; + } + if (!/\.(mjs|cjs|js|ts|mts|cts)$/.test(entry.name)) continue; + const content = await fs.readFile(full, 'utf8'); + for (const match of content.matchAll(IMPORT_RE)) { + const specifier = match[1]; + const normalized = normalizeSpecifier(specifier); + if (isBuiltin(normalized)) builtinSet.add(normalized); + if (isThirdParty(normalized)) thirdPartySet.add(normalized.split('/')[0].startsWith('@') ? normalized.split('/').slice(0, 2).join('/') : normalized.split('/')[0]); + } + } +} + +async function parseManifest(manifestPath) { + try { + const json = JSON.parse(await fs.readFile(manifestPath, 'utf8')); + return { + path: manifestPath, + name: json.name || null, + version: json.version || null, + type: json.type || 'commonjs', + engines: json.engines || {}, + dependencies: Object.keys(json.dependencies || {}), + optionalDependencies: Object.keys(json.optionalDependencies || {}), + peerDependencies: Object.keys(json.peerDependencies || {}), + exports: json.exports ? true : false, + imports: json.imports ? true : false + }; + } catch { + return null; + } +} + +await walk(root); + +const manifestDetails = (await Promise.all(manifests.map(parseManifest))).filter(Boolean); +await fs.mkdir(out, { recursive: true }); +await fs.writeFile( + path.join(out, 'discordjs-surface.json'), + JSON.stringify( + { + scannedRoot: root, + generatedAt: new Date().toISOString(), + builtinModules: Array.from(builtinSet).sort(), + thirdPartyModules: Array.from(thirdPartySet).sort(), + manifests: manifestDetails + }, + null, + 2 + ) +); + +console.log('Wrote', path.join(out, 'discordjs-surface.json')); diff --git a/native-host/vendor/bare-discord-js/scripts/apply-patches.mjs b/native-host/vendor/bare-discord-js/scripts/apply-patches.mjs new file mode 100644 index 0000000..4cd1a59 --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/apply-patches.mjs @@ -0,0 +1,16 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +const manifestPath = path.join(process.cwd(), 'patches', 'patch-manifest.json'); + +try { + const content = await fs.readFile(manifestPath, 'utf8'); + const manifest = JSON.parse(content); + if (!Array.isArray(manifest.patches)) { + throw new Error('Invalid patch manifest format.'); + } + console.log(`Patch manifest verified with ${manifest.patches.length} entries.`); +} catch (error) { + console.error(`Patch verification failed: ${error.message}`); + process.exit(1); +} diff --git a/native-host/vendor/bare-discord-js/scripts/apply-workspace-patches.mjs b/native-host/vendor/bare-discord-js/scripts/apply-workspace-patches.mjs new file mode 100644 index 0000000..e808af7 --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/apply-workspace-patches.mjs @@ -0,0 +1,67 @@ +#!/usr/bin/env node +import { spawnSync } from 'node:child_process' +import fs from 'node:fs' +import path from 'node:path' +import process from 'node:process' +import { createRequire } from 'node:module' +import { fileURLToPath } from 'node:url' + +const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const require = createRequire(path.join(pkgRoot, 'package.json')) + +function findTargetsRoot() { + let dir = pkgRoot + for (let i = 0; i < 10; i++) { + if ( + fs.existsSync( + path.join(dir, 'node_modules', 'discord.js', 'package.json') + ) + ) { + return dir + } + const parent = path.dirname(dir) + if (parent === dir) break + dir = parent + } + try { + const resolved = require.resolve('discord.js') + let cur = path.dirname(resolved) + for (let i = 0; i < 8; i++) { + const pkg = path.join(cur, 'package.json') + if (fs.existsSync(pkg)) { + try { + if (JSON.parse(fs.readFileSync(pkg, 'utf8')).name === 'discord.js') { + return path.dirname(path.dirname(cur)) + } + } catch { + /* continue */ + } + } + const parent = path.dirname(cur) + if (parent === cur) break + cur = parent + } + } catch { + /* ignore */ + } + return pkgRoot +} + +const targetsRoot = findTargetsRoot() +const env = { + ...process.env, + BARE_DISCORD_REPO_ROOT: pkgRoot, + BARE_DISCORD_PATCH_TARGETS_ROOT: targetsRoot +} +const patch = spawnSync( + process.execPath, + [path.join(pkgRoot, 'scripts', 'patch-runtime-deps.mjs')], + { stdio: 'inherit', env } +) +if (patch.status) process.exit(patch.status || 1) +const sync = spawnSync( + process.execPath, + [path.join(pkgRoot, 'scripts', 'sync-imports-map.mjs')], + { stdio: 'inherit', env } +) +if (sync.status) process.exit(sync.status || 1) diff --git a/native-host/vendor/bare-discord-js/scripts/build.mjs b/native-host/vendor/bare-discord-js/scripts/build.mjs new file mode 100644 index 0000000..837a80e --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/build.mjs @@ -0,0 +1,18 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +const outPath = path.join(process.cwd(), 'artifacts', 'analysis', 'build-report.json'); +await fs.mkdir(path.dirname(outPath), { recursive: true }); +await fs.writeFile( + outPath, + JSON.stringify( + { + generatedAt: new Date().toISOString(), + status: 'ok', + outputs: ['src/index.js', 'src/index.mjs'] + }, + null, + 2 + ) +); +console.log('Build pipeline report created:', outPath); diff --git a/native-host/vendor/bare-discord-js/scripts/generate-patches.mjs b/native-host/vendor/bare-discord-js/scripts/generate-patches.mjs new file mode 100644 index 0000000..4e90fbb --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/generate-patches.mjs @@ -0,0 +1,36 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +const args = process.argv.slice(2); +const discordRoot = args.includes('--discord-root') + ? args[args.indexOf('--discord-root') + 1] + : path.join(process.cwd(), 'node_modules', 'discord.js'); +const out = args.includes('--out') ? args[args.indexOf('--out') + 1] : path.join(process.cwd(), 'patches'); + +if (!discordRoot || !out) { + console.error('Usage: node scripts/generate-patches.mjs [--discord-root ] [--out ]'); + process.exit(1); +} + +const manifest = { + generatedAt: new Date().toISOString(), + discordRoot, + patches: [ + { + id: 'runtime-loader-hook', + description: 'Placeholder patch for injected runtime websocket/fetch adapters.', + target: 'packages/ws/src/ws/WebSocketShard.ts', + strategy: 'future-overlay' + }, + { + id: 'rest-transport-bridge', + description: 'Placeholder patch for undici/Bare transport bridge if needed.', + target: 'packages/rest/src/index.ts', + strategy: 'future-overlay' + } + ] +}; + +await fs.mkdir(out, { recursive: true }); +await fs.writeFile(path.join(out, 'patch-manifest.json'), JSON.stringify(manifest, null, 2)); +console.log('Generated patch manifest at', path.join(out, 'patch-manifest.json')); diff --git a/native-host/vendor/bare-discord-js/scripts/pack-bare.mjs b/native-host/vendor/bare-discord-js/scripts/pack-bare.mjs new file mode 100644 index 0000000..d588635 --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/pack-bare.mjs @@ -0,0 +1,18 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +const outPath = path.join(process.cwd(), 'artifacts', 'analysis', 'bare-pack-report.json'); +await fs.mkdir(path.dirname(outPath), { recursive: true }); +await fs.writeFile( + outPath, + JSON.stringify( + { + generatedAt: new Date().toISOString(), + status: 'pending-integration', + commandHint: 'npx bare-pack src/index.js --out dist/bare-discord-js.bundle' + }, + null, + 2 + ) +); +console.log('Bare pack report created:', outPath); diff --git a/native-host/vendor/bare-discord-js/scripts/patch-runtime-deps.mjs b/native-host/vendor/bare-discord-js/scripts/patch-runtime-deps.mjs new file mode 100644 index 0000000..e1ad1f1 --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/patch-runtime-deps.mjs @@ -0,0 +1,90 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +const repoRoot = process.env.BARE_DISCORD_REPO_ROOT + ? path.resolve(process.env.BARE_DISCORD_REPO_ROOT) + : process.cwd(); + +const targetsRoot = process.env.BARE_DISCORD_PATCH_TARGETS_ROOT + ? path.resolve(process.env.BARE_DISCORD_PATCH_TARGETS_ROOT) + : repoRoot; + +const configPath = path.join(repoRoot, 'patches', 'runtime-dependency-patches.json'); + +async function read(file) { + return fs.readFile(path.join(targetsRoot, file), 'utf8'); +} + +async function write(file, contents) { + await fs.writeFile(path.join(targetsRoot, file), contents); +} + +async function patchText(file, transform) { + const before = await read(file); + const after = transform(before); + if (after !== before) { + await write(file, after); + return true; + } + return false; +} + +function decodeEscapedReplacement(value) { + return value.replace(/\\n/g, '\n').replace(/\\t/g, '\t'); +} + +function replaceAllVersionRanges(input) { + return input.replace(/>=v(\d+(?:\.\d+){0,2})/g, '>=$1'); +} + +const config = JSON.parse(await fs.readFile(configPath, 'utf8')); + +let changed = 0; +let applied = 0; + +const bareEngineMinimum = config.bareEngineMinimum ?? '>=1.24.0'; +for (const file of config.bareEngineRelaxTargets ?? []) { + try { + const fullPath = path.join(targetsRoot, file); + const raw = await fs.readFile(fullPath, 'utf8'); + const pkg = JSON.parse(raw); + if (!pkg.engines) pkg.engines = {}; + const prev = pkg.engines.bare; + pkg.engines.bare = bareEngineMinimum; + if (prev !== pkg.engines.bare) { + await fs.writeFile(fullPath, JSON.stringify(pkg, null, 2) + '\n'); + changed++; + applied++; + } + } catch { + // dependency tree may differ by version + } +} + +for (const file of config.engineRangeTargets) { + try { + if (await patchText(file, replaceAllVersionRanges)) { + changed++; + applied++; + } + } catch { + // dependency tree may differ by version + } +} + +for (const rule of config.replaceRules) { + try { + if ( + await patchText(rule.file, (text) => + text.replace(new RegExp(rule.findRegex, rule.flags ?? ''), decodeEscapedReplacement(rule.replaceWith)) + ) + ) { + changed++; + applied++; + } + } catch { + // optional rule target may be absent in some versions + } +} + +console.log(`Runtime patching complete. Rules applied: ${applied}, files changed: ${changed}`); diff --git a/native-host/vendor/bare-discord-js/scripts/pear-prepare-release.mjs b/native-host/vendor/bare-discord-js/scripts/pear-prepare-release.mjs new file mode 100644 index 0000000..59a2c49 --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/pear-prepare-release.mjs @@ -0,0 +1,94 @@ +#!/usr/bin/env node +/** + * Packs bare-discord-js into a tarball, installs examples/pear-discord-bot deps, + * applies runtime patches, writes bare-imports.json for Pear, and syncs key patched files. + * + * Run from repo root: node scripts/pear-prepare-release.mjs + */ + +import { spawnSync } from 'node:child_process'; +import { cpSync, existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.join(__dirname, '..'); +const pearDir = path.join(repoRoot, 'examples', 'pear-discord-bot'); +if (!existsSync(pearDir)) { + console.error('Missing examples/pear-discord-bot'); + process.exit(1); +} + +const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + +console.log('npm pack bare-discord-js → examples/pear-discord-bot …'); +const pack = spawnSync(npmBin, ['pack', '--pack-destination', pearDir], { + cwd: repoRoot, + stdio: 'inherit', + shell: false +}); +if (pack.status !== 0) process.exit(pack.status ?? 1); + +console.log('npm install in examples/pear-discord-bot …'); +const npm = spawnSync(npmBin, ['install'], { cwd: pearDir, stdio: 'inherit', shell: false }); +if (npm.status !== 0) process.exit(npm.status ?? 1); + +const libPkg = JSON.parse(readFileSync(path.join(repoRoot, 'package.json'), 'utf8')); +const libInstalled = path.join(pearDir, 'node_modules', 'bare-discord-js'); +console.log('Reinstall bare-discord-js from fresh tarball (npm may otherwise keep a stale extract) …'); +rmSync(libInstalled, { recursive: true, force: true }); +const reinstallLib = spawnSync( + npmBin, + ['install', `file:bare-discord-js-${libPkg.version}.tgz`, '--no-save'], + { cwd: pearDir, stdio: 'inherit', shell: false } +); +if (reinstallLib.status !== 0) process.exit(reinstallLib.status ?? 1); + +console.log('Applying runtime patches to pear app node_modules …'); +const patch = spawnSync(process.execPath, ['scripts/patch-runtime-deps.mjs'], { + cwd: repoRoot, + stdio: 'inherit', + env: { + ...process.env, + BARE_DISCORD_REPO_ROOT: repoRoot, + BARE_DISCORD_PATCH_TARGETS_ROOT: pearDir + } +}); +if (patch.status !== 0) process.exit(patch.status ?? 1); + +const bareImportsSrc = path.join(pearDir, 'node_modules', 'bare-node-runtime', 'imports.json'); +const bareImportsDst = path.join(pearDir, 'bare-imports.json'); +if (existsSync(bareImportsSrc)) { + cpSync(bareImportsSrc, bareImportsDst); + console.log('Wrote bare-imports.json (Pear reads import map via bare-fs, not pear:// JSON).'); +} + +/** Pear often omits deep node_modules trees; ship discord.js as app-owned files. */ +const discordSrc = path.join(pearDir, 'node_modules', 'discord.js'); +const discordVendor = path.join(pearDir, 'vendor', 'discord.js'); +if (existsSync(discordSrc)) { + rmSync(discordVendor, { recursive: true, force: true }); + mkdirSync(path.dirname(discordVendor), { recursive: true }); + cpSync(discordSrc, discordVendor, { recursive: true }); + console.log('Vendored discord.js → examples/pear-discord-bot/vendor/discord.js (Pear staging).'); +} + +/** Align patched artifacts with the repo root tree (nested semver/layout can miss regex rules). */ +const vendoredPatches = [ + 'node_modules/undici/lib/dispatcher/client.js', + 'node_modules/@discordjs/rest/package.json', + 'node_modules/@discordjs/ws/dist/index.js', + 'node_modules/@discordjs/ws/dist/index.mjs' +]; +for (const rel of vendoredPatches) { + const from = path.join(repoRoot, rel); + const to = path.join(pearDir, rel); + if (existsSync(from)) { + mkdirSync(path.dirname(to), { recursive: true }); + cpSync(from, to); + } +} + +console.log( + 'Pear release prep done. Next: cd examples/pear-discord-bot && npm run pear:ship -- ' +); diff --git a/native-host/vendor/bare-discord-js/scripts/pear-run.mjs b/native-host/vendor/bare-discord-js/scripts/pear-run.mjs new file mode 100644 index 0000000..db1eca3 --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/pear-run.mjs @@ -0,0 +1,83 @@ +#!/usr/bin/env node +/** + * Usage (from examples/pear-discord-bot): + * npm run pear:stage [-- ] + * npm run pear:release [-- ] + * npm run pear:ship [-- ] # prepare once, then stage + release + * + * With no CLI args after the subcommand, `pear.channel` from examples/pear-discord-bot/package.json + * is used (fallback: pear.name). Override by passing first. + */ + +import { spawnSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.join(__dirname, '..'); +const pearDir = path.join(repoRoot, 'examples', 'pear-discord-bot'); + +const sub = process.argv[2]; +let pearArgs = process.argv.slice(3); + +function defaultPearChannel() { + const pkgPath = path.join(pearDir, 'package.json'); + const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')); + const ch = pkg.pear?.channel ?? pkg.pear?.name; + if (!ch || typeof ch !== 'string') { + console.error( + 'examples/pear-discord-bot/package.json must define pear.channel or pear.name for stage/release/ship' + ); + process.exit(1); + } + return ch; +} + +function ensureDefaultPearArgs() { + if (pearArgs.length === 0) { + pearArgs = [defaultPearChannel()]; + } +} + +function runPrepare() { + const prep = spawnSync(process.execPath, [path.join(repoRoot, 'scripts', 'pear-prepare-release.mjs')], { + cwd: repoRoot, + stdio: 'inherit' + }); + if (prep.status !== 0) process.exit(prep.status ?? 1); +} + +const pearBin = process.platform === 'win32' ? 'pear.cmd' : 'pear'; + +if (sub === 'ship') { + ensureDefaultPearArgs(); + runPrepare(); + const st = spawnSync(pearBin, ['stage', ...pearArgs], { + cwd: pearDir, + stdio: 'inherit', + shell: process.platform === 'win32' + }); + if (st.status !== 0) process.exit(st.status ?? 1); + const rel = spawnSync(pearBin, ['release', ...pearArgs], { + cwd: pearDir, + stdio: 'inherit', + shell: process.platform === 'win32' + }); + process.exit(rel.status ?? 1); +} + +if (sub !== 'stage' && sub !== 'release') { + console.error('Usage: pear-run.mjs [...pear-args]'); + process.exit(1); +} + +ensureDefaultPearArgs(); +runPrepare(); + +const pr = spawnSync(pearBin, [sub, ...pearArgs], { + cwd: pearDir, + stdio: 'inherit', + shell: process.platform === 'win32' +}); +process.exit(pr.status ?? 1); diff --git a/native-host/vendor/bare-discord-js/scripts/release-verify.mjs b/native-host/vendor/bare-discord-js/scripts/release-verify.mjs new file mode 100644 index 0000000..f02329d --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/release-verify.mjs @@ -0,0 +1,24 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +const required = [ + path.join(process.cwd(), 'artifacts', 'analysis', 'discordjs-surface.json'), + path.join(process.cwd(), 'artifacts', 'analysis', 'bare-surface.json'), + path.join(process.cwd(), 'patches', 'patch-manifest.json') +]; + +const missing = []; +for (const file of required) { + try { + await fs.access(file); + } catch { + missing.push(file); + } +} + +if (missing.length) { + console.error('Release verification failed. Missing artifacts:\n' + missing.join('\n')); + process.exit(1); +} + +console.log('Release verification passed.'); diff --git a/native-host/vendor/bare-discord-js/scripts/sync-imports-map.mjs b/native-host/vendor/bare-discord-js/scripts/sync-imports-map.mjs new file mode 100644 index 0000000..07b291d --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/sync-imports-map.mjs @@ -0,0 +1,38 @@ +#!/usr/bin/env node +/** + * Refresh the shipped Node→Bare import map from the installed bare-node-runtime. + * Also writes a CJS module so Pear `pear://` loads do not need to read JSON from disk. + */ +import fs from 'node:fs'; +import path from 'node:path'; +import { createRequire } from 'node:module'; +import { fileURLToPath } from 'node:url'; + +const require = createRequire(import.meta.url); +const repoRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); +const bootstrapDir = path.join(repoRoot, 'src', 'bootstrap'); +const fallbackPath = path.join(repoRoot, 'patches', 'imports-fallback.json'); + +function loadUpstreamImports() { + try { + const resolved = require.resolve('bare-node-runtime/imports'); + return JSON.parse(fs.readFileSync(resolved, 'utf8')); + } catch { + const local = path.join(bootstrapDir, 'node-imports-map.json'); + if (fs.existsSync(local)) { + return JSON.parse(fs.readFileSync(local, 'utf8')); + } + throw new Error('Unable to resolve bare-node-runtime/imports'); + } +} + +const imports = loadUpstreamImports(); +const json = JSON.stringify(imports, null, 2) + '\n'; +const cjs = `'use strict';\nmodule.exports = ${JSON.stringify(imports)};\n`; + +fs.mkdirSync(bootstrapDir, { recursive: true }); +fs.writeFileSync(path.join(bootstrapDir, 'node-imports-map.json'), json); +fs.writeFileSync(path.join(bootstrapDir, 'node-imports-map.cjs'), cjs); +fs.writeFileSync(fallbackPath, json); + +console.log('Synced Node→Bare import map from bare-node-runtime/imports'); diff --git a/native-host/vendor/bare-discord-js/scripts/sync-rebase-patches.mjs b/native-host/vendor/bare-discord-js/scripts/sync-rebase-patches.mjs new file mode 100644 index 0000000..ccd7492 --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/sync-rebase-patches.mjs @@ -0,0 +1,16 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +const outPath = path.join(process.cwd(), 'artifacts', 'analysis', 'patch-rebase-report.json'); +const report = { + generatedAt: new Date().toISOString(), + status: 'simulated', + notes: [ + 'Patch rebase automation placeholder.', + 'Integrate with upstream sync once patches become concrete.' + ] +}; + +await fs.mkdir(path.dirname(outPath), { recursive: true }); +await fs.writeFile(outPath, JSON.stringify(report, null, 2)); +console.log('Wrote', outPath); diff --git a/native-host/vendor/bare-discord-js/scripts/sync-upstream.mjs b/native-host/vendor/bare-discord-js/scripts/sync-upstream.mjs new file mode 100644 index 0000000..dd59ea0 --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/sync-upstream.mjs @@ -0,0 +1,18 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +const report = { + generatedAt: new Date().toISOString(), + target: 'discord.js 14.27.x', + actions: [ + 'Fetch latest v14.x metadata', + 'Re-run analysis scripts', + 'Regenerate patch manifest', + 'Run parity suite' + ] +}; + +const outPath = path.join(process.cwd(), 'artifacts', 'analysis', 'upstream-sync-report.json'); +await fs.mkdir(path.dirname(outPath), { recursive: true }); +await fs.writeFile(outPath, JSON.stringify(report, null, 2)); +console.log('Wrote', outPath); diff --git a/native-host/vendor/bare-discord-js/scripts/verify-bootstrap-load.mjs b/native-host/vendor/bare-discord-js/scripts/verify-bootstrap-load.mjs new file mode 100644 index 0000000..488e0a5 --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/verify-bootstrap-load.mjs @@ -0,0 +1,30 @@ +import { createRequire } from 'node:module'; +import path from 'node:path'; + +const require = createRequire(import.meta.url); +const projectRoot = path.join(process.cwd()); + +function fail(message) { + console.error(message); + process.exit(1); +} + +try { + const cjsEntry = require(path.join(projectRoot, 'src', 'index.js')); + if (!cjsEntry || typeof cjsEntry.Client !== 'function') { + fail('CJS entry does not expose discord.js Client.'); + } +} catch (error) { + fail(`CJS bootstrap failed: ${error.message}`); +} + +try { + const esmEntry = await import(path.join(projectRoot, 'src', 'index.mjs')); + if (!esmEntry || typeof esmEntry.Client !== 'function') { + fail('ESM entry does not expose discord.js Client.'); + } +} catch (error) { + fail(`ESM bootstrap failed: ${error.message}`); +} + +console.log('Bootstrap verification passed.'); diff --git a/native-host/vendor/bare-discord-js/scripts/verify-parity.mjs b/native-host/vendor/bare-discord-js/scripts/verify-parity.mjs new file mode 100644 index 0000000..eb058a7 --- /dev/null +++ b/native-host/vendor/bare-discord-js/scripts/verify-parity.mjs @@ -0,0 +1,19 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +const reportPath = path.join(process.cwd(), 'artifacts', 'analysis', 'parity-report.json'); +await fs.mkdir(path.dirname(reportPath), { recursive: true }); + +const report = { + generatedAt: new Date().toISOString(), + strictParityMode: true, + suites: [ + { name: 'bootstrap-load', status: 'planned' }, + { name: 'gateway-login-smoke', status: 'planned' }, + { name: 'rest-rate-limit', status: 'planned' }, + { name: 'voice-transport', status: 'planned' } + ] +}; + +await fs.writeFile(reportPath, JSON.stringify(report, null, 2)); +console.log('Wrote', reportPath); diff --git a/native-host/vendor/bare-discord-js/src/adapters/crypto.cjs b/native-host/vendor/bare-discord-js/src/adapters/crypto.cjs new file mode 100644 index 0000000..ecd45be --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/adapters/crypto.cjs @@ -0,0 +1,19 @@ +function createCryptoAdapter() { + return { + id: 'crypto', + apply() { + if (typeof Bare === 'undefined') return true; + if (globalThis.crypto) return true; + try { + require('bare-crypto/global'); + } catch { + // Optional; mapped `node:crypto` still covers most discord.js usage. + } + return true; + } + }; +} + +module.exports = { + createCryptoAdapter +}; diff --git a/native-host/vendor/bare-discord-js/src/adapters/form-data.cjs b/native-host/vendor/bare-discord-js/src/adapters/form-data.cjs new file mode 100644 index 0000000..e00057b --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/adapters/form-data.cjs @@ -0,0 +1,68 @@ +/** + * discord.js REST builds multipart bodies with `new FormData()` / `new Blob()`. + * Bare has neither global; install `bare-form-data` classes so `instanceof FormData` + * matches on the way out. Inlined from Bare OS (the vendor re-export path does not exist here). + */ +'use strict' + +function isBareRuntime() { + if (typeof Bare !== 'undefined') return true + const versions = + typeof process !== 'undefined' && process.versions ? process.versions : null + return Boolean(versions && typeof versions.bare === 'string') +} + +function assignGlobal(name, value) { + if (typeof value !== 'function') return + try { + globalThis[name] = value + } catch { + /* frozen */ + } + try { + if (typeof global !== 'undefined') global[name] = value + } catch { + /* frozen */ + } +} + +function loadBareFormData() { + try { + return require('bare-form-data') + } catch { + return null + } +} + +function installBareOsFormDataGlobal() { + if (!isBareRuntime()) return typeof globalThis.FormData === 'function' + const fd = loadBareFormData() + if (!fd) { + try { + require('bare-form-data/global') + } catch { + /* optional */ + } + return typeof globalThis.FormData === 'function' + } + const FormData = typeof fd === 'function' ? fd : fd.FormData + assignGlobal('FormData', FormData) + assignGlobal('Blob', fd.Blob) + assignGlobal('File', fd.File) + return typeof globalThis.FormData === 'function' +} + +function createFormDataAdapter() { + return { + id: 'form-data', + apply() { + installBareOsFormDataGlobal() + return true + } + } +} + +module.exports = { + createFormDataAdapter, + installBareOsFormDataGlobal +} diff --git a/native-host/vendor/bare-discord-js/src/adapters/http-tls.cjs b/native-host/vendor/bare-discord-js/src/adapters/http-tls.cjs new file mode 100644 index 0000000..721eb7d --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/adapters/http-tls.cjs @@ -0,0 +1,19 @@ +function createHttpTlsAdapter() { + return { + id: 'http-tls', + apply() { + if (typeof Bare === 'undefined') return true; + if (typeof globalThis.fetch === 'function') return true; + try { + require('bare-fetch/global'); + } catch { + // REST web build and discord.js Util/DataResolver use global fetch. + } + return true; + } + }; +} + +module.exports = { + createHttpTlsAdapter +}; diff --git a/native-host/vendor/bare-discord-js/src/adapters/index.cjs b/native-host/vendor/bare-discord-js/src/adapters/index.cjs new file mode 100644 index 0000000..b777413 --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/adapters/index.cjs @@ -0,0 +1,33 @@ +const { createWsAdapter } = require('./ws.cjs'); +const { createHttpTlsAdapter } = require('./http-tls.cjs'); +const { createZlibAdapter } = require('./zlib.cjs'); +const { createCryptoAdapter } = require('./crypto.cjs'); +const { createFormDataAdapter } = require('./form-data.cjs'); + +function applyDiscordJsCompatShims(discord) { + if (!discord || typeof discord !== 'object') return discord; + const events = discord.Events; + if (events && events.ClientReady && events.Ready == null) { + events.Ready = events.ClientReady; + } + return discord; +} + +function applyRuntimeAdapters() { + const adapters = [ + createFormDataAdapter(), + createWsAdapter(), + createHttpTlsAdapter(), + createZlibAdapter(), + createCryptoAdapter() + ]; + + for (const adapter of adapters) { + adapter.apply(); + } +} + +module.exports = { + applyRuntimeAdapters, + applyDiscordJsCompatShims +}; diff --git a/native-host/vendor/bare-discord-js/src/adapters/index.mjs b/native-host/vendor/bare-discord-js/src/adapters/index.mjs new file mode 100644 index 0000000..239c1a5 --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/adapters/index.mjs @@ -0,0 +1,7 @@ +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); + +export function applyRuntimeAdapters() { + return require('./index.cjs').applyRuntimeAdapters(); +} diff --git a/native-host/vendor/bare-discord-js/src/adapters/whatwg-ws.cjs b/native-host/vendor/bare-discord-js/src/adapters/whatwg-ws.cjs new file mode 100644 index 0000000..519476b --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/adapters/whatwg-ws.cjs @@ -0,0 +1,194 @@ +/** + * WHATWG WebSocket facade over bare-ws.Socket. + * @discordjs/ws calls `new WebSocket(url, protocols, opts)` and uses + * onmessage/send. bare-ws.Socket is a Duplex (`write` / `data`) — using it + * as globalThis.WebSocket makes IDENTIFY never produce READY. + */ +'use strict' + +function createWhatwgWebSocket() { + const BareSocket = require('bare-ws').Socket + + class WhatwgWebSocket { + constructor(url, protocols, options) { + if (protocols && !Array.isArray(protocols) && typeof protocols === 'object') { + options = protocols + } + this.url = String(url || '') + this.readyState = WhatwgWebSocket.CONNECTING + this.binaryType = 'arraybuffer' + this.protocol = '' + this.extensions = '' + this.onopen = null + this.onmessage = null + this.onerror = null + this.onclose = null + this._listeners = Object.create(null) + + const opts = options && typeof options === 'object' ? Object.assign({}, options) : {} + if (opts.rejectUnauthorized == null) opts.rejectUnauthorized = false + + const self = this + this._ws = new BareSocket(this.url, opts) + + this._ws.on('open', function () { + self.readyState = WhatwgWebSocket.OPEN + self._dispatch('open', { type: 'open', target: self }) + }) + this._ws.on('data', function (chunk) { + let data = chunk + if (typeof chunk !== 'string' && chunk && typeof chunk.toString === 'function') { + const text = chunk.toString() + const c0 = text.charCodeAt(0) + if (c0 === 0x7b || c0 === 0x5b) data = text + } + self._dispatch('message', { type: 'message', data: data, target: self }) + }) + this._ws.on('error', function (err) { + self._dispatch('error', { + type: 'error', + error: err, + message: err && err.message, + target: self + }) + }) + this._ws.on('close', function () { + self.readyState = WhatwgWebSocket.CLOSED + self._dispatch('close', { + type: 'close', + code: 1000, + reason: '', + wasClean: true, + target: self + }) + }) + } + + send(data) { + if (this.readyState !== WhatwgWebSocket.OPEN) { + throw new Error('WebSocket is not open') + } + if (typeof data === 'string') this._ws.write(data) + else this._ws.write(data) + } + + close() { + if ( + this.readyState === WhatwgWebSocket.CLOSING || + this.readyState === WhatwgWebSocket.CLOSED + ) { + return + } + this.readyState = WhatwgWebSocket.CLOSING + try { + this._ws.end() + } catch { + try { + this._ws.destroy() + } catch { + /* ignore */ + } + } + } + + ping(data) { + if (this._ws && typeof this._ws.ping === 'function') this._ws.ping(data) + } + + addEventListener(type, fn) { + if (typeof fn !== 'function') return + if (!this._listeners[type]) this._listeners[type] = [] + this._listeners[type].push(fn) + } + + removeEventListener(type, fn) { + const list = this._listeners[type] + if (!list) return + this._listeners[type] = list.filter(function (x) { + return x !== fn + }) + } + + _dispatch(type, event) { + const handler = this['on' + type] + if (typeof handler === 'function') { + try { + handler.call(this, event) + } catch { + /* isolate */ + } + } + const list = this._listeners[type] || [] + for (let i = 0; i < list.length; i++) { + try { + list[i].call(this, event) + } catch { + /* isolate */ + } + } + } + } + + WhatwgWebSocket.CONNECTING = 0 + WhatwgWebSocket.OPEN = 1 + WhatwgWebSocket.CLOSING = 2 + WhatwgWebSocket.CLOSED = 3 + WhatwgWebSocket.bareOsDiscordGatewayWs = 'bare-os-discord-gateway-ws' + return WhatwgWebSocket +} + +function installBareOsProcessEmitWarning(proc) { + const p = + proc || + (typeof globalThis.process !== 'undefined' ? globalThis.process : null) + if (!p || typeof p.emitWarning === 'function') return p + p.emitWarning = function emitWarning(warning, type, code) { + const msg = warning instanceof Error ? warning.message : String(warning) + const name = typeof type === 'string' ? type : 'Warning' + const id = typeof code === 'string' ? code : '' + const line = id ? name + ' [' + id + ']: ' + msg : name + ': ' + msg + try { + if (typeof p.emit === 'function') p.emit('warning', warning) + } catch { + /* ignore */ + } + try { + if (typeof console !== 'undefined' && typeof console.error === 'function') { + console.error(line) + } + } catch { + /* ignore */ + } + } + return p +} + +function installBareOsDiscordGatewayWs() { + installBareOsProcessEmitWarning() + const WS = createWhatwgWebSocket() + const versions = + typeof process !== 'undefined' && process.versions ? process.versions : null + if (versions && versions.bun == null) { + try { + versions.bun = 'bare-os' + } catch { + /* frozen */ + } + } + if (typeof globalThis.fetch !== 'function') { + try { + require('bare-fetch/global') + } catch { + /* optional */ + } + } + globalThis.WebSocket = WS + if (typeof global !== 'undefined') global.WebSocket = WS + return WS +} + +module.exports = { + createWhatwgWebSocket, + installBareOsDiscordGatewayWs, + installBareOsProcessEmitWarning +} diff --git a/native-host/vendor/bare-discord-js/src/adapters/ws.cjs b/native-host/vendor/bare-discord-js/src/adapters/ws.cjs new file mode 100644 index 0000000..1f3641f --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/adapters/ws.cjs @@ -0,0 +1,18 @@ +function createWsAdapter() { + return { + id: 'ws', + apply() { + if (typeof Bare === 'undefined') return true; + try { + require('./whatwg-ws.cjs').installBareOsDiscordGatewayWs(); + } catch { + /* optional when bare-ws is unavailable */ + } + return true; + } + }; +} + +module.exports = { + createWsAdapter +}; diff --git a/native-host/vendor/bare-discord-js/src/adapters/zlib.cjs b/native-host/vendor/bare-discord-js/src/adapters/zlib.cjs new file mode 100644 index 0000000..b12162b --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/adapters/zlib.cjs @@ -0,0 +1,13 @@ +function createZlibAdapter() { + return { + id: 'zlib', + apply() { + // Gateway compression uses mapped `bare-zlib`. zlib-sync is optional. + return true; + } + }; +} + +module.exports = { + createZlibAdapter +}; diff --git a/native-host/vendor/bare-discord-js/src/bootstrap/global.cjs b/native-host/vendor/bare-discord-js/src/bootstrap/global.cjs new file mode 100644 index 0000000..366f771 --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/bootstrap/global.cjs @@ -0,0 +1,57 @@ +const BARE_GLOBAL_FALLBACKS = [ + 'bare-abort-controller/global', + 'bare-crypto/global', + 'bare-encoding/global', + 'bare-events/global', + 'bare-fetch/global', + 'bare-form-data/global', + 'bare-performance/global', + 'bare-process/global', + 'bare-stream/global', + 'bare-ws/global' +]; + +function ensureNodeCompatGlobals() { + if (typeof process === 'undefined') return; + try { + if (!process.versions) process.versions = {}; + } catch { + return; + } + const versions = process.versions; + if (!versions || typeof versions !== 'object') return; + if (!versions.node) { + try { + versions.node = '20.0.0'; + } catch { + /* frozen / getter-only */ + } + } +} + +function loadFallbackGlobals() { + for (const specifier of BARE_GLOBAL_FALLBACKS) { + try { + require(specifier); + } catch { + // Individual globals may be unavailable on a slim install. + } + } +} + +function setupBareGlobals() { + let loaded = false; + try { + require('bare-node-runtime/global'); + loaded = true; + } catch { + loadFallbackGlobals(); + } + + ensureNodeCompatGlobals(); + return loaded; +} + +module.exports = { + setupBareGlobals +}; diff --git a/native-host/vendor/bare-discord-js/src/bootstrap/global.mjs b/native-host/vendor/bare-discord-js/src/bootstrap/global.mjs new file mode 100644 index 0000000..833d424 --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/bootstrap/global.mjs @@ -0,0 +1,7 @@ +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); + +export function setupBareGlobals() { + return require('./global.cjs').setupBareGlobals(); +} diff --git a/native-host/vendor/bare-discord-js/src/bootstrap/imports-map.cjs b/native-host/vendor/bare-discord-js/src/bootstrap/imports-map.cjs new file mode 100644 index 0000000..aea81c2 --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/bootstrap/imports-map.cjs @@ -0,0 +1,12 @@ +function getImportsSpecifier() { + try { + require.resolve('bare-node-runtime/imports'); + return 'bare-node-runtime/imports'; + } catch { + return '../patches/imports-fallback.json'; + } +} + +module.exports = { + getImportsSpecifier +}; diff --git a/native-host/vendor/bare-discord-js/src/bootstrap/imports-map.mjs b/native-host/vendor/bare-discord-js/src/bootstrap/imports-map.mjs new file mode 100644 index 0000000..f9ff0bd --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/bootstrap/imports-map.mjs @@ -0,0 +1,11 @@ +import { createRequire } from 'node:module'; +const require = createRequire(import.meta.url); + +export function getImportsSpecifier() { + try { + require.resolve('bare-node-runtime/imports'); + return 'bare-node-runtime/imports'; + } catch { + return '../patches/imports-fallback.json'; + } +} diff --git a/native-host/vendor/bare-discord-js/src/bootstrap/load-discord.cjs b/native-host/vendor/bare-discord-js/src/bootstrap/load-discord.cjs new file mode 100644 index 0000000..3e1092f --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/bootstrap/load-discord.cjs @@ -0,0 +1,350 @@ +const { getImportsSpecifier } = require('./imports-map.cjs'); + +function isBareRuntime() { + return typeof Bare !== 'undefined'; +} + +const fs = isBareRuntime() ? require('bare-fs') : require('node:fs'); +const path = isBareRuntime() ? require('bare-path') : require('node:path'); +const bareUrl = isBareRuntime() ? require('bare-url') : require('node:url'); + +/** `bare-path` join breaks `pear://key/...` → `pear:/key/...`; keep drive URLs intact. */ +function normalizePearDriveUrl(u) { + if (typeof u !== 'string' || !u.startsWith('pear:')) return u; + const t = u.replace(/\/+$/, ''); + if (t.startsWith('pear://')) return t; + return `pear://${t.replace(/^pear:\/?/, '')}`; +} + +function joinUnderRoot(root, ...segments) { + const base = normalizePearDriveUrl(root); + if (typeof base === 'string' && base.startsWith('pear://')) { + const tail = segments + .map((s) => String(s).replace(/^\/+|\/+$/g, '')) + .filter(Boolean) + .join('/'); + return tail ? `${base}/${tail}` : base; + } + return path.join(base, ...segments); +} + +/** `bare-path` dirname breaks `pear://` URLs; used when walking resolved discord paths. */ +function pearSafeDirname(filePath) { + const s = normalizePearDriveUrl(String(filePath)); + if (!s.startsWith('pear://')) { + return path.dirname(filePath); + } + try { + const u = new bareUrl.URL(s); + const pathname = (u.pathname || '').replace(/\/+$/, ''); + const slash = pathname.lastIndexOf('/'); + if (slash <= 0) { + return `${u.protocol}//${u.host}`; + } + return `${u.protocol}//${u.host}${pathname.slice(0, slash)}`; + } catch { + const trimmed = s.replace(/\/+$/, ''); + const idx = trimmed.lastIndexOf('/'); + if (idx <= 6) return trimmed; + return trimmed.slice(0, idx); + } +} + +function dirnameSafe(p) { + const s = String(p); + if (s.startsWith('pear:')) return pearSafeDirname(s); + return path.dirname(p); +} + +/** + * Bare-module CJS passes the *Module instance itself* as the `module` arg to the wrapped + * function (see `bare-module/index.js` _evaluate → createRequire(this._url, { module: this })). + * That instance carries `_protocol`/`_resolutions`/`_cache` from the loader. On Pear, that + * loader is the runtime which set a `pear://`-aware protocol; we propagate it via `referrer` + * to any `Module.createRequire` call so resolution can `protocol.exists/read` Pear URLs. + */ +function findPearAwareReferrer(currentModule) { + const candidates = []; + if (currentModule && typeof currentModule === 'object') candidates.push(currentModule); + try { + if (require.main) candidates.push(require.main); + } catch { + // ignore + } + try { + const cache = require.cache; + if (cache && typeof cache === 'object') { + for (const key of Object.keys(cache)) { + if (typeof key === 'string' && key.startsWith('pear:')) { + candidates.push(cache[key]); + } + } + } + } catch { + // ignore + } + + for (const c of candidates) { + if ( + c && + typeof c === 'object' && + c._protocol && + typeof c._protocol.exists === 'function' && + typeof c._protocol.read === 'function' + ) { + return c; + } + } + return null; +} + +/** + * Load Node→Bare import mappings. Order: + * 1. `BARE_DISCORD_IMPORT_MAP_PATH` when present (app `bare-imports.json` after prepare). + * 2. **`require('./node-imports-map.cjs')`** — always shipped with bare-discord-js; works on + * `pear://` drives (reading JSON via `require.resolve` + `readFileSync` breaks: resolves to + * bogus `/node_modules/...` host paths). + * 3. `bare-node-runtime/imports` JSON file. + * 4. Sibling `node-imports-map.json` via filesystem. + * + * Note: `examples/pear-discord-bot/.gitignore` lists `bare-imports.json`, so Pear stage often + * omits it; (2) keeps `pear run pear://…` working from any cwd. Local `pear run .` still uses (1) + * when the file exists on disk. + */ +/** + * Some Bare modules in the upstream Node→Bare imports map self-initialise on require + * (`bare-worker` spawns a Thread, `bare-vm`/`bare-v8`/`bare-inspector` load native bindings). + * For runtime aliasing inside discord.js's transitive deps we don't need those — undici + * `require('node:worker_threads')` lazily, only on opt-in features. Redirect the spec-keys to + * `bare-node-runtime/unsupported` (a no-op stub `bare-discord-js/preload-bare-modules.cjs` + * already includes) so `pear stage` doesn't pull side-effecting modules into the bundle. + */ +const SAFE_OVERRIDES = (() => { + const stub = { bare: 'bare-node-runtime/unsupported', default: 'bare-node-runtime/unsupported' }; + const keys = [ + 'worker_threads', + 'node:worker_threads', + 'vm', + 'node:vm', + 'v8', + 'node:v8', + 'inspector', + 'node:inspector', + 'inspector/promises', + 'node:inspector/promises', + 'child_process', + 'node:child_process', + 'repl', + 'node:repl', + 'tty', + 'node:tty', + 'readline', + 'node:readline', + 'readline/promises', + 'node:readline/promises', + 'sqlite', + 'node:sqlite' + ]; + const out = {}; + for (const k of keys) out[k] = stub; + return out; +})(); + +function applyImportsOverrides(map) { + if (!map || typeof map !== 'object') return map; + return Object.assign({}, map, SAFE_OVERRIDES); +} + +function loadRuntimeImportsMap() { + const envPath = process.env.BARE_DISCORD_IMPORT_MAP_PATH; + if (typeof envPath === 'string' && envPath.length > 0) { + try { + if (fs.existsSync(envPath)) { + return applyImportsOverrides(JSON.parse(fs.readFileSync(envPath, 'utf8'))); + } + } catch { + // bare-fs may reject non-file URLs; the embedded map below covers pear:// runs. + } + } + try { + return applyImportsOverrides(require('./node-imports-map.cjs')); + } catch { + // ignore + } + try { + const candidate = require.resolve('bare-node-runtime/imports'); + if (fs.existsSync(candidate)) { + return applyImportsOverrides(JSON.parse(fs.readFileSync(candidate, 'utf8'))); + } + } catch { + // bare-node-runtime may be unavailable. + } + const embedded = path.join(__dirname, 'node-imports-map.json'); + try { + if (fs.existsSync(embedded)) { + return applyImportsOverrides(JSON.parse(fs.readFileSync(embedded, 'utf8'))); + } + } catch { + // ignore + } + return null; +} + +function resolveCreateRequireAnchor() { + const env = process.env.BARE_DISCORD_REQUIRE_ANCHOR; + if (typeof env === 'string' && env.length > 0) { + return normalizePearDriveUrl(env); + } + return path.join(__dirname, '..', 'index.js'); +} + +/** + * discord.js package "." entry — Bare cannot resolve the bare specifier from the + * package's own package.json anchor; load ./src/index.js (exports.require). + */ +function requireDiscordDotEntry(req, packageJsonPath) { + let pkg = null; + try { + pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + } catch { + return req('./src/index.js'); + } + let rel = null; + const dot = pkg.exports && pkg.exports['.']; + if (dot && typeof dot === 'object' && dot !== null && dot.require) { + const r = dot.require; + rel = typeof r === 'string' ? r : r.default; + } + if (!rel && pkg.main) rel = pkg.main; + if (!rel) rel = './index.js'; + if (!rel.startsWith('.')) rel = `./${rel}`; + return req(rel); +} + +/** + * Nested install, Pear hoisted sibling, monorepo hoisted. + * Pear: `path.join(__dirname, '..', '..', …)` can normalize to bogus host paths like + * `file:///node_modules/discord.js/…` when `__dirname` is wrong; prefer resolution via + * `require.resolve('discord.js', { paths })` then walk up to `discord.js`'s package.json. + * Apps may set `BARE_DISCORD_DISCORD_ROOT` to a vendored tree (e.g. `./vendor/discord.js`). + */ +function resolveDiscordPackageJsonPath() { + const vendorRoot = process.env.BARE_DISCORD_DISCORD_ROOT; + if (typeof vendorRoot === 'string' && vendorRoot.length > 0) { + return joinUnderRoot(vendorRoot, 'package.json'); + } + + const bareDiscordPkgRoot = path.join(__dirname, '..', '..'); + function discordPkgFromResolvedEntry() { + let entry = null; + try { + entry = require.resolve('discord.js', { paths: [bareDiscordPkgRoot] }); + } catch { + try { + entry = require.resolve('discord.js'); + } catch { + return null; + } + } + let dir = dirnameSafe(entry); + for (let i = 0; i < 8; i++) { + const pkgJson = joinUnderRoot(dir, 'package.json'); + try { + if (fs.existsSync(pkgJson)) { + const name = JSON.parse(fs.readFileSync(pkgJson, 'utf8')).name; + if (name === 'discord.js') return pkgJson; + } + } catch { + // ignore + } + const parent = dirnameSafe(dir); + if (parent === dir) break; + dir = parent; + } + return null; + } + + const resolved = discordPkgFromResolvedEntry(); + if (resolved) return resolved; + + const candidates = [ + path.join(__dirname, '..', '..', '..', 'discord.js', 'package.json'), + path.join(__dirname, '..', '..', '..', 'node_modules', 'discord.js', 'package.json'), + path.join(__dirname, '..', '..', '..', '..', 'node_modules', 'discord.js', 'package.json'), + path.join(__dirname, '..', 'node_modules', 'discord.js', 'package.json') + ]; + for (const p of candidates) { + try { + if (fs.existsSync(p)) return p; + } catch { + // bare-fs may throw on some hosts. + } + } + return null; +} + +function inBareAppBundle(currentModule) { + const hints = [ + typeof __filename === 'string' ? __filename : '', + typeof __dirname === 'string' ? __dirname : '', + currentModule && currentModule.filename, + currentModule && currentModule.url, + currentModule && currentModule._url + ]; + for (const h of hints) { + const s = String(h || ''); + if (s.startsWith('bare:') || s.includes('app.bundle')) return true; + } + return false; +} + +function loadDiscordWithRuntimeMappings(currentModule) { + if (!isBareRuntime()) { + return require('discord.js'); + } + + // bare-pack rewrites this literal require into the app.bundle binding. + // createRequire(anchor).require('discord.js') does not resolve under bare:/app.bundle/. + if (inBareAppBundle(currentModule)) { + return require('discord.js'); + } + + const map = loadRuntimeImportsMap(); + const referrer = findPearAwareReferrer(currentModule); + + if (map) { + const Module = require('bare-module'); + + const discordPkgPath = resolveDiscordPackageJsonPath(); + if (discordPkgPath) { + try { + const opts = referrer ? { imports: map, referrer } : { imports: map }; + const req = Module.createRequire(discordPkgPath, opts); + return requireDiscordDotEntry(req, discordPkgPath); + } catch (err) { + console.error('[bare-discord-js] discord bootstrap failed:', err && err.message); + // Fall through: packed or hoisted require('discord.js') may still work. + } + } + + try { + const anchor = resolveCreateRequireAnchor(); + const opts = referrer ? { imports: map, referrer } : { imports: map }; + const req = Module.createRequire(anchor, opts); + return req('discord.js'); + } catch { + return require('discord.js'); + } + } + + const imports = getImportsSpecifier(); + try { + return require('discord.js', { with: { imports } }); + } catch { + return require('discord.js'); + } +} + +module.exports = { + loadDiscordWithRuntimeMappings +}; diff --git a/native-host/vendor/bare-discord-js/src/bootstrap/load-discord.mjs b/native-host/vendor/bare-discord-js/src/bootstrap/load-discord.mjs new file mode 100644 index 0000000..4c98bd0 --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/bootstrap/load-discord.mjs @@ -0,0 +1,11 @@ +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); + +/** + * Share the CJS bootstrap so Bare import maps, Pear referrers, and Node + * `require('discord.js')` stay on one path. + */ +export async function loadDiscordWithRuntimeMappings() { + return require('./load-discord.cjs').loadDiscordWithRuntimeMappings(); +} diff --git a/native-host/vendor/bare-discord-js/src/bootstrap/node-imports-map.cjs b/native-host/vendor/bare-discord-js/src/bootstrap/node-imports-map.cjs new file mode 100644 index 0000000..e5ffb78 --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/bootstrap/node-imports-map.cjs @@ -0,0 +1,2 @@ +'use strict'; +module.exports = {"assert":{"bare":"bare-assert","default":"assert"},"node:assert":{"bare":"bare-assert","default":"assert"},"assert/strict":{"bare":"bare-assert/strict","default":"assert/strict"},"node:assert/strict":{"bare":"bare-assert/strict","default":"assert/strict"},"async_hooks":{"bare":"bare-async-hooks","default":"async_hooks"},"node:async_hooks":{"bare":"bare-async-hooks","default":"async_hooks"},"buffer":{"bare":"bare-buffer","default":"buffer"},"node:buffer":{"bare":"bare-buffer","default":"buffer"},"child_process":{"bare":"bare-subprocess","default":"child_process"},"node:child_process":{"bare":"bare-subprocess","default":"child_process"},"cluster":{"bare":"bare-node-runtime/unsupported","default":"cluster"},"node:cluster":{"bare":"bare-node-runtime/unsupported","default":"cluster"},"console":{"bare":"bare-console","default":"console"},"node:console":{"bare":"bare-console","default":"console"},"constants":{"bare":"bare-node-runtime/unsupported","default":"constants"},"node:constants":{"bare":"bare-node-runtime/unsupported","default":"constants"},"crypto":{"bare":"bare-crypto","default":"crypto"},"node:crypto":{"bare":"bare-crypto","default":"crypto"},"dgram":{"bare":"bare-dgram","default":"dgram"},"node:dgram":{"bare":"bare-dgram","default":"dgram"},"diagnostics_channel":{"bare":"bare-diagnostics-channel","default":"diagnostics_channel"},"node:diagnostics_channel":{"bare":"bare-diagnostics-channel","default":"diagnostics_channel"},"dns":{"bare":"bare-dns","default":"dns"},"node:dns":{"bare":"bare-dns","default":"dns"},"dns/promises":{"bare":"bare-dns/promises","default":"dns/promises"},"node:dns/promises":{"bare":"bare-dns/promises","default":"dns/promises"},"domain":{"bare":"bare-node-runtime/unsupported","default":"domain"},"node:domain":{"bare":"bare-node-runtime/unsupported","default":"domain"},"events":{"bare":"bare-events","default":"events"},"node:events":{"bare":"bare-events","default":"events"},"fs":{"bare":"bare-fs","default":"fs"},"node:fs":{"bare":"bare-fs","default":"fs"},"fs/promises":{"bare":"bare-fs/promises","default":"fs/promises"},"node:fs/promises":{"bare":"bare-fs/promises","default":"fs/promises"},"http":{"bare":"bare-http1","default":"http"},"node:http":{"bare":"bare-http1","default":"http"},"http2":{"bare":"bare-node-runtime/unsupported","default":"http2"},"node:http2":{"bare":"bare-node-runtime/unsupported","default":"http2"},"https":{"bare":"bare-https","default":"https"},"node:https":{"bare":"bare-https","default":"https"},"inspector":{"bare":"bare-inspector","default":"inspector"},"node:inspector":{"bare":"bare-inspector","default":"inspector"},"inspector/promises":{"bare":"bare-inspector/promises","default":"inspector/promises"},"node:inspector/promises":{"bare":"bare-inspector/promises","default":"inspector/promises"},"module":{"bare":"bare-module","default":"module"},"node:module":{"bare":"bare-module","default":"module"},"net":{"bare":"bare-net","default":"net"},"node:net":{"bare":"bare-net","default":"net"},"os":{"bare":"bare-os","default":"os"},"node:os":{"bare":"bare-os","default":"os"},"path":{"bare":"bare-path","default":"path"},"node:path":{"bare":"bare-path","default":"path"},"path/posix":{"bare":"bare-path/posix","default":"path/posix"},"node:path/posix":{"bare":"bare-path/posix","default":"path/posix"},"path/win32":{"bare":"bare-path/win32","default":"path/win32"},"node:path/win32":{"bare":"bare-path/win32","default":"path/win32"},"perf_hooks":{"bare":"bare-performance","default":"perf_hooks"},"node:perf_hooks":{"bare":"bare-performance","default":"perf_hooks"},"process":{"bare":"bare-process","default":"process"},"node:process":{"bare":"bare-process","default":"process"},"punycode":{"bare":"bare-punycode","default":"punycode"},"node:punycode":{"bare":"bare-punycode","default":"punycode"},"querystring":{"bare":"bare-querystring","default":"querystring"},"node:querystring":{"bare":"bare-querystring","default":"querystring"},"readline":{"bare":"bare-readline","default":"readline"},"node:readline":{"bare":"bare-readline","default":"readline"},"readline/promises":{"bare":"bare-readline/promises","default":"readline/promises"},"node:readline/promises":{"bare":"bare-readline/promises","default":"readline/promises"},"repl":{"bare":"bare-repl","default":"repl"},"node:repl":{"bare":"bare-repl","default":"repl"},"sea":{"bare":"bare-node-runtime/unsupported","default":"sea"},"node:sea":{"bare":"bare-node-runtime/unsupported","default":"sea"},"sqlite":{"bare":"bare-sqlite","default":"sqlite"},"node:sqlite":{"bare":"bare-sqlite","default":"sqlite"},"stream":{"bare":"bare-stream","default":"stream"},"node:stream":{"bare":"bare-stream","default":"stream"},"stream/consumers":{"bare":"bare-stream/consumers","default":"stream/consumers"},"node:stream/consumers":{"bare":"bare-stream/consumers","default":"stream/consumers"},"stream/promises":{"bare":"bare-stream/promises","default":"stream/promises"},"node:stream/promises":{"bare":"bare-stream/promises","default":"stream/promises"},"stream/web":{"bare":"bare-stream/web","default":"stream/web"},"node:stream/web":{"bare":"bare-stream/web","default":"stream/web"},"string_decoder":{"bare":"bare-string-decoder","default":"string_decoder"},"node:string_decoder":{"bare":"bare-string-decoder","default":"string_decoder"},"sys":{"bare":"bare-node-runtime/unsupported","default":"sys"},"node:sys":{"bare":"bare-node-runtime/unsupported","default":"sys"},"test":{"bare":"bare-node-runtime/unsupported","default":"test"},"node:test":{"bare":"bare-node-runtime/unsupported","default":"test"},"test/reporters":{"bare":"bare-node-runtime/unsupported","default":"test/reporters"},"node:test/reporters":{"bare":"bare-node-runtime/unsupported","default":"test/reporters"},"timers":{"bare":"bare-timers","default":"timers"},"node:timers":{"bare":"bare-timers","default":"timers"},"timers/promises":{"bare":"bare-timers/promises","default":"timers/promises"},"node:timers/promises":{"bare":"bare-timers/promises","default":"timers/promises"},"tls":{"bare":"bare-tls","default":"tls"},"node:tls":{"bare":"bare-tls","default":"tls"},"trace_events":{"bare":"bare-node-runtime/unsupported","default":"trace_events"},"node:trace_events":{"bare":"bare-node-runtime/unsupported","default":"trace_events"},"tty":{"bare":"bare-tty","default":"tty"},"node:tty":{"bare":"bare-tty","default":"tty"},"url":{"bare":"bare-url","default":"url"},"node:url":{"bare":"bare-url","default":"url"},"util":{"bare":"bare-utils","default":"util"},"node:util":{"bare":"bare-utils","default":"util"},"util/types":{"bare":"bare-utils/types","default":"util/types"},"node:util/types":{"bare":"bare-utils/types","default":"util/types"},"v8":{"bare":"bare-v8","default":"v8"},"node:v8":{"bare":"bare-v8","default":"v8"},"vm":{"bare":"bare-vm","default":"vm"},"node:vm":{"bare":"bare-vm","default":"vm"},"wasi":{"bare":"bare-node-runtime/unsupported","default":"wasi"},"node:wasi":{"bare":"bare-node-runtime/unsupported","default":"wasi"},"worker_threads":{"bare":"bare-worker","default":"worker_threads"},"node:worker_threads":{"bare":"bare-worker","default":"worker_threads"},"zlib":{"bare":"bare-zlib","default":"zlib"},"node:zlib":{"bare":"bare-zlib","default":"zlib"}}; diff --git a/native-host/vendor/bare-discord-js/src/bootstrap/node-imports-map.json b/native-host/vendor/bare-discord-js/src/bootstrap/node-imports-map.json new file mode 100644 index 0000000..79016c6 --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/bootstrap/node-imports-map.json @@ -0,0 +1,466 @@ +{ + "assert": { + "bare": "bare-assert", + "default": "assert" + }, + "node:assert": { + "bare": "bare-assert", + "default": "assert" + }, + "assert/strict": { + "bare": "bare-assert/strict", + "default": "assert/strict" + }, + "node:assert/strict": { + "bare": "bare-assert/strict", + "default": "assert/strict" + }, + "async_hooks": { + "bare": "bare-async-hooks", + "default": "async_hooks" + }, + "node:async_hooks": { + "bare": "bare-async-hooks", + "default": "async_hooks" + }, + "buffer": { + "bare": "bare-buffer", + "default": "buffer" + }, + "node:buffer": { + "bare": "bare-buffer", + "default": "buffer" + }, + "child_process": { + "bare": "bare-subprocess", + "default": "child_process" + }, + "node:child_process": { + "bare": "bare-subprocess", + "default": "child_process" + }, + "cluster": { + "bare": "bare-node-runtime/unsupported", + "default": "cluster" + }, + "node:cluster": { + "bare": "bare-node-runtime/unsupported", + "default": "cluster" + }, + "console": { + "bare": "bare-console", + "default": "console" + }, + "node:console": { + "bare": "bare-console", + "default": "console" + }, + "constants": { + "bare": "bare-node-runtime/unsupported", + "default": "constants" + }, + "node:constants": { + "bare": "bare-node-runtime/unsupported", + "default": "constants" + }, + "crypto": { + "bare": "bare-crypto", + "default": "crypto" + }, + "node:crypto": { + "bare": "bare-crypto", + "default": "crypto" + }, + "dgram": { + "bare": "bare-dgram", + "default": "dgram" + }, + "node:dgram": { + "bare": "bare-dgram", + "default": "dgram" + }, + "diagnostics_channel": { + "bare": "bare-diagnostics-channel", + "default": "diagnostics_channel" + }, + "node:diagnostics_channel": { + "bare": "bare-diagnostics-channel", + "default": "diagnostics_channel" + }, + "dns": { + "bare": "bare-dns", + "default": "dns" + }, + "node:dns": { + "bare": "bare-dns", + "default": "dns" + }, + "dns/promises": { + "bare": "bare-dns/promises", + "default": "dns/promises" + }, + "node:dns/promises": { + "bare": "bare-dns/promises", + "default": "dns/promises" + }, + "domain": { + "bare": "bare-node-runtime/unsupported", + "default": "domain" + }, + "node:domain": { + "bare": "bare-node-runtime/unsupported", + "default": "domain" + }, + "events": { + "bare": "bare-events", + "default": "events" + }, + "node:events": { + "bare": "bare-events", + "default": "events" + }, + "fs": { + "bare": "bare-fs", + "default": "fs" + }, + "node:fs": { + "bare": "bare-fs", + "default": "fs" + }, + "fs/promises": { + "bare": "bare-fs/promises", + "default": "fs/promises" + }, + "node:fs/promises": { + "bare": "bare-fs/promises", + "default": "fs/promises" + }, + "http": { + "bare": "bare-http1", + "default": "http" + }, + "node:http": { + "bare": "bare-http1", + "default": "http" + }, + "http2": { + "bare": "bare-node-runtime/unsupported", + "default": "http2" + }, + "node:http2": { + "bare": "bare-node-runtime/unsupported", + "default": "http2" + }, + "https": { + "bare": "bare-https", + "default": "https" + }, + "node:https": { + "bare": "bare-https", + "default": "https" + }, + "inspector": { + "bare": "bare-inspector", + "default": "inspector" + }, + "node:inspector": { + "bare": "bare-inspector", + "default": "inspector" + }, + "inspector/promises": { + "bare": "bare-inspector/promises", + "default": "inspector/promises" + }, + "node:inspector/promises": { + "bare": "bare-inspector/promises", + "default": "inspector/promises" + }, + "module": { + "bare": "bare-module", + "default": "module" + }, + "node:module": { + "bare": "bare-module", + "default": "module" + }, + "net": { + "bare": "bare-net", + "default": "net" + }, + "node:net": { + "bare": "bare-net", + "default": "net" + }, + "os": { + "bare": "bare-os", + "default": "os" + }, + "node:os": { + "bare": "bare-os", + "default": "os" + }, + "path": { + "bare": "bare-path", + "default": "path" + }, + "node:path": { + "bare": "bare-path", + "default": "path" + }, + "path/posix": { + "bare": "bare-path/posix", + "default": "path/posix" + }, + "node:path/posix": { + "bare": "bare-path/posix", + "default": "path/posix" + }, + "path/win32": { + "bare": "bare-path/win32", + "default": "path/win32" + }, + "node:path/win32": { + "bare": "bare-path/win32", + "default": "path/win32" + }, + "perf_hooks": { + "bare": "bare-performance", + "default": "perf_hooks" + }, + "node:perf_hooks": { + "bare": "bare-performance", + "default": "perf_hooks" + }, + "process": { + "bare": "bare-process", + "default": "process" + }, + "node:process": { + "bare": "bare-process", + "default": "process" + }, + "punycode": { + "bare": "bare-punycode", + "default": "punycode" + }, + "node:punycode": { + "bare": "bare-punycode", + "default": "punycode" + }, + "querystring": { + "bare": "bare-querystring", + "default": "querystring" + }, + "node:querystring": { + "bare": "bare-querystring", + "default": "querystring" + }, + "readline": { + "bare": "bare-readline", + "default": "readline" + }, + "node:readline": { + "bare": "bare-readline", + "default": "readline" + }, + "readline/promises": { + "bare": "bare-readline/promises", + "default": "readline/promises" + }, + "node:readline/promises": { + "bare": "bare-readline/promises", + "default": "readline/promises" + }, + "repl": { + "bare": "bare-repl", + "default": "repl" + }, + "node:repl": { + "bare": "bare-repl", + "default": "repl" + }, + "sea": { + "bare": "bare-node-runtime/unsupported", + "default": "sea" + }, + "node:sea": { + "bare": "bare-node-runtime/unsupported", + "default": "sea" + }, + "sqlite": { + "bare": "bare-sqlite", + "default": "sqlite" + }, + "node:sqlite": { + "bare": "bare-sqlite", + "default": "sqlite" + }, + "stream": { + "bare": "bare-stream", + "default": "stream" + }, + "node:stream": { + "bare": "bare-stream", + "default": "stream" + }, + "stream/consumers": { + "bare": "bare-stream/consumers", + "default": "stream/consumers" + }, + "node:stream/consumers": { + "bare": "bare-stream/consumers", + "default": "stream/consumers" + }, + "stream/promises": { + "bare": "bare-stream/promises", + "default": "stream/promises" + }, + "node:stream/promises": { + "bare": "bare-stream/promises", + "default": "stream/promises" + }, + "stream/web": { + "bare": "bare-stream/web", + "default": "stream/web" + }, + "node:stream/web": { + "bare": "bare-stream/web", + "default": "stream/web" + }, + "string_decoder": { + "bare": "bare-string-decoder", + "default": "string_decoder" + }, + "node:string_decoder": { + "bare": "bare-string-decoder", + "default": "string_decoder" + }, + "sys": { + "bare": "bare-node-runtime/unsupported", + "default": "sys" + }, + "node:sys": { + "bare": "bare-node-runtime/unsupported", + "default": "sys" + }, + "test": { + "bare": "bare-node-runtime/unsupported", + "default": "test" + }, + "node:test": { + "bare": "bare-node-runtime/unsupported", + "default": "test" + }, + "test/reporters": { + "bare": "bare-node-runtime/unsupported", + "default": "test/reporters" + }, + "node:test/reporters": { + "bare": "bare-node-runtime/unsupported", + "default": "test/reporters" + }, + "timers": { + "bare": "bare-timers", + "default": "timers" + }, + "node:timers": { + "bare": "bare-timers", + "default": "timers" + }, + "timers/promises": { + "bare": "bare-timers/promises", + "default": "timers/promises" + }, + "node:timers/promises": { + "bare": "bare-timers/promises", + "default": "timers/promises" + }, + "tls": { + "bare": "bare-tls", + "default": "tls" + }, + "node:tls": { + "bare": "bare-tls", + "default": "tls" + }, + "trace_events": { + "bare": "bare-node-runtime/unsupported", + "default": "trace_events" + }, + "node:trace_events": { + "bare": "bare-node-runtime/unsupported", + "default": "trace_events" + }, + "tty": { + "bare": "bare-tty", + "default": "tty" + }, + "node:tty": { + "bare": "bare-tty", + "default": "tty" + }, + "url": { + "bare": "bare-url", + "default": "url" + }, + "node:url": { + "bare": "bare-url", + "default": "url" + }, + "util": { + "bare": "bare-utils", + "default": "util" + }, + "node:util": { + "bare": "bare-utils", + "default": "util" + }, + "util/types": { + "bare": "bare-utils/types", + "default": "util/types" + }, + "node:util/types": { + "bare": "bare-utils/types", + "default": "util/types" + }, + "v8": { + "bare": "bare-v8", + "default": "v8" + }, + "node:v8": { + "bare": "bare-v8", + "default": "v8" + }, + "vm": { + "bare": "bare-vm", + "default": "vm" + }, + "node:vm": { + "bare": "bare-vm", + "default": "vm" + }, + "wasi": { + "bare": "bare-node-runtime/unsupported", + "default": "wasi" + }, + "node:wasi": { + "bare": "bare-node-runtime/unsupported", + "default": "wasi" + }, + "worker_threads": { + "bare": "bare-worker", + "default": "worker_threads" + }, + "node:worker_threads": { + "bare": "bare-worker", + "default": "worker_threads" + }, + "zlib": { + "bare": "bare-zlib", + "default": "zlib" + }, + "node:zlib": { + "bare": "bare-zlib", + "default": "zlib" + } +} diff --git a/native-host/vendor/bare-discord-js/src/bootstrap/preload-bare-modules.cjs b/native-host/vendor/bare-discord-js/src/bootstrap/preload-bare-modules.cjs new file mode 100644 index 0000000..b1b3a66 --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/bootstrap/preload-bare-modules.cjs @@ -0,0 +1,58 @@ +/** + * Force the Pear bundler to statically include the Bare modules referenced by the runtime + * Node→Bare imports map (`./node-imports-map.cjs`). Without these *literal-string* requires + * the bundler's static analysis cannot trace `require('node:util')` → `bare-utils` etc., so + * `pear stage` skips them and `pear run pear://…` fails with MODULE_NOT_FOUND for `node:util`. + * + * Only modules actually used by discord.js's transitive deps via the imports map are listed. + * Side-effecting Bare modules (`bare-worker` instantiates a Thread on first require, + * `bare-vm`/`bare-v8`/`bare-inspector` load native bindings unconditionally) are intentionally + * omitted — pulling them in here breaks app startup even when discord.js never asks for them. + * + * Sub-paths must match each package's `exports` field; e.g. `bare-assert` has no `./strict`, + * `bare-dns` has no `./promises`. Including a non-exported subpath causes + * `PACKAGE_PATH_NOT_EXPORTED` during `pear stage`. + */ + +'use strict'; + +try { require('bare-assert'); } catch {} +try { require('bare-async-hooks'); } catch {} +try { require('bare-buffer'); } catch {} +try { require('bare-console'); } catch {} +try { require('bare-crypto'); } catch {} +try { require('bare-diagnostics-channel'); } catch {} +try { require('bare-encoding'); } catch {} +try { require('bare-events'); } catch {} +try { require('bare-fetch'); } catch {} +try { require('bare-form-data'); } catch {} +try { require('bare-form-data/global'); } catch {} +try { require('bare-fs'); } catch {} +try { require('bare-fs/promises'); } catch {} +try { require('bare-fs/constants'); } catch {} +try { require('bare-http1'); } catch {} +try { require('bare-https'); } catch {} +try { require('bare-module'); } catch {} +try { require('bare-net'); } catch {} +try { require('bare-os'); } catch {} +try { require('bare-path'); } catch {} +try { require('bare-path/posix'); } catch {} +try { require('bare-path/win32'); } catch {} +try { require('bare-performance'); } catch {} +try { require('bare-process'); } catch {} +try { require('bare-querystring'); } catch {} +try { require('bare-stream'); } catch {} +try { require('bare-stream/promises'); } catch {} +try { require('bare-stream/web'); } catch {} +try { require('bare-string-decoder'); } catch {} +try { require('bare-timers'); } catch {} +try { require('bare-timers/promises'); } catch {} +try { require('bare-tls'); } catch {} +try { require('bare-url'); } catch {} +try { require('bare-utils'); } catch {} +try { require('bare-utils/types'); } catch {} +try { require('bare-ws'); } catch {} +try { require('bare-zlib'); } catch {} +try { require('bare-node-runtime/unsupported'); } catch {} + +module.exports = {}; diff --git a/native-host/vendor/bare-discord-js/src/index.js b/native-host/vendor/bare-discord-js/src/index.js new file mode 100644 index 0000000..6e52a2e --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/index.js @@ -0,0 +1,12 @@ +const { setupBareGlobals } = require('./bootstrap/global.cjs'); +const { loadDiscordWithRuntimeMappings } = require('./bootstrap/load-discord.cjs'); +const { applyRuntimeAdapters, applyDiscordJsCompatShims } = require('./adapters/index.cjs'); + +require('./bootstrap/preload-bare-modules.cjs'); + +setupBareGlobals(); +applyRuntimeAdapters(); + +const discord = applyDiscordJsCompatShims(loadDiscordWithRuntimeMappings(module)); + +module.exports = discord; diff --git a/native-host/vendor/bare-discord-js/src/index.mjs b/native-host/vendor/bare-discord-js/src/index.mjs new file mode 100644 index 0000000..bb653ca --- /dev/null +++ b/native-host/vendor/bare-discord-js/src/index.mjs @@ -0,0 +1,30 @@ +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); +const discord = require('./index.js'); + +export default discord; +export const { + ActionRowBuilder, + AttachmentBuilder, + ButtonBuilder, + ChannelType, + Client, + Collection, + EmbedBuilder, + Events, + GatewayIntentBits, + IntentsBitField, + MessageFlags, + ModalBuilder, + Options, + Partials, + PermissionsBitField, + REST, + Routes, + ShardingManager, + SlashCommandBuilder, + StringSelectMenuBuilder, + TextInputBuilder, + version +} = discord; diff --git a/package.json b/package.json index 30a808c..9a52900 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "build:dist:package": "node scripts/build-distributable.js --all --package", "build:dist:media": "node scripts/build-distributable.js --package", "install:capability:media": "bash scripts/install-capability-media.sh", + "vendor:discord": "node scripts/vendor-bare-discord-js.mjs", "test": "node scripts/test-host-units.js && npm --prefix native-host run test:bare", "test:qvac-smoke": "npm --prefix native-host run test:qvac-smoke" }, diff --git a/scripts/serve-examples.js b/scripts/serve-examples.js index 2b8fcba..1a20099 100755 --- a/scripts/serve-examples.js +++ b/scripts/serve-examples.js @@ -110,6 +110,9 @@ server.listen(PORT, HOST, () => { console.log(` ${base}/whiteboard/`); console.log(` ${base}/screenshare/`); console.log(` ${base}/data-demo/`); + console.log(` ${base}/qvac-chat/`); + console.log(` ${base}/agent-studio/`); + console.log(` ${base}/discord-bot/`); console.log(''); console.log('Do not open examples via file:// — Chrome treats each file as a unique origin.'); console.log('Press Ctrl+C to stop.'); diff --git a/scripts/test-host-units.js b/scripts/test-host-units.js index 502ddd7..e7b555a 100644 --- a/scripts/test-host-units.js +++ b/scripts/test-host-units.js @@ -123,10 +123,48 @@ function testOriginAllowlist() { assert.strictEqual(allow.isQvacDisabled({ type: 'capability', payload: { pack: 'agent', cmd: 'create' } }, {}), true); assert.strictEqual(allow.isQvacDisabled({ type: 'capability', payload: { pack: 'agent', cmd: 'status' } }, {}), false); assert.strictEqual(allow.isQvacDisabled({ type: 'fs.write' }, {}), false); + assert.strictEqual(allow.isCapabilityType('discord.construct'), true); + assert.strictEqual(allow.isDiscordDisabled({ type: 'discord.construct' }, {}), true); + assert.strictEqual(allow.isDiscordDisabled({ type: 'discord.surface' }, {}), false); + assert.strictEqual(allow.isDiscordDisabled({ type: 'discord.status' }, {}), false); + assert.strictEqual(allow.isDiscordDisabled({ type: 'discord.construct' }, { discordEnabled: true }), false); + assert.strictEqual(allow.isDiscordDisabled({ type: 'discord.setEnabled' }, { discordEnabled: true }), true); + assert.strictEqual(allow.isDiscordDisabled({ type: 'capability', payload: { pack: 'discord', cmd: 'login' } }, {}), true); } testOriginAllowlist(); +function testDiscordSnapshot() { + const snap = require('../native-host/discord/snapshot.js'); + const { normalizeDiscordToken } = require('../native-host/discord/load-discord.js'); + assert.strictEqual(normalizeDiscordToken(' abc.def \n'), 'abc.def'); + assert.strictEqual(normalizeDiscordToken('\uFEFFtok'), 'tok'); + const handles = snap.createHandleTable(); + const ix = { + commandName: 'ping', + isChatInputCommand() { return true; }, + isButton() { return false; }, + reply() { return Promise.resolve(); }, + toJSON() { return { id: '1', commandName: 'ping' }; }, + user: { id: '99', tag: 'u#0001' }, + }; + Object.defineProperty(ix, 'constructor', { value: { name: 'ChatInputCommandInteraction' } }); + const s = snap.snapshotValue(ix, handles, 0); + assert.ok(s._handle, 'handle interned'); + assert.strictEqual(s.isChatInputCommand, true); + assert.strictEqual(s.commandName, 'ping'); + assert.ok(s._methods.indexOf('reply') >= 0, 'reply method listed'); + const constants = snap.serializeConstants({ + GatewayIntentBits: { Guilds: 1, DirectMessages: 4096 }, + Client: function Client() {}, + Events: { ClientReady: 'clientReady' }, + }); + assert.strictEqual(constants.GatewayIntentBits.Guilds, 1); + assert.ok(constants._classes.indexOf('Client') >= 0); +} + +testDiscordSnapshot(); + function testQvacCatalog() { const cat = require('../native-host/qvac/catalog.js'); assert.strictEqual(cat.resolveModelConstant('qwen3.5-4b'), 'QWEN3_5_4B_MULTIMODAL_Q4_K_M'); diff --git a/scripts/vendor-bare-discord-js.mjs b/scripts/vendor-bare-discord-js.mjs new file mode 100644 index 0000000..4b9040d --- /dev/null +++ b/scripts/vendor-bare-discord-js.mjs @@ -0,0 +1,77 @@ +#!/usr/bin/env node +/** + * Copy bare-discord-js sources into native-host/vendor/bare-discord-js + * (no node_modules). Replaces any previous vendor tree, then inlines FormData. + * + * Env: BARE_OS_BARE_DISCORD_JS_SRC — default /home/raven/dev/bare-discord-js + * Fallback: Bare OS vendor at ../bare-operating-system/... if present. + */ +import { execFileSync } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const dest = path.join(root, 'native-host', 'vendor', 'bare-discord-js'); +const defaultSrc = '/home/raven/dev/bare-discord-js'; +const bareOsVendor = '/home/raven/dev/bare-operating-system/packages/bare-os-booter/vendor/bare-discord-js'; +const src = String( + process.env.BARE_OS_BARE_DISCORD_JS_SRC || + (fs.existsSync(path.join(defaultSrc, 'package.json')) ? defaultSrc : bareOsVendor) +).trim(); + +if (!fs.existsSync(path.join(src, 'package.json'))) { + console.error('vendor-bare-discord-js: missing source:', src); + process.exit(1); +} + +const srcPkg = JSON.parse(fs.readFileSync(path.join(src, 'package.json'), 'utf8')); +if (srcPkg.name !== 'bare-discord-js') { + console.error('vendor-bare-discord-js: unexpected package name:', srcPkg.name); + process.exit(1); +} + +fs.mkdirSync(path.dirname(dest), { recursive: true }); +execFileSync( + 'rsync', + [ + '-a', + '--delete', + '--exclude', + 'node_modules', + '--exclude', + '.git', + '--exclude', + 'artifacts', + '--exclude', + 'examples', + '--exclude', + 'test', + '--exclude', + 'src/bundles', + '--exclude', + '*.tgz', + '--exclude', + 'package-lock.json', + path.join(src, '/'), + dest + path.sep, + ], + { stdio: 'inherit' } +); + +const formDataSrc = path.join(root, 'native-host', 'vendor', 'bare-discord-js', 'src', 'adapters', 'form-data.cjs'); +const installer = path.join( + '/home/raven/dev/bare-operating-system/packages/bare-os-booter/lib/services/bare-os-discord-form-data.cjs' +); +if (fs.existsSync(formDataSrc)) { + const cur = fs.readFileSync(formDataSrc, 'utf8'); + if (cur.includes('../../../../lib/services/bare-os-discord-form-data.cjs')) { + if (fs.existsSync(installer)) { + fs.writeFileSync(formDataSrc, fs.readFileSync(installer)); + console.log('vendor-bare-discord-js: inlined FormData installer'); + } + } +} + +console.log('vendor-bare-discord-js: synced →', dest, `(${srcPkg.version})`);