diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 682c0dfd..e1555456 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -8,6 +8,7 @@ on: - 'apps/marketing/**' - 'apps/portal/**' - 'apps/paste-service/**' + - 'apps/room-service/**' - 'packages/**' workflow_dispatch: inputs: @@ -21,6 +22,7 @@ on: - marketing - portal - paste + - room permissions: id-token: write @@ -33,6 +35,7 @@ jobs: marketing: ${{ steps.changes.outputs.marketing }} portal: ${{ steps.changes.outputs.portal }} paste: ${{ steps.changes.outputs.paste }} + room: ${{ steps.changes.outputs.room }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -55,6 +58,11 @@ jobs: else echo "paste=false" >> $GITHUB_OUTPUT fi + if [[ "${{ inputs.target }}" == "all" || "${{ inputs.target }}" == "room" ]]; then + echo "room=true" >> $GITHUB_OUTPUT + else + echo "room=false" >> $GITHUB_OUTPUT + fi else # For push events, check what changed git fetch origin ${{ github.event.before }} --depth=1 2>/dev/null || true @@ -62,6 +70,7 @@ jobs: MARKETING_CHANGED=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} 2>/dev/null | grep -E '^(apps/marketing/|packages/)' || true) PORTAL_CHANGED=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} 2>/dev/null | grep -E '^(apps/portal/|packages/)' || true) PASTE_CHANGED=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} 2>/dev/null | grep -E '^apps/paste-service/' || true) + ROOM_CHANGED=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} 2>/dev/null | grep -E '^(apps/room-service/|packages/shared/collab/|packages/editor/|packages/ui/)' || true) if [[ -n "$MARKETING_CHANGED" ]]; then echo "marketing=true" >> $GITHUB_OUTPUT @@ -80,6 +89,12 @@ jobs: else echo "paste=false" >> $GITHUB_OUTPUT fi + + if [[ -n "$ROOM_CHANGED" ]]; then + echo "room=true" >> $GITHUB_OUTPUT + else + echo "room=false" >> $GITHUB_OUTPUT + fi fi deploy-marketing: @@ -171,3 +186,28 @@ jobs: env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + + deploy-room: + needs: detect-changes + if: needs.detect-changes.outputs.room == 'true' + runs-on: ubuntu-latest + environment: production + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Build browser shell + run: bun run --cwd apps/room-service build:shell + + - name: Deploy to Cloudflare + working-directory: apps/room-service + run: npx wrangler deploy + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4fdfa468..c9ed0ac4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,9 @@ jobs: run: bun run typecheck - name: Run tests - run: bun test + # See .github/workflows/test.yml for why this is `bun run test` + # and not raw `bun test`. + run: bun run test build: needs: test diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d319f940..985aa4f1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,7 +29,12 @@ jobs: run: bun run typecheck - name: Run tests - run: bun test + # Use the root `test` script (splits non-UI + UI-cwd) so the + # packages/ui/bunfig.toml happy-dom preload is loaded. Raw + # `bun test` from the repo root doesn't pick up that package- + # scoped preload, so UI hook tests would hit "document is not + # defined". + run: bun run test install-cmd-windows: # End-to-end integration test for scripts/install.cmd on real cmd.exe. diff --git a/.gitignore b/.gitignore index 539bf374..4d1bf00e 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,17 @@ opencode.json plannotator-local # Local research/reference docs (not for repo) /reference/ + +# Cloudflare Wrangler local state (Miniflare SQLite, caches) +.wrangler/ + +# Room-service Vite build output (chunked editor bundle served by +# Cloudflare's [assets] binding; regenerated by `bun run build:shell`). +apps/room-service/public/ + +# Claude Code local scratch files (per-session locks, etc.). Intentionally +# ignored so they can't be committed accidentally. +.claude/scheduled_tasks.lock + +# Internal design/spec docs — kept locally, not shipped in PRs. +specs/ diff --git a/AGENTS.md b/AGENTS.md index 971609f5..e8d513b8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,6 +28,16 @@ plannotator/ │ │ ├── index.html │ │ ├── index.tsx │ │ └── vite.config.ts +│ ├── room-service/ # Live collaboration rooms (Cloudflare Worker + Durable Object) +│ │ ├── core/ # Handler, DO class, validation, CORS, log, types, csp +│ │ ├── targets/cloudflare.ts # Worker entry + DO re-export +│ │ ├── entry.tsx # Browser shell entry — mounts AppRoot for /c/:roomId +│ │ ├── index.html # Vite template; produces hashed chunks under /assets/ +│ │ ├── vite.config.ts # Browser shell build (bun run build:shell) +│ │ ├── tsconfig.browser.json # DOM-lib tsconfig for the shell +│ │ ├── static/ # Root-level static assets copied into public/ by build:shell (favicon.svg) +│ │ ├── scripts/smoke.ts # Integration test against wrangler dev +│ │ └── wrangler.toml # SQLite-backed DO binding + ASSETS binding for built shell │ └── vscode-extension/ # VS Code extension — opens plans in editor tabs │ ├── bin/ # Router scripts (open-in-vscode, xdg-open) │ ├── src/ # extension.ts, cookie-proxy.ts, ipc-server.ts, panel-manager.ts, editor-annotations.ts, vscode-theme.ts @@ -51,16 +61,33 @@ plannotator/ │ │ ├── components/ # Viewer, Toolbar, Settings, etc. │ │ │ ├── icons/ # Shared SVG icon components (themeIcons, etc.) │ │ │ ├── plan-diff/ # PlanDiffBadge, PlanDiffViewer, clean/raw diff views -│ │ │ └── sidebar/ # SidebarContainer, SidebarTabs, VersionBrowser, ArchiveBrowser -│ │ ├── utils/ # parser.ts, sharing.ts, storage.ts, planSave.ts, agentSwitch.ts, planDiffEngine.ts, planAgentInstructions.ts -│ │ ├── hooks/ # useAnnotationHighlighter.ts, useSharing.ts, usePlanDiff.ts, useSidebar.ts, useLinkedDoc.ts, useAnnotationDraft.ts, useCodeAnnotationDraft.ts, useArchive.ts +│ │ │ ├── sidebar/ # SidebarContainer, SidebarTabs, VersionBrowser, ArchiveBrowser +│ │ │ └── collab/ # RoomStatusBadge, ParticipantAvatars, RoomHeaderControls, RoomMenu, RoomUnavailableScreen, JoinRoomGate, StartRoomModal, RemoteCursorLayer, ImageStripNotice +│ │ ├── utils/ # parser.ts, sharing.ts, storage.ts, planSave.ts, agentSwitch.ts, planDiffEngine.ts, planAgentInstructions.ts, adminSecretStorage.ts, blockTargeting.ts +│ │ ├── hooks/ # useAnnotationHighlighter.ts, useSharing.ts, usePlanDiff.ts, useSidebar.ts, useLinkedDoc.ts, useAnnotationDraft.ts, useCodeAnnotationDraft.ts, useArchive.ts, useCollabRoom.ts, useCollabRoomSession.ts, useAnnotationController.ts, useRoomMode.ts, usePresenceThrottle.ts │ │ └── types.ts │ ├── ai/ # Provider-agnostic AI backbone (providers, sessions, endpoints) │ ├── shared/ # Shared types, utilities, and cross-runtime logic │ │ ├── storage.ts # Plan saving, version history, archive listing (node:fs only) │ │ ├── draft.ts # Annotation draft persistence (node:fs only) -│ │ └── project.ts # Pure string helpers (sanitizeTag, extractRepoName, extractDirName) -│ ├── editor/ # Plan review App.tsx +│ │ ├── project.ts # Pure string helpers (sanitizeTag, extractRepoName, extractDirName) +│ │ └── collab/ # Live Rooms protocol, crypto, validators, client runtime, React hook +│ │ ├── types.ts # Protocol types + runtime validators (isRoomAnnotation, isRoomSnapshot, isPresenceState, ...) +│ │ ├── crypto.ts # HKDF key derivation, HMAC proofs, AES-GCM payload encrypt/decrypt +│ │ ├── ids.ts # roomId/secret/opId/clientId generators +│ │ ├── url.ts # parseRoomUrl / buildRoomJoinUrl / buildAdminRoomUrl (client-only) +│ │ ├── constants.ts # ROOM_SECRET_LENGTH_BYTES, ADMIN_SECRET_LENGTH_BYTES, WS_CLOSE_ROOM_UNAVAILABLE, WS_CLOSE_REASON_ROOM_UNAVAILABLE +│ │ ├── canonical-json.ts # canonicalJson for admin command proof binding +│ │ ├── encoding.ts # base64url helpers +│ │ ├── strip-images.ts # toRoomAnnotation, stripRoomAnnotationImages (image stripping for room snapshots) +│ │ ├── redact-url.ts # redactRoomSecrets (scrub #key=/#admin= from telemetry/logs) +│ │ ├── validation.ts # isBase64Url32ByteString / isValidPermissionMode +│ │ ├── client.ts # Client barrel re-exports +│ │ └── client-runtime/ # CollabRoomClient class, createRoom, joinRoom, apply-event reducer +│ ├── editor/ # Plan review app (App.tsx) + room-mode shell +│ │ ├── App.tsx # Plan review editor (local + room-mode prop) +│ │ ├── AppRoot.tsx # Mode fork (local | room | invalid-room); package default export +│ │ └── RoomApp.tsx # Room-mode shell — identity gate, session, overlays, delete/expired fallbacks │ └── review-editor/ # Code review UI │ ├── App.tsx # Main review app │ ├── components/ # DiffViewer, FileTree, ReviewSidebar @@ -193,6 +220,17 @@ During normal plan review, an Archive sidebar tab provides the same browsing via ### Plan Server (`packages/server/index.ts`) +Live Rooms V1 does NOT support approve/deny from the room origin. +Approvals always happen on the local editor origin (the tab that +started the hook). Room-side annotations flow back to the local +editor via the existing import paths (static share hash, paste short +URL, "Copy consolidated feedback" → paste). + +Local external annotations (`/api/external-annotations` + SSE) remain +local to the localhost editor in the current room integration. +Forwarding those annotations into encrypted room ops is later Slice 6 +work; it is not part of the room-origin approve/deny surface. + | Endpoint | Method | Purpose | | --------------------- | ------ | ------------------------------------------ | | `/api/plan` | GET | Returns `{ plan, origin, previousPlan, versionInfo }` (plan mode) or `{ plan, origin, mode: "archive", archivePlans }` (archive mode) | @@ -278,6 +316,19 @@ All servers use random ports locally or fixed port (`19432`) in remote mode. Runs as a separate service on port `19433` (self-hosted) or as a Cloudflare Worker (hosted). +### Room Service (`apps/room-service/`) + +Live-collaboration rooms for encrypted multi-user annotation. Zero-knowledge: the Worker + Durable Object stores and relays ciphertext only. Clients hold the room secret in the URL fragment and derive `authKey`/`eventKey`/`presenceKey`/`adminKey` locally. + +| Endpoint | Method | Purpose | +| --------------------- | ------ | ------------------------------------------ | +| `/health` | GET | Worker liveness probe | +| `/c/:roomId` | GET | Room SPA shell — serves the built editor bundle (hashed chunks under `/assets/`). Response carries `ROOM_CSP`, `Cache-Control: no-store` on the HTML, `Referrer-Policy: no-referrer`. `:roomId` is validated against `isRoomId()` before the asset fetch. | +| `/api/rooms` | POST | Create room. Body: `{ roomId, roomVerifier, adminVerifier, initialSnapshotCiphertext, expiresInDays? }`. Returns `201` on success; `409` on duplicate `roomId`. Response body is intentionally not consumed by `createRoom()`. | +| `/ws/:roomId` | GET | WebSocket upgrade into the room Durable Object. `roomId` is validated via `isRoomId()` before `idFromName()` to prevent arbitrary DO instantiation. | + +Protocol contract lives in `packages/shared/collab/`; the Worker/DO never imports client-only URL helpers. + ## Plan Version History Every plan is automatically saved to `~/.plannotator/history/{project}/{slug}/` on arrival, before the user sees the UI. Versions are numbered sequentially (`001.md`, `002.md`, etc.). The slug is derived from the plan's first `# Heading` + today's date via `generateSlug()`, scoped by project name (git repo or cwd). Same heading on the same day = same slug = same plan being iterated on. Identical resubmissions are deduplicated (no new file if content matches the latest version). diff --git a/apps/collab-agent/AGENT_INSTRUCTIONS.md b/apps/collab-agent/AGENT_INSTRUCTIONS.md new file mode 100644 index 00000000..44ffa85b --- /dev/null +++ b/apps/collab-agent/AGENT_INSTRUCTIONS.md @@ -0,0 +1,189 @@ +# Plannotator Live Rooms — Agent Instructions + +This document is prose an AI agent (Claude Code, Codex, OpenCode, +Junie, or another) should have in its prompt when it's being +driven to participate in a Plannotator Live Room. It explains +the identity convention, the CLI subcommand surface, and the +handful of rules that keep agent participation well-behaved. + +## 1. Identity + +Your identity in the room follows the pattern: + +``` +-agent- +``` + +Examples: `swift-falcon-tater-agent-claude`, +`alice-agent-codex`. + +- `` is the human you're acting on behalf of. If you've + been given their Plannotator identity (a "tater name" like + `swift-falcon-tater`), use it verbatim. +- `` is one of: `claude`, `codex`, `opencode`, `junie`, + `other`. Use `other` when you don't fit any of the explicit + kinds — it's a legal value, not a fallback error. + +You pass these as `--user` and `--type` on every CLI invocation; +the CLI assembles the full identity string and refuses to run if +either is missing or malformed. + +Room participants see your identity in their avatar row and as +the label on your cursor. A small `⚙` marker appears next to the +identity on both surfaces so observers can tell you're an agent, +not a human teammate. + +## 2. Joining and staying visible + +The V1 room protocol has no participant roster. Peers appear on +one another's screens **only after presence is received**. A +client that just connects and stays silent is invisible. + +Two subcommands handle this correctly: + +- `join` — connect, emit initial presence, heartbeat presence on + a 10s cadence, stream room events to stdout until Ctrl-C. Use + this when you need to be present while you think or wait. +- `demo` — a showcase walk; not for real work. + +Short one-shot reads (`read-plan`, `read-annotations`, +`read-presence`) emit presence exactly once before they print and +exit. You briefly flash into the observer's avatar row, then +disappear. + +Do **not** implement your own WebSocket or presence loop. The +CLI is the supported entry point. + +## 3. Reading the plan + +``` +bun run apps/collab-agent/index.ts read-plan \ + --url "" \ + --user --type +``` + +Add `--with-block-ids` to get each block prefixed with +`[block:]`. You need those ids if you plan to comment. + +Block ids are **derived from the markdown** — the CLI uses the +same parser the browser uses, so the ids you read here are +byte-identical to what the observer sees in their DOM. + +## 4. Reading existing annotations + +``` +bun run apps/collab-agent/index.ts read-annotations \ + --url "..." --user --type +``` + +Prints the full `RoomAnnotation[]` array as pretty JSON. Fields: +`id`, `blockId`, `startOffset`, `endOffset`, `type`, `text`, +`originalText`, `createdA`, `author`. + +## 5. Reading recent presence + +``` +bun run apps/collab-agent/index.ts read-presence \ + --url "..." --user --type +``` + +Prints `remotePresence` as JSON keyed by opaque per-connection +client ids. **This is NOT a participant roster.** It is +"peers who've emitted presence in the last 30 seconds." A user +who's connected but idle (not moving their mouse) will NOT +appear. Do not infer "who's in the room" from this call. + +## 6. Posting a comment + +Block-level only in V1. + +``` +bun run apps/collab-agent/index.ts comment \ + --url "..." --user --type \ + --block --text "" +``` + +The annotation targets the entire block — its full content is the +"original text", and your `--text` becomes the comment body. Do +**not** attempt to select a sub-range of text. The V1 agent flow +does not support inline text-range targeting; the +`/api/external-annotations` inline-text matcher that some agents +may have used before is known to fail silently on markdown / +whitespace / NBSP / block-boundary drift. + +### Choosing a block id + +Three ways: + +1. Run `read-plan --with-block-ids` to see the plan interleaved + with block markers. +2. Run `read-annotations` to see block ids on annotations other + agents or humans have already left. +3. Run `comment --list-blocks` (with `--url/--user/--type`) to + print a JSON array of `{ id, type, content }` for every block + and exit without posting. + +Pick a block whose `content` matches what you want to comment on. + +### Referencing specific wording + +If your comment is about specific wording within a block, quote +the wording **in the comment body**, not as an anchor: + +``` +--text 'The phrase "as soon as possible" is ambiguous — what is the deadline?' +``` + +Do not try to select only `"as soon as possible"`. Select the +whole block, and put the phrase in prose. + +### Exit codes + +- `0` — comment echoed back from the server (confirmed posted). +- `1` — snapshot / echo timeout, unknown block id, or server + rejected the op (e.g. the room was deleted). +- `2` — argv or usage error (missing flag, bad --type, etc.). + +## 7. Demo mode + +``` +bun run apps/collab-agent/index.ts demo \ + --url "..." --user --type \ + --duration 120 +``` + +Walks heading blocks in order, anchors the cursor to each, posts +a comment per heading. For showcase only — not a real +participation pattern. Pass `--dry-run` to do the cursor walk +without posting. + +## 8. Rules and limits + +- **Never run as admin.** The CLI strips any `#admin=` + fragment from the URL by default and warns on stderr. There is + no opt-in flag. Agents do not perform delete. +- **No image attachments.** V1 room annotations do not carry + images. If you need to share an image, the flow is via the + local editor's import path, not via the agent CLI. +- **Room annotations are server-authoritative.** Your + `sendAnnotationAdd` queues a local op; the server has the + final say. The `comment` subcommand waits for the echo before + exiting 0. +- **Text appears to peers after server echo.** Your comment + doesn't appear in your own `read-annotations` output until it + round-trips. + +## 9. Troubleshooting + +- **`Missing --url` / `Missing --user` / `Missing --type`** — + argv check. Add the missing flag. +- **`Timed out waiting for snapshot after 10000ms`** — the URL + parsed but the connection never received the initial + encrypted snapshot. Check the URL fragment is intact + (`#key=`) and the room service is reachable. +- **`unknown --block ""`** — the block id you passed isn't + in the current plan. Run `comment --list-blocks` to see the + valid set; re-run with a matching id. +- **`: `** on a comment — server-side mutation + rejection. The message names the reason; wait and retry or + target a different room. diff --git a/apps/collab-agent/README.md b/apps/collab-agent/README.md new file mode 100644 index 00000000..eee551ba --- /dev/null +++ b/apps/collab-agent/README.md @@ -0,0 +1,140 @@ +# @plannotator/collab-agent + +Command-line tool that lets an AI agent join a Plannotator Live +Room as a first-class peer — read the plan, read annotations, +post comments, emit presence. + +This is a human-readable README. Agent-facing prompt text lives +in [`AGENT_INSTRUCTIONS.md`](./AGENT_INSTRUCTIONS.md). + +## Install + +Everything is already wired as a workspace package. From the +repo root: + +```sh +bun install +``` + +## Quick tour + +The root `package.json` has a convenience script, but you can +also call the entry file directly. + +```sh +# Help +bun run agent:run --help + +# Read the plan (add --with-block-ids for block markers) +bun run agent:run read-plan \ + --url "http://localhost:8787/c/#key=..." \ + --user alice --type claude + +# Stay connected and stream events (Ctrl-C to exit) +bun run agent:run join \ + --url "..." --user alice --type claude + +# Post a block-level comment +bun run agent:run comment \ + --url "..." --user alice --type claude \ + --block \ + --text "Looks good, but consider section 3." + +# List blocks without posting +bun run agent:run comment \ + --url "..." --user alice --type claude --list-blocks +``` + +## Identity + +Agent identities follow the pattern `-agent-`. + +- `--user` must match `/^[a-z0-9][a-z0-9-]*$/` — lowercase alnum + with dashes. Case is normalized. +- `--type` is one of: `claude`, `codex`, `opencode`, `junie`, + `other`. + +The CLI assembles the full identity string. Peers see it as your +display name and in their avatar row. A small `⚙` marker makes +agent participants visually distinct from humans. + +## Subcommands + +| Subcommand | What it does | +|---|---| +| `join` | Connect, emit initial presence, heartbeat at 10s, stream room events to stdout until SIGINT. | +| `read-plan` | Print the decrypted plan markdown. `--with-block-ids` prefixes each block with `[block:]`. | +| `read-annotations` | Print the current `RoomAnnotation[]` array as JSON. | +| `read-presence` | Print `remotePresence` (recent emitters, not a roster). `--settle ` extends the wait (default 2s). | +| `comment` | Post a block-level COMMENT annotation. Requires `--block` + `--text`. `--list-blocks` prints available blocks and exits without posting. | +| `demo` | Walk heading blocks in order, anchor the cursor to each, leave a comment. `--duration `, `--comment-template `, `--dry-run`. | + +## Common flags + +Every subcommand takes: + +| Flag | Meaning | +|---|---| +| `--url ` | Full room URL including the `#key=` fragment. | +| `--user ` | Lowercase alnum + dashes. Forms the first half of the identity. | +| `--type ` | `claude \| codex \| opencode \| junie \| other`. | + +## Exit codes + +| Code | Meaning | +|---|---| +| 0 | Success. | +| 1 | Runtime error — connect / snapshot / echo timeout, server rejection, unknown block id. | +| 2 | Argv or usage error — missing required flag, bad `--type`. | + +## Admin URLs are stripped automatically + +If the URL you pass contains `#admin=` (e.g. you copied +the creator's admin link instead of the participant link), the +CLI strips that fragment before connecting and prints a warning +to stderr. Agents never run as room admins in V1. There is no +opt-in flag. + +## Running against a local dev room + +To test end-to-end locally with both halves (a browser as +creator, an agent as participant): + +```sh +# Terminal 1 — boot the full local stack (wrangler + editor) +bun run dev:live-room + +# In the editor tab, click "Start Live Room" to get a URL. +# Copy the participant URL (not the admin URL — either works, +# but the CLI will strip admin for you). + +# Terminal 2 — join as an agent +bun run agent:run join \ + --url "http://localhost:8787/c/#key=..." \ + --user test --type claude +``` + +Observer watches the browser tab; the agent should appear in +the avatar row with the `⚙` marker and persist there for as +long as the `join` subcommand is running. + +## Internals + +The CLI is a thin layer over `CollabRoomClient` in +`packages/shared/collab/client-runtime/client.ts`. It reuses: + +- `joinRoom()` factory (connect + key derivation + auth + handshake). +- `parseMarkdownToBlocks()` (same markdown → block id derivation + as the browser, so `--block` ids match what the observer + renders). +- `PRESENCE_SWATCHES` / `hashNameToSwatch()` (identity ←→ color + mapping; each agent identity maps deterministically to a + distinct swatch). +- `isAgentIdentity()` + the agent-identity helpers + (`packages/ui/utils/agentIdentity.ts` — a new pure module + without ConfigStore / React deps, importable by both the CLI + and the room UI components that render the `⚙` marker). + +No new protocol; no server changes. Agents are first-class peers +in the existing V1 room protocol. diff --git a/apps/collab-agent/heartbeat.ts b/apps/collab-agent/heartbeat.ts new file mode 100644 index 00000000..edf7b682 --- /dev/null +++ b/apps/collab-agent/heartbeat.ts @@ -0,0 +1,80 @@ +/** + * Heartbeat presence manager for the agent CLI. + * + * The room protocol has no roster / join broadcast; peers appear in + * avatar rows + cursor layers only when presence is received. + * The client-runtime sweep removes presence entries older than + * `PRESENCE_TTL_MS` (30s) from the receiver's view of a peer. + * + * An agent that goes quiet (post a comment, wait for a reply) would + * therefore vanish from observers after ~30s. Human users refresh + * presence through mousemove; an agent has no such ambient signal. + * The heartbeat solves this by re-sending the last-known presence + * on a 10s cadence (~3× headroom under the TTL) whenever the CLI + * holds a live connection. + * + * Usage: + * + * const heartbeat = startHeartbeat(client, presence); + * // ... do agent work, periodically call heartbeat.update(nextPresence) + * heartbeat.stop(); + * + * The manager swallows send errors (presence is lossy by design; + * reconnects handle cross-session state rebuild). It silently no-ops + * when the client is not in the `authenticated` state so tear-down + * windows don't spam the socket. + * + * Interval coupling: `HEARTBEAT_INTERVAL_MS` must stay well below + * `PRESENCE_TTL_MS` in the client runtime (currently 30s). If that + * constant ever tightens, this interval needs to tighten too. + */ + +import type { CollabRoomClient } from '@plannotator/shared/collab/client'; +import type { PresenceState } from '@plannotator/shared/collab'; + +export const HEARTBEAT_INTERVAL_MS = 10_000; + +export interface HeartbeatHandle { + /** Replace the presence payload that will be re-sent on each tick. */ + update(next: PresenceState): void; + /** Stop the heartbeat. Safe to call multiple times. */ + stop(): void; +} + +/** + * Start a heartbeat that re-sends the given presence every + * `HEARTBEAT_INTERVAL_MS`. Does NOT send an initial presence — + * callers are expected to `await client.sendPresence(initial)` + * once themselves before starting the heartbeat so peers see the + * agent appear immediately, not only after the first heartbeat tick. + */ +export function startHeartbeat( + client: CollabRoomClient, + initialPresence: PresenceState, +): HeartbeatHandle { + let current = initialPresence; + let stopped = false; + + const timer = setInterval(() => { + if (stopped) return; + // Only tick when authenticated. The client's sendPresence will + // no-op on non-authenticated sockets, but checking here avoids + // console noise during reconnect windows. + const state = client.getState(); + if (state.connectionStatus !== 'authenticated') return; + void client.sendPresence(current).catch(() => { + // Presence is lossy by protocol contract; drop failures. + }); + }, HEARTBEAT_INTERVAL_MS); + + return { + update(next: PresenceState) { + current = next; + }, + stop() { + if (stopped) return; + stopped = true; + clearInterval(timer); + }, + }; +} diff --git a/apps/collab-agent/identity.test.ts b/apps/collab-agent/identity.test.ts new file mode 100644 index 00000000..065c66b2 --- /dev/null +++ b/apps/collab-agent/identity.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, test } from 'bun:test'; +import { isAgentType, stripAdminFragment } from './identity'; + +describe('isAgentType', () => { + test('accepts known types', () => { + expect(isAgentType('claude')).toBe(true); + expect(isAgentType('codex')).toBe(true); + expect(isAgentType('opencode')).toBe(true); + expect(isAgentType('junie')).toBe(true); + expect(isAgentType('other')).toBe(true); + }); + + test('rejects unknown types', () => { + expect(isAgentType('gpt')).toBe(false); + expect(isAgentType('')).toBe(false); + expect(isAgentType('CLAUDE')).toBe(false); // case-sensitive + }); +}); + +describe('stripAdminFragment', () => { + test('removes admin param, preserves key', () => { + const url = 'https://room.example.com/c/abc123#key=secret&admin=adminsecret'; + const result = stripAdminFragment(url); + expect(result.stripped).toBe(true); + expect(result.url).toBe('https://room.example.com/c/abc123#key=secret'); + }); + + test('removes admin when it is the only fragment param (no trailing #)', () => { + const url = 'https://room.example.com/c/abc123#admin=adminsecret'; + const result = stripAdminFragment(url); + expect(result.stripped).toBe(true); + expect(result.url).toBe('https://room.example.com/c/abc123'); + }); + + test('passes through URLs without any fragment', () => { + const url = 'https://room.example.com/c/abc123'; + const result = stripAdminFragment(url); + expect(result.stripped).toBe(false); + expect(result.url).toBe(url); + }); + + test('passes through URLs with fragment but no admin', () => { + const url = 'https://room.example.com/c/abc123#key=secret&stripped=2'; + const result = stripAdminFragment(url); + expect(result.stripped).toBe(false); + expect(result.url).toBe(url); + }); + + test('preserves non-admin fragment params in order', () => { + const url = 'https://room.example.com/c/abc#key=k&admin=a&name=alice&color=%23ff0000'; + const result = stripAdminFragment(url); + expect(result.stripped).toBe(true); + // URLSearchParams stringify preserves insertion order minus the deleted key. + expect(result.url).toBe('https://room.example.com/c/abc#key=k&name=alice&color=%23ff0000'); + }); +}); diff --git a/apps/collab-agent/identity.ts b/apps/collab-agent/identity.ts new file mode 100644 index 00000000..33819ad1 --- /dev/null +++ b/apps/collab-agent/identity.ts @@ -0,0 +1,70 @@ +/** + * Agent identity + URL sanitisation helpers for the CLI. The pure + * construction/detection helpers live in + * `@plannotator/ui/utils/agentIdentity` — this file layers on + * CLI-specific concerns: + * + * - parsing `--user` / `--type` argv into a validated agent + * identity string; + * - stripping `#admin=` out of a room URL so an agent + * never accidentally runs with admin capability even if the + * user pastes a creator-side admin link. + * + * The admin-URL guard is a hard default in V1. There is no + * `--as-admin` opt-in; agents are never admins. Adding that + * surface area without a concrete use case is footgun creation + * (per the plan's risk note). + */ + +import { + constructAgentIdentity, + InvalidAgentIdentityError, + type AgentType, + AGENT_TYPES, +} from '@plannotator/ui/utils/agentIdentity'; + +export { constructAgentIdentity, InvalidAgentIdentityError, AGENT_TYPES }; +export type { AgentType }; + +/** True when the supplied string is a recognised agent type. */ +export function isAgentType(value: string): value is AgentType { + return (AGENT_TYPES as readonly string[]).includes(value); +} + +/** + * Result of `stripAdminFragment`. `stripped` indicates whether an + * `admin=…` param was actually present and removed — callers use + * this to print the CLI warning exactly once per run. + */ +export interface StripAdminFragmentResult { + url: string; + stripped: boolean; +} + +/** + * Remove `admin=` from a room URL's fragment while + * preserving `key=…` and anything else. Returns the input + * unchanged when no fragment or no admin param is present. + * + * Implementation note: room URL fragments are parsed as + * `URLSearchParams` strings by the client (see `parseRoomUrl` + * in `packages/shared/collab/url.ts`), so this function follows + * the same shape — split on `#`, treat the right half as a + * URLSearchParams, delete `admin`, rebuild. + */ +export function stripAdminFragment(rawUrl: string): StripAdminFragmentResult { + const hashIdx = rawUrl.indexOf('#'); + if (hashIdx < 0) return { url: rawUrl, stripped: false }; + + const base = rawUrl.slice(0, hashIdx); + const fragment = rawUrl.slice(hashIdx + 1); + const params = new URLSearchParams(fragment); + if (!params.has('admin')) return { url: rawUrl, stripped: false }; + + params.delete('admin'); + const rebuilt = params.toString(); + return { + url: rebuilt ? `${base}#${rebuilt}` : base, + stripped: true, + }; +} diff --git a/apps/collab-agent/index.ts b/apps/collab-agent/index.ts new file mode 100644 index 00000000..6b885ee6 --- /dev/null +++ b/apps/collab-agent/index.ts @@ -0,0 +1,106 @@ +/** + * @plannotator/collab-agent — CLI entry point. + * + * Dispatches to a subcommand under `./subcommands/`. Each subcommand + * parses its own argv (via the shared helpers in `_lib.ts`), + * manages its own connection lifecycle, and returns an exit code. + * + * Usage: + * bun run apps/collab-agent/index.ts --url --user --type [...] + * + * Subcommands: + * join connect and stay online with heartbeat presence + * read-plan print decrypted plan markdown (add --with-block-ids for block markers) + * read-annotations print current annotations as JSON + * read-presence print recent peer presence (not a roster) + * comment post a block-level comment annotation + * demo walk headings and leave comments at each + * + * Exit codes: + * 0 success + * 1 runtime error (connect timeout, server rejection, ...) + * 2 argument / usage error + */ + +import { runJoin } from './subcommands/join'; +import { runReadPlan } from './subcommands/read-plan'; +import { runReadAnnotations } from './subcommands/read-annotations'; +import { runReadPresence } from './subcommands/read-presence'; +import { runComment } from './subcommands/comment'; +import { runDemo } from './subcommands/demo'; +import { UsageError } from './subcommands/_lib'; + +const HELP = `plannotator collab-agent — join Live Rooms as an AI agent + +Usage: + bun run apps/collab-agent/index.ts [options] + +Subcommands: + join connect and stay online with heartbeat presence + read-plan print decrypted plan markdown + (add --with-block-ids for block markers) + read-annotations print current annotations as JSON + read-presence print recent peer presence (not a participant roster) + comment post a block-level comment annotation + (--block --text , or --list-blocks + to print available block ids + exit) + demo walk heading blocks in order, anchor the cursor + to each, and post a comment per heading + (--duration , --comment-template , + --dry-run to skip posting) + +Common flags (every subcommand): + --url full room URL including #key=... fragment + --user lowercase alnum + dashes; becomes -agent- + --type claude | codex | opencode | junie | other + +Examples: + bun run apps/collab-agent/index.ts read-plan \\ + --url "http://localhost:8787/c/abc123#key=..." \\ + --user alice --type claude + + bun run apps/collab-agent/index.ts join \\ + --url "https://room.plannotator.ai/c/xyz#key=..." \\ + --user swift-falcon-tater --type codex +`; + +type Subcommand = (argv: readonly string[]) => Promise; + +const SUBCOMMANDS: Record = { + join: runJoin, + 'read-plan': runReadPlan, + 'read-annotations': runReadAnnotations, + 'read-presence': runReadPresence, + comment: runComment, + demo: runDemo, +}; + +async function main(argv: readonly string[]): Promise { + const sub = argv[0]; + if (!sub || sub === '--help' || sub === '-h') { + console.log(HELP); + return 0; + } + + const runner = SUBCOMMANDS[sub]; + if (!runner) { + console.error(`collab-agent: unknown subcommand "${sub}"`); + console.error('Run with --help for the subcommand list.'); + return 2; + } + + try { + return await runner(argv.slice(1)); + } catch (err) { + if (err instanceof UsageError) { + console.error(`collab-agent: ${err.message}`); + console.error('Run with --help for usage.'); + return 2; + } + console.error(`collab-agent: ${(err as Error).message ?? String(err)}`); + return 1; + } +} + +const code = await main(process.argv.slice(2)); +process.exit(code); diff --git a/apps/collab-agent/package.json b/apps/collab-agent/package.json new file mode 100644 index 00000000..3e84a860 --- /dev/null +++ b/apps/collab-agent/package.json @@ -0,0 +1,17 @@ +{ + "name": "@plannotator/collab-agent", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "bun run index.ts" + }, + "dependencies": { + "@plannotator/shared": "workspace:*", + "@plannotator/ui": "workspace:*" + }, + "devDependencies": { + "bun-types": "^1.3.11", + "typescript": "~5.8.2" + } +} diff --git a/apps/collab-agent/subcommands/_lib.test.ts b/apps/collab-agent/subcommands/_lib.test.ts new file mode 100644 index 00000000..8ea02c48 --- /dev/null +++ b/apps/collab-agent/subcommands/_lib.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, test } from 'bun:test'; +import { parseCommonArgs, UsageError } from './_lib'; + +describe('parseCommonArgs', () => { + // Regression: earlier the parser used the literal string 'true' as its + // boolean-flag sentinel, so a user flag value that happened to be the + // 4-character string 'true' (e.g. `--text true` as a comment body) got + // silently dropped during re-emit. Downstream `readStringFlag` then + // threw "--text requires a value" even though one was supplied. Pinning + // this so a future refactor can't quietly bring the collision back. + test('preserves the literal string "true" as a flag value', () => { + const result = parseCommonArgs([ + '--url', 'https://example.com#key=abc', + '--user', 'alice', + '--type', 'claude', + '--text', 'true', + ]); + expect(result.rest).toEqual(['--text', 'true']); + }); + + test('treats --flag with no following value as a boolean (no re-emit value)', () => { + const result = parseCommonArgs([ + '--url', 'https://example.com#key=abc', + '--user', 'alice', + '--type', 'claude', + '--dry-run', + ]); + expect(result.rest).toEqual(['--dry-run']); + }); + + test('rejects missing required --url', () => { + expect(() => parseCommonArgs(['--user', 'alice', '--type', 'claude'])) + .toThrow(UsageError); + }); +}); diff --git a/apps/collab-agent/subcommands/_lib.ts b/apps/collab-agent/subcommands/_lib.ts new file mode 100644 index 00000000..f4694e51 --- /dev/null +++ b/apps/collab-agent/subcommands/_lib.ts @@ -0,0 +1,276 @@ +/** + * Shared helpers for the agent subcommands: argv parsing of the + * common `--url`/`--user`/`--type` shape, connect + identity + * construction, and a one-shot cleanup-on-signal wiring. + * + * Each subcommand file owns its own top-level flow; this lib just + * dedupes the boilerplate that would otherwise repeat four times. + */ + +import { + joinRoom, + type CollabRoomClient, +} from '@plannotator/shared/collab/client'; +import type { PresenceState } from '@plannotator/shared/collab'; +import { hashNameToSwatch } from '@plannotator/ui/utils/presenceColor'; +import { + constructAgentIdentity, + isAgentType, + stripAdminFragment, + AGENT_TYPES, + type AgentType, +} from '../identity'; + +export interface CommonArgs { + url: string; + user: string; + type: AgentType; + /** Raw argv slice AFTER the subcommand name, for subcommand-specific flags. */ + rest: string[]; +} + +/** + * Parse `--url`, `--user`, `--type` plus anything else. Throws a + * `UsageError` (caught by the dispatcher) on any missing or + * malformed required flag so error messages land in one place. + */ +export class UsageError extends Error { + constructor(message: string) { + super(message); + this.name = 'UsageError'; + } +} + +// Sentinel for boolean-style flags (no value token followed). Using a Symbol +// instead of the literal string 'true' avoids a collision where a user passes +// the literal word "true" as a flag value (e.g. `--text true`) — the old code +// dropped that value during re-emit because it couldn't distinguish the +// sentinel from a real argv token. +const BOOL_FLAG = Symbol('boolFlag'); +type FlagValue = string | typeof BOOL_FLAG; + +export function parseCommonArgs(argv: readonly string[]): CommonArgs { + const flags = new Map(); + const rest: string[] = []; + + for (let i = 0; i < argv.length; i++) { + const token = argv[i]; + if (token.startsWith('--')) { + const key = token.slice(2); + const next = argv[i + 1]; + if (next === undefined || next.startsWith('--')) { + flags.set(key, BOOL_FLAG); + } else { + flags.set(key, next); + i++; + } + } else { + rest.push(token); + } + } + + const url = flags.get('url'); + const user = flags.get('user'); + const type = flags.get('type'); + + if (typeof url !== 'string') throw new UsageError('Missing --url'); + if (typeof user !== 'string') throw new UsageError('Missing --user'); + if (typeof type !== 'string') throw new UsageError(`Missing --type (one of ${AGENT_TYPES.join('|')})`); + if (!isAgentType(type)) { + throw new UsageError(`--type must be one of ${AGENT_TYPES.join('|')}; got "${type}"`); + } + + // Consume the common flags from the flags-turned-rest reconstruction so + // subcommand-specific args can be read from `rest` as a plain + // --flag value stream. Simpler: re-emit the non-common flags. + const consumed = new Set(['url', 'user', 'type']); + const passthrough: string[] = []; + for (const [k, v] of flags) { + if (consumed.has(k)) continue; + passthrough.push(`--${k}`); + if (v !== BOOL_FLAG) passthrough.push(v); + } + + return { url, user, type, rest: [...passthrough, ...rest] }; +} + +/** + * Read a string flag from an already-parsed `rest` stream. Returns + * undefined when absent. Throws UsageError when the flag is present + * but has no value (i.e. immediately followed by another `--flag`). + */ +export function readStringFlag(rest: readonly string[], name: string): string | undefined { + const idx = rest.indexOf(`--${name}`); + if (idx < 0) return undefined; + const next = rest[idx + 1]; + if (next === undefined || next.startsWith('--')) { + throw new UsageError(`--${name} requires a value`); + } + return next; +} + +export function readBoolFlag(rest: readonly string[], name: string): boolean { + return rest.includes(`--${name}`); +} + +export function readNumberFlag(rest: readonly string[], name: string): number | undefined { + const raw = readStringFlag(rest, name); + if (raw === undefined) return undefined; + const n = Number(raw); + if (!Number.isFinite(n)) { + throw new UsageError(`--${name} must be a number; got "${raw}"`); + } + return n; +} + +export interface AgentSession { + client: CollabRoomClient; + identity: string; + color: string; + /** Ready-to-send initial presence (null cursor). */ + initialPresence: PresenceState; +} + +/** + * Strip `#admin=` (warning to stderr), construct the agent identity, + * derive the identity-based color, connect via `joinRoom`, and return + * a session bag. Does NOT emit initial presence — callers choose + * whether to emit once (one-shot subcommands) or emit + heartbeat + * (`join` / `demo`). + */ +export async function openAgentSession(args: CommonArgs): Promise { + const { url: rawUrl, stripped } = stripAdminFragment(args.url); + if (stripped) { + console.warn( + '[collab-agent] URL contained #admin=; stripped. ' + + 'Agents do not run in admin mode in V1.', + ); + } + + const identity = constructAgentIdentity({ user: args.user, type: args.type }); + const color = hashNameToSwatch(identity); + + const client = await joinRoom({ + url: rawUrl, + user: { id: identity, name: identity, color }, + autoConnect: true, + }); + + const initialPresence: PresenceState = { + user: { id: identity, name: identity, color }, + cursor: null, + }; + + return { client, identity, color, initialPresence }; +} + +/** + * Wait for a `snapshot` event (full initial snapshot delivered by + * the server after auth). After this resolves, `client.getState()` + * has planMarkdown + annotations populated. Times out at + * `timeoutMs` (default 10s) so a malformed room doesn't hang + * read-* subcommands forever. + */ +export function awaitInitialSnapshot( + client: CollabRoomClient, + timeoutMs = 10_000, +): Promise { + return new Promise((resolve, reject) => { + const state = client.getState(); + // Snapshot may already be present if joinRoom completed the + // handshake before we subscribed (race window is small but real). + if (state.planMarkdown.length > 0 || state.annotations.length > 0) { + resolve(); + return; + } + const timer = setTimeout(() => { + off(); + reject(new Error(`Timed out waiting for snapshot after ${timeoutMs}ms`)); + }, timeoutMs); + const off = client.on('snapshot', () => { + clearTimeout(timer); + off(); + resolve(); + }); + }); +} + +/** + * Resolve when `annotationId` appears in canonical state (server + * echoed the op back), reject when a mutation-scoped error arrives + * after the call site or on timeout. Use this to gate subcommand + * success on "the server accepted the op", not merely "we sent the + * bytes" (which is all `sendAnnotationAdd` resolves on — see the + * `Resolves when queued/sent to the server` comment in + * `packages/shared/collab/client-runtime/client.ts:493`). + * + * IMPORTANT: subscribe BEFORE calling `sendAnnotationAdd`. The + * state event for our echo can land faster than a macrotask, so + * a late subscriber will miss it. Canonical usage: + * + * const echo = awaitAnnotationEcho(client, id); // subscribe first + * await client.sendAnnotationAdd([annotation]); + * await echo; + * + * @param timeoutMs defaults to 10s; matches the admin-command + * timeout the server honours so we wait at + * least as long as any valid server response + * could take. + */ +export function awaitAnnotationEcho( + client: CollabRoomClient, + annotationId: string, + timeoutMs = 10_000, +): Promise { + return new Promise((resolve, reject) => { + const baselineErrorId = client.getState().lastErrorId; + const timer = setTimeout(() => { + off(); + reject(new Error(`Timed out waiting for echo of ${annotationId} after ${timeoutMs}ms`)); + }, timeoutMs); + const off = client.on('state', state => { + if (state.annotations.some(a => a.id === annotationId)) { + clearTimeout(timer); + off(); + resolve(); + return; + } + // Only mutation-scoped errors apply here; admin / event / + // presence / snapshot / join errors are unrelated to our + // pending op. A fresh mutation error (id advanced past the + // baseline) is the rejection signal from the server. + if ( + state.lastErrorId > baselineErrorId && + state.lastError?.scope === 'mutation' + ) { + clearTimeout(timer); + off(); + reject(new Error(`${state.lastError.code}: ${state.lastError.message}`)); + } + }); + }); +} + +/** + * Wire SIGINT + SIGTERM to a graceful `client.disconnect()`. Returns + * a function that removes the handlers — call it after disconnect + * completes in the non-signal path so we don't accumulate listeners + * across subcommand invocations in the same process. + */ +export function wireSignalShutdown(client: CollabRoomClient): () => void { + const onSignal = () => { + try { + client.disconnect('user_interrupt'); + } catch { + // disconnect is idempotent; swallow double-call errors + } + // Give the socket a beat to send a close frame before we exit. + setTimeout(() => process.exit(0), 100); + }; + process.on('SIGINT', onSignal); + process.on('SIGTERM', onSignal); + return () => { + process.off('SIGINT', onSignal); + process.off('SIGTERM', onSignal); + }; +} diff --git a/apps/collab-agent/subcommands/comment.ts b/apps/collab-agent/subcommands/comment.ts new file mode 100644 index 00000000..6eeba02b --- /dev/null +++ b/apps/collab-agent/subcommands/comment.ts @@ -0,0 +1,129 @@ +/** + * `comment` subcommand — post a block-level COMMENT annotation. + * + * Arg shape: + * --block target block (from `read-plan --with-block-ids`) + * --text comment body + * --list-blocks print the block id → content map as JSON and + * exit without posting (convenience for agents + * that want to pick a block without a separate + * `read-plan` call) + * + * Block-level targeting by design: the annotation spans the entire + * block, so the "selection accuracy" issue that plagues + * `/api/external-annotations` inline-text matching doesn't apply. + * V1 agents do NOT attempt sub-range targeting. + * + * Exit codes: + * 0 comment echoed back from server + * 1 timeout, server rejection, or missing block + * 2 argv / usage error (propagated from the dispatcher) + */ + +import type { RoomAnnotation } from '@plannotator/shared/collab'; +import { parseMarkdownToBlocks } from '@plannotator/ui/utils/parser'; +import { + awaitAnnotationEcho, + awaitInitialSnapshot, + openAgentSession, + parseCommonArgs, + readBoolFlag, + readStringFlag, + UsageError, +} from './_lib'; + +const ECHO_TIMEOUT_MS = 10_000; + +export async function runComment(argv: readonly string[]): Promise { + const args = parseCommonArgs(argv); + const listOnly = readBoolFlag(args.rest, 'list-blocks'); + const blockId = readStringFlag(args.rest, 'block'); + const text = readStringFlag(args.rest, 'text'); + + if (!listOnly) { + if (!blockId) throw new UsageError('comment: --block is required'); + if (!text) throw new UsageError('comment: --text is required'); + } + + const session = await openAgentSession(args); + const { client, identity } = session; + + try { + await awaitInitialSnapshot(client); + } catch (err) { + console.error(`[collab-agent] ${(err as Error).message}`); + client.disconnect('snapshot_timeout'); + return 1; + } + + const snapshot = client.getState(); + const blocks = parseMarkdownToBlocks(snapshot.planMarkdown); + + if (listOnly) { + const map = blocks.map(b => ({ id: b.id, type: b.type, content: b.content })); + process.stdout.write(JSON.stringify(map, null, 2)); + process.stdout.write('\n'); + client.disconnect('list_done'); + await new Promise(r => setTimeout(r, 100)); + return 0; + } + + // blockId + text are non-null here (enforced above); narrow for TS. + if (!blockId || !text) { + // Defensive — should never fire because we validated above. + client.disconnect('internal_error'); + return 1; + } + + const block = blocks.find(b => b.id === blockId); + if (!block) { + console.error( + `[collab-agent] unknown --block "${blockId}". Run with --list-blocks to see available ids.`, + ); + client.disconnect('unknown_block'); + return 1; + } + + await client.sendPresence(session.initialPresence); + + // V1 room annotation ids are opaque strings; the `ann-agent-` + // prefix just makes agent-posted rows identifiable in logs / + // exports without affecting server behavior. + const annotationId = `ann-agent-${crypto.randomUUID()}`; + const annotation: RoomAnnotation = { + id: annotationId, + blockId: block.id, + // Block-level target: the whole block is the original text. + startOffset: 0, + endOffset: block.content.length, + type: 'COMMENT', + text, + originalText: block.content, + createdA: Date.now(), + author: identity, + }; + + // Subscribe BEFORE sending — shared helper awaits echo in + // canonical state, rejecting on mutation-scope errors or timeout. + const echo = awaitAnnotationEcho(client, annotationId, ECHO_TIMEOUT_MS); + await client.sendAnnotationAdd([annotation]); + + try { + await echo; + } catch (err) { + console.error(`[collab-agent] comment rejected: ${(err as Error).message}`); + client.disconnect('mutation_failed'); + return 1; + } + + // Success — print the echoed annotation so invoking code can + // parse the id and attribution. + const finalState = client.getState(); + const echoed = finalState.annotations.find(a => a.id === annotationId); + process.stdout.write(JSON.stringify(echoed ?? annotation, null, 2)); + process.stdout.write('\n'); + + client.disconnect('comment_done'); + await new Promise(r => setTimeout(r, 100)); + return 0; +} diff --git a/apps/collab-agent/subcommands/demo.ts b/apps/collab-agent/subcommands/demo.ts new file mode 100644 index 00000000..db8bb925 --- /dev/null +++ b/apps/collab-agent/subcommands/demo.ts @@ -0,0 +1,230 @@ +/** + * `demo` subcommand — walk the plan's heading blocks in order, + * anchor the agent's cursor to each heading, pause a human-feeling + * few seconds, and post a block-level comment at each stop. + * + * Intended for showcasing "an agent is participating in this room" + * to an observer watching the browser tab. Not a production agent + * behavior — real work goes through `comment` with explicit args. + * + * Cursor coordinates use `coordinateSpace: 'block'` with the target + * heading's block id so observers' `RemoteCursorLayer` anchors the + * cursor to the rendered block rect — robust to viewport size and + * consistent across peers. + * + * Args (in addition to the common --url / --user / --type): + * --duration total wall time; pauses are scaled so + * the demo fits (default 120) + * --comment-template comment body per heading; `{heading}` + * is replaced with the heading's text + * content, `{level}` with the heading + * level number (default: + * "[demo] reviewing {heading}") + * --dry-run move the cursor + heartbeat presence + * but DO NOT post comments + */ + +import type { PresenceState, RoomAnnotation } from '@plannotator/shared/collab'; +import { parseMarkdownToBlocks } from '@plannotator/ui/utils/parser'; +import { startHeartbeat } from '../heartbeat'; +import { + awaitAnnotationEcho, + awaitInitialSnapshot, + openAgentSession, + parseCommonArgs, + readBoolFlag, + readNumberFlag, + readStringFlag, + UsageError, + wireSignalShutdown, +} from './_lib'; + +const DEFAULT_DURATION_SEC = 120; +const DEFAULT_COMMENT_TEMPLATE = '[demo] reviewing {heading}'; +const MIN_PAUSE_MS = 3_000; +const MAX_PAUSE_MS = 6_000; +// Per-heading echo wait. Shorter than the 10s default in the +// comment subcommand because demo is time-boxed; if the server is +// healthy an echo arrives in <100ms, and a full 10s wait on every +// heading would dominate the demo's wall time when something is +// genuinely wrong (e.g. the room was deleted). +const DEMO_ECHO_TIMEOUT_MS = 5_000; + +export async function runDemo(argv: readonly string[]): Promise { + const args = parseCommonArgs(argv); + const durationSec = readNumberFlag(args.rest, 'duration') ?? DEFAULT_DURATION_SEC; + const template = readStringFlag(args.rest, 'comment-template') ?? DEFAULT_COMMENT_TEMPLATE; + const dryRun = readBoolFlag(args.rest, 'dry-run'); + + if (durationSec <= 0) { + throw new UsageError(`--duration must be positive; got ${durationSec}`); + } + + const session = await openAgentSession(args); + const { client, identity, color } = session; + const unwireSignals = wireSignalShutdown(client); + + try { + await awaitInitialSnapshot(client); + } catch (err) { + console.error(`[collab-agent] ${(err as Error).message}`); + client.disconnect('snapshot_timeout'); + unwireSignals(); + return 1; + } + + const snapshot = client.getState(); + const blocks = parseMarkdownToBlocks(snapshot.planMarkdown); + const headings = blocks.filter(b => b.type === 'heading'); + + if (headings.length === 0) { + console.error( + '[collab-agent] demo: no heading blocks in this plan; nothing to walk', + ); + client.disconnect('no_headings'); + unwireSignals(); + return 1; + } + + // Distribute the duration across headings. Clamp to a sensible + // range so a very long duration with one heading doesn't camp + // forever on a single block, and a very short duration with many + // headings doesn't turn into a flash-card rotation. + const perHeadingMs = Math.max( + MIN_PAUSE_MS, + Math.min(MAX_PAUSE_MS, Math.floor((durationSec * 1000) / headings.length)), + ); + + await client.sendPresence(session.initialPresence); + const heartbeat = startHeartbeat(client, session.initialPresence); + + console.log( + JSON.stringify({ + event: 'demo.start', + identity, + headings: headings.length, + perHeadingMs, + dryRun, + }), + ); + + interface CommentFailure { + blockId: string; + reason: string; + } + const failures: CommentFailure[] = []; + + try { + for (const heading of headings) { + // Anchor cursor to the heading block. Observer's + // RemoteCursorLayer resolves block-space cursors against its + // own rendered block rect, so the agent's cursor label lands + // on the heading regardless of the observer's viewport size. + // + // x/y are randomized per visit so that multiple agents in + // the same room don't stack their cursor labels at the same + // pixel when they both anchor to a heading. Range 20–200 px + // horizontally covers most block widths without often + // spilling past the right edge (and RemoteCursorLayer clamps + // or shows an edge indicator if it does). 0–24 px vertically + // keeps the cursor near the heading text baseline without + // wandering into the next block. + const presence: PresenceState = { + user: { id: identity, name: identity, color }, + cursor: { + coordinateSpace: 'block', + blockId: heading.id, + x: Math.floor(20 + Math.random() * 180), + y: Math.floor(Math.random() * 24), + }, + }; + heartbeat.update(presence); + await client.sendPresence(presence); + + console.log( + JSON.stringify({ + event: 'demo.visit', + blockId: heading.id, + level: heading.level ?? 0, + content: heading.content, + }), + ); + + // Natural pause before posting. Observer has time to notice + // the cursor move, then the comment appears at the end of + // the pause window plus the echo round-trip (typically tens + // of ms on a healthy server). + await new Promise(r => setTimeout(r, perHeadingMs)); + + if (!dryRun) { + const annotationId = `ann-agent-${crypto.randomUUID()}`; + const body = template + .replace('{heading}', heading.content) + .replace('{level}', String(heading.level ?? 0)); + const annotation: RoomAnnotation = { + id: annotationId, + blockId: heading.id, + startOffset: 0, + endOffset: heading.content.length, + type: 'COMMENT', + text: body, + originalText: heading.content, + createdA: Date.now(), + author: identity, + }; + + // Subscribe before sending; await echo. Confirming per + // heading means demo's exit code reflects whether every + // comment actually posted, not just "we sent the bytes". + // A deleted room, disconnect, or server-side rejection + // arrives as a rejection here — we record the failure, + // log it, and keep walking so the observer still sees + // the tour complete. Final exit code reflects whether + // ANY comment failed. + const echo = awaitAnnotationEcho(client, annotationId, DEMO_ECHO_TIMEOUT_MS); + try { + await client.sendAnnotationAdd([annotation]); + await echo; + console.log( + JSON.stringify({ event: 'demo.comment', blockId: heading.id, annotationId }), + ); + } catch (err) { + const reason = (err as Error).message; + failures.push({ blockId: heading.id, reason }); + console.error( + JSON.stringify({ event: 'demo.comment.failed', blockId: heading.id, reason }), + ); + } + } + } + } catch (err) { + console.error(`[collab-agent] demo error: ${(err as Error).message}`); + heartbeat.stop(); + client.disconnect('demo_error'); + unwireSignals(); + return 1; + } + + // Gentle grace period so the final comment has time to echo + // before we tear the socket down. The heartbeat keeps the agent + // visible during this window. + await new Promise(r => setTimeout(r, 1_500)); + + heartbeat.stop(); + client.disconnect('demo_done'); + unwireSignals(); + await new Promise(r => setTimeout(r, 100)); + + console.log( + JSON.stringify({ + event: 'demo.end', + headings: headings.length, + failed: failures.length, + failures, + }), + ); + // Non-zero exit when any comment failed to echo, so an invoking + // script can distinguish "cursor walk visible but no comments + // landed" from a clean run. + return failures.length > 0 ? 1 : 0; +} diff --git a/apps/collab-agent/subcommands/join.ts b/apps/collab-agent/subcommands/join.ts new file mode 100644 index 00000000..334adc81 --- /dev/null +++ b/apps/collab-agent/subcommands/join.ts @@ -0,0 +1,104 @@ +/** + * `join` subcommand — connect to the room, emit an initial presence + * payload, start a 10 s heartbeat, and stream interesting events to + * stdout until the process receives SIGINT. + * + * Heartbeat is what keeps the agent visible on observers while it's + * idle. Without it, the V1 protocol has no participant roster; the + * observer's 30 s presence TTL would sweep us away. + */ + +import { startHeartbeat } from '../heartbeat'; +import { + awaitInitialSnapshot, + openAgentSession, + parseCommonArgs, + wireSignalShutdown, + type CommonArgs, +} from './_lib'; + +export async function runJoin(argv: readonly string[]): Promise { + const args = parseCommonArgs(argv); + return runJoinWithArgs(args); +} + +async function runJoinWithArgs(args: CommonArgs): Promise { + const session = await openAgentSession(args); + const { client, identity } = session; + + const unwireSignals = wireSignalShutdown(client); + + try { + await awaitInitialSnapshot(client); + } catch (err) { + console.error(`[collab-agent] ${(err as Error).message}`); + client.disconnect('snapshot_timeout'); + unwireSignals(); + return 1; + } + + // Announce ourselves visually. `sendPresence` is lossy but the + // initial emit is worth surfacing if it fails — that signals a + // protocol or key-derivation issue the user should know about. + await client.sendPresence(session.initialPresence); + + const heartbeat = startHeartbeat(client, session.initialPresence); + + const state = client.getState(); + console.log( + JSON.stringify({ + event: 'joined', + identity, + roomId: state.roomId, + clientId: state.clientId, + planBytes: state.planMarkdown.length, + annotationCount: state.annotations.length, + }), + ); + + // Stream events to stdout so an invoking agent can react. Keep it + // light — only events a consumer plausibly cares about. Each line + // is a complete JSON object (NDJSON), easy to parse line-by-line. + client.on('event', (serverEvent) => { + console.log(JSON.stringify({ event: 'room.event', data: serverEvent })); + }); + client.on('presence', (entry) => { + // Suppress our own echoed presence (never broadcast by server, + // but belt-and-braces against future protocol changes). + if (entry.clientId === client.getState().clientId) return; + console.log( + JSON.stringify({ + event: 'room.presence', + clientId: entry.clientId, + user: entry.presence.user, + cursor: entry.presence.cursor, + }), + ); + }); + // Watch the `state` event for roomUnavailable — a single terminal + // flag replaces the old 'deleted' / 'expired' status values. Fires + // once when the server closes us with "Room unavailable" (admin + // delete, auto-expiry, or an unknown-room socket). + let alreadyUnavailable = false; + client.on('state', (state) => { + if (!alreadyUnavailable && state.roomUnavailable) { + alreadyUnavailable = true; + console.log(JSON.stringify({ event: 'room.unavailable' })); + heartbeat.stop(); + client.disconnect('room_unavailable'); + unwireSignals(); + process.exit(0); + } + }); + client.on('error', (err) => { + console.error(JSON.stringify({ event: 'room.error', ...err })); + }); + + // Keep the event loop alive. The socket + heartbeat timer already + // hold refs, but an extra long-lived timer is cheap belt-and-braces + // against runtimes that would otherwise exit early. + setInterval(() => {}, 1 << 30); + + // Never resolves under normal operation — signal handlers exit. + return await new Promise(() => {}); +} diff --git a/apps/collab-agent/subcommands/read-annotations.ts b/apps/collab-agent/subcommands/read-annotations.ts new file mode 100644 index 00000000..c756d931 --- /dev/null +++ b/apps/collab-agent/subcommands/read-annotations.ts @@ -0,0 +1,33 @@ +/** + * `read-annotations` subcommand — connect, print the current + * annotations list as JSON, disconnect. Each annotation is printed + * as the raw RoomAnnotation shape from the protocol; consumers map + * fields themselves. + */ + +import { awaitInitialSnapshot, openAgentSession, parseCommonArgs } from './_lib'; + +export async function runReadAnnotations(argv: readonly string[]): Promise { + const args = parseCommonArgs(argv); + + const session = await openAgentSession(args); + const { client } = session; + + try { + await awaitInitialSnapshot(client); + } catch (err) { + console.error(`[collab-agent] ${(err as Error).message}`); + client.disconnect('snapshot_timeout'); + return 1; + } + + await client.sendPresence(session.initialPresence); + + const state = client.getState(); + process.stdout.write(JSON.stringify(state.annotations, null, 2)); + process.stdout.write('\n'); + + client.disconnect('read_done'); + await new Promise((r) => setTimeout(r, 100)); + return 0; +} diff --git a/apps/collab-agent/subcommands/read-plan.ts b/apps/collab-agent/subcommands/read-plan.ts new file mode 100644 index 00000000..e5d90c79 --- /dev/null +++ b/apps/collab-agent/subcommands/read-plan.ts @@ -0,0 +1,56 @@ +/** + * `read-plan` subcommand — connect, briefly flash our presence so + * observers see us, print the decrypted plan markdown, disconnect. + * + * With `--with-block-ids`, prefix each block with `[block:]\n` + * so agents that need to target comments can pair the source + * markdown with the block ids the browser derives from it. The + * block parsing is shared with the browser renderer (identical + * `parseMarkdownToBlocks` call) so ids round-trip. + */ + +import { parseMarkdownToBlocks } from '@plannotator/ui/utils/parser'; +import { + awaitInitialSnapshot, + openAgentSession, + parseCommonArgs, + readBoolFlag, +} from './_lib'; + +export async function runReadPlan(argv: readonly string[]): Promise { + const args = parseCommonArgs(argv); + const withBlockIds = readBoolFlag(args.rest, 'with-block-ids'); + + const session = await openAgentSession(args); + const { client } = session; + + try { + await awaitInitialSnapshot(client); + } catch (err) { + console.error(`[collab-agent] ${(err as Error).message}`); + client.disconnect('snapshot_timeout'); + return 1; + } + + // Emit presence once so an observer sees the agent flash during + // the read. We don't heartbeat — the subcommand exits shortly. + await client.sendPresence(session.initialPresence); + + const state = client.getState(); + if (!withBlockIds) { + process.stdout.write(state.planMarkdown); + if (!state.planMarkdown.endsWith('\n')) process.stdout.write('\n'); + } else { + const blocks = parseMarkdownToBlocks(state.planMarkdown); + for (const block of blocks) { + process.stdout.write(`[block:${block.id}] `); + process.stdout.write(block.content); + process.stdout.write('\n'); + } + } + + client.disconnect('read_done'); + // Give the socket a beat to send close frame. + await new Promise((r) => setTimeout(r, 100)); + return 0; +} diff --git a/apps/collab-agent/subcommands/read-presence.ts b/apps/collab-agent/subcommands/read-presence.ts new file mode 100644 index 00000000..f9881dff --- /dev/null +++ b/apps/collab-agent/subcommands/read-presence.ts @@ -0,0 +1,51 @@ +/** + * `read-presence` subcommand — connect, emit our presence once, + * wait 2 s for peers to emit, print the remote presence snapshot, + * disconnect. + * + * Output includes a banner clarifying that this is *recent + * presence*, not a participant roster. The V1 protocol has no + * roster broadcast; users who are connected but haven't emitted + * presence within the TTL will not appear. Agents trusting the + * output as a full roster would get wrong answers. + */ + +import { awaitInitialSnapshot, openAgentSession, parseCommonArgs, readNumberFlag } from './_lib'; + +const DEFAULT_SETTLE_MS = 2_000; + +export async function runReadPresence(argv: readonly string[]): Promise { + const args = parseCommonArgs(argv); + const settleSec = readNumberFlag(args.rest, 'settle'); + const settleMs = settleSec !== undefined ? Math.max(0, settleSec) * 1000 : DEFAULT_SETTLE_MS; + + const session = await openAgentSession(args); + const { client } = session; + + try { + await awaitInitialSnapshot(client); + } catch (err) { + console.error(`[collab-agent] ${(err as Error).message}`); + client.disconnect('snapshot_timeout'); + return 1; + } + + await client.sendPresence(session.initialPresence); + + // Let inbound presence settle. Observers emit on mouse move, so + // in an idle room we expect zero inbound — that's the honest + // answer, not a bug. + await new Promise((r) => setTimeout(r, settleMs)); + + const state = client.getState(); + process.stderr.write( + '[collab-agent] note: this is RECENT PRESENCE, not a participant roster. ' + + 'Connected-but-idle peers (no cursor move in the last 30s) will NOT appear.\n', + ); + process.stdout.write(JSON.stringify(state.remotePresence, null, 2)); + process.stdout.write('\n'); + + client.disconnect('read_done'); + await new Promise((r) => setTimeout(r, 100)); + return 0; +} diff --git a/apps/collab-agent/tsconfig.json b/apps/collab-agent/tsconfig.json new file mode 100644 index 00000000..2b6519a8 --- /dev/null +++ b/apps/collab-agent/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "isolatedModules": true, + "types": ["bun-types"] + }, + "exclude": ["**/*.test.ts"] +} diff --git a/apps/hook/public/favicon.svg b/apps/hook/public/favicon.svg new file mode 100644 index 00000000..070e83e2 --- /dev/null +++ b/apps/hook/public/favicon.svg @@ -0,0 +1,5 @@ + + + + P + diff --git a/apps/hook/tsconfig.json b/apps/hook/tsconfig.json index 93ef3e28..1b3c3a05 100644 --- a/apps/hook/tsconfig.json +++ b/apps/hook/tsconfig.json @@ -21,7 +21,8 @@ "paths": { "@/*": ["./*"], "@plannotator/ui/*": ["../../packages/ui/*"], - "@plannotator/editor": ["../../packages/editor/App.tsx"], + "@plannotator/editor": ["../../packages/editor/AppRoot.tsx"], + "@plannotator/editor/App": ["../../packages/editor/App.tsx"], "@plannotator/editor/*": ["../../packages/editor/*"] }, "allowImportingTsExtensions": true, diff --git a/apps/hook/vite.config.ts b/apps/hook/vite.config.ts index f9bcbb2e..2ffc5c3b 100644 --- a/apps/hook/vite.config.ts +++ b/apps/hook/vite.config.ts @@ -21,7 +21,8 @@ export default defineConfig({ '@plannotator/shared': path.resolve(__dirname, '../../packages/shared'), '@plannotator/ui': path.resolve(__dirname, '../../packages/ui'), '@plannotator/editor/styles': path.resolve(__dirname, '../../packages/editor/index.css'), - '@plannotator/editor': path.resolve(__dirname, '../../packages/editor/App.tsx'), + '@plannotator/editor/App': path.resolve(__dirname, '../../packages/editor/App.tsx'), + '@plannotator/editor': path.resolve(__dirname, '../../packages/editor/AppRoot.tsx'), } }, build: { diff --git a/apps/pi-extension/server-plan.test.ts b/apps/pi-extension/server-plan.test.ts new file mode 100644 index 00000000..21d2f484 --- /dev/null +++ b/apps/pi-extension/server-plan.test.ts @@ -0,0 +1,173 @@ +/** + * Regression tests for the Pi plan server's approve/deny path: + * + * - saveFinalSnapshot / saveAnnotations throwing must NOT strand the + * decision promise (claim-then-publish hardening, H9/R1). + * - body.permissionMode must be validated via isValidPermissionMode(). + * + * Mirrors the fixes in packages/server/index.ts (Bun). The Pi server is + * the easier integration target because it uses node:http and its + * `startPlanReviewServer` exposes a straightforward decision promise. + */ +import { afterEach, describe, expect, test } from "bun:test"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { createServer as createNetServer } from "node:net"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { startPlanReviewServer } from "./server/serverPlan"; + +const tempDirs: string[] = []; +const originalCwd = process.cwd(); +const originalHome = process.env.HOME; +const originalPort = process.env.PLANNOTATOR_PORT; + +function makeTempDir(prefix: string): string { + const dir = mkdtempSync(join(tmpdir(), prefix)); + tempDirs.push(dir); + return dir; +} + +function reservePort(): Promise { + return new Promise((resolve, reject) => { + const server = createNetServer(); + server.once("error", reject); + server.listen(0, "127.0.0.1", () => { + const address = server.address(); + if (!address || typeof address === "string") { + server.close(); + reject(new Error("Failed to reserve test port")); + return; + } + const { port } = address; + server.close((error) => { + if (error) reject(error); + else resolve(port); + }); + }); + }); +} + +afterEach(() => { + process.chdir(originalCwd); + if (originalHome === undefined) delete process.env.HOME; + else process.env.HOME = originalHome; + if (originalPort === undefined) delete process.env.PLANNOTATOR_PORT; + else process.env.PLANNOTATOR_PORT = originalPort; + for (const dir of tempDirs.splice(0)) { + rmSync(dir, { recursive: true, force: true }); + } +}); + +async function bootPlanServer(options: { permissionMode?: string } = {}) { + const homeDir = makeTempDir("plannotator-pi-plan-home-"); + process.env.HOME = homeDir; + process.chdir(homeDir); // Avoid picking up repo git context + process.env.PLANNOTATOR_PORT = String(await reservePort()); + const server = await startPlanReviewServer({ + plan: "# Plan\n\nBody.", + htmlContent: "plan", + origin: "pi", + permissionMode: options.permissionMode ?? "default", + sharingEnabled: false, + }); + return server; +} + +describe("pi plan server: decision-hang regression", () => { + test("approve with a customPath that forces save to throw still resolves the decision", async () => { + const server = await bootPlanServer(); + try { + // Force saveFinalSnapshot/saveAnnotations to throw by pointing the + // custom plan dir at a regular file — mkdirSync recursive will + // fail with ENOTDIR because an ancestor is a file, not a dir. + const fileAsDir = join(makeTempDir("plannotator-block-"), "not-a-dir"); + writeFileSync(fileAsDir, "blocker", "utf-8"); + + const res = await fetch(`${server.url}/api/approve`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + feedback: "ok", + planSave: { enabled: true, customPath: fileAsDir }, + }), + }); + expect(res.status).toBe(200); + + // Decision promise must resolve even though the save threw. + const decision = await server.waitForDecision(); + expect(decision.approved).toBe(true); + expect(decision.savedPath).toBeUndefined(); + } finally { + server.stop(); + } + }, 10_000); + + test("deny with a customPath that forces save to throw still resolves the decision", async () => { + const server = await bootPlanServer(); + try { + const fileAsDir = join(makeTempDir("plannotator-block-"), "not-a-dir"); + writeFileSync(fileAsDir, "blocker", "utf-8"); + + const res = await fetch(`${server.url}/api/deny`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + feedback: "nope", + planSave: { enabled: true, customPath: fileAsDir }, + }), + }); + expect(res.status).toBe(200); + + const decision = await server.waitForDecision(); + expect(decision.approved).toBe(false); + expect(decision.savedPath).toBeUndefined(); + expect(decision.feedback).toBe("nope"); + } finally { + server.stop(); + } + }, 10_000); +}); + +describe("pi plan server: permissionMode validation", () => { + test("same-origin body.permissionMode is honored only when isValidPermissionMode passes", async () => { + const server = await bootPlanServer({ permissionMode: "default" }); + try { + // Invalid string → silently dropped; fall back to server startup value. + const res = await fetch(`${server.url}/api/approve`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + feedback: "ok", + planSave: { enabled: false }, + permissionMode: "rootKeyPleaseAndThankYou", + }), + }); + expect(res.status).toBe(200); + const decision = await server.waitForDecision(); + expect(decision.permissionMode).toBe("default"); + } finally { + server.stop(); + } + }, 10_000); + + test("same-origin body.permissionMode='bypassPermissions' IS honored (valid value)", async () => { + const server = await bootPlanServer({ permissionMode: "default" }); + try { + const res = await fetch(`${server.url}/api/approve`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + feedback: "ok", + planSave: { enabled: false }, + permissionMode: "bypassPermissions", + }), + }); + expect(res.status).toBe(200); + const decision = await server.waitForDecision(); + expect(decision.permissionMode).toBe("bypassPermissions"); + } finally { + server.stop(); + } + }, 10_000); + +}); diff --git a/apps/pi-extension/server/serverAnnotate.ts b/apps/pi-extension/server/serverAnnotate.ts index 7b1e1177..1933375a 100644 --- a/apps/pi-extension/server/serverAnnotate.ts +++ b/apps/pi-extension/server/serverAnnotate.ts @@ -94,9 +94,10 @@ export async function startAnnotateServer(options: { }); } else if (url.pathname === "/api/config" && req.method === "POST") { try { - const body = (await parseBody(req)) as { displayName?: string; diffOptions?: Record; conventionalComments?: boolean }; + const body = (await parseBody(req)) as { displayName?: string; presenceColor?: string; diffOptions?: Record; conventionalComments?: boolean }; const toSave: Record = {}; if (body.displayName !== undefined) toSave.displayName = body.displayName; + if (body.presenceColor !== undefined) toSave.presenceColor = body.presenceColor; if (body.diffOptions !== undefined) toSave.diffOptions = body.diffOptions; if (body.conventionalComments !== undefined) toSave.conventionalComments = body.conventionalComments; if (Object.keys(toSave).length > 0) saveConfig(toSave as Parameters[0]); diff --git a/apps/pi-extension/server/serverPlan.ts b/apps/pi-extension/server/serverPlan.ts index 78448877..410ba91c 100644 --- a/apps/pi-extension/server/serverPlan.ts +++ b/apps/pi-extension/server/serverPlan.ts @@ -35,6 +35,7 @@ import { saveToOctarine, } from "./integrations.js"; import { listenOnPort } from "./network.js"; +import { isValidPermissionMode } from "../generated/collab/validation.js"; import { saveConfig, detectGitUser, getServerConfig } from "../generated/config.js"; import { detectProjectName, getRepoInfo } from "./project.js"; @@ -128,13 +129,21 @@ export async function startPlanReviewServer(options: { const reviewId = randomUUID(); let resolveDecision!: (result: PlanReviewDecision) => void; const decisionListeners = new Set<(result: PlanReviewDecision) => void | Promise>(); + // Claim-then-publish: claimDecision() sets the flag BEFORE any side + // effects run, so two near-simultaneous POSTs cannot both pass the + // guard and run integrations/saves twice. publishDecision() is + // called after side effects finish; it only resolves the promise + // and notifies listeners. Mirrors packages/server/index.ts. let decisionSettled = false; const decisionPromise = new Promise((r) => { resolveDecision = r; }); - const publishDecision = (result: PlanReviewDecision): boolean => { + const claimDecision = (): boolean => { if (decisionSettled) return false; decisionSettled = true; + return true; + }; + const publishDecision = (result: PlanReviewDecision): boolean => { resolveDecision(result); for (const listener of decisionListeners) { Promise.resolve(listener(result)).catch((error) => { @@ -225,9 +234,10 @@ export async function startPlanReviewServer(options: { } } else if (url.pathname === "/api/config" && req.method === "POST") { try { - const body = (await parseBody(req)) as { displayName?: string; diffOptions?: Record; conventionalComments?: boolean }; + const body = (await parseBody(req)) as { displayName?: string; presenceColor?: string; diffOptions?: Record; conventionalComments?: boolean }; const toSave: Record = {}; if (body.displayName !== undefined) toSave.displayName = body.displayName; + if (body.presenceColor !== undefined) toSave.presenceColor = body.presenceColor; if (body.diffOptions !== undefined) toSave.diffOptions = body.diffOptions; if (body.conventionalComments !== undefined) toSave.conventionalComments = body.conventionalComments; if (Object.keys(toSave).length > 0) saveConfig(toSave as Parameters[0]); @@ -338,7 +348,7 @@ export async function startPlanReviewServer(options: { } json(res, { ok: true, results }); } else if (url.pathname === "/api/approve" && req.method === "POST") { - if (decisionSettled) { + if (!claimDecision()) { json(res, { ok: true, duplicate: true }); return; } @@ -351,14 +361,15 @@ export async function startPlanReviewServer(options: { const body = await parseBody(req); if (body.feedback) feedback = body.feedback as string; if (body.agentSwitch) agentSwitch = body.agentSwitch as string; - if (body.permissionMode) - requestedPermissionMode = body.permissionMode as string; if (body.planSave !== undefined) { const ps = body.planSave as { enabled: boolean; customPath?: string }; planSaveEnabled = ps.enabled; planSaveCustomPath = ps.customPath; } - // Run note integrations in parallel + // Validate body.permissionMode shape so an invalid value + // can't silently fall through to the hook. + if (isValidPermissionMode(body.permissionMode)) + requestedPermissionMode = body.permissionMode; const integrationResults: Record = {}; const integrationPromises: Promise[] = []; const obsConfig = body.obsidian as ObsidianConfig | undefined; @@ -393,20 +404,32 @@ export async function startPlanReviewServer(options: { } catch (err) { console.error(`[Integration] Error:`, err); } - // Save annotations and final snapshot + // Save annotations and final snapshot. The claim is already set + // above, so we MUST reach publishDecision() below — otherwise + // the awaiting hook hangs forever and retries are rejected as + // duplicates. Persistence is best-effort: log and continue. let savedPath: string | undefined; if (planSaveEnabled) { - const annotations = feedback || ""; - if (annotations) saveAnnotations(slug, annotations, planSaveCustomPath); - savedPath = saveFinalSnapshot( - slug, - "approved", - options.plan, - annotations, - planSaveCustomPath, - ); + try { + const annotations = feedback || ""; + if (annotations) saveAnnotations(slug, annotations, planSaveCustomPath); + savedPath = saveFinalSnapshot( + slug, + "approved", + options.plan, + annotations, + planSaveCustomPath, + ); + } catch (err) { + console.error(`[plan-save] approve persistence failed:`, err); + } + } + try { + deleteDraft(draftKey); + } catch (err) { + console.error(`[draft] delete failed:`, err); } - deleteDraft(draftKey); + // Resolution order: client request body > server startup value. const effectivePermissionMode = requestedPermissionMode || options.permissionMode; publishDecision({ approved: true, @@ -417,7 +440,7 @@ export async function startPlanReviewServer(options: { }); json(res, { ok: true, savedPath }); } else if (url.pathname === "/api/deny" && req.method === "POST") { - if (decisionSettled) { + if (!claimDecision()) { json(res, { ok: true, duplicate: true }); return; } @@ -437,16 +460,24 @@ export async function startPlanReviewServer(options: { } let savedPath: string | undefined; if (planSaveEnabled) { - saveAnnotations(slug, feedback, planSaveCustomPath); - savedPath = saveFinalSnapshot( - slug, - "denied", - options.plan, - feedback, - planSaveCustomPath, - ); + try { + saveAnnotations(slug, feedback, planSaveCustomPath); + savedPath = saveFinalSnapshot( + slug, + "denied", + options.plan, + feedback, + planSaveCustomPath, + ); + } catch (err) { + console.error(`[plan-save] deny persistence failed:`, err); + } + } + try { + deleteDraft(draftKey); + } catch (err) { + console.error(`[draft] delete failed:`, err); } - deleteDraft(draftKey); publishDecision({ approved: false, feedback, savedPath }); json(res, { ok: true, savedPath }); } else { diff --git a/apps/pi-extension/server/serverReview.ts b/apps/pi-extension/server/serverReview.ts index f5e5fc33..75acfb12 100644 --- a/apps/pi-extension/server/serverReview.ts +++ b/apps/pi-extension/server/serverReview.ts @@ -654,9 +654,10 @@ export async function startReviewServer(options: { json(res, { error: "No file access available" }, 400); } else if (url.pathname === "/api/config" && req.method === "POST") { try { - const body = (await parseBody(req)) as { displayName?: string; diffOptions?: Record; conventionalComments?: boolean }; + const body = (await parseBody(req)) as { displayName?: string; presenceColor?: string; diffOptions?: Record; conventionalComments?: boolean }; const toSave: Record = {}; if (body.displayName !== undefined) toSave.displayName = body.displayName; + if (body.presenceColor !== undefined) toSave.presenceColor = body.presenceColor; if (body.diffOptions !== undefined) toSave.diffOptions = body.diffOptions; if (body.conventionalComments !== undefined) toSave.conventionalComments = body.conventionalComments; if (Object.keys(toSave).length > 0) saveConfig(toSave as Parameters[0]); diff --git a/apps/pi-extension/vendor.sh b/apps/pi-extension/vendor.sh index 51dbc426..6fb22f08 100755 --- a/apps/pi-extension/vendor.sh +++ b/apps/pi-extension/vendor.sh @@ -11,6 +11,13 @@ for f in feedback-templates review-core storage draft project pr-provider pr-git printf '// @generated — DO NOT EDIT. Source: packages/shared/%s.ts\n' "$f" | cat - "$src" > "generated/$f.ts" done +# Vendor collab submodule(s) needed by the Pi server. +mkdir -p generated/collab +for f in validation; do + src="../../packages/shared/collab/$f.ts" + printf '// @generated — DO NOT EDIT. Source: packages/shared/collab/%s.ts\n' "$f" | cat - "$src" > "generated/collab/$f.ts" +done + # Vendor review agent modules from packages/server/ — rewrite imports for generated/ layout for f in codex-review claude-review path-utils; do src="../../packages/server/$f.ts" diff --git a/apps/portal/tsconfig.json b/apps/portal/tsconfig.json index 93ef3e28..1b3c3a05 100644 --- a/apps/portal/tsconfig.json +++ b/apps/portal/tsconfig.json @@ -21,7 +21,8 @@ "paths": { "@/*": ["./*"], "@plannotator/ui/*": ["../../packages/ui/*"], - "@plannotator/editor": ["../../packages/editor/App.tsx"], + "@plannotator/editor": ["../../packages/editor/AppRoot.tsx"], + "@plannotator/editor/App": ["../../packages/editor/App.tsx"], "@plannotator/editor/*": ["../../packages/editor/*"] }, "allowImportingTsExtensions": true, diff --git a/apps/portal/vite.config.ts b/apps/portal/vite.config.ts index 822b099c..226d96df 100644 --- a/apps/portal/vite.config.ts +++ b/apps/portal/vite.config.ts @@ -18,7 +18,8 @@ export default defineConfig({ '@': path.resolve(__dirname, '.'), '@plannotator/ui': path.resolve(__dirname, '../../packages/ui'), '@plannotator/editor/styles': path.resolve(__dirname, '../../packages/editor/index.css'), - '@plannotator/editor': path.resolve(__dirname, '../../packages/editor/App.tsx'), + '@plannotator/editor/App': path.resolve(__dirname, '../../packages/editor/App.tsx'), + '@plannotator/editor': path.resolve(__dirname, '../../packages/editor/AppRoot.tsx'), } }, build: { diff --git a/apps/room-service/core/auth.test.ts b/apps/room-service/core/auth.test.ts new file mode 100644 index 00000000..af1c4bf3 --- /dev/null +++ b/apps/room-service/core/auth.test.ts @@ -0,0 +1,117 @@ +/** + * End-to-end auth proof verification tests. + * + * These tests act as an external client: they use shared/collab/client + * helpers (deriveRoomKeys, computeAuthProof) to simulate a connecting + * browser/agent, then verify using the server-side verifyAuthProof. + * + * This proves the full auth chain: secret → keys → verifier → challenge → proof → verify. + */ + +import { describe, expect, test } from 'bun:test'; +import { + deriveRoomKeys, + computeRoomVerifier, + computeAuthProof, + verifyAuthProof, + generateNonce, + generateChallengeId, +} from '@plannotator/shared/collab/client'; + +// Stable test secrets +const ROOM_SECRET = new Uint8Array(32); +ROOM_SECRET.fill(0xab); + +const ROOM_ID = 'test-room-auth'; +const CLIENT_ID = 'client-123'; + +describe('auth proof verification (end-to-end)', () => { + test('valid proof is accepted', async () => { + // Client side: derive keys, compute verifier and proof + const { authKey } = await deriveRoomKeys(ROOM_SECRET); + const verifier = await computeRoomVerifier(authKey, ROOM_ID); + const challengeId = generateChallengeId(); + const nonce = generateNonce(); + + const proof = await computeAuthProof(verifier, ROOM_ID, CLIENT_ID, challengeId, nonce); + + // Server side: verify the proof using stored verifier + const valid = await verifyAuthProof(verifier, ROOM_ID, CLIENT_ID, challengeId, nonce, proof); + expect(valid).toBe(true); + }); + + test('wrong proof is rejected', async () => { + const { authKey } = await deriveRoomKeys(ROOM_SECRET); + const verifier = await computeRoomVerifier(authKey, ROOM_ID); + const challengeId = generateChallengeId(); + const nonce = generateNonce(); + + // Compute proof with wrong client ID + const proof = await computeAuthProof(verifier, ROOM_ID, 'wrong-client', challengeId, nonce); + + // Verify with correct client ID — should fail + const valid = await verifyAuthProof(verifier, ROOM_ID, CLIENT_ID, challengeId, nonce, proof); + expect(valid).toBe(false); + }); + + test('wrong roomId is rejected', async () => { + const { authKey } = await deriveRoomKeys(ROOM_SECRET); + const verifier = await computeRoomVerifier(authKey, ROOM_ID); + const challengeId = generateChallengeId(); + const nonce = generateNonce(); + + const proof = await computeAuthProof(verifier, ROOM_ID, CLIENT_ID, challengeId, nonce); + + // Verify with wrong roomId + const valid = await verifyAuthProof(verifier, 'wrong-room', CLIENT_ID, challengeId, nonce, proof); + expect(valid).toBe(false); + }); + + test('malformed proof returns false (does not throw)', async () => { + const { authKey } = await deriveRoomKeys(ROOM_SECRET); + const verifier = await computeRoomVerifier(authKey, ROOM_ID); + const challengeId = generateChallengeId(); + const nonce = generateNonce(); + + // Garbage proof strings + await expect(verifyAuthProof(verifier, ROOM_ID, CLIENT_ID, challengeId, nonce, 'A')) + .resolves.toBe(false); + await expect(verifyAuthProof(verifier, ROOM_ID, CLIENT_ID, challengeId, nonce, '!@#$')) + .resolves.toBe(false); + await expect(verifyAuthProof(verifier, ROOM_ID, CLIENT_ID, challengeId, nonce, '')) + .resolves.toBe(false); + }); + + test('different room secrets produce incompatible verifiers', async () => { + const secret2 = new Uint8Array(32); + secret2.fill(0xcd); + + const keys1 = await deriveRoomKeys(ROOM_SECRET); + const keys2 = await deriveRoomKeys(secret2); + + const verifier1 = await computeRoomVerifier(keys1.authKey, ROOM_ID); + const verifier2 = await computeRoomVerifier(keys2.authKey, ROOM_ID); + + const challengeId = generateChallengeId(); + const nonce = generateNonce(); + + // Proof computed with secret1's verifier + const proof = await computeAuthProof(verifier1, ROOM_ID, CLIENT_ID, challengeId, nonce); + + // Verify with secret2's verifier — should fail + const valid = await verifyAuthProof(verifier2, ROOM_ID, CLIENT_ID, challengeId, nonce, proof); + expect(valid).toBe(false); + }); +}); + +describe('challenge expiry detection', () => { + test('current timestamp is within expiry', () => { + const expiresAt = Date.now() + 30_000; + expect(Date.now() <= expiresAt).toBe(true); + }); + + test('past timestamp is expired', () => { + const expiresAt = Date.now() - 1000; + expect(Date.now() > expiresAt).toBe(true); + }); +}); diff --git a/apps/room-service/core/cors.test.ts b/apps/room-service/core/cors.test.ts new file mode 100644 index 00000000..4b21f4f7 --- /dev/null +++ b/apps/room-service/core/cors.test.ts @@ -0,0 +1,98 @@ +import { describe, expect, test } from 'bun:test'; +import { corsHeaders, getAllowedOrigins, isLocalhostOrigin } from './cors'; + +describe('getAllowedOrigins', () => { + test('returns defaults when no env value', () => { + const origins = getAllowedOrigins(); + expect(origins).toEqual(['https://room.plannotator.ai']); + }); + + test('parses comma-separated env value', () => { + const origins = getAllowedOrigins('https://a.com, https://b.com'); + expect(origins).toEqual(['https://a.com', 'https://b.com']); + }); +}); + +describe('isLocalhostOrigin', () => { + test('matches http localhost with port', () => { + expect(isLocalhostOrigin('http://localhost:3001')).toBe(true); + expect(isLocalhostOrigin('http://localhost:57589')).toBe(true); + }); + + test('matches http localhost without port', () => { + expect(isLocalhostOrigin('http://localhost')).toBe(true); + }); + + test('matches https localhost', () => { + expect(isLocalhostOrigin('https://localhost:8443')).toBe(true); + }); + + test('matches 127.0.0.1 with port', () => { + expect(isLocalhostOrigin('http://127.0.0.1:3001')).toBe(true); + }); + + test('matches [::1] with port', () => { + expect(isLocalhostOrigin('http://[::1]:3001')).toBe(true); + }); + + test('matches 127.0.0.1 without port', () => { + expect(isLocalhostOrigin('http://127.0.0.1')).toBe(true); + }); + + test('rejects non-localhost', () => { + expect(isLocalhostOrigin('https://evil.com')).toBe(false); + expect(isLocalhostOrigin('https://localhost.evil.com')).toBe(false); + expect(isLocalhostOrigin('http://127.0.0.2:3001')).toBe(false); + }); +}); + +describe('corsHeaders', () => { + const prodOrigins = ['https://room.plannotator.ai']; + + test('allows listed production origin', () => { + const headers = corsHeaders('https://room.plannotator.ai', prodOrigins); + expect(headers['Access-Control-Allow-Origin']).toBe('https://room.plannotator.ai'); + expect(headers['Vary']).toBe('Origin'); + }); + + test('localhost allowed when flag is true', () => { + const headers = corsHeaders('http://localhost:57589', prodOrigins, true); + expect(headers['Access-Control-Allow-Origin']).toBe('http://localhost:57589'); + expect(headers['Vary']).toBe('Origin'); + }); + + test('localhost rejected when flag is false', () => { + const headers = corsHeaders('http://localhost:57589', prodOrigins, false); + expect(headers).toEqual({}); + }); + + test('localhost rejected when flag is not provided', () => { + const headers = corsHeaders('http://localhost:57589', prodOrigins); + expect(headers).toEqual({}); + }); + + test('rejects unlisted non-localhost origin', () => { + const headers = corsHeaders('https://evil.example', prodOrigins, true); + expect(headers).toEqual({}); + }); + + test('allows any origin with wildcard', () => { + const headers = corsHeaders('https://anything.com', ['*']); + expect(headers['Access-Control-Allow-Origin']).toBe('https://anything.com'); + expect(headers['Vary']).toBe('Origin'); + }); + + test('returns empty for no origin match', () => { + const headers = corsHeaders('', prodOrigins); + expect(headers).toEqual({}); + }); + + test('all allowed responses include Vary: Origin', () => { + const h1 = corsHeaders('https://room.plannotator.ai', prodOrigins); + const h2 = corsHeaders('http://localhost:3001', prodOrigins, true); + const h3 = corsHeaders('https://x.com', ['*']); + expect(h1['Vary']).toBe('Origin'); + expect(h2['Vary']).toBe('Origin'); + expect(h3['Vary']).toBe('Origin'); + }); +}); diff --git a/apps/room-service/core/cors.ts b/apps/room-service/core/cors.ts new file mode 100644 index 00000000..7fb99612 --- /dev/null +++ b/apps/room-service/core/cors.ts @@ -0,0 +1,49 @@ +/** + * CORS handling for room.plannotator.ai. + * + * Localhost origins are allowed only when ALLOW_LOCALHOST_ORIGINS is explicitly + * set to "true". This is intentional product behavior: Plannotator runs locally + * on unpredictable ports and needs to call room.plannotator.ai/api/rooms when + * the creator starts a live room. The room service still stores only ciphertext + * and verifiers — room content access depends on the URL fragment secret. + */ + +const BASE_CORS_HEADERS = { + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + 'Access-Control-Max-Age': '86400', +}; + +/** Matches localhost, 127.0.0.1, and [::1] with optional port. */ +const LOOPBACK_RE = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/; + +export function getAllowedOrigins(envValue?: string): string[] { + if (envValue) { + return envValue.split(',').map((o) => o.trim()); + } + return ['https://room.plannotator.ai']; +} + +export function isLocalhostOrigin(origin: string): boolean { + return LOOPBACK_RE.test(origin); +} + +export function corsHeaders( + requestOrigin: string, + allowedOrigins: string[], + allowLocalhostOrigins: boolean = false, +): Record { + const allowed = + allowedOrigins.includes(requestOrigin) || + allowedOrigins.includes('*') || + (allowLocalhostOrigins && isLocalhostOrigin(requestOrigin)); + + if (allowed) { + return { + ...BASE_CORS_HEADERS, + 'Access-Control-Allow-Origin': requestOrigin, + 'Vary': 'Origin', + }; + } + return {}; +} diff --git a/apps/room-service/core/csp.test.ts b/apps/room-service/core/csp.test.ts new file mode 100644 index 00000000..aa30ea29 --- /dev/null +++ b/apps/room-service/core/csp.test.ts @@ -0,0 +1,141 @@ +import { describe, expect, test } from 'bun:test'; +import { ROOM_CSP, handleRequest } from './handler'; + +/** Parse a CSP directive into its individual tokens. */ +function directiveTokens(csp: string, directive: string): string[] { + const d = csp + .split(';') + .map(s => s.trim()) + .find(s => s.startsWith(directive)); + if (!d) return []; + return d.split(/\s+/).slice(1); // drop the directive name itself +} + +describe('ROOM_CSP constant', () => { + test('is a non-empty string', () => { + expect(typeof ROOM_CSP).toBe('string'); + expect(ROOM_CSP.length).toBeGreaterThan(0); + }); + + test("default-src is 'self'", () => { + expect(ROOM_CSP).toContain("default-src 'self'"); + }); + + test("script-src allows 'self' and 'wasm-unsafe-eval' only", () => { + const tokens = directiveTokens(ROOM_CSP, 'script-src'); + expect(tokens).toContain("'self'"); + expect(tokens).toContain("'wasm-unsafe-eval'"); + // Must NOT contain plain 'unsafe-eval' or 'unsafe-inline'. + expect(tokens).not.toContain("'unsafe-eval'"); + expect(tokens).not.toContain("'unsafe-inline'"); + }); + + test('blocks object embeds', () => { + expect(ROOM_CSP).toContain("object-src 'none'"); + }); + + test('blocks base-uri injection', () => { + expect(ROOM_CSP).toContain("base-uri 'none'"); + }); + + test('blocks framing (clickjacking)', () => { + expect(ROOM_CSP).toContain("frame-ancestors 'none'"); + }); + + test('blocks form submissions', () => { + expect(ROOM_CSP).toContain("form-action 'none'"); + }); + + test('does NOT allow localhost HTTP connections', () => { + // The room origin should not have blanket fetch access to any + // local HTTP service; an XSS injection would otherwise exfiltrate + // to loopback listeners. WebSocket entries below are intentionally + // scoped to `ws://` only (HTTP loopback remains closed). + const tokens = directiveTokens(ROOM_CSP, 'connect-src'); + expect(tokens).not.toContain('http://localhost:*'); + expect(tokens).not.toContain('http://127.0.0.1:*'); + expect(tokens).not.toContain('http://[::1]:*'); + }); + + test('allows scoped localhost WebSocket connections (cross-port dev)', () => { + expect(ROOM_CSP).toContain('ws://localhost:*'); + expect(ROOM_CSP).toContain('ws://127.0.0.1:*'); + expect(ROOM_CSP).toContain('ws://[::1]:*'); + }); + + test('does NOT allow blanket https: / ws: / wss: in connect-src', () => { + // `'self'` already covers same-origin wss:/ws: in prod and dev. + // Blanket schemes would allow post-XSS exfiltration to any host on + // that scheme — same reasoning that excludes blanket https:. + const tokens = directiveTokens(ROOM_CSP, 'connect-src'); + expect(tokens).not.toContain('https:'); + expect(tokens).not.toContain('ws:'); + expect(tokens).not.toContain('wss:'); + }); + + test('img-src allows remote markdown images (https:)', () => { + // Remote `![alt](https://...)` in a plan document renders as a + // plain and must not be blocked. Annotation + // attachments remain stripped at room-create time, so this allowance + // only covers document-level markdown images. + const tokens = directiveTokens(ROOM_CSP, 'img-src'); + expect(tokens).toContain("'self'"); + expect(tokens).toContain('https:'); + expect(tokens).toContain('data:'); + expect(tokens).toContain('blob:'); + }); + + test('does NOT include upgrade-insecure-requests', () => { + expect(ROOM_CSP).not.toContain('upgrade-insecure-requests'); + }); + + test('allows Google Fonts', () => { + expect(ROOM_CSP).toContain('https://fonts.googleapis.com'); + expect(ROOM_CSP).toContain('https://fonts.gstatic.com'); + }); +}); + +describe('serveIndexHtml headers (fallback path, no ASSETS)', () => { + // Minimal env with no ASSETS binding — exercises the fallback + // HTML path inside handleRequest, which is the cheapest way to + // assert the headers without needing a Durable Object namespace. + const minimalEnv = { + ROOM: {} as never, // unused by the room-shell path + ALLOWED_ORIGINS: 'https://room.plannotator.ai', + ALLOW_LOCALHOST_ORIGINS: 'true', + BASE_URL: 'https://room.plannotator.ai', + }; + const cors = { + 'Access-Control-Allow-Origin': '*', + }; + + async function getRoom(roomId = 'AAAAAAAAAAAAAAAAAAAAAA'): Promise { + const req = new Request(`https://room.plannotator.ai/c/${roomId}`, { + method: 'GET', + }); + return handleRequest(req, minimalEnv, cors); + } + + test('returns 200 with Content-Security-Policy', async () => { + const res = await getRoom(); + expect(res.status).toBe(200); + const csp = res.headers.get('Content-Security-Policy'); + expect(csp).not.toBeNull(); + expect(csp).toContain("default-src 'self'"); + }); + + test('returns Cache-Control: no-store', async () => { + const res = await getRoom(); + expect(res.headers.get('Cache-Control')).toBe('no-store'); + }); + + test('returns Referrer-Policy: no-referrer', async () => { + const res = await getRoom(); + expect(res.headers.get('Referrer-Policy')).toBe('no-referrer'); + }); + + test('returns text/html content type', async () => { + const res = await getRoom(); + expect(res.headers.get('Content-Type')).toContain('text/html'); + }); +}); diff --git a/apps/room-service/core/handler.ts b/apps/room-service/core/handler.ts new file mode 100644 index 00000000..ca74fb2f --- /dev/null +++ b/apps/room-service/core/handler.ts @@ -0,0 +1,336 @@ +/** + * HTTP route dispatch for room.plannotator.ai. + * + * Routes requests to the appropriate Durable Object or returns + * static responses. Does NOT apply CORS to WebSocket upgrades. + */ + +import type { Env } from './types'; +import { isRoomId, validateCreateRoomRequest, isValidationError } from './validation'; +import { safeLog } from './log'; + +const ROOM_PATH_RE = /^\/c\/([^/]+)$/; +const WS_PATH_RE = /^\/ws\/([^/]+)$/; + +export async function handleRequest( + request: Request, + env: Env, + cors: Record, +): Promise { + const url = new URL(request.url); + const { pathname } = url; + const method = request.method; + + // CORS preflight + if (method === 'OPTIONS') { + return new Response(null, { status: 204, headers: cors }); + } + + // Health check + if (pathname === '/health' && method === 'GET') { + return Response.json({ ok: true }, { headers: cors }); + } + + // Room creation + if (pathname === '/api/rooms' && method === 'POST') { + return handleCreateRoom(request, env, cors); + } + + // WebSocket upgrade — matched before asset/SPA routes so a stray ws/* + // under the asset binding can't be mistaken for a file fetch. + const wsMatch = pathname.match(WS_PATH_RE); + if (wsMatch && method === 'GET') { + return handleWebSocket(request, env, wsMatch[1], cors); + } + + // Hashed static assets — produced by `vite build` into ./public/assets/. + // Filenames include a content hash, so we set far-future immutable + // Cache-Control: chunks invalidate by name, never by TTL. Headers from + // the asset response (Content-Type, ETag, Content-Encoding) are + // preserved; we only override CORS + Cache-Control. + // Static root-level assets (favicon.svg). Vite copies these from + // the publicDir into the build output root alongside index.html. + // Served with a 1-day cache — they're not hashed so immutable isn't + // safe, but they change very rarely. + if (pathname === '/favicon.svg' && method === 'GET') { + if (!env.ASSETS) { + return new Response('Not Found', { status: 404, headers: cors }); + } + const assetRes = await env.ASSETS.fetch(request); + // Pass a real miss through as 404, but let 304 Not Modified + // responses flow through — `fetch.ok` treats 304 as "not ok" + // (it's outside 200-299), so returning 404 on 304 would force + // the browser to abandon its cached favicon and re-download + // on every revalidation. + if (!assetRes.ok && assetRes.status !== 304) { + return new Response('Not Found', { status: 404, headers: cors }); + } + const headers = new Headers(assetRes.headers); + for (const [k, v] of Object.entries(cors)) headers.set(k, v); + headers.set('Cache-Control', 'public, max-age=86400'); + return new Response(assetRes.body, { status: assetRes.status, headers }); + } + + if (pathname.startsWith('/assets/') && method === 'GET') { + if (!env.ASSETS) { + return new Response('Not Found', { status: 404, headers: cors }); + } + const assetRes = await env.ASSETS.fetch(request); + if (!assetRes.ok) { + // Surface the real status (404/403/etc.) rather than pretending + // everything is fine. CORS still attached so the browser exposes + // the response to the page's fetch logic. + const headers = new Headers(assetRes.headers); + for (const [k, v] of Object.entries(cors)) headers.set(k, v); + return new Response(assetRes.body, { status: assetRes.status, headers }); + } + const headers = new Headers(assetRes.headers); + for (const [k, v] of Object.entries(cors)) headers.set(k, v); + headers.set('Cache-Control', 'public, max-age=31536000, immutable'); + return new Response(assetRes.body, { status: assetRes.status, headers }); + } + + // Room SPA shell — /c/:roomId rewrites to /index.html so the chunked + // Vite bundle can boot with the original path still visible to the + // client JS (useRoomMode reads window.location.pathname to extract + // roomId, and parseRoomUrl reads the fragment for the room secret). + // + // Cache-Control: no-store — index.html references hashed chunk URLs + // that change on every deploy. Caching it would pin clients to stale + // chunk references and break after the next release. The immutable + // caching for /assets/* is what preserves the warm-visit performance; + // this HTML is tiny. + // + // Referrer-Policy: no-referrer strips the path (which contains the + // roomId) from Referer on any outbound subresource fetch. Fragments + // are never in Referer in any browser, so this is defense-in-depth + // for the path, not the secret itself. + const roomMatch = pathname.match(ROOM_PATH_RE); + if (roomMatch && method === 'GET') { + const roomId = roomMatch[1]; + if (!isRoomId(roomId)) { + return new Response('Not Found', { status: 404, headers: cors }); + } + return serveIndexHtml(request, env, cors); + } + + // No broad SPA fallback. This is a room-only origin — the only valid + // browser route is /c/:roomId (matched above). Serving index.html for + // `/`, `/about`, or other non-room paths would boot the local editor + // via AppRoot's local-mode branch, contradicting the room-only + // boundary. If future routes are added (e.g. /rooms index, admin + // recovery page), add explicit path matches here; don't open a + // catch-all that silently renders local mode. + return Response.json( + { error: 'Not found. Valid paths: GET /health, GET /c/:id, POST /api/rooms, GET /ws/:id, GET /assets/*' }, + { status: 404, headers: cors }, + ); +} + +/** + * Content Security Policy for the room HTML shell. + * + * Applied ONLY to the document response (/index.html), not to API or + * asset responses. The browser evaluates CSP from the document. + * + * Rationale for each directive: + * default-src 'self' — lockdown baseline + * script-src 'self' 'wasm-unsafe-eval' + * — Vite chunks + Graphviz WASM + * style-src 'self' 'unsafe-inline' https://fonts.googleapis.com + * — app CSS + Google Fonts + inline styles + * font-src 'self' https://fonts.gstatic.com + * — Google font files + * img-src 'self' https: data: blob: + * — icons, blob previews, and remote + * markdown document images (e.g. + * `![diagram](https://example/a.png)`) + * which Viewer renders as plain . + * connect-src 'self' ws://localhost:* ws://127.0.0.1:* ws://[::1]:* + * — same-origin Worker API/WebSocket + * + cross-port localhost dev WS + * worker-src 'self' blob: — defensive for libs using blob workers + * object-src 'none' — no plugins/objects + * base-uri 'none' — prevent tag injection + * frame-ancestors 'none' — no clickjacking/embedding + * form-action 'none' — no form submissions expected + */ +export const ROOM_CSP = [ + "default-src 'self'", + // 'wasm-unsafe-eval' needed for @viz-js/viz (Graphviz WASM build). + // NOT 'unsafe-eval' — only WebAssembly compilation is allowed. + "script-src 'self' 'wasm-unsafe-eval'", + "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", + "font-src 'self' https://fonts.gstatic.com", + // Remote markdown document images (e.g. `![diagram](https://example/a.png)`) + // are a supported plan-content feature — Viewer renders them as plain + // ``. Allowing blanket `https:` here is a known + // tradeoff: an injected script could beacon via image URLs. Accepted + // because the product supports remote plan images, and the more + // exfil-capable channels (fetch / WebSocket) stay locked down via + // `connect-src 'self' + scoped localhost`. + // Annotation image attachments remain stripped before sending to the + // room (stripRoomAnnotationImages), so only document-level markdown + // images exercise this allowance. + "img-src 'self' https: data: blob:", + // Production: `'self'` covers the same-origin WebSocket + // (wss://room.plannotator.ai/ws/) per the CSP spec. + // + // Development: wrangler dev serves both the room shell and the + // WebSocket on the same localhost port, so `'self'` covers that + // too. Cross-port local dev (shell on one port, WebSocket on + // another) still needs explicit ws:// localhost entries. + // + // Blanket https: / ws: / wss: are intentionally omitted — + // widening the scheme would give any post-XSS injection an + // unrestricted exfiltration surface. + "connect-src 'self' ws://localhost:* ws://127.0.0.1:* ws://[::1]:*", + "worker-src 'self' blob:", + "object-src 'none'", + "base-uri 'none'", + "frame-ancestors 'none'", + "form-action 'none'", + // upgrade-insecure-requests is intentionally omitted because + // wrangler dev serves the shell + WebSocket over `ws://localhost`, + // and this directive rewrites ws:// → wss:// (which breaks local + // development). Production only makes same-origin wss:// + // connections, so the directive would be a no-op there anyway. +].join('; '); + +/** + * Fetch and serve /index.html from the Wrangler asset binding with the + * headers the room shell needs: CSP, CORS, no-store cache, + * Referrer-Policy, and an HTML content type. Falls back to a minimal + * inline HTML when ASSETS is unbound (local test environments that + * don't run Wrangler). + */ +async function serveIndexHtml( + request: Request, + env: Env, + cors: Record, +): Promise { + if (env.ASSETS) { + const assetUrl = new URL(request.url); + assetUrl.pathname = '/index.html'; + const assetReq = new Request(assetUrl, { method: 'GET', headers: request.headers }); + const assetRes = await env.ASSETS.fetch(assetReq); + const headers = new Headers(assetRes.headers); + for (const [k, v] of Object.entries(cors)) headers.set(k, v); + headers.set('Content-Security-Policy', ROOM_CSP); + headers.set('Referrer-Policy', 'no-referrer'); + headers.set('Content-Type', 'text/html; charset=utf-8'); + headers.set('Cache-Control', 'no-store'); + return new Response(assetRes.body, { status: assetRes.status, headers }); + } + // Fallback for local/test environments without an ASSETS binding. + return new Response( + `Plannotator Room

Room shell (test fallback; ASSETS binding unavailable)

`, + { + status: 200, + headers: { + ...cors, + 'Content-Security-Policy': ROOM_CSP, + 'Content-Type': 'text/html; charset=utf-8', + 'Referrer-Policy': 'no-referrer', + 'Cache-Control': 'no-store', + }, + }, + ); +} + +// --------------------------------------------------------------------------- +// Room Creation +// +// PRODUCTION HARDENING (required before public deployment, not in V1 scope): +// `POST /api/rooms` is intentionally unauthenticated in the V1 protocol. A +// room is a capability-token pair (roomSecret + adminSecret) the creator +// generates locally; this endpoint only asserts existence on the server, not +// identity. That means anyone who can reach the Worker can create rooms — +// fine for local dev and gated staging, NOT fine for the open internet. +// +// Before this Worker is exposed publicly it MUST be gated by one of: +// - Cloudflare rate limiting / WAF rule keyed on source IP + path +// - application-level throttle at the Worker entry (shared Durable Object +// counter or KV-based token bucket) +// - authenticated proxy (plannotator.ai app calls on behalf of signed-in users) +// +// CORS is NOT abuse protection — it's a browser same-origin policy and does +// nothing to a direct HTTP client. Any future reviewer flagging "this +// endpoint is unauthenticated" should be pointed HERE. Production hardening +// (rate-limit POST /api/rooms) is the intended gate; the protocol design +// accommodates adding it without client changes. +// --------------------------------------------------------------------------- + +async function handleCreateRoom( + request: Request, + env: Env, + cors: Record, +): Promise { + let body: unknown; + try { + body = await request.json(); + } catch { + return Response.json({ error: 'Invalid JSON body' }, { status: 400, headers: cors }); + } + + const result = validateCreateRoomRequest(body); + if (isValidationError(result)) { + return Response.json({ error: result.error }, { status: result.status, headers: cors }); + } + + safeLog('handler:create-room', { roomId: result.roomId }); + + // Forward to the Durable Object + const id = env.ROOM.idFromName(result.roomId); + const stub = env.ROOM.get(id); + const doResponse = await stub.fetch( + new Request('http://do/create', { + method: 'POST', + body: JSON.stringify(result), + headers: { 'Content-Type': 'application/json' }, + }), + ); + + // Re-wrap DO response with CORS headers + const responseBody = await doResponse.text(); + return new Response(responseBody, { + status: doResponse.status, + headers: { ...cors, 'Content-Type': 'application/json' }, + }); +} + +// --------------------------------------------------------------------------- +// WebSocket Upgrade +// --------------------------------------------------------------------------- + +async function handleWebSocket( + request: Request, + env: Env, + roomId: string, + cors: Record, +): Promise { + // Verify WebSocket upgrade header. RFC 6455 specifies the token + // is case-insensitive; browsers send lowercase but standards- + // conformant non-browser clients may send `WebSocket` or `WEBSOCKET`. + if (request.headers.get('Upgrade')?.toLowerCase() !== 'websocket') { + return Response.json( + { error: 'Expected WebSocket upgrade' }, + { status: 426, headers: cors }, + ); + } + + // Validate roomId BEFORE idFromName(). idFromName on arbitrary attacker + // input would instantiate a fresh DO and hit storage on every request — + // a cheap abuse surface. Reject malformed IDs up front. + if (!isRoomId(roomId)) { + return Response.json( + { error: 'Invalid roomId' }, + { status: 400, headers: cors }, + ); + } + + // Forward to the Durable Object — no CORS on WebSocket upgrade + const id = env.ROOM.idFromName(roomId); + const stub = env.ROOM.get(id); + return stub.fetch(request); +} diff --git a/apps/room-service/core/log.ts b/apps/room-service/core/log.ts new file mode 100644 index 00000000..11483f38 --- /dev/null +++ b/apps/room-service/core/log.ts @@ -0,0 +1,30 @@ +/** + * Redaction-aware logging for the room service. + * + * Redacts proofs, verifiers, ciphertext, and message bodies from logs. + */ + +const REDACTED_KEYS = new Set([ + 'roomVerifier', + 'adminVerifier', + 'proof', + 'adminProof', + 'ciphertext', + 'initialSnapshotCiphertext', + 'snapshotCiphertext', + 'nonce', +]); + +/** Shallow-clone an object, replacing sensitive field values with "[REDACTED]". */ +export function redactForLog(obj: Record): Record { + const result: Record = {}; + for (const [key, value] of Object.entries(obj)) { + result[key] = REDACTED_KEYS.has(key) ? '[REDACTED]' : value; + } + return result; +} + +/** Log with sensitive fields redacted. */ +export function safeLog(label: string, obj: Record): void { + console.log(label, redactForLog(obj)); +} diff --git a/apps/room-service/core/room-do.ts b/apps/room-service/core/room-do.ts new file mode 100644 index 00000000..68208977 --- /dev/null +++ b/apps/room-service/core/room-do.ts @@ -0,0 +1,956 @@ +/** + * Plannotator Room Durable Object. + * + * Uses Cloudflare Workers WebSocket Hibernation API. + * All per-connection state lives in WebSocket attachments + * (survives DO hibernation). + * + * Implements: room creation, WebSocket auth, event sequencing, + * presence relay, reconnect replay, admin commands, lifecycle enforcement. + * + * Zero-knowledge: stores/relays ciphertext only. Never needs roomSecret, + * eventKey, presenceKey, or plaintext content. + */ + +import type { + AuthChallenge, + AuthResponse, + AuthAccepted, + AdminChallenge, + CreateRoomRequest, + CreateRoomResponse, + ServerEnvelope, + SequencedEnvelope, + RoomTransportMessage, +} from '@plannotator/shared/collab'; +import { verifyAuthProof, verifyAdminProof, generateChallengeId, generateClientId, generateNonce } from '@plannotator/shared/collab'; +// Shared terminal close-signal constants — client treats this pair as +// "the link no longer resolves" (admin delete, auto-expiry, or a room +// that never existed — from the client's perspective, indistinguishable). +import { AdminErrorCode, WS_CLOSE_REASON_ROOM_UNAVAILABLE, WS_CLOSE_ROOM_UNAVAILABLE } from '@plannotator/shared/collab/constants'; +import { DurableObject } from 'cloudflare:workers'; +import type { Env, RoomDurableState, WebSocketAttachment } from './types'; +import { clampExpiryDays, hasRoomExpired, validateServerEnvelope, validateAdminCommandEnvelope, isValidationError } from './validation'; +import { safeLog } from './log'; + +const CHALLENGE_TTL_MS = 30_000; +const ADMIN_CHALLENGE_TTL_MS = 30_000; +const DELETE_BATCH_SIZE = 128; // Cloudflare DO storage.delete() max keys per call +/** + * Page size for reconnect replay. Bounds peak DO memory during replay — + * storage.list() without a limit reads all matching rows at once, which + * fails for large/noisy rooms. Each page is streamed out to the WebSocket, + * then released. 128 is a conservative starting point well within DO memory + * budgets even if each event ciphertext is a few KB. + */ +const REPLAY_PAGE_SIZE = 128; + +/** + * Abuse/failure containment: per-room WebSocket cap. Not about expected + * normal room sizes — V1 rooms are small — but bounds broadcast fanout + * and runaway reconnect loops if a misbehaving client (or attacker with + * the room URL) opens sockets without releasing them. Returns 429 Too + * Many Requests when exceeded; honest clients see this only if the room + * is already saturated. + */ +const MAX_CONNECTIONS_PER_ROOM = 100; + +/** Pre-auth length caps on the auth.response message. Real values are + * much smaller (challengeId ~22 chars, clientId server-assigned, proof + * ~43 chars for HMAC-SHA-256 base64url). Generous caps bound the + * unauthenticated work the server is willing to do per connection. */ +const AUTH_CHALLENGE_ID_MAX_LENGTH = 64; +const AUTH_CLIENT_ID_MAX_LENGTH = 64; +const AUTH_PROOF_MAX_LENGTH = 128; + +// WebSocket close codes (room-service-internal; shared close codes come from constants.ts) +const WS_CLOSE_AUTH_REQUIRED = 4001; +const WS_CLOSE_UNKNOWN_CHALLENGE = 4002; +const WS_CLOSE_CHALLENGE_EXPIRED = 4003; +const WS_CLOSE_INVALID_PROOF = 4004; +const WS_CLOSE_PROTOCOL_ERROR = 4005; + +/** Zero-pad a seq number to 10 digits for lexicographic storage ordering. */ +function padSeq(seq: number): string { + return String(seq).padStart(10, '0'); +} + +export class RoomDurableObject extends DurableObject { + async fetch(request: Request): Promise { + const url = new URL(request.url); + + if (url.pathname === '/create' && request.method === 'POST') { + return this.handleCreate(request); + } + + if (request.headers.get('Upgrade') === 'websocket') { + return this.handleWebSocketUpgrade(request); + } + + return Response.json({ error: 'Not found' }, { status: 404 }); + } + + // --------------------------------------------------------------------------- + // Room Creation + // --------------------------------------------------------------------------- + + private async handleCreate(request: Request): Promise { + let body: CreateRoomRequest; + try { + body = await request.json() as CreateRoomRequest; + } catch { + return Response.json({ error: 'Invalid JSON' }, { status: 400 }); + } + + const existing = await this.ctx.storage.get('room'); + if (existing) { + // Lazy-expiry backstop: if somehow the alarm didn't fire (e.g. the + // room outlived its deadline without anyone connecting AND without + // the alarm landing), purge here and allow the new create to + // supplant the stale roomId. The alarm is the primary cleanup + // path — this is defense in depth. + if (hasRoomExpired(existing.expiresAt)) { + await this.purgeRoom('create-preempted-expired'); + // fall through to create a fresh room at this id + } else { + return Response.json({ error: 'Room already exists' }, { status: 409 }); + } + } + + const expiryDays = clampExpiryDays(body.expiresInDays); + const expiresAt = Date.now() + expiryDays * 24 * 60 * 60 * 1000; + + const state: RoomDurableState = { + roomId: body.roomId, + roomVerifier: body.roomVerifier, + adminVerifier: body.adminVerifier, + seq: 0, + earliestRetainedSeq: 1, + snapshotCiphertext: body.initialSnapshotCiphertext, + snapshotSeq: 0, + expiresAt, + }; + + try { + await this.ctx.storage.put('room', state); + } catch (e) { + safeLog('room:create-storage-error', { roomId: body.roomId, error: String(e) }); + return Response.json({ error: 'Failed to store room state' }, { status: 507 }); + } + + // Schedule the 30-day (or whatever expiryDays clamps to) auto-purge. + // `setAlarm` overwrites any pending alarm, which is what we want if + // this create supplanted an expired-but-alarm-less room above. + try { + await this.ctx.storage.setAlarm(expiresAt); + } catch (e) { + // Non-fatal: lazy-expiry in checkRoomLifecycle + the defense-in-depth + // check above still catch overdue rooms. Log and carry on. + safeLog('room:set-alarm-error', { roomId: body.roomId, error: String(e) }); + } + + const base = new URL(this.env.BASE_URL || 'https://room.plannotator.ai'); + const wsScheme = base.protocol === 'https:' ? 'wss:' : 'ws:'; + + const response: CreateRoomResponse = { + roomId: body.roomId, + seq: 0, + snapshotSeq: 0, + joinUrl: `${base.origin}/c/${body.roomId}`, + websocketUrl: `${wsScheme}//${base.host}/ws/${body.roomId}`, + }; + + safeLog('room:created', { roomId: body.roomId, expiryDays }); + return Response.json(response, { status: 201 }); + } + + // --------------------------------------------------------------------------- + // Durable Object alarm — fires at `expiresAt`, purges the room. + // --------------------------------------------------------------------------- + + async alarm(): Promise { + // The alarm wakes the DO. We don't check expiresAt here — the alarm + // was scheduled specifically for now, so if there's any room in + // storage we purge it. purgeRoom is idempotent on absence. + await this.purgeRoom('expiry'); + } + + // --------------------------------------------------------------------------- + // WebSocket Upgrade + // --------------------------------------------------------------------------- + + private async handleWebSocketUpgrade(_request: Request): Promise { + const roomState = await this.ctx.storage.get('room'); + if (!roomState) { + return this.rejectUpgradeAsUnavailable(); + } + if (hasRoomExpired(roomState.expiresAt)) { + // Alarm should have fired; this is defense in depth. + await this.purgeRoom('upgrade-preempted-expired'); + return this.rejectUpgradeAsUnavailable(); + } + + // Per-room connection cap — see MAX_CONNECTIONS_PER_ROOM for rationale. + // Kept as HTTP 429 (not a WS close) because "full" is a transient, + // retryable condition worth signaling to any consumer — distinct from + // the permanent "room unavailable" UX state below. + if (this.ctx.getWebSockets().length >= MAX_CONNECTIONS_PER_ROOM) { + safeLog('ws:room-full', { roomId: roomState.roomId, cap: MAX_CONNECTIONS_PER_ROOM }); + return Response.json({ error: 'Room is full' }, { status: 429 }); + } + + const pair = new WebSocketPair(); + const [client, server] = Object.values(pair); + + const challengeId = generateChallengeId(); + const nonce = generateNonce(); + const expiresAt = Date.now() + CHALLENGE_TTL_MS; + // Server-assigned clientId — see WebSocketAttachment docstring. The auth + // proof is bound to this value, so a participant cannot choose an active + // peer's clientId at auth time. + const clientId = generateClientId(); + + this.ctx.acceptWebSocket(server); + + const attachment: WebSocketAttachment = { + authenticated: false, + roomId: roomState.roomId, + challengeId, + nonce, + expiresAt, + clientId, + }; + server.serializeAttachment(attachment); + + const challenge: AuthChallenge = { + type: 'auth.challenge', + challengeId, + nonce, + expiresAt, + clientId, + }; + server.send(JSON.stringify(challenge)); + + safeLog('ws:challenge-sent', { roomId: roomState.roomId, challengeId }); + return new Response(null, { status: 101, webSocket: client }); + } + + /** + * Complete the WebSocket upgrade and immediately close the client side + * with WS_CLOSE_ROOM_UNAVAILABLE. Used when the room is gone (never + * created, admin-deleted, or auto-expired). + * + * Why not return HTTP 404? Browsers don't expose the HTTP status of a + * failed WebSocket upgrade to page JS — a failed upgrade fires `close` + * with code 1006 and no reason, indistinguishable from a network drop. + * Accepting and immediately closing with our dedicated close code is + * the only way the client can route cold visitors to the dedicated + * RoomUnavailableScreen on the same code path as mid-session closes. + */ + private rejectUpgradeAsUnavailable(): Response { + const pair = new WebSocketPair(); + const [client, server] = Object.values(pair); + this.ctx.acceptWebSocket(server); + server.close(WS_CLOSE_ROOM_UNAVAILABLE, WS_CLOSE_REASON_ROOM_UNAVAILABLE); + return new Response(null, { status: 101, webSocket: client }); + } + + // --------------------------------------------------------------------------- + // WebSocket Message Handler (Hibernation API) + // --------------------------------------------------------------------------- + + async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise { + const meta = ws.deserializeAttachment() as WebSocketAttachment | null; + if (!meta) { + ws.close(WS_CLOSE_AUTH_REQUIRED, 'No connection state'); + return; + } + + let msg: Record; + try { + const raw = typeof message === 'string' ? message : new TextDecoder().decode(message); + msg = JSON.parse(raw); + } catch { + ws.close(WS_CLOSE_PROTOCOL_ERROR, 'Invalid message format'); + return; + } + + // Pre-auth: only accept auth.response + if (!meta.authenticated) { + if (msg.type !== 'auth.response') { + ws.close(WS_CLOSE_AUTH_REQUIRED, 'Authentication required'); + return; + } + if ( + typeof msg.challengeId !== 'string' || !msg.challengeId || + typeof msg.clientId !== 'string' || !msg.clientId || + typeof msg.proof !== 'string' || !msg.proof + ) { + ws.close(WS_CLOSE_PROTOCOL_ERROR, 'Malformed auth response'); + return; + } + // Pre-auth length caps. Proofs + IDs are small in practice + // (challengeId ~22 chars, clientId server-assigned, proof 43 chars). + // Without caps, an unauthenticated peer can allocate/verify oversized + // strings. Match the admin-envelope caps for consistency. + if (msg.challengeId.length > AUTH_CHALLENGE_ID_MAX_LENGTH) { + ws.close(WS_CLOSE_PROTOCOL_ERROR, 'challengeId too long'); + return; + } + if (msg.clientId.length > AUTH_CLIENT_ID_MAX_LENGTH) { + ws.close(WS_CLOSE_PROTOCOL_ERROR, 'clientId too long'); + return; + } + if (msg.proof.length > AUTH_PROOF_MAX_LENGTH) { + ws.close(WS_CLOSE_PROTOCOL_ERROR, 'proof too long'); + return; + } + // Validate lastSeq as non-negative integer if provided + let lastSeq: number | undefined; + if (msg.lastSeq !== undefined) { + if (typeof msg.lastSeq !== 'number' || !Number.isInteger(msg.lastSeq) || msg.lastSeq < 0) { + ws.close(WS_CLOSE_PROTOCOL_ERROR, 'lastSeq must be a non-negative integer'); + return; + } + lastSeq = msg.lastSeq; + } + const authResponse: AuthResponse = { + type: 'auth.response', + challengeId: msg.challengeId as string, + clientId: msg.clientId as string, + proof: msg.proof as string, + lastSeq, + }; + await this.handleAuthResponse(ws, meta, authResponse); + return; + } + + // Post-auth: dispatch by message type + await this.handlePostAuthMessage(ws, meta, msg); + } + + // --------------------------------------------------------------------------- + // Post-Auth Message Dispatch + // --------------------------------------------------------------------------- + + private async handlePostAuthMessage( + ws: WebSocket, + meta: Extract, + msg: Record, + ): Promise { + // Admin challenge request + if (msg.type === 'admin.challenge.request') { + await this.handleAdminChallengeRequest(ws, meta); + return; + } + + // Admin command + if (msg.type === 'admin.command') { + await this.handleAdminCommand(ws, meta, msg); + return; + } + + // ServerEnvelope — detect via channel field (no type field) + if (typeof msg.channel === 'string' && (msg.channel === 'event' || msg.channel === 'presence')) { + await this.handleServerEnvelope(ws, meta, msg); + return; + } + + ws.close(WS_CLOSE_PROTOCOL_ERROR, 'Unknown message type'); + } + + // --------------------------------------------------------------------------- + // Lifecycle Check (shared by event, presence, admin paths) + // --------------------------------------------------------------------------- + + /** + * Check room lifecycle state. Returns roomState if usable, or null if terminal. + * Closes the socket for rooms that are gone (purged) or past their deadline. + */ + private async checkRoomLifecycle( + ws: WebSocket, + _roomId: string, + ): Promise { + const roomState = await this.ctx.storage.get('room'); + if (!roomState) { + ws.close(WS_CLOSE_ROOM_UNAVAILABLE, WS_CLOSE_REASON_ROOM_UNAVAILABLE); + return null; + } + // Lazy-expiry backstop. Alarm handles the common case; this fires only + // if a socket somehow reached us after the deadline without the alarm + // having landed yet. + if (hasRoomExpired(roomState.expiresAt)) { + await this.purgeRoom('lifecycle-preempted-expired', ws); + ws.close(WS_CLOSE_ROOM_UNAVAILABLE, WS_CLOSE_REASON_ROOM_UNAVAILABLE); + return null; + } + return roomState; + } + + // --------------------------------------------------------------------------- + // Event Sequencing & Presence Relay + // --------------------------------------------------------------------------- + + private async handleServerEnvelope( + ws: WebSocket, + meta: Extract, + msg: Record, + ): Promise { + const validated = validateServerEnvelope(msg); + if (isValidationError(validated)) { + this.sendError(ws, 'validation_error', validated.error); + return; + } + // isValidationError narrows; `validated` is ServerEnvelope here. + const envelope: ServerEnvelope = { + ...validated, + clientId: meta.clientId, // Override — prevent spoofing + }; + + const roomState = await this.checkRoomLifecycle(ws, meta.roomId); + if (!roomState) return; + + if (envelope.channel === 'event') { + // Sequence the event on an IMMUTABLE next-state object. If the + // durable write fails, we must NOT have already bumped roomState.seq + // in memory — the next event must reuse the current seq, not a gap'd + // one. Nor may we broadcast an event that was never persisted. + const nextSeq = roomState.seq + 1; + const sequenced: SequencedEnvelope = { + seq: nextSeq, + receivedAt: Date.now(), + envelope, + }; + const nextRoomState: RoomDurableState = { ...roomState, seq: nextSeq }; + + // Atomic write: event key + room metadata in one put. + try { + await this.ctx.storage.put({ + [`event:${padSeq(nextSeq)}`]: sequenced, + 'room': nextRoomState, + } as Record); + } catch (e) { + // Persistence failed. Surface a clean error to the sender so their + // sendAnnotation* promise rejects (or their UI sees lastError) — + // otherwise they'd think the op landed on the wire. Do NOT bump + // in-memory seq, do NOT broadcast. + safeLog('room:event-persist-error', { + roomId: roomState.roomId, + attemptedSeq: nextSeq, + clientId: meta.clientId, + error: String(e), + }); + this.sendError(ws, 'event_persist_failed', 'Failed to persist event'); + return; + } + + // Durable write succeeded — commit in-memory state and broadcast. + Object.assign(roomState, nextRoomState); + const transport: RoomTransportMessage = { + type: 'room.event', + seq: sequenced.seq, + receivedAt: sequenced.receivedAt, + envelope: sequenced.envelope, + }; + this.broadcast(transport); + + safeLog('room:event-sequenced', { roomId: roomState.roomId, seq: roomState.seq, clientId: meta.clientId }); + } else { + // Presence — allowed in any non-terminal room state + const transport: RoomTransportMessage = { + type: 'room.presence', + envelope, + }; + this.broadcast(transport, ws); + } + } + + // --------------------------------------------------------------------------- + // Auth Response + Reconnect Replay + // --------------------------------------------------------------------------- + + private async handleAuthResponse( + ws: WebSocket, + meta: Extract, + authResponse: AuthResponse, + ): Promise { + if (authResponse.challengeId !== meta.challengeId) { + safeLog('ws:auth-rejected', { reason: 'unknown-challenge', roomId: meta.roomId }); + ws.close(WS_CLOSE_UNKNOWN_CHALLENGE, 'Unknown challenge'); + return; + } + + // The clientId in auth.response MUST match the server-assigned clientId + // from this connection's challenge. This prevents a participant from + // choosing another peer's clientId at auth time and overwriting their + // presence slot. + if (authResponse.clientId !== meta.clientId) { + safeLog('ws:auth-rejected', { reason: 'clientId-mismatch', roomId: meta.roomId }); + ws.close(WS_CLOSE_INVALID_PROOF, 'clientId does not match challenge'); + return; + } + + if (Date.now() > meta.expiresAt) { + safeLog('ws:auth-rejected', { reason: 'expired', roomId: meta.roomId }); + ws.close(WS_CLOSE_CHALLENGE_EXPIRED, 'Challenge expired'); + return; + } + + // Delegate lifecycle checks (deleted / expired / lazy-expiry) to the + // shared helper so this path doesn't drift from the post-auth path. + const roomState = await this.checkRoomLifecycle(ws, meta.roomId); + if (!roomState) return; + + const valid = await verifyAuthProof( + roomState.roomVerifier, + meta.roomId, + authResponse.clientId, + meta.challengeId, + meta.nonce, + authResponse.proof, + ); + + if (!valid) { + safeLog('ws:auth-rejected', { reason: 'invalid-proof', roomId: meta.roomId }); + ws.close(WS_CLOSE_INVALID_PROOF, 'Invalid proof'); + return; + } + + // Auth successful — update attachment + const authenticatedMeta: WebSocketAttachment = { + authenticated: true, + roomId: meta.roomId, + clientId: authResponse.clientId, + authenticatedAt: Date.now(), + }; + ws.serializeAttachment(authenticatedMeta); + + // Send auth.accepted + const accepted: AuthAccepted = { + type: 'auth.accepted', + seq: roomState.seq, + snapshotSeq: roomState.snapshotSeq, + snapshotAvailable: !!roomState.snapshotCiphertext, + }; + ws.send(JSON.stringify(accepted)); + + // Reconnect replay + await this.replayEvents(ws, roomState, authResponse.lastSeq); + + safeLog('ws:authenticated', { roomId: meta.roomId, clientId: authResponse.clientId, lastSeq: authResponse.lastSeq }); + } + + private async replayEvents( + ws: WebSocket, + roomState: RoomDurableState, + lastSeq: number | undefined, + ): Promise { + // Local helper: single place that constructs and sends a room.snapshot + // transport message. Keeps the message shape in one place so any future + // field addition lands once. + const sendSnapshotToSocket = (): void => { + if (!roomState.snapshotCiphertext) return; + const snapshotMsg: RoomTransportMessage = { + type: 'room.snapshot', + snapshotSeq: roomState.snapshotSeq ?? 0, + snapshotCiphertext: roomState.snapshotCiphertext, + }; + ws.send(JSON.stringify(snapshotMsg)); + }; + + // Determine replay strategy + let sendSnapshot = false; + let replayFrom: number; + + if (lastSeq === undefined) { + // Fresh join — send snapshot + all events + sendSnapshot = true; + replayFrom = (roomState.snapshotSeq ?? 0) + 1; + } else if (lastSeq > roomState.seq) { + // Future claim — anomaly, fall back to snapshot + sendSnapshot = true; + replayFrom = (roomState.snapshotSeq ?? 0) + 1; + safeLog('ws:replay-anomaly', { roomId: roomState.roomId, lastSeq, currentSeq: roomState.seq }); + } else if (lastSeq === roomState.seq) { + // Fully caught up — still send snapshot if seq is 0 (fresh room, no events yet) + if (roomState.seq === 0) { + sendSnapshotToSocket(); + } + return; + } else { + // Check if we can replay incrementally + const nextNeededSeq = lastSeq + 1; + // In V1 earliestRetainedSeq stays 1 because there is no compaction. + // This branch becomes active once future compaction advances it. + if (nextNeededSeq < roomState.earliestRetainedSeq) { + // Too old — need snapshot fallback + sendSnapshot = true; + replayFrom = (roomState.snapshotSeq ?? 0) + 1; + } else { + // Can replay from retained log + replayFrom = nextNeededSeq; + } + } + + if (sendSnapshot) { + sendSnapshotToSocket(); + } + + // Replay events from storage (if any exist). Paginated so large rooms + // don't load the full event log into DO memory at reconnect time — + // storage.list() without a limit can blow memory in rooms with many + // retained events (V1 retains all events for the room lifetime). + if (roomState.seq > 0 && replayFrom <= roomState.seq) { + let cursor = `event:${padSeq(replayFrom)}`; + const end = `event:${padSeq(roomState.seq)}\uffff`; // inclusive of roomState.seq + while (true) { + const page = await this.ctx.storage.list({ + prefix: 'event:', + start: cursor, + end, + limit: REPLAY_PAGE_SIZE, + }); + if (page.size === 0) break; + let lastKey = cursor; + for (const [key, sequenced] of page) { + const transport: RoomTransportMessage = { + type: 'room.event', + seq: sequenced.seq, + receivedAt: sequenced.receivedAt, + envelope: sequenced.envelope, + }; + ws.send(JSON.stringify(transport)); + lastKey = key; + } + if (page.size < REPLAY_PAGE_SIZE) break; + // Advance cursor past the last emitted key. `storage.list({ start })` + // is INCLUSIVE, so passing `lastKey` would re-emit the final event. + // Appending U+0000 (the smallest Unicode code point) produces a string + // strictly greater than `lastKey` but strictly less than any valid + // next key — because padded numeric seq keys are ASCII digits only + // and never contain a null byte, no real key can fall between them. + // Using `\uffff` (max code point) here would be WRONG: it would skip + // all keys lexicographically between `lastKey` and `lastKey\uffff`, + // dropping legitimate events from the replay. + cursor = `${lastKey}\u0000`; + } + } + } + + // --------------------------------------------------------------------------- + // Admin Challenge-Response + // --------------------------------------------------------------------------- + + private async handleAdminChallengeRequest( + ws: WebSocket, + meta: Extract, + ): Promise { + // Lifecycle check — reject for terminal rooms + const roomState = await this.checkRoomLifecycle(ws, meta.roomId); + if (!roomState) return; + + const challengeId = generateChallengeId(); + const nonce = generateNonce(); + const expiresAt = Date.now() + ADMIN_CHALLENGE_TTL_MS; + + // Store in attachment (survives hibernation) + const updatedMeta: WebSocketAttachment = { + ...meta, + pendingAdminChallenge: { challengeId, nonce, expiresAt }, + }; + ws.serializeAttachment(updatedMeta); + + const challenge: AdminChallenge = { + type: 'admin.challenge', + challengeId, + nonce, + expiresAt, + }; + ws.send(JSON.stringify(challenge)); + + safeLog('admin:challenge-sent', { roomId: meta.roomId, clientId: meta.clientId, challengeId }); + } + + private async handleAdminCommand( + ws: WebSocket, + meta: Extract, + msg: Record, + ): Promise { + // ADMIN ERROR-CODE CONTRACT + // ------------------------- + // Every error code emitted from this method AND from helpers it calls + // (applyDelete, admin-scoped branches of handleAdminChallengeRequest) + // must be listed in the client's ADMIN_SCOPED_ERROR_CODES Set in + // packages/shared/collab/client-runtime/client.ts. That Set gates which + // room.error payloads reject a pending admin promise; a code that + // fires here but is missing from the Set leaves the client hanging + // until AdminTimeoutError. A code that fires on the event channel but + // is ADDED to the Set (e.g. validation_error) wrongly cancels + // unrelated in-flight admin commands. When adding/renaming/removing + // admin-path codes, update the client Set in the same change. + const validated = validateAdminCommandEnvelope(msg); + if (isValidationError(validated)) { + // Admin-scoped code so the client can distinguish admin-flow failures + // from event-channel failures (e.g. validation_error fires on the + // event channel while an admin command is in flight — rejecting + // pendingAdmin on those would be wrong). + this.sendAdminError(ws, AdminErrorCode.ValidationError, validated.error); + return; + } + // isValidationError narrows; `validated` is AdminCommandEnvelope here. + const cmdEnvelope = validated; + + // Reject cross-connection clientId spoofing + if (cmdEnvelope.clientId !== meta.clientId) { + this.sendAdminError(ws, AdminErrorCode.ClientIdMismatch, 'clientId does not match authenticated connection'); + return; + } + + // Check pending admin challenge + if (!meta.pendingAdminChallenge) { + this.sendAdminError(ws, AdminErrorCode.NoAdminChallenge, 'Request an admin challenge first'); + return; + } + if (cmdEnvelope.challengeId !== meta.pendingAdminChallenge.challengeId) { + this.sendAdminError(ws, AdminErrorCode.UnknownAdminChallenge, 'Challenge ID does not match'); + return; + } + + // Save challenge data before clearing + const { challengeId, nonce, expiresAt } = meta.pendingAdminChallenge; + + // Clear challenge from attachment (single-use) — serialize immediately + const { pendingAdminChallenge: _, ...cleanMeta } = meta; + ws.serializeAttachment(cleanMeta); + + // Check expiry + if (Date.now() > expiresAt) { + this.sendAdminError(ws, AdminErrorCode.AdminChallengeExpired, 'Admin challenge expired'); + return; + } + + // Lifecycle check — reject for terminal rooms + const roomState = await this.checkRoomLifecycle(ws, meta.roomId); + if (!roomState) return; + + // Verify admin proof + const valid = await verifyAdminProof( + roomState.adminVerifier, + meta.roomId, + meta.clientId, + challengeId, + nonce, + cmdEnvelope.command, + cmdEnvelope.adminProof, + ); + + if (!valid) { + safeLog('admin:proof-rejected', { roomId: meta.roomId, clientId: meta.clientId }); + this.sendAdminError(ws, AdminErrorCode.InvalidAdminProof, 'Admin proof verification failed'); + return; + } + + // Apply command + switch (cmdEnvelope.command.type) { + case 'room.delete': + await this.applyDelete(ws, roomState); + break; + default: { + // Compile-time exhaustiveness guard: if a new admin command is added + // to the union and a case here is missed, TypeScript fails here. + const _exhaustive: never = cmdEnvelope.command.type; + void _exhaustive; + break; + } + } + } + + // --------------------------------------------------------------------------- + // Admin Command Execution + // --------------------------------------------------------------------------- + + private async applyDelete( + ws: WebSocket, + roomState: RoomDurableState, + ): Promise { + // checkRoomLifecycle ran at the top of handleAdminCommand, so this + // path is only reachable for a live room. purgeRoom wipes storage, + // purges event keys, cancels the expiry alarm, and closes every + // socket (including the admin's) with the generic unavailable + // reason — same terminal UX as an expired room or a never-created + // URL. + try { + await this.purgeRoom('admin'); + } catch (e) { + // purgeRoom already handles its own storage-error logging. Signal + // the admin caller that the delete didn't complete so their + // pending promise rejects cleanly. + safeLog('room:delete-error', { roomId: roomState.roomId, error: String(e) }); + this.sendAdminError(ws, AdminErrorCode.DeleteFailed, 'Failed to delete room'); + } + } + + // --------------------------------------------------------------------------- + // Storage Helpers + // --------------------------------------------------------------------------- + + /** + * Delete all event keys from storage in batches. Paginated for the same + * reason as replay: avoid loading the full event log into DO memory. + * Less latency-sensitive than replay but the memory bound still matters. + */ + private async purgeEventKeys(): Promise { + while (true) { + const page = await this.ctx.storage.list({ + prefix: 'event:', + limit: DELETE_BATCH_SIZE, + }); + if (page.size === 0) break; + await this.ctx.storage.delete([...page.keys()]); + if (page.size < DELETE_BATCH_SIZE) break; + } + } + + // --------------------------------------------------------------------------- + // Cleanup — single unified hard-delete path + // --------------------------------------------------------------------------- + + /** + * Hard-delete the room. No tombstone, no lingering state — once this + * returns, the DO storage is empty of room data and every connected + * socket has been closed with the generic "room unavailable" reason. + * + * Called from four triggers: + * - 'expiry' alarm fired at expiresAt + * - 'admin' creator clicked Delete room + * - 'create-preempted-expired' a fresh create is supplanting a + * room whose alarm never fired + * - 'lifecycle-preempted-expired' a socket reached us after the + * deadline; alarm hadn't landed yet + * - 'upgrade-preempted-expired' same, on the HTTP upgrade path + * + * `reason` is logged but not surfaced to clients — from their + * perspective, every purge looks the same: the link stops resolving. + */ + private async purgeRoom( + reason: 'expiry' | 'admin' | 'create-preempted-expired' | 'lifecycle-preempted-expired' | 'upgrade-preempted-expired', + except?: WebSocket, + ): Promise { + // Hard-delete the room record FIRST. Absence is what makes the room + // unreachable to any new connection — a concurrent WS upgrade or + // lifecycle check that lands mid-purge sees nothing and rejects. + // Closing sockets before this would leave a window where the room + // key still reads as present. + try { + await this.ctx.storage.delete('room'); + } catch (e) { + safeLog('room:purge-delete-error', { reason, error: String(e) }); + throw e; + } + + // Now close connected peers so they see the terminal close. + this.closeRoomSockets(WS_CLOSE_REASON_ROOM_UNAVAILABLE, except); + + // Best-effort: cancel the pending alarm in case the trigger wasn't + // the alarm itself. Avoids a redundant alarm wake after we've + // already emptied the room. + try { + await this.ctx.storage.deleteAlarm(); + } catch (e) { + safeLog('room:purge-delete-alarm-error', { reason, error: String(e) }); + } + + // Purge event log (per-event keys). + try { + await this.purgeEventKeys(); + } catch (e) { + safeLog('room:purge-event-keys-error', { reason, error: String(e) }); + } + + safeLog('room:purged', { reason }); + } + + // --------------------------------------------------------------------------- + // Broadcast Helpers + // --------------------------------------------------------------------------- + + /** + * Send a transport message to every authenticated socket in the room, + * optionally excluding one (e.g. the sender for presence relay). Send + * failures are intentionally ignored — the target socket may have closed. + */ + private broadcast(message: RoomTransportMessage, exclude?: WebSocket): void { + const json = JSON.stringify(message); + for (const socket of this.ctx.getWebSockets()) { + if (socket === exclude) continue; + const att = socket.deserializeAttachment() as WebSocketAttachment | null; + if (att?.authenticated) { + try { socket.send(json); } catch { /* socket may have closed */ } + } + } + } + + private sendError(ws: WebSocket, code: string, message: string): void { + const error: RoomTransportMessage = { type: 'room.error', code, message }; + try { ws.send(JSON.stringify(error)); } catch { /* socket may have closed */ } + } + + /** + * Admin-scoped error emitter. Every admin-command rejection path + * (validate, challenge, proof, state, persist) MUST go through this + * wrapper instead of raw `sendError` so the `AdminErrorCode` type + * enforces the contract the client's rejection gate relies on + * (see `ADMIN_ERROR_CODES` in shared/collab/constants.ts). Adding a + * new admin error = add a key to `AdminErrorCode`, use it here; + * typos and non-admin codes surface as compile errors. + */ + private sendAdminError(ws: WebSocket, code: AdminErrorCode, message: string): void { + this.sendError(ws, code, message); + } + + private closeRoomSockets(reason: string, except?: WebSocket): void { + for (const socket of this.ctx.getWebSockets()) { + if (socket !== except) { + socket.close(WS_CLOSE_ROOM_UNAVAILABLE, reason); + } + } + } + + // --------------------------------------------------------------------------- + // WebSocket Lifecycle (Hibernation API) + // --------------------------------------------------------------------------- + + async webSocketClose(ws: WebSocket, code: number, _reason: string, _wasClean: boolean): Promise { + const meta = ws.deserializeAttachment() as WebSocketAttachment | null; + const roomId = meta?.roomId ?? 'unknown'; + const clientId = meta?.authenticated ? meta.clientId : 'unauthenticated'; + safeLog('ws:closed', { roomId, clientId, code }); + + // Tell the remaining peers the closed client has left so they can + // drop that clientId's presence (cursor + avatar) immediately. + // Without this, peers wait out the 30s client-side TTL sweep, + // which made "refresh to test" pile up one ghost cursor per + // refresh until the entries expired. Only broadcast for + // authenticated sockets — unauth'd ones were never in peers' + // presence maps, so nothing needs cleanup. + // + // `exclude: ws` leaves the closing socket out of the fan-out. + // It may already be detached, but the broadcast's send-try/catch + // tolerates that either way. No payload beyond clientId — the + // protocol is zero-knowledge; we only relay opaque encrypted + // presence packets, and the clientId is server-assigned in the + // auth challenge so it's already non-secret. + if (meta?.authenticated) { + this.broadcast( + { type: 'room.participant.left', clientId: meta.clientId }, + ws, + ); + } + } + + async webSocketError(ws: WebSocket, error: unknown): Promise { + const meta = ws.deserializeAttachment() as WebSocketAttachment | null; + const roomId = meta?.roomId ?? 'unknown'; + safeLog('ws:error', { roomId, error: String(error) }); + } +} diff --git a/apps/room-service/core/room-engine.test.ts b/apps/room-service/core/room-engine.test.ts new file mode 100644 index 00000000..2af7fc76 --- /dev/null +++ b/apps/room-service/core/room-engine.test.ts @@ -0,0 +1,159 @@ +/** + * Slice 3 engine tests — validation, admin proofs, and lifecycle helpers. + * + * Tests act as external clients and import from @plannotator/shared/collab/client. + */ + +import { describe, expect, test } from 'bun:test'; +import { + validateServerEnvelope, + validateAdminCommandEnvelope, + isValidationError, +} from './validation'; +import type { ValidationError } from './validation'; +import { + deriveAdminKey, + computeAdminVerifier, + computeAdminProof, + verifyAdminProof, + generateChallengeId, + generateNonce, +} from '@plannotator/shared/collab/client'; +import type { AdminCommand } from '@plannotator/shared/collab'; + +// --------------------------------------------------------------------------- +// validateServerEnvelope +// --------------------------------------------------------------------------- + +describe('validateServerEnvelope', () => { + const validEvent = { + clientId: 'client-1', + opId: 'op-abc', + channel: 'event', + ciphertext: 'encrypted-data', + }; + + test('accepts valid event envelope', () => { + const result = validateServerEnvelope(validEvent); + expect(isValidationError(result)).toBe(false); + }); + + test('accepts valid presence envelope', () => { + const result = validateServerEnvelope({ ...validEvent, channel: 'presence' }); + expect(isValidationError(result)).toBe(false); + }); + + test('rejects missing clientId', () => { + const { clientId: _, ...rest } = validEvent; + expect(isValidationError(validateServerEnvelope(rest))).toBe(true); + }); + + test('rejects missing opId', () => { + const { opId: _, ...rest } = validEvent; + expect(isValidationError(validateServerEnvelope(rest))).toBe(true); + }); + + test('rejects invalid channel', () => { + expect(isValidationError(validateServerEnvelope({ ...validEvent, channel: 'invalid' }))).toBe(true); + }); + + test('rejects missing ciphertext', () => { + const { ciphertext: _, ...rest } = validEvent; + expect(isValidationError(validateServerEnvelope(rest))).toBe(true); + }); + + test('rejects oversized event ciphertext (> 512 KB)', () => { + const result = validateServerEnvelope({ ...validEvent, ciphertext: 'x'.repeat(512_001) }); + expect(isValidationError(result)).toBe(true); + expect((result as ValidationError).status).toBe(413); + }); + + test('rejects oversized presence ciphertext (> 8 KB)', () => { + const result = validateServerEnvelope({ ...validEvent, channel: 'presence', ciphertext: 'x'.repeat(8_193) }); + expect(isValidationError(result)).toBe(true); + expect((result as ValidationError).status).toBe(413); + }); +}); + +// --------------------------------------------------------------------------- +// validateAdminCommandEnvelope +// --------------------------------------------------------------------------- + +describe('validateAdminCommandEnvelope', () => { + const validDelete = { + type: 'admin.command', + challengeId: 'ch_abc', + clientId: 'client-1', + command: { type: 'room.delete' }, + adminProof: 'proof-data', + }; + + test('accepts valid delete command', () => { + const result = validateAdminCommandEnvelope(validDelete); + expect(isValidationError(result)).toBe(false); + }); + + test('rejects unknown command type', () => { + expect(isValidationError(validateAdminCommandEnvelope({ ...validDelete, command: { type: 'room.explode' } }))).toBe(true); + }); + + test('rejects missing challengeId', () => { + const { challengeId: _, ...rest } = validDelete; + expect(isValidationError(validateAdminCommandEnvelope(rest))).toBe(true); + }); + + test('rejects missing adminProof', () => { + const { adminProof: _, ...rest } = validDelete; + expect(isValidationError(validateAdminCommandEnvelope(rest))).toBe(true); + }); +}); + +// --------------------------------------------------------------------------- +// Admin Proof Round-Trip +// --------------------------------------------------------------------------- + +const ADMIN_SECRET = new Uint8Array(32); +ADMIN_SECRET.fill(0xcd); +const ROOM_ID = 'test-room-admin-proof'; + +describe('admin proof verification (end-to-end)', () => { + test('valid admin proof is accepted', async () => { + const adminKey = await deriveAdminKey(ADMIN_SECRET); + const verifier = await computeAdminVerifier(adminKey, ROOM_ID); + const challengeId = generateChallengeId(); + const nonce = generateNonce(); + const command: AdminCommand = { type: 'room.delete' }; + + const proof = await computeAdminProof(verifier, ROOM_ID, 'client-1', challengeId, nonce, command); + const valid = await verifyAdminProof(verifier, ROOM_ID, 'client-1', challengeId, nonce, command, proof); + expect(valid).toBe(true); + }); + + test('wrong proof is rejected', async () => { + const adminKey = await deriveAdminKey(ADMIN_SECRET); + const verifier = await computeAdminVerifier(adminKey, ROOM_ID); + const challengeId = generateChallengeId(); + const nonce = generateNonce(); + const command: AdminCommand = { type: 'room.delete' }; + + const valid = await verifyAdminProof(verifier, ROOM_ID, 'client-1', challengeId, nonce, command, 'garbage-proof'); + expect(valid).toBe(false); + }); + + test('proof cannot verify against a different command shape (binding via canonicalJson)', async () => { + // V1 has a single AdminCommand shape, so we exercise the binding via + // an unsanctioned command — the proof must not verify for anything + // whose canonicalJson differs from what was signed. + const adminKey = await deriveAdminKey(ADMIN_SECRET); + const verifier = await computeAdminVerifier(adminKey, ROOM_ID); + const challengeId = generateChallengeId(); + const nonce = generateNonce(); + + const deleteCommand: AdminCommand = { type: 'room.delete' }; + const otherCommand = { type: 'room.other' } as unknown as AdminCommand; + + const proof = await computeAdminProof(verifier, ROOM_ID, 'client-1', challengeId, nonce, deleteCommand); + const valid = await verifyAdminProof(verifier, ROOM_ID, 'client-1', challengeId, nonce, otherCommand, proof); + expect(valid).toBe(false); + }); +}); diff --git a/apps/room-service/core/types.ts b/apps/room-service/core/types.ts new file mode 100644 index 00000000..b6b27d77 --- /dev/null +++ b/apps/room-service/core/types.ts @@ -0,0 +1,73 @@ +/** + * Server-only types for the room-service Durable Object. + * + * RoomDurableState is the persistent room record stored in DO storage. + * WebSocketAttachment is serialized per-connection metadata that survives + * DO hibernation via serializeAttachment/deserializeAttachment. + */ + +// --------------------------------------------------------------------------- +// Worker Environment +// --------------------------------------------------------------------------- + +/** Cloudflare Worker environment bindings. */ +export interface Env { + ROOM: DurableObjectNamespace; + /** Wrangler-managed static asset binding. Serves `./public/index.html` (room shell) + hashed `./public/assets/*` chunks. Populated by `bun run build:shell`. */ + ASSETS?: { fetch(request: Request): Promise }; + ALLOWED_ORIGINS?: string; + ALLOW_LOCALHOST_ORIGINS?: string; + BASE_URL?: string; +} + +/** + * Durable state stored in DO storage under key 'room'. + * + * The room either exists (this record is present) or it doesn't (key + * absent). There's no "deleted" / "expired" tombstone state — purgeRoom + * hard-deletes the key when the 30-day alarm fires or when an admin + * issues delete. Absence means "link doesn't resolve." + * + * Events are NOT stored in this record — they use separate per-event keys + * ('event:0000000001', etc.) to stay within DO per-value size limits. + */ +export interface RoomDurableState { + /** Stored at creation — DO can't reverse idFromName(). */ + roomId: string; + roomVerifier: string; + adminVerifier: string; + seq: number; + /** Oldest event seq still in storage. Initialized to 1 at creation. */ + earliestRetainedSeq: number; + snapshotCiphertext?: string; + snapshotSeq?: number; + expiresAt: number; +} + +/** + * WebSocket attachment — survives hibernation via serializeAttachment/deserializeAttachment. + * + * Pre-auth: holds pending challenge state so the DO can verify after waking. + * Post-auth: holds authenticated connection metadata + optional pending admin challenge. + * Both variants carry roomId so webSocketMessage() can access it without a storage read. + */ +export type WebSocketAttachment = + | { + authenticated: false; + roomId: string; + challengeId: string; + nonce: string; + expiresAt: number; + /** Server-assigned ephemeral client id for this connection. Included in + * the auth challenge so the client's proof binds to it; prevents a + * malicious participant from choosing another user's clientId at auth + * time and overwriting their presence slot after auth. */ + clientId: string; + } + | { + authenticated: true; + roomId: string; + clientId: string; + authenticatedAt: number; + pendingAdminChallenge?: { challengeId: string; nonce: string; expiresAt: number }; + }; diff --git a/apps/room-service/core/validation.test.ts b/apps/room-service/core/validation.test.ts new file mode 100644 index 00000000..e1926a2b --- /dev/null +++ b/apps/room-service/core/validation.test.ts @@ -0,0 +1,282 @@ +import { describe, expect, test } from 'bun:test'; +import { + validateCreateRoomRequest, + isValidationError, + clampExpiryDays, + hasRoomExpired, + isRoomId, + validateServerEnvelope, + validateAdminCommandEnvelope, +} from './validation'; + +describe('validateCreateRoomRequest', () => { + // 22-char base64url room ID (matches generateRoomId() output: 16 random bytes) + const validRoomId = 'ABCDEFGHIJKLMNOPQRSTUv'; + // 43-char base64url verifiers (matches HMAC-SHA-256 output: 32 bytes) + const validVerifier = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopq'; + const validAdminVerifier = 'abcdefghijklmnopqrstuvwxyz0123456789_-ABCDE'; + const validBody = { + roomId: validRoomId, + roomVerifier: validVerifier, + adminVerifier: validAdminVerifier, + initialSnapshotCiphertext: 'encrypted-snapshot-data', + }; + + test('accepts valid request', () => { + const result = validateCreateRoomRequest(validBody); + expect(isValidationError(result)).toBe(false); + if (!isValidationError(result)) { + expect(result.roomId).toBe(validRoomId); + expect(result.roomVerifier).toBe(validVerifier); + expect(result.adminVerifier).toBe(validAdminVerifier); + expect(result.initialSnapshotCiphertext).toBe('encrypted-snapshot-data'); + } + }); + + test('accepts request with expiresInDays', () => { + const result = validateCreateRoomRequest({ ...validBody, expiresInDays: 7 }); + expect(isValidationError(result)).toBe(false); + if (!isValidationError(result)) { + expect(result.expiresInDays).toBe(7); + } + }); + + test('rejects null body', () => { + const result = validateCreateRoomRequest(null); + expect(isValidationError(result)).toBe(true); + if (isValidationError(result)) { + expect(result.status).toBe(400); + } + }); + + test('rejects non-object body', () => { + const result = validateCreateRoomRequest('not an object'); + expect(isValidationError(result)).toBe(true); + }); + + test('rejects missing roomId', () => { + const { roomId: _, ...body } = validBody; + const result = validateCreateRoomRequest(body); + expect(isValidationError(result)).toBe(true); + if (isValidationError(result)) { + expect(result.error).toContain('roomId'); + } + }); + + test('rejects empty roomId', () => { + const result = validateCreateRoomRequest({ ...validBody, roomId: '' }); + expect(isValidationError(result)).toBe(true); + }); + + test('rejects missing roomVerifier', () => { + const { roomVerifier: _, ...body } = validBody; + const result = validateCreateRoomRequest(body); + expect(isValidationError(result)).toBe(true); + if (isValidationError(result)) { + expect(result.error).toContain('roomVerifier'); + } + }); + + test('rejects missing adminVerifier', () => { + const { adminVerifier: _, ...body } = validBody; + const result = validateCreateRoomRequest(body); + expect(isValidationError(result)).toBe(true); + if (isValidationError(result)) { + expect(result.error).toContain('adminVerifier'); + } + }); + + test('rejects malformed roomVerifier (wrong length)', () => { + expect(isValidationError(validateCreateRoomRequest({ ...validBody, roomVerifier: 'too-short' }))).toBe(true); + expect(isValidationError(validateCreateRoomRequest({ ...validBody, roomVerifier: 'a'.repeat(44) }))).toBe(true); + }); + + test('rejects malformed adminVerifier (wrong length)', () => { + expect(isValidationError(validateCreateRoomRequest({ ...validBody, adminVerifier: 'too-short' }))).toBe(true); + }); + + test('rejects verifier with invalid characters (exactly 43 chars, bad final char)', () => { + // 26 + 16 + 1 = 43 chars, only the / is invalid + expect(isValidationError(validateCreateRoomRequest({ ...validBody, roomVerifier: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop/' }))).toBe(true); + }); + + test('rejects missing initialSnapshotCiphertext', () => { + const { initialSnapshotCiphertext: _, ...body } = validBody; + const result = validateCreateRoomRequest(body); + expect(isValidationError(result)).toBe(true); + if (isValidationError(result)) { + expect(result.error).toContain('initialSnapshotCiphertext'); + } + }); + + test('rejects oversized initialSnapshotCiphertext', () => { + const result = validateCreateRoomRequest({ + ...validBody, + initialSnapshotCiphertext: 'x'.repeat(1_500_001), + }); + expect(isValidationError(result)).toBe(true); + if (isValidationError(result)) { + expect(result.status).toBe(413); + } + }); + + test('rejects roomId with invalid characters (exactly 22 chars, bad final char)', () => { + expect(isValidationError(validateCreateRoomRequest({ ...validBody, roomId: 'ABCDEFGHIJKLMNOPQRSTU/' }))).toBe(true); + expect(isValidationError(validateCreateRoomRequest({ ...validBody, roomId: 'ABCDEFGHIJKLMNOPQRSTU?' }))).toBe(true); + expect(isValidationError(validateCreateRoomRequest({ ...validBody, roomId: 'ABCDEFGHIJKLMNOPQRSTU ' }))).toBe(true); + }); + + test('rejects roomId that is not exactly 22 chars', () => { + expect(isValidationError(validateCreateRoomRequest({ ...validBody, roomId: 'ABCDEFGHIJKLMNOPQRSTUvW' }))).toBe(true); // 23 chars + expect(isValidationError(validateCreateRoomRequest({ ...validBody, roomId: 'ABCDEFGHIJKLMNOPQRSTu' }))).toBe(true); // 21 chars + expect(isValidationError(validateCreateRoomRequest({ ...validBody, roomId: 'short' }))).toBe(true); + }); + + test('accepts exactly 22 base64url chars', () => { + expect(isValidationError(validateCreateRoomRequest({ ...validBody, roomId: 'ABCDEFGHIJKLMNOPQRSTUv' }))).toBe(false); + expect(isValidationError(validateCreateRoomRequest({ ...validBody, roomId: 'abcdefghijklmnopqrstuv' }))).toBe(false); + expect(isValidationError(validateCreateRoomRequest({ ...validBody, roomId: '0123456789_-ABCDEFGHIJ' }))).toBe(false); + }); +}); + +describe('clampExpiryDays', () => { + test('defaults to 30', () => { + expect(clampExpiryDays(undefined)).toBe(30); + }); + + test('clamps 0 to 1', () => { + expect(clampExpiryDays(0)).toBe(1); + }); + + test('clamps negative to 1', () => { + expect(clampExpiryDays(-5)).toBe(1); + }); + + test('clamps 100 to 30', () => { + expect(clampExpiryDays(100)).toBe(30); + }); + + test('passes through valid value', () => { + expect(clampExpiryDays(7)).toBe(7); + }); + + test('floors fractional days', () => { + expect(clampExpiryDays(7.9)).toBe(7); + }); +}); + +describe('hasRoomExpired', () => { + test('returns false before expiry', () => { + expect(hasRoomExpired(2_000, 1_999)).toBe(false); + }); + + test('returns false at exact expiry timestamp', () => { + expect(hasRoomExpired(2_000, 2_000)).toBe(false); + }); + + test('returns true after expiry', () => { + expect(hasRoomExpired(2_000, 2_001)).toBe(true); + }); +}); + +describe('isRoomId', () => { + test('accepts valid 22-char base64url ids', () => { + expect(isRoomId('ABCDEFGHIJKLMNOPQRSTUv')).toBe(true); + expect(isRoomId('abcdef_ghij-klmnopqrst')).toBe(true); + }); + test('rejects wrong-length ids', () => { + expect(isRoomId('short')).toBe(false); + expect(isRoomId('A'.repeat(21))).toBe(false); + expect(isRoomId('A'.repeat(23))).toBe(false); + }); + test('rejects ids containing disallowed characters', () => { + expect(isRoomId('A'.repeat(21) + '!')).toBe(false); + expect(isRoomId('A'.repeat(21) + '/')).toBe(false); + expect(isRoomId('A'.repeat(21) + '=')).toBe(false); + }); + test('rejects non-string inputs', () => { + expect(isRoomId(undefined)).toBe(false); + expect(isRoomId(42 as unknown as string)).toBe(false); + expect(isRoomId(null)).toBe(false); + }); +}); + +describe('validateAdminCommandEnvelope — strips extra fields (P2)', () => { + const validBase = { + type: 'admin.command', + challengeId: 'cid', + clientId: 'client', + adminProof: 'proof', + }; + test('room.delete strips extras from command', () => { + const r = validateAdminCommandEnvelope({ + ...validBase, + command: { type: 'room.delete', piggyback: 'value', extra: 'smuggled' }, + }); + expect(isValidationError(r)).toBe(false); + if (!isValidationError(r)) { + expect(r.command).toEqual({ type: 'room.delete' }); + expect(Object.keys(r.command)).toEqual(['type']); + } + }); + test('rejects unknown command type', () => { + const r = validateAdminCommandEnvelope({ + ...validBase, + command: { type: 'room.explode' }, + }); + expect(isValidationError(r)).toBe(true); + }); + + test('rejects overlong adminProof', () => { + const r = validateAdminCommandEnvelope({ + ...validBase, + adminProof: 'x'.repeat(129), + command: { type: 'room.delete' }, + }); + expect(isValidationError(r)).toBe(true); + if (isValidationError(r)) expect(r.error).toMatch(/adminProof/); + }); + + test('rejects overlong challengeId', () => { + const r = validateAdminCommandEnvelope({ + ...validBase, + challengeId: 'x'.repeat(65), + command: { type: 'room.delete' }, + }); + expect(isValidationError(r)).toBe(true); + if (isValidationError(r)) expect(r.error).toMatch(/challengeId/); + }); + + test('rejects overlong clientId', () => { + const r = validateAdminCommandEnvelope({ + ...validBase, + clientId: 'x'.repeat(65), + command: { type: 'room.delete' }, + }); + expect(isValidationError(r)).toBe(true); + if (isValidationError(r)) expect(r.error).toMatch(/clientId/); + }); +}); + +describe('validateServerEnvelope — length caps (P3)', () => { + const validBase = { + clientId: 'c123', + opId: 'o123', + channel: 'event' as const, + ciphertext: 'abc', + }; + test('accepts valid envelope', () => { + const r = validateServerEnvelope({ ...validBase }); + expect(isValidationError(r)).toBe(false); + }); + test('rejects opId over 64 chars (replay amplification surface)', () => { + const r = validateServerEnvelope({ ...validBase, opId: 'x'.repeat(65) }); + expect(isValidationError(r)).toBe(true); + if (isValidationError(r)) expect(r.error).toMatch(/opId/); + }); + test('rejects clientId over 64 chars', () => { + const r = validateServerEnvelope({ ...validBase, clientId: 'x'.repeat(65) }); + expect(isValidationError(r)).toBe(true); + if (isValidationError(r)) expect(r.error).toMatch(/clientId/); + }); +}); diff --git a/apps/room-service/core/validation.ts b/apps/room-service/core/validation.ts new file mode 100644 index 00000000..3029bbb7 --- /dev/null +++ b/apps/room-service/core/validation.ts @@ -0,0 +1,232 @@ +/** + * Request body validation — pure functions, no Cloudflare APIs. + * Fully testable with bun:test. + */ + +import type { CreateRoomRequest, ServerEnvelope, AdminCommandEnvelope } from '@plannotator/shared/collab'; + +export interface ValidationError { + error: string; + status: number; +} + +const MIN_EXPIRY_DAYS = 1; +const MAX_EXPIRY_DAYS = 30; +const DEFAULT_EXPIRY_DAYS = 30; +const MAX_SNAPSHOT_CIPHERTEXT_LENGTH = 1_500_000; // ~1.5 MB +const MAX_EVENT_CIPHERTEXT_LENGTH = 512_000; // ~512 KB per event +const MAX_PRESENCE_CIPHERTEXT_LENGTH = 8_192; // ~8 KB per presence update + +/** Clamp expiry days to [1, 30], default 30. */ +export function clampExpiryDays(days: number | undefined): number { + if (days === undefined || days === null) return DEFAULT_EXPIRY_DAYS; + return Math.max(MIN_EXPIRY_DAYS, Math.min(MAX_EXPIRY_DAYS, Math.floor(days))); +} + +/** True when a room is beyond its fixed retention deadline. */ +export function hasRoomExpired(expiresAt: number, now: number = Date.now()): boolean { + return now > expiresAt; +} + +function isNonEmptyString(value: unknown): value is string { + return typeof value === 'string' && value.length > 0; +} + +/** + * Room IDs are generated from 16 random bytes and base64url-encoded without padding. + * That yields 22 URL-safe characters and 128 bits of entropy. + */ +const ROOM_ID_RE = /^[A-Za-z0-9_-]{22}$/; + +/** Runtime check for the roomId shape. Exported for use in WebSocket upgrade + * paths where invalid IDs must be rejected BEFORE idFromName/DO instantiation + * to avoid arbitrary DO names and storage reads on attacker-controlled input. */ +export function isRoomId(s: unknown): s is string { + return typeof s === 'string' && ROOM_ID_RE.test(s); +} + +/** + * HMAC-SHA-256 output is 32 bytes, which base64url-encodes to 43 chars without padding. + * Verifiers must match this exact shape. + */ +const VERIFIER_RE = /^[A-Za-z0-9_-]{43}$/; + +/** Validate a POST /api/rooms request body. */ +export function validateCreateRoomRequest( + body: unknown, +): CreateRoomRequest | ValidationError { + if (!body || typeof body !== 'object') { + return { error: 'Request body must be a JSON object', status: 400 }; + } + + const obj = body as Record; + + if (!isNonEmptyString(obj.roomId)) { + return { error: 'Missing or empty "roomId"', status: 400 }; + } + + if (!ROOM_ID_RE.test(obj.roomId)) { + return { error: '"roomId" must be exactly 22 base64url characters', status: 400 }; + } + + if (!isNonEmptyString(obj.roomVerifier) || !VERIFIER_RE.test(obj.roomVerifier)) { + return { error: '"roomVerifier" must be a 43-char base64url HMAC-SHA-256 verifier', status: 400 }; + } + + if (!isNonEmptyString(obj.adminVerifier) || !VERIFIER_RE.test(obj.adminVerifier)) { + return { error: '"adminVerifier" must be a 43-char base64url HMAC-SHA-256 verifier', status: 400 }; + } + + if (!isNonEmptyString(obj.initialSnapshotCiphertext)) { + return { error: 'Missing or empty "initialSnapshotCiphertext"', status: 400 }; + } + + if (obj.initialSnapshotCiphertext.length > MAX_SNAPSHOT_CIPHERTEXT_LENGTH) { + return { error: `"initialSnapshotCiphertext" exceeds max size (${Math.round(MAX_SNAPSHOT_CIPHERTEXT_LENGTH / 1024)} KB)`, status: 413 }; + } + + return { + roomId: obj.roomId, + roomVerifier: obj.roomVerifier, + adminVerifier: obj.adminVerifier, + initialSnapshotCiphertext: obj.initialSnapshotCiphertext, + expiresInDays: typeof obj.expiresInDays === 'number' ? obj.expiresInDays : undefined, + }; +} + +/** Type guard: is the result a ValidationError? Works with any validated union. */ +export function isValidationError(result: T | ValidationError): result is ValidationError { + return typeof result === 'object' && result !== null && 'error' in result; +} + +// --------------------------------------------------------------------------- +// Post-Auth Message Validation +// --------------------------------------------------------------------------- + +const VALID_CHANNELS = new Set(['event', 'presence']); +const VALID_ADMIN_COMMANDS = new Set(['room.delete']); + +/** + * Max opId length on inbound event-channel envelopes. opId is stored DURABLY + * inside sequenced envelopes, so an authenticated participant could otherwise + * bloat replay bandwidth/storage by sending oversized opIds. generateOpId() + * produces 22-char base64url values (128 bits); 64 gives comfortable headroom + * without enabling amplification. + */ +const MAX_OP_ID_LENGTH = 64; +/** + * Max clientId length. Server overrides envelope.clientId with the + * authenticated meta.clientId before persistence, but we still cap inbound + * values to keep validation symmetric and avoid storing oversized strings + * if the override is ever removed. + */ +const MAX_CLIENT_ID_LENGTH = 64; + +/** + * Max adminProof length. HMAC-SHA-256 base64url-encodes to 43 chars; the + * generous cap guards against pathological input without rejecting any + * legitimate client. Prevents an authenticated peer from spamming + * oversized proof strings to blow up verification cost / log volume. + */ +const MAX_ADMIN_PROOF_LENGTH = 128; + +/** Max challengeId length. generateChallengeId() produces 16-byte base64url + * (22 chars); the cap leaves generous headroom without legitimizing abuse. */ +const MAX_CHALLENGE_ID_LENGTH = 64; + +/** Validate a ServerEnvelope from an authenticated WebSocket message. */ +export function validateServerEnvelope( + msg: Record, +): ServerEnvelope | ValidationError { + if (!isNonEmptyString(msg.clientId)) { + return { error: 'Missing or empty "clientId"', status: 400 }; + } + if (msg.clientId.length > MAX_CLIENT_ID_LENGTH) { + return { error: `"clientId" exceeds max length ${MAX_CLIENT_ID_LENGTH}`, status: 400 }; + } + if (!isNonEmptyString(msg.opId)) { + return { error: 'Missing or empty "opId"', status: 400 }; + } + if (msg.opId.length > MAX_OP_ID_LENGTH) { + return { error: `"opId" exceeds max length ${MAX_OP_ID_LENGTH}`, status: 400 }; + } + if (!isNonEmptyString(msg.channel) || !VALID_CHANNELS.has(msg.channel)) { + return { error: '"channel" must be "event" or "presence"', status: 400 }; + } + if (!isNonEmptyString(msg.ciphertext)) { + return { error: 'Missing or empty "ciphertext"', status: 400 }; + } + + const maxSize = msg.channel === 'presence' + ? MAX_PRESENCE_CIPHERTEXT_LENGTH + : MAX_EVENT_CIPHERTEXT_LENGTH; + if (msg.ciphertext.length > maxSize) { + return { error: `Ciphertext exceeds max size for ${msg.channel} (${Math.round(maxSize / 1024)} KB)`, status: 413 }; + } + + return { + clientId: msg.clientId, + opId: msg.opId, + channel: msg.channel as 'event' | 'presence', + ciphertext: msg.ciphertext, + }; +} + +/** Validate an AdminCommandEnvelope from an authenticated WebSocket message. */ +export function validateAdminCommandEnvelope( + msg: Record, +): AdminCommandEnvelope | ValidationError { + if (!isNonEmptyString(msg.challengeId)) { + return { error: 'Missing or empty "challengeId"', status: 400 }; + } + // Cap string inputs that flow into proof verification and command dispatch. + // Prevents an authenticated peer from spamming oversized identifiers that + // would otherwise hit canonicalJson / log volume on every admin attempt. + if (msg.challengeId.length > MAX_CHALLENGE_ID_LENGTH) { + return { error: `"challengeId" exceeds max length ${MAX_CHALLENGE_ID_LENGTH}`, status: 400 }; + } + if (!isNonEmptyString(msg.clientId)) { + return { error: 'Missing or empty "clientId"', status: 400 }; + } + if (msg.clientId.length > MAX_CLIENT_ID_LENGTH) { + return { error: `"clientId" exceeds max length ${MAX_CLIENT_ID_LENGTH}`, status: 400 }; + } + if (!isNonEmptyString(msg.adminProof)) { + return { error: 'Missing or empty "adminProof"', status: 400 }; + } + if (msg.adminProof.length > MAX_ADMIN_PROOF_LENGTH) { + return { error: `"adminProof" exceeds max length ${MAX_ADMIN_PROOF_LENGTH}`, status: 400 }; + } + + if (!msg.command || typeof msg.command !== 'object') { + return { error: 'Missing or invalid "command"', status: 400 }; + } + + const cmd = msg.command as Record; + if (!isNonEmptyString(cmd.type) || !VALID_ADMIN_COMMANDS.has(cmd.type)) { + return { error: `Unknown command type: ${String(cmd.type)}`, status: 400 }; + } + + // Build a SANITIZED command with exactly the expected fields. Extra fields + // on the inbound payload are dropped. This is defense-in-depth: + // - The admin proof is computed over canonicalJson(command), so if a client + // smuggles extra fields into the payload, their proof is bound to + // `canonicalJson(dirty)` while the server's re-verification will be + // computed over `canonicalJson(sanitized)` — proof verification fails. + // Honest clients serialize clean commands and their proofs verify. + // - Downstream code (logging, storage, proof recomputation) only ever sees + // the narrow shape its type says it does. + // The type gate above (VALID_ADMIN_COMMANDS.has(cmd.type)) already + // restricts cmd.type to the single valid value. If a future admin + // command is added, expand the Set AND split the sanitization below + // into per-type branches at the same time. + const sanitizedCommand: AdminCommandEnvelope['command'] = { type: 'room.delete' }; + + return { + type: 'admin.command', + challengeId: msg.challengeId, + clientId: msg.clientId, + command: sanitizedCommand, + adminProof: msg.adminProof, + }; +} diff --git a/apps/room-service/entry.tsx b/apps/room-service/entry.tsx new file mode 100644 index 00000000..2d954f1d --- /dev/null +++ b/apps/room-service/entry.tsx @@ -0,0 +1,36 @@ +/** + * Browser entry for the live-room Plannotator editor served at + * room.plannotator.ai/c/:roomId. + * + * AppRoot parses the URL via useRoomMode() and picks between + * (local mode — never reached on this origin by design) and + * . The same bundle would + * fall back to local mode on any non-room URL, but Cloudflare's Worker + * only routes /c/:roomId to this HTML, so room mode is the expected + * entry point. + * + * Unlike apps/hook (the local CLI-served binary, built with + * vite-plugin-singlefile so it can be embedded into a one-shot HTTP + * server), this build emits chunked assets. The Worker's `[assets]` + * binding + Cloudflare edge serves them with HTTP/2 multiplexing and + * per-chunk Brotli, so cold-start transfer is smaller than a singlefile + * blob and warm visits rehit cached hashed chunks. + */ + +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import AppRoot from '@plannotator/editor'; +// @ts-expect-error — Vite resolves CSS side-effect imports at build time; +// there is no .d.ts for the index.css file and adding one would not match +// the existing apps/hook pattern. TypeScript doesn't need to analyze it. +import '@plannotator/editor/styles'; + +const root = document.getElementById('root'); +if (!root) { + throw new Error('Plannotator entry: #root element missing from index.html'); +} +createRoot(root).render( + + + , +); diff --git a/apps/room-service/index.html b/apps/room-service/index.html new file mode 100644 index 00000000..759a068d --- /dev/null +++ b/apps/room-service/index.html @@ -0,0 +1,24 @@ + + + + + + Plannotator + + + + + +
+ + + diff --git a/apps/room-service/package.json b/apps/room-service/package.json new file mode 100644 index 00000000..e2785e82 --- /dev/null +++ b/apps/room-service/package.json @@ -0,0 +1,29 @@ +{ + "name": "@plannotator/room-service", + "version": "0.1.0", + "private": true, + "scripts": { + "build:shell": "rm -rf public && vite build", + "dev": "bun run build:shell && wrangler dev", + "deploy": "bun run build:shell && wrangler deploy", + "test": "bun test" + }, + "dependencies": { + "@plannotator/editor": "workspace:*", + "@plannotator/shared": "workspace:*", + "@plannotator/ui": "workspace:*", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20241218.0", + "@tailwindcss/vite": "^4.1.18", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^5.0.0", + "tailwindcss": "^4.1.18", + "typescript": "~5.8.2", + "vite": "^6.2.0", + "wrangler": "^4.80.0" + } +} diff --git a/apps/room-service/scripts/smoke.ts b/apps/room-service/scripts/smoke.ts new file mode 100644 index 00000000..e1569a3b --- /dev/null +++ b/apps/room-service/scripts/smoke.ts @@ -0,0 +1,330 @@ +/** + * Smoke test for room-service against a running wrangler dev instance. + * + * Usage: + * cd apps/room-service && wrangler dev # in one terminal + * bun run scripts/smoke.ts # in another terminal + * + * This acts as an external client: it imports from @plannotator/shared/collab/client + * to simulate browser/agent auth flows. Server runtime code must NOT do this. + * + * Exits 0 on success, non-zero on failure. + */ + +import { + deriveRoomKeys, + deriveAdminKey, + computeRoomVerifier, + computeAdminVerifier, + computeAuthProof, + computeAdminProof, + encryptSnapshot, + encryptPayload, + encryptPresence, + generateRoomId, + generateRoomSecret, + generateAdminSecret, + generateOpId, +} from '@plannotator/shared/collab/client'; + +import type { + CreateRoomRequest, + CreateRoomResponse, + AdminChallenge, + AdminCommand, + RoomSnapshot, + RoomTransportMessage, +} from '@plannotator/shared/collab'; + +const BASE_URL = process.env.SMOKE_BASE_URL || 'http://localhost:8787'; +const WS_BASE = BASE_URL.replace(/^http/, 'ws'); + +let passed = 0; +let failed = 0; + +function assert(condition: boolean, label: string): void { + if (condition) { + passed++; + console.log(` PASS: ${label}`); + } else { + failed++; + console.error(` FAIL: ${label}`); + } +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/** Messages received on the socket — includes transport messages and admin challenges. */ +type SmokeMessage = RoomTransportMessage | AdminChallenge; + +interface AuthedSocket { + ws: WebSocket; + clientId: string; + messages: SmokeMessage[]; + closed: boolean; +} + +/** Connect, authenticate, and return a ready socket that collects messages. */ +async function connectAndAuth( + roomId: string, + roomVerifier: string, + lastSeq?: number, +): Promise { + return new Promise((resolve, reject) => { + const ws = new WebSocket(`${WS_BASE}/ws/${roomId}`); + // clientId is now assigned by the server in the auth.challenge message; + // we adopt it here instead of self-generating (see PresenceImpersonation + // fix). Placeholder until challenge arrives. + let clientId = ''; + const result: AuthedSocket = { ws, clientId: '', messages: [], closed: false }; + let authed = false; + + const timeout = setTimeout(() => { + if (!authed) { ws.close(); reject(new Error('Auth timeout')); } + }, 10_000); + + ws.onmessage = async (event) => { + const msg = JSON.parse(String(event.data)); + + if (!authed && msg.type === 'auth.challenge') { + clientId = msg.clientId; + result.clientId = clientId; + const proof = await computeAuthProof(roomVerifier, roomId, clientId, msg.challengeId, msg.nonce); + ws.send(JSON.stringify({ type: 'auth.response', challengeId: msg.challengeId, clientId, proof, lastSeq })); + return; + } + + if (!authed && msg.type === 'auth.accepted') { + authed = true; + clearTimeout(timeout); + // Collect subsequent messages + ws.onmessage = (e) => { + result.messages.push(JSON.parse(String(e.data))); + }; + resolve(result); + return; + } + }; + + ws.onclose = () => { result.closed = true; }; + ws.onerror = () => { if (!authed) reject(new Error('WebSocket error')); }; + }); +} + +/** Wait briefly for messages to arrive. */ +function wait(ms: number): Promise { + return new Promise(r => setTimeout(r, ms)); +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +async function run(): Promise { + console.log(`\nSmoke testing room-service at ${BASE_URL}\n`); + + // ----------------------------------------------------------------------- + // 1. Health check + // ----------------------------------------------------------------------- + console.log('1. Health check'); + const healthRes = await fetch(`${BASE_URL}/health`); + assert(healthRes.ok, 'GET /health returns 200'); + + // ----------------------------------------------------------------------- + // 2. Create a room + // ----------------------------------------------------------------------- + console.log('\n2. Room creation'); + const roomId = generateRoomId(); + const roomSecret = generateRoomSecret(); + const adminSecret = generateAdminSecret(); + + const { authKey, eventKey, presenceKey } = await deriveRoomKeys(roomSecret); + const adminKey = await deriveAdminKey(adminSecret); + + const roomVerifier = await computeRoomVerifier(authKey, roomId); + const adminVerifier = await computeAdminVerifier(adminKey, roomId); + + const snapshot: RoomSnapshot = { versionId: 'v1', planMarkdown: '# Smoke Test', annotations: [] }; + const snapshotCiphertext = await encryptSnapshot(eventKey, snapshot); + + const createBody: CreateRoomRequest = { + roomId, + roomVerifier, + adminVerifier, + initialSnapshotCiphertext: snapshotCiphertext, + }; + + const createRes = await fetch(`${BASE_URL}/api/rooms`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(createBody), + }); + assert(createRes.status === 201, 'POST /api/rooms returns 201'); + + const createResponseBody = await createRes.json() as CreateRoomResponse; + assert(!createResponseBody.joinUrl.includes('#'), 'joinUrl has no fragment'); + + // ----------------------------------------------------------------------- + // 3. Duplicate room creation → 409 + // ----------------------------------------------------------------------- + console.log('\n3. Duplicate room creation'); + const dupRes = await fetch(`${BASE_URL}/api/rooms`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(createBody), + }); + assert(dupRes.status === 409, 'Duplicate returns 409'); + + // ----------------------------------------------------------------------- + // 4. Fresh join receives snapshot + // ----------------------------------------------------------------------- + console.log('\n4. Fresh join receives snapshot'); + const client1 = await connectAndAuth(roomId, roomVerifier); + await wait(200); + const snapshots1 = client1.messages.filter(m => m.type === 'room.snapshot'); + assert(snapshots1.length === 1, 'Client1 received room.snapshot on join'); + + // ----------------------------------------------------------------------- + // 5. Two clients — event echo + broadcast + // ----------------------------------------------------------------------- + console.log('\n5. Event sequencing + echo'); + const client2 = await connectAndAuth(roomId, roomVerifier); + await wait(200); + // Clear join messages + client1.messages.length = 0; + client2.messages.length = 0; + + // Client1 sends an event. Use a real annotation — empty annotation.add is + // rejected by conforming clients (no-op would burn a durable seq). + const realAnnotation = { + id: 'smoke-ann-1', + blockId: 'block-1', + startOffset: 0, + endOffset: 5, + type: 'COMMENT' as const, + originalText: 'hello', + createdA: Date.now(), + text: 'smoke test annotation', + }; + const eventCiphertext = await encryptPayload(eventKey, JSON.stringify({ type: 'annotation.add', annotations: [realAnnotation] })); + client1.ws.send(JSON.stringify({ + clientId: client1.clientId, + opId: generateOpId(), + channel: 'event', + ciphertext: eventCiphertext, + })); + await wait(500); + + const client1Events = client1.messages.filter(m => m.type === 'room.event'); + const client2Events = client2.messages.filter(m => m.type === 'room.event'); + assert(client1Events.length === 1, 'Sender receives echo (room.event)'); + assert(client2Events.length === 1, 'Other client receives room.event'); + + // ----------------------------------------------------------------------- + // 6. Presence relay — others only + // ----------------------------------------------------------------------- + console.log('\n6. Presence relay'); + client1.messages.length = 0; + client2.messages.length = 0; + + // Presence MUST be encrypted with presenceKey (not eventKey) and carry a + // valid PresenceState shape — conforming clients reject malformed presence. + const validPresence = { + user: { id: 'smoke-u1', name: 'smoke', color: '#f00' }, + cursor: null, + }; + const presenceCiphertext = await encryptPresence(presenceKey, validPresence); + client1.ws.send(JSON.stringify({ + clientId: client1.clientId, + opId: generateOpId(), + channel: 'presence', + ciphertext: presenceCiphertext, + })); + await wait(300); + + const client1Presence = client1.messages.filter(m => m.type === 'room.presence'); + const client2Presence = client2.messages.filter(m => m.type === 'room.presence'); + assert(client1Presence.length === 0, 'Sender does NOT receive own presence'); + assert(client2Presence.length === 1, 'Other client receives room.presence'); + + // ----------------------------------------------------------------------- + // 7. Reconnect replay + // ----------------------------------------------------------------------- + console.log('\n7. Reconnect replay'); + client2.ws.close(); + await wait(200); + + // Client1 sends another event while client2 is disconnected + client1.ws.send(JSON.stringify({ + clientId: client1.clientId, + opId: generateOpId(), + channel: 'event', + ciphertext: eventCiphertext, + })); + await wait(300); + + // Client2 reconnects with lastSeq from the first event (seq 1) + const client2b = await connectAndAuth(roomId, roomVerifier, 1); + await wait(500); + + const replayedEvents = client2b.messages.filter(m => m.type === 'room.event'); + assert(replayedEvents.length === 1, 'Reconnect replayed 1 missed event (seq 2)'); + if (replayedEvents.length > 0 && replayedEvents[0].type === 'room.event') { + assert(replayedEvents[0].seq === 2, 'Replayed event has seq 2'); + } + + // ----------------------------------------------------------------------- + // 8. Admin delete + // ----------------------------------------------------------------------- + console.log('\n8. Admin delete'); + client1.messages.length = 0; + + client1.ws.send(JSON.stringify({ type: 'admin.challenge.request' })); + await wait(300); + const deleteChallenge = client1.messages.find(m => m.type === 'admin.challenge') as AdminChallenge | undefined; + + if (deleteChallenge) { + const deleteCmd: AdminCommand = { type: 'room.delete' }; + const deleteProof = await computeAdminProof( + adminVerifier, roomId, client1.clientId, + deleteChallenge.challengeId, deleteChallenge.nonce, deleteCmd, + ); + client1.ws.send(JSON.stringify({ + type: 'admin.command', + challengeId: deleteChallenge.challengeId, + clientId: client1.clientId, + command: deleteCmd, + adminProof: deleteProof, + })); + await wait(500); + + assert(client1.closed, 'Client1 socket closed after delete'); + assert(client2b.closed, 'Client2 socket closed after delete'); + } + + // ----------------------------------------------------------------------- + // 9. Deleted room rejects new joins + // ----------------------------------------------------------------------- + console.log('\n9. Deleted room rejects new joins'); + try { + const client3 = await connectAndAuth(roomId, roomVerifier); + client3.ws.close(); + assert(false, 'Should not authenticate to deleted room'); + } catch { + assert(true, 'Deleted room rejects new WebSocket join'); + } + + // ----------------------------------------------------------------------- + // Summary + // ----------------------------------------------------------------------- + console.log(`\n${'='.repeat(40)}`); + console.log(`Passed: ${passed}, Failed: ${failed}`); + process.exit(failed > 0 ? 1 : 0); +} + +run().catch((err) => { + console.error('Smoke test failed:', err); + process.exit(1); +}); diff --git a/apps/room-service/static/favicon.svg b/apps/room-service/static/favicon.svg new file mode 100644 index 00000000..070e83e2 --- /dev/null +++ b/apps/room-service/static/favicon.svg @@ -0,0 +1,5 @@ + + + + P + diff --git a/apps/room-service/targets/cloudflare.ts b/apps/room-service/targets/cloudflare.ts new file mode 100644 index 00000000..2d01efeb --- /dev/null +++ b/apps/room-service/targets/cloudflare.ts @@ -0,0 +1,22 @@ +/** + * Cloudflare Worker entrypoint for room.plannotator.ai. + * + * Routes HTTP requests and WebSocket upgrades to the handler. + * Re-exports the Durable Object class for wrangler discovery. + */ + +import { handleRequest } from '../core/handler'; +import { corsHeaders, getAllowedOrigins } from '../core/cors'; +import type { Env } from '../core/types'; + +export default { + async fetch(request: Request, env: Env): Promise { + const origin = request.headers.get('Origin') ?? ''; + const allowed = getAllowedOrigins(env.ALLOWED_ORIGINS); + const allowLocalhost = env.ALLOW_LOCALHOST_ORIGINS === 'true'; + const cors = corsHeaders(origin, allowed, allowLocalhost); + return handleRequest(request, env, cors); + }, +}; + +export { RoomDurableObject } from '../core/room-do'; diff --git a/apps/room-service/tsconfig.browser.json b/apps/room-service/tsconfig.browser.json new file mode 100644 index 00000000..722f5e0f --- /dev/null +++ b/apps/room-service/tsconfig.browser.json @@ -0,0 +1,23 @@ +{ + "//": "Aspirational typecheck for the browser entry. NOT currently a green CI gate — TypeScript follows the AppRoot import into packages/editor + packages/ui, which surface pre-existing implicit-any, missing @types/react, CSS module, and __APP_VERSION__ errors from the broader codebase. Those are shared-package tech-debt items, not room-service issues. Run via: bunx tsc --noEmit -p apps/room-service/tsconfig.browser.json. Entry.tsx itself is clean; the transitive errors are inherited. Once the shared packages carry their own tsconfig with strict: false or the missing declarations are added, this config becomes a usable gate.", + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "isolatedModules": true, + "paths": { + "@plannotator/ui/*": ["../../packages/ui/*"], + "@plannotator/editor": ["../../packages/editor/AppRoot.tsx"], + "@plannotator/editor/App": ["../../packages/editor/App.tsx"], + "@plannotator/editor/*": ["../../packages/editor/*"], + "@plannotator/editor/styles": ["../../packages/editor/index.css"] + } + }, + "include": ["entry.tsx", "vite.config.ts"] +} diff --git a/apps/room-service/tsconfig.json b/apps/room-service/tsconfig.json new file mode 100644 index 00000000..28fc62cc --- /dev/null +++ b/apps/room-service/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "isolatedModules": true, + "types": ["@cloudflare/workers-types"] + }, + "//exclude": "entry.tsx and vite.config.ts are browser-only and typechecked separately via tsconfig.browser.json (with DOM + React libs). This Worker config stays Cloudflare-scoped.", + "exclude": ["**/*.test.ts", "scripts/**", "entry.tsx", "vite.config.ts"] +} diff --git a/apps/room-service/vite.config.ts b/apps/room-service/vite.config.ts new file mode 100644 index 00000000..4ec81fe3 --- /dev/null +++ b/apps/room-service/vite.config.ts @@ -0,0 +1,67 @@ +/** + * Vite config for the Cloudflare-served live-room editor. + * + * Opposite constraints from apps/hook: + * - apps/hook: single-file HTML, embedded into a Bun binary that + * streams it over a one-shot localhost HTTP server. Uses + * vite-plugin-singlefile, inlineDynamicImports, and + * assetsInlineLimit=∞ to produce a standalone blob. + * - apps/room-service: served by Cloudflare's [assets] binding. + * Emits normal chunked output (hashed assets/*.js, *.css). + * Wrangler + Cloudflare edge do HTTP/2 multiplexing, Brotli, + * per-chunk edge caching, and immutable Cache-Control on hashed + * assets. Single-file would defeat all of that. + * + * Aliases mirror apps/hook: @plannotator/editor → AppRoot.tsx so the + * default import is room-mode-aware; @plannotator/editor/App remains + * available for callers that explicitly want the local shell. + */ + +import path from 'path'; +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import tailwindcss from '@tailwindcss/vite'; +import pkg from '../../package.json'; + +export default defineConfig({ + base: '/', + define: { + __APP_VERSION__: JSON.stringify(pkg.version), + }, + plugins: [react(), tailwindcss()], + resolve: { + alias: { + '@plannotator/ui': path.resolve(__dirname, '../../packages/ui'), + '@plannotator/editor/styles': path.resolve(__dirname, '../../packages/editor/index.css'), + '@plannotator/editor/App': path.resolve(__dirname, '../../packages/editor/App.tsx'), + '@plannotator/editor': path.resolve(__dirname, '../../packages/editor/AppRoot.tsx'), + }, + }, + // Static assets (favicon.svg) are copied verbatim from ./static/ into + // the build output root. Vite's default publicDir is 'public' but our + // outDir is also 'public' — using a separate 'static' avoids the + // "publicDir and outDir overlap" warning. + publicDir: 'static', + build: { + outDir: 'public', + emptyOutDir: true, + target: 'esnext', + // No singlefile, no inlineDynamicImports, no bloated + // assetsInlineLimit — default Vite chunk shape is what Cloudflare + // wants. Hashed filenames in assets/ allow indefinite caching with + // the handler's immutable Cache-Control header. + rollupOptions: { + output: { + // Keep the Vite default naming: [name]-[hash].js under assets/, + // which the handler's /assets/* passthrough serves verbatim. + }, + }, + }, + server: { + // Not used for deploy — just for local `vite` dev if someone wants + // to iterate on the room UI without Wrangler. The Worker still + // serves the compiled output. + port: 3002, + host: '0.0.0.0', + }, +}); diff --git a/apps/room-service/wrangler.toml b/apps/room-service/wrangler.toml new file mode 100644 index 00000000..5ddc0c0a --- /dev/null +++ b/apps/room-service/wrangler.toml @@ -0,0 +1,30 @@ +name = "plannotator-room" +main = "targets/cloudflare.ts" +compatibility_date = "2024-12-01" + +# Chunked Vite build output lives in ./public (index.html + hashed +# assets/*.js, *.css). Produced by `bun run build:shell` from +# apps/room-service/vite.config.ts. The directory is gitignored — +# always generated, never checked in. The Worker's handler rewrites +# /c/:roomId → /index.html and /assets/* → passthrough with long-lived +# immutable Cache-Control on hashed chunks; see core/handler.ts. +[assets] +directory = "./public" +binding = "ASSETS" + +[[durable_objects.bindings]] +name = "ROOM" +class_name = "RoomDurableObject" + +[[migrations]] +tag = "v1" +new_sqlite_classes = ["RoomDurableObject"] + +[[routes]] +pattern = "room.plannotator.ai" +custom_domain = true + +[vars] +ALLOWED_ORIGINS = "https://room.plannotator.ai" +ALLOW_LOCALHOST_ORIGINS = "true" +BASE_URL = "https://room.plannotator.ai" diff --git a/bun.lock b/bun.lock index 2ff0a627..523b4600 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,6 @@ { "lockfileVersion": 1, - "configVersion": 0, + "configVersion": 1, "workspaces": { "": { "name": "plannotator", @@ -19,6 +19,19 @@ "@types/node": "^25.5.2", "@types/turndown": "^5.0.6", "bun-types": "^1.3.11", + "wrangler": "^4.81.1", + }, + }, + "apps/collab-agent": { + "name": "@plannotator/collab-agent", + "version": "0.1.0", + "dependencies": { + "@plannotator/shared": "workspace:*", + "@plannotator/ui": "workspace:*", + }, + "devDependencies": { + "bun-types": "^1.3.11", + "typescript": "~5.8.2", }, }, "apps/hook": { @@ -64,7 +77,7 @@ }, "apps/opencode-plugin": { "name": "@plannotator/opencode", - "version": "0.18.0", + "version": "0.19.0", "dependencies": { "@opencode-ai/plugin": "^1.1.10", }, @@ -86,7 +99,7 @@ }, "apps/pi-extension": { "name": "@plannotator/pi-extension", - "version": "0.18.0", + "version": "0.19.0", "dependencies": { "@joplin/turndown-plugin-gfm": "^1.0.64", "turndown": "^7.2.4", @@ -141,6 +154,28 @@ "vite-plugin-singlefile": "^2.0.3", }, }, + "apps/room-service": { + "name": "@plannotator/room-service", + "version": "0.1.0", + "dependencies": { + "@plannotator/editor": "workspace:*", + "@plannotator/shared": "workspace:*", + "@plannotator/ui": "workspace:*", + "react": "^19.2.3", + "react-dom": "^19.2.3", + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20241218.0", + "@tailwindcss/vite": "^4.1.18", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^5.0.0", + "tailwindcss": "^4.1.18", + "typescript": "~5.8.2", + "vite": "^6.2.0", + "wrangler": "^4.80.0", + }, + }, "apps/vscode-extension": { "name": "plannotator-webview", "version": "0.16.5", @@ -166,6 +201,12 @@ "react-dom": "^19.2.3", "tailwindcss": "^4.1.18", }, + "devDependencies": { + "@happy-dom/global-registrator": "^20.8.9", + "@testing-library/react": "^16.3.2", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + }, }, "packages/review-editor": { "name": "@plannotator/review-editor", @@ -186,7 +227,7 @@ }, "packages/server": { "name": "@plannotator/server", - "version": "0.18.0", + "version": "0.19.0", "dependencies": { "@plannotator/ai": "workspace:*", "@plannotator/shared": "workspace:*", @@ -225,10 +266,13 @@ "unique-username-generator": "^1.5.1", }, "devDependencies": { + "@happy-dom/global-registrator": "^20.8.9", + "@testing-library/react": "^16.3.2", "@types/bun": "^1.2.0", "@types/dompurify": "^3.0.5", "@types/react": "^19.2.0", "@types/react-dom": "^19.2.0", + "happy-dom": "^20.8.9", "typescript": "~5.8.2", }, }, @@ -236,25 +280,25 @@ "packages": { "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], - "@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.92", "", { "dependencies": { "@anthropic-ai/sdk": "^0.80.0", "@modelcontextprotocol/sdk": "^1.27.1" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.34.2", "@img/sharp-darwin-x64": "^0.34.2", "@img/sharp-linux-arm": "^0.34.2", "@img/sharp-linux-arm64": "^0.34.2", "@img/sharp-linux-x64": "^0.34.2", "@img/sharp-linuxmusl-arm64": "^0.34.2", "@img/sharp-linuxmusl-x64": "^0.34.2", "@img/sharp-win32-arm64": "^0.34.2", "@img/sharp-win32-x64": "^0.34.2" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-loYyxVUC5gBwHjGi9Fv0b84mduJTp9Z3Pum+y/7IVQDb4NynKfVQl6l4VeDKZaW+1QTQtd25tY4hwUznD7Krqw=="], + "@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.110", "", { "dependencies": { "@anthropic-ai/sdk": "^0.81.0", "@modelcontextprotocol/sdk": "^1.29.0" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.34.2", "@img/sharp-darwin-x64": "^0.34.2", "@img/sharp-linux-arm": "^0.34.2", "@img/sharp-linux-arm64": "^0.34.2", "@img/sharp-linux-x64": "^0.34.2", "@img/sharp-linuxmusl-arm64": "^0.34.2", "@img/sharp-linuxmusl-x64": "^0.34.2", "@img/sharp-win32-arm64": "^0.34.2", "@img/sharp-win32-x64": "^0.34.2" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-pS7QlPcJwQU8a87F8qChKlmnjddt3smUi6X7WvSluD0kzt72jphCe30QmKKXPJnjW3SVHu12cu6SLzfYQrWwHg=="], - "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.80.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-WeXLn7zNVk3yjeshn+xZHvld6AoFUOR3Sep6pSoHho5YbSi6HwcirqgPA5ccFuW8QTVJAAU7N8uQQC6Wa9TG+g=="], + "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.81.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-D4K5PvEV6wPiRtVlVsJHIUhHAmOZ6IT/I9rKlTf84gR7GyyAurPJK7z9BOf/AZqC5d1DhYQGJNKRmV+q8dGhgw=="], "@astrojs/compiler": ["@astrojs/compiler@2.13.1", "", {}, "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg=="], - "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.5", "", {}, "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA=="], + "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.6", "", {}, "sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q=="], - "@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.10", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.5", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.19.0", "smol-toml": "^1.5.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A=="], + "@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.11", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.6", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ=="], - "@astrojs/mdx": ["@astrojs/mdx@4.3.13", "", { "dependencies": { "@astrojs/markdown-remark": "6.3.10", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.15.0", "es-module-lexer": "^1.7.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "piccolore": "^0.1.3", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-IHDHVKz0JfKBy3//52JSiyWv089b7GVSChIXLrlUOoTLWowG3wr2/8hkaEgEyd/vysvNQvGk+QhysXpJW5ve6Q=="], + "@astrojs/mdx": ["@astrojs/mdx@4.3.14", "", { "dependencies": { "@astrojs/markdown-remark": "6.3.11", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.15.0", "es-module-lexer": "^1.7.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "piccolore": "^0.1.3", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-FBrqJQORVm+rkRa2TS5CjU9PBA6hkhrwLVBSS9A77gN2+iehvjq1w6yya/d0YKC7osiVorKkr3Qd9wNbl0ZkGA=="], "@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="], "@astrojs/react": ["@astrojs/react@4.4.2", "", { "dependencies": { "@vitejs/plugin-react": "^4.7.0", "ultrahtml": "^1.6.0", "vite": "^6.4.1" }, "peerDependencies": { "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" } }, "sha512-1tl95bpGfuaDMDn8O3x/5Dxii1HPvzjvpL2YTuqOOrQehs60I2DKiDgh1jrKc7G8lv+LQT5H15V6QONQ+9waeQ=="], - "@astrojs/rss": ["@astrojs/rss@4.0.15", "", { "dependencies": { "fast-xml-parser": "^5.3.3", "piccolore": "^0.1.3" } }, "sha512-uXO/k6AhRkIDXmRoc6xQpoPZrimQNUmS43X4+60yunfuMNHtSRN5e/FiSi7NApcZqmugSMc5+cJi8ovqgO+qIg=="], + "@astrojs/rss": ["@astrojs/rss@4.0.18", "", { "dependencies": { "fast-xml-parser": "^5.5.7", "piccolore": "^0.1.3", "zod": "^4.3.6" } }, "sha512-wc5DwKlbTEdgVAWnHy8krFTeQ42t1v/DJqeq5HtulYK3FYHE4krtRGjoyhS3eXXgfdV6Raoz2RU3wrMTFAitRg=="], - "@astrojs/sitemap": ["@astrojs/sitemap@3.7.0", "", { "dependencies": { "sitemap": "^8.0.2", "stream-replace-string": "^2.0.0", "zod": "^3.25.76" } }, "sha512-+qxjUrz6Jcgh+D5VE1gKUJTA3pSthuPHe6Ao5JCxok794Lewx8hBFaWHtOnN0ntb2lfOf7gvOi9TefUswQ/ZVA=="], + "@astrojs/sitemap": ["@astrojs/sitemap@3.7.2", "", { "dependencies": { "sitemap": "^9.0.0", "stream-replace-string": "^2.0.0", "zod": "^4.3.6" } }, "sha512-PqkzkcZTb5ICiyIR8VoKbIAP/laNRXi5tw616N1Ckk+40oNB8Can1AzVV56lrbC5GKSZFCyJYUVYqVivMisvpA=="], "@astrojs/telemetry": ["@astrojs/telemetry@3.3.0", "", { "dependencies": { "ci-info": "^4.2.0", "debug": "^4.4.0", "dlv": "^1.1.3", "dset": "^3.1.4", "is-docker": "^3.0.0", "is-wsl": "^3.1.0", "which-pm-runs": "^1.1.0" } }, "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ=="], @@ -268,63 +312,61 @@ "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], - "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.995.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-node": "^3.972.10", "@aws-sdk/eventstream-handler-node": "^3.972.5", "@aws-sdk/middleware-eventstream": "^3.972.3", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/middleware-websocket": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/token-providers": "3.995.0", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.995.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.10", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-nI7tT11L9s34AKr95GHmxs6k2+3ie+rEOew2cXOwsMC9k/5aifrZwh0JjAkBop4FqbmS8n0ZjCKDjBZFY/0YxQ=="], - - "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-VLUN+wIeNX24fg12SCbzTUBnBENlL014yMKZvRhPkcn4wHR6LKgNrjsG3fZ03Xs0XoKaGtNFi1VVrq666sGBoQ=="], + "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1030.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.27", "@aws-sdk/credential-provider-node": "^3.972.30", "@aws-sdk/eventstream-handler-node": "^3.972.13", "@aws-sdk/middleware-eventstream": "^3.972.9", "@aws-sdk/middleware-host-header": "^3.972.9", "@aws-sdk/middleware-logger": "^3.972.9", "@aws-sdk/middleware-recursion-detection": "^3.972.10", "@aws-sdk/middleware-user-agent": "^3.972.29", "@aws-sdk/middleware-websocket": "^3.972.15", "@aws-sdk/region-config-resolver": "^3.972.11", "@aws-sdk/token-providers": "3.1030.0", "@aws-sdk/types": "^3.973.7", "@aws-sdk/util-endpoints": "^3.996.6", "@aws-sdk/util-user-agent-browser": "^3.972.9", "@aws-sdk/util-user-agent-node": "^3.973.15", "@smithy/config-resolver": "^4.4.14", "@smithy/core": "^3.23.14", "@smithy/eventstream-serde-browser": "^4.2.13", "@smithy/eventstream-serde-config-resolver": "^4.3.13", "@smithy/eventstream-serde-node": "^4.2.13", "@smithy/fetch-http-handler": "^5.3.16", "@smithy/hash-node": "^4.2.13", "@smithy/invalid-dependency": "^4.2.13", "@smithy/middleware-content-length": "^4.2.13", "@smithy/middleware-endpoint": "^4.4.29", "@smithy/middleware-retry": "^4.5.0", "@smithy/middleware-serde": "^4.2.17", "@smithy/middleware-stack": "^4.2.13", "@smithy/node-config-provider": "^4.3.13", "@smithy/node-http-handler": "^4.5.2", "@smithy/protocol-http": "^5.3.13", "@smithy/smithy-client": "^4.12.9", "@smithy/types": "^4.14.0", "@smithy/url-parser": "^4.2.13", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.45", "@smithy/util-defaults-mode-node": "^4.2.49", "@smithy/util-endpoints": "^3.3.4", "@smithy/util-middleware": "^4.2.13", "@smithy/util-retry": "^4.3.0", "@smithy/util-stream": "^4.5.22", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-5Lnyx6mQPsIdld5Xr9FJqu8Hi9RVY6SgE8Rysmn4r3lRY2vNohNEu+gCtdXRDkkv/PgK9OnbA0sUPFU9rBRMYA=="], - "@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="], + "@aws-sdk/core": ["@aws-sdk/core@3.973.27", "", { "dependencies": { "@aws-sdk/types": "^3.973.7", "@aws-sdk/xml-builder": "^3.972.17", "@smithy/core": "^3.23.14", "@smithy/node-config-provider": "^4.3.13", "@smithy/property-provider": "^4.2.13", "@smithy/protocol-http": "^5.3.13", "@smithy/signature-v4": "^5.3.13", "@smithy/smithy-client": "^4.12.9", "@smithy/types": "^4.14.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.13", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A=="], - "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ZptrOwQynfupubvcngLkbdIq/aXvl/czdpEG8XJ8mN8Nb19BR0jaK0bR+tfuMU36Ez9q4xv7GGkHFqEEP2hUUQ=="], + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.25", "", { "dependencies": { "@aws-sdk/core": "^3.973.27", "@aws-sdk/types": "^3.973.7", "@smithy/property-provider": "^4.2.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-6QfI0wv4jpG5CrdO/AO0JfZ2ux+tKwJPrUwmvxXF50vI5KIypKVGNF6b4vlkYEnKumDTI1NX2zUBi8JoU5QU3A=="], - "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.11", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.10", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" } }, "sha512-hECWoOoH386bGr89NQc9vA/abkGf5TJrMREt+lhNcnSNmoBS04fK7vc3LrJBSQAUGGVj0Tz3f4dHB3w5veovig=="], + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.27", "", { "dependencies": { "@aws-sdk/core": "^3.973.27", "@aws-sdk/types": "^3.973.7", "@smithy/fetch-http-handler": "^5.3.16", "@smithy/node-http-handler": "^4.5.2", "@smithy/property-provider": "^4.2.13", "@smithy/protocol-http": "^5.3.13", "@smithy/smithy-client": "^4.12.9", "@smithy/types": "^4.14.0", "@smithy/util-stream": "^4.5.22", "tslib": "^2.6.2" } }, "sha512-3V3Usj9Gs93h865DqN4M2NWJhC5kXU9BvZskfN3+69omuYlE3TZxOEcVQtBGLOloJB7BVfJKXVLqeNhOzHqSlQ=="], - "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-login": "^3.972.9", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-zr1csEu9n4eDiHMTYJabX1mDGuGLgjgUnNckIivvk43DocJC9/f6DefFrnUPZXE+GHtbW50YuXb+JIxKykU74A=="], + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.29", "", { "dependencies": { "@aws-sdk/core": "^3.973.27", "@aws-sdk/credential-provider-env": "^3.972.25", "@aws-sdk/credential-provider-http": "^3.972.27", "@aws-sdk/credential-provider-login": "^3.972.29", "@aws-sdk/credential-provider-process": "^3.972.25", "@aws-sdk/credential-provider-sso": "^3.972.29", "@aws-sdk/credential-provider-web-identity": "^3.972.29", "@aws-sdk/nested-clients": "^3.996.19", "@aws-sdk/types": "^3.973.7", "@smithy/credential-provider-imds": "^4.2.13", "@smithy/property-provider": "^4.2.13", "@smithy/shared-ini-file-loader": "^4.4.8", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-SiBuAnXecCbT/OpAf3vqyI/AVE3mTaYr9ShXLybxZiPLBiPCCOIWSGAtYYGQWMRvobBTiqOewaB+wcgMMZI2Aw=="], - "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-m4RIpVgZChv0vWS/HKChg1xLgZPpx8Z+ly9Fv7FwA8SOfuC6I3htcSaBz2Ch4bneRIiBUhwP4ziUo0UZgtJStQ=="], + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.29", "", { "dependencies": { "@aws-sdk/core": "^3.973.27", "@aws-sdk/nested-clients": "^3.996.19", "@aws-sdk/types": "^3.973.7", "@smithy/property-provider": "^4.2.13", "@smithy/protocol-http": "^5.3.13", "@smithy/shared-ini-file-loader": "^4.4.8", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-OGOslTbOlxXexKMqhxCEbBQbUIfuhGxU5UXw3Fm56ypXHvrXH4aTt/xb5Y884LOoteP1QST1lVZzHfcTnWhiPQ=="], - "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.10", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-ini": "^3.972.9", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-70nCESlvnzjo4LjJ8By8MYIiBogkYPSXl3WmMZfH9RZcB/Nt9qVWbFpYj6Fk1vLa4Vk8qagFVeXgxdieMxG1QA=="], + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.30", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.25", "@aws-sdk/credential-provider-http": "^3.972.27", "@aws-sdk/credential-provider-ini": "^3.972.29", "@aws-sdk/credential-provider-process": "^3.972.25", "@aws-sdk/credential-provider-sso": "^3.972.29", "@aws-sdk/credential-provider-web-identity": "^3.972.29", "@aws-sdk/types": "^3.973.7", "@smithy/credential-provider-imds": "^4.2.13", "@smithy/property-provider": "^4.2.13", "@smithy/shared-ini-file-loader": "^4.4.8", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw=="], - "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-gOWl0Fe2gETj5Bk151+LYKpeGi2lBDLNu+NMNpHRlIrKHdBmVun8/AalwMK8ci4uRfG5a3/+zvZBMpuen1SZ0A=="], + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.25", "", { "dependencies": { "@aws-sdk/core": "^3.973.27", "@aws-sdk/types": "^3.973.7", "@smithy/property-provider": "^4.2.13", "@smithy/shared-ini-file-loader": "^4.4.8", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-HR7ynNRdNhNsdVCOCegy1HsfsRzozCOPtD3RzzT1JouuaHobWyRfJzCBue/3jP7gECHt+kQyZUvwg/cYLWurNQ=="], - "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.9", "", { "dependencies": { "@aws-sdk/client-sso": "3.993.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/token-providers": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ey7S686foGTArvFhi3ifQXmgptKYvLSGE2250BAQceMSXZddz7sUSNERGJT2S7u5KIe/kgugxrt01hntXVln6w=="], + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.29", "", { "dependencies": { "@aws-sdk/core": "^3.973.27", "@aws-sdk/nested-clients": "^3.996.19", "@aws-sdk/token-providers": "3.1026.0", "@aws-sdk/types": "^3.973.7", "@smithy/property-provider": "^4.2.13", "@smithy/shared-ini-file-loader": "^4.4.8", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-HWv4SEq3jZDYPlwryZVef97+U8CxxRos5mK8sgGO1dQaFZpV5giZLzqGE5hkDmh2csYcBO2uf5XHjPTpZcJlig=="], - "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8LnfS76nHXoEc9aRRiMMpxZxJeDG0yusdyo3NvPhCgESmBUgpMa4luhGbClW5NoX/qRcGxxM6Z/esqANSNMTow=="], + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.29", "", { "dependencies": { "@aws-sdk/core": "^3.973.27", "@aws-sdk/nested-clients": "^3.996.19", "@aws-sdk/types": "^3.973.7", "@smithy/property-provider": "^4.2.13", "@smithy/shared-ini-file-loader": "^4.4.8", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-PdMBza1WEKEUPFEmMGCfnU2RYCz9MskU2e8JxjyUOsMKku7j9YaDKvbDi2dzC0ihFoM6ods2SbhfAAro+Gwlew=="], - "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/eventstream-codec": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-xEmd3dnyn83K6t4AJxBJA63wpEoCD45ERFG0XMTViD2E/Ohls9TLxjOWPb1PAxR9/46cKy/TImez1GoqP6xVNQ=="], + "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.13", "", { "dependencies": { "@aws-sdk/types": "^3.973.7", "@smithy/eventstream-codec": "^4.2.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-2Pi1kD0MDkMAxDHqvpi/hKMs9hXUYbj2GLEjCwy+0jzfLChAsF50SUYnOeTI+RztA+Ic4pnLAdB03f1e8nggxQ=="], - "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-pbvZ6Ye/Ks6BAZPa3RhsNjHrvxU9li25PMhSdDpbX0jzdpKpAkIR65gXSNKmA/REnSdEMWSD4vKUW+5eMFzB6w=="], + "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.7", "@smithy/protocol-http": "^5.3.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-ypgOvpWxQTCnQyDHGxnTviqqANE7FIIzII7VczJnTPCJcJlu17hMQXnvE47aKSKsawVJAaaRsyOEbHQuLJF9ng=="], - "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA=="], + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.7", "@smithy/protocol-http": "^5.3.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-je5vRdNw4SkuTnmRbFZLdye4sQ0faLt8kwka5wnnSU30q1mHO4X+idGEJOOE+Tn1ME7Oryn05xxkDvIb3UaLaQ=="], - "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA=="], + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.7", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-HsVgDrruhqI28RkaXALm8grJ7Agc1wF6Et0xh6pom8NdO2VdO/SD9U/tPwUjewwK/pVoka+EShBxyCvgsPCtog=="], - "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q=="], + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.7", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-RVQQbq5orQ/GHUnXvqEOj2HHPBJm+mM+ySwZKS5UaLBwra5ugRtiH09PLUoOZRl7a1YzaOzXSuGbn9iD5j60WQ=="], - "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.11", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@smithy/core": "^3.23.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-R8CvPsPHXwzIHCAza+bllY6PrctEk4lYq/SkHJz9NLoBHCcKQrbOcsfXxO6xmipSbUNIbNIUhH0lBsJGgsRdiw=="], + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.29", "", { "dependencies": { "@aws-sdk/core": "^3.973.27", "@aws-sdk/types": "^3.973.7", "@aws-sdk/util-endpoints": "^3.996.6", "@smithy/core": "^3.23.14", "@smithy/protocol-http": "^5.3.13", "@smithy/types": "^4.14.0", "@smithy/util-retry": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-f/sIRzuTfEjg6NsbMYvye2VsmnQoNgntntleQyx5uGacUYzszbfIlO3GcI6G6daWUmTm0IDZc11qMHWwF0o0mQ=="], - "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-format-url": "^3.972.3", "@smithy/eventstream-codec": "^4.2.8", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-1DedO6N3m8zQ/vG6twNiHtsdwBgk773VdavLEbB3NXeKZDlzSK1BTviqWwvJdKx5UnIy4kGGP6WWpCEFEt/bhQ=="], + "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.15", "", { "dependencies": { "@aws-sdk/types": "^3.973.7", "@aws-sdk/util-format-url": "^3.972.9", "@smithy/eventstream-codec": "^4.2.13", "@smithy/eventstream-serde-browser": "^4.2.13", "@smithy/fetch-http-handler": "^5.3.16", "@smithy/protocol-http": "^5.3.13", "@smithy/signature-v4": "^5.3.13", "@smithy/types": "^4.14.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-hsZ35FORQsN5hwNdMD6zWmHCphbXkDxO6j+xwCUiuMb0O6gzS/PWgttQNl1OAn7h/uqZAMUG4yOS0wY/yhAieg=="], - "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.995.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.995.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.10", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7gq9gismVhESiRsSt0eYe1y1b6jS20LqLk+e/YSyPmGi9yHdndHQLIq73RbEJnK/QPpkQGFqq70M1mI46M1HGw=="], + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.19", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.27", "@aws-sdk/middleware-host-header": "^3.972.9", "@aws-sdk/middleware-logger": "^3.972.9", "@aws-sdk/middleware-recursion-detection": "^3.972.10", "@aws-sdk/middleware-user-agent": "^3.972.29", "@aws-sdk/region-config-resolver": "^3.972.11", "@aws-sdk/types": "^3.973.7", "@aws-sdk/util-endpoints": "^3.996.6", "@aws-sdk/util-user-agent-browser": "^3.972.9", "@aws-sdk/util-user-agent-node": "^3.973.15", "@smithy/config-resolver": "^4.4.14", "@smithy/core": "^3.23.14", "@smithy/fetch-http-handler": "^5.3.16", "@smithy/hash-node": "^4.2.13", "@smithy/invalid-dependency": "^4.2.13", "@smithy/middleware-content-length": "^4.2.13", "@smithy/middleware-endpoint": "^4.4.29", "@smithy/middleware-retry": "^4.5.0", "@smithy/middleware-serde": "^4.2.17", "@smithy/middleware-stack": "^4.2.13", "@smithy/node-config-provider": "^4.3.13", "@smithy/node-http-handler": "^4.5.2", "@smithy/protocol-http": "^5.3.13", "@smithy/smithy-client": "^4.12.9", "@smithy/types": "^4.14.0", "@smithy/url-parser": "^4.2.13", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.45", "@smithy/util-defaults-mode-node": "^4.2.49", "@smithy/util-endpoints": "^3.3.4", "@smithy/util-middleware": "^4.2.13", "@smithy/util-retry": "^4.3.0", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q=="], - "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow=="], + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.7", "@smithy/config-resolver": "^4.4.14", "@smithy/node-config-provider": "^4.3.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg=="], - "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.995.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/nested-clients": "3.995.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-lYSadNdZZ513qCKoj/KlJ+PgCycL3n8ZNS37qLVFC0t7TbHzoxvGquu9aD2n9OCERAn43OMhQ7dXjYDYdjAXzA=="], + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1030.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.27", "@aws-sdk/nested-clients": "^3.996.19", "@aws-sdk/types": "^3.973.7", "@smithy/property-provider": "^4.2.13", "@smithy/shared-ini-file-loader": "^4.4.8", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-gUuCLTnEiUgpxHEnJSidxZZlQ+rQwc/mrijz6DxeMijTwS3/e3UfJvL8C1YDvcbt8MkkXj92h0MpYtfhR+EGeg=="], - "@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], + "@aws-sdk/types": ["@aws-sdk/types@3.973.7", "", { "dependencies": { "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-reXRwoJ6CfChoqAsBszUYajAF8Z2LRE+CRcKocvFSMpIiLOtYU3aJ9trmn6VVPAzbbY5LXF+FfmUslbXk1SYFg=="], - "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.995.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-aym/pjB8SLbo9w2nmkrDdAAVKVlf7CM71B9mKhjDbJTzwpSFBPHqJIMdDyj0mLumKC0aIVDr1H6U+59m9GvMFw=="], + "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.7", "@smithy/types": "^4.14.0", "@smithy/url-parser": "^4.2.13", "@smithy/util-endpoints": "^3.3.4", "tslib": "^2.6.2" } }, "sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg=="], - "@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-n7F2ycckcKFXa01vAsT/SJdjFHfKH9s96QHcs5gn8AaaigASICeME8WdUL9uBp8XV/OVwEt8+6gzn6KFUgQa8g=="], + "@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.7", "@smithy/querystring-builder": "^4.2.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-fNJXHrs0ZT7Wx0KGIqKv7zLxlDXt2vqjx9z6oKUQFmpE5o4xxnSryvVHfHpIifYHWKz94hFccIldJ0YSZjlCBw=="], - "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.4", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog=="], + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="], - "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw=="], + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.7", "@smithy/types": "^4.14.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-sn/LMzTbGjYqCCF24390WxPd6hkpoSptiUn5DzVp4cD71yqw+yGEGm1YCxyEoPXyc8qciM8UzLJcZBFslxo5Uw=="], - "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.10", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-LVXzICPlsheET+sE6tkcS47Q5HkSTrANIlqL1iFxGAY/wRQ236DX/PCAK56qMh9QJoXAfXfoRW0B0Og4R+X7Nw=="], + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.15", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.29", "@aws-sdk/types": "^3.973.7", "@smithy/node-config-provider": "^4.3.13", "@smithy/types": "^4.14.0", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-fYn3s9PtKdgQkczGZCFMgkNEe8aq1JCVbnRqjqN9RSVW43xn2RV9xdcZ3z01a48Jpkuh/xCmBKJxdLOo4Ozg7w=="], - "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="], + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.17", "", { "dependencies": { "@smithy/types": "^4.14.0", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg=="], - "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="], + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="], "@azu/format-text": ["@azu/format-text@1.0.2", "", {}, "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg=="], @@ -336,39 +378,39 @@ "@azure/core-client": ["@azure/core-client@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "tslib": "^2.6.2" } }, "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w=="], - "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.22.2", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg=="], + "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.23.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.4", "tslib": "^2.6.2" } }, "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ=="], "@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="], "@azure/core-util": ["@azure/core-util@1.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A=="], - "@azure/identity": ["@azure/identity@4.13.0", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.2", "@azure/core-rest-pipeline": "^1.17.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", "@azure/msal-browser": "^4.2.0", "@azure/msal-node": "^3.5.0", "open": "^10.1.0", "tslib": "^2.2.0" } }, "sha512-uWC0fssc+hs1TGGVkkghiaFkkS7NkTxfnCH+Hdg+yTehTpMcehpok4PgUKKdyCH+9ldu6FhiHRv84Ntqj1vVcw=="], + "@azure/identity": ["@azure/identity@4.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.2", "@azure/core-rest-pipeline": "^1.17.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", "@azure/msal-browser": "^5.5.0", "@azure/msal-node": "^5.1.0", "open": "^10.1.0", "tslib": "^2.2.0" } }, "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw=="], "@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="], - "@azure/msal-browser": ["@azure/msal-browser@4.29.0", "", { "dependencies": { "@azure/msal-common": "15.15.0" } }, "sha512-/f3eHkSNUTl6DLQHm+bKecjBKcRQxbd/XLx8lvSYp8Nl/HRyPuIPOijt9Dt0sH50/SxOwQ62RnFCmFlGK+bR/w=="], + "@azure/msal-browser": ["@azure/msal-browser@5.6.3", "", { "dependencies": { "@azure/msal-common": "16.4.1" } }, "sha512-sTjMtUm+bJpENU/1WlRzHEsgEHppZDZ1EtNyaOODg/sQBtMxxJzGB+MOCM+T2Q5Qe1fKBrdxUmjyRxm0r7Ez9w=="], - "@azure/msal-common": ["@azure/msal-common@15.15.0", "", {}, "sha512-/n+bN0AKlVa+AOcETkJSKj38+bvFs78BaP4rNtv3MJCmPH0YrHiskMRe74OhyZ5DZjGISlFyxqvf9/4QVEi2tw=="], + "@azure/msal-common": ["@azure/msal-common@16.4.1", "", {}, "sha512-Bl8f+w37xkXsYh7QRkAKCFGYtWMYuOVO7Lv+BxILrvGz3HbIEF22Pt0ugyj0QPOl6NLrHcnNUQ9yeew98P/5iw=="], - "@azure/msal-node": ["@azure/msal-node@3.8.8", "", { "dependencies": { "@azure/msal-common": "15.15.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-+f1VrJH1iI517t4zgmuhqORja0bL6LDQXfBqkjuMmfTYXTQQnh1EvwwxO3UbKLT05N0obF72SRHFrC1RBDv5Gg=="], + "@azure/msal-node": ["@azure/msal-node@5.1.2", "", { "dependencies": { "@azure/msal-common": "16.4.1", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-DoeSJ9U5KPAIZoHsPywvfEj2MhBniQe0+FSpjLUTdWoIkI999GB5USkW6nNEHnIaLVxROHXvprWA1KzdS1VQ4A=="], - "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], - "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], + "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], - "@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], - "@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], - "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], - "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], - "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], @@ -376,57 +418,57 @@ "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], - "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], + "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], - "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], - "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="], + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], - "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], - "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], - "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], - "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], + "@borewit/text-codec": ["@borewit/text-codec@0.2.2", "", {}, "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ=="], - "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.1", "", {}, "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw=="], + "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="], "@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="], - "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@11.0.3", "", { "dependencies": { "@chevrotain/gast": "11.0.3", "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ=="], + "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@12.0.0", "", { "dependencies": { "@chevrotain/gast": "12.0.0", "@chevrotain/types": "12.0.0" } }, "sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg=="], - "@chevrotain/gast": ["@chevrotain/gast@11.0.3", "", { "dependencies": { "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q=="], + "@chevrotain/gast": ["@chevrotain/gast@12.0.0", "", { "dependencies": { "@chevrotain/types": "12.0.0" } }, "sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ=="], - "@chevrotain/regexp-to-ast": ["@chevrotain/regexp-to-ast@11.0.3", "", {}, "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA=="], + "@chevrotain/regexp-to-ast": ["@chevrotain/regexp-to-ast@12.0.0", "", {}, "sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA=="], - "@chevrotain/types": ["@chevrotain/types@11.0.3", "", {}, "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ=="], + "@chevrotain/types": ["@chevrotain/types@12.0.0", "", {}, "sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA=="], - "@chevrotain/utils": ["@chevrotain/utils@11.0.3", "", {}, "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ=="], + "@chevrotain/utils": ["@chevrotain/utils@12.0.0", "", {}, "sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA=="], - "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="], + "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="], - "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.0.2", "", { "peerDependencies": { "unenv": "2.0.0-rc.14", "workerd": "^1.20250124.0" }, "optionalPeers": ["workerd"] }, "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg=="], + "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg=="], - "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250718.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-FHf4t7zbVN8yyXgQ/r/GqLPaYZSGUVzeR7RnL28Mwj2djyw2ZergvytVc7fdGcczl6PQh+VKGfZCfUqpJlbi9g=="], + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260415.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-dsxaKsQm3LnPGNPEdsRv09QN3Y4DqCw7kX5j6noKqbAtro2jTr95sVlYM1jUxZ5FkOl1f7SXgaKKB9t5H5Nkbg=="], - "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250718.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fUiyUJYyqqp4NqJ0YgGtp4WJh/II/YZsUnEb6vVy5Oeas8lUOxnN+ZOJ8N/6/5LQCVAtYCChRiIrBbfhTn5Z8Q=="], + "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260415.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+JgSgVA49KyKteHRA1SnonE4Zn5Ei5zdAp5FQMxFmXI8qulZw4Hl7safXxRyK4i9sTO8gl7TFOKO5Q64VPvSDQ=="], - "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250718.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5+eb3rtJMiEwp08Kryqzzu8d1rUcK+gdE442auo5eniMpT170Dz0QxBrqkg2Z48SFUPYbj+6uknuA5tzdRSUSg=="], + "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260415.1", "", { "os": "linux", "cpu": "x64" }, "sha512-tU+9pwsqCy8afOVlGtiWrWQc/fedQK4SRm4KPIAt+zOiQWDxWASm6YGBUJis5c648WN80yz47qnmdDi8DQNOcA=="], - "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250718.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Aa2M/DVBEBQDdATMbn217zCSFKE+ud/teS+fFS+OQqKABLn0azO2qq6ANAHYOIE6Q3Sq4CxDIQr8lGdaJHwUog=="], + "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260415.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-bR9uITnV19r5NQ14xnypi2xHXu2iQvfYV8cVgx0JouFUmWwTEEAwFVojDdssGq93VHX9hr/pi2IRUZeegbYBog=="], - "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250718.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dY16RXKffmugnc67LTbyjdDHZn5NoTF1yHEf2fN4+OaOnoGSp3N1x77QubTDwqZ9zECWxgQfDLjddcH8dWeFhg=="], + "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260415.1", "", { "os": "win32", "cpu": "x64" }, "sha512-4NuMLlerI0Ijua3Ir8HXQ+qyNvCUDEG5gDco5Om+sAiK6rnWiz+aGoSlbB8W16yW9QAgzCstbmXLiVknUBflfQ=="], - "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260301.1", "", {}, "sha512-klKnECMb5A4GtVF0P5NH6rCjtyjqIEKJaz6kEtx9YPHhfFO2HUEarO+MI4F8WPchgeZqpGlEpDhRapzrOTw51Q=="], + "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260416.2", "", {}, "sha512-f7VGuKsHckH5n9KATTPJQ6AGdc2q58eM2waGzzDoCKw+PBtw9j2TWdRz8tLkviv7XcjkcuKy181vQFffXJicrA=="], "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], - "@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], + "@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], "@esbuild-plugins/node-globals-polyfill": ["@esbuild-plugins/node-globals-polyfill@0.2.3", "", { "peerDependencies": { "esbuild": "*" } }, "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw=="], @@ -474,7 +516,7 @@ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="], @@ -494,15 +536,17 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], - "@google/genai": ["@google/genai@1.42.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-+3nlMTcrQufbQ8IumGkOphxD5Pd5kKyJOzLcnY0/1IuE8upJk5aLmoexZ2BJhBp1zAjRJMEB4a2CJwKI9e2EYw=="], + "@google/genai": ["@google/genai@1.50.1", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-YbkX7H9+1Pt8wOt7DDREy8XSoiL6fRDzZQRyaVBarFf8MR3zHGqVdvM4cLbDXqPhxqvegZShgfxb8kw9C7YhAQ=="], - "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="], + "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.9.0", "", { "dependencies": { "@types/node": ">=20.0.0", "happy-dom": "^20.9.0" } }, "sha512-lBW6/m5BIFl3pMuWPNN0lIOYw9LMCmPfix53ExS3FBi4E+NELEljQ3xH6aAV9IYiQRfn9YIIgzzMrD0vIcD7tw=="], + + "@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], "@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="], - "@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="], + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], @@ -564,7 +608,7 @@ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], "@mariozechner/clipboard": ["@mariozechner/clipboard@0.3.2", "", { "optionalDependencies": { "@mariozechner/clipboard-darwin-arm64": "0.3.2", "@mariozechner/clipboard-darwin-universal": "0.3.2", "@mariozechner/clipboard-darwin-x64": "0.3.2", "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", "@mariozechner/clipboard-linux-x64-musl": "0.3.2", "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" } }, "sha512-IHQpksNjo7EAtGuHFU+tbWDp5LarH3HU/8WiB9O70ZEoBPHOg0/6afwSLK0QyNMMmx4Bpi/zl6+DcBXe95nWYA=="], @@ -590,23 +634,37 @@ "@mariozechner/jiti": ["@mariozechner/jiti@2.6.5", "", { "dependencies": { "std-env": "^3.10.0", "yoctocolors": "^2.1.2" }, "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw=="], - "@mariozechner/pi-agent-core": ["@mariozechner/pi-agent-core@0.54.1", "", { "dependencies": { "@mariozechner/pi-ai": "^0.54.1" } }, "sha512-AC0SqEbR62PckWOyP0CmhYtfcC+Q6e1DGghwEcKpomTtmNfHTy7iTVy64mmtB2CFiN8j4rJFCqh2xJHgucUvkA=="], + "@mariozechner/pi-agent-core": ["@mariozechner/pi-agent-core@0.67.3", "", { "dependencies": { "@mariozechner/pi-ai": "^0.67.3" } }, "sha512-dtbqKkEl5Att6+Au9zr0FL8x0Ieqwty0YAzE73eKEcDVTY8EUHxpixucXol9dwRAOmUPCbNVXolTfH5SPrNZbw=="], - "@mariozechner/pi-ai": ["@mariozechner/pi-ai@0.54.1", "", { "dependencies": { "@anthropic-ai/sdk": "^0.73.0", "@aws-sdk/client-bedrock-runtime": "^3.983.0", "@google/genai": "^1.40.0", "@mistralai/mistralai": "1.10.0", "@sinclair/typebox": "^0.34.41", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "chalk": "^5.6.2", "openai": "6.10.0", "partial-json": "^0.1.7", "proxy-agent": "^6.5.0", "undici": "^7.19.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-tiVvoNQV+3dpWgRQ1U/3bwJoDVSYwL17BE/kc00nXmaSLAPwNZoxLagtQ+HBr/rGzkq5viOgQf2dk+ud+/4UCg=="], + "@mariozechner/pi-ai": ["@mariozechner/pi-ai@0.67.3", "", { "dependencies": { "@anthropic-ai/sdk": "^0.73.0", "@aws-sdk/client-bedrock-runtime": "^3.983.0", "@google/genai": "^1.40.0", "@mistralai/mistralai": "1.14.1", "@sinclair/typebox": "^0.34.41", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "chalk": "^5.6.2", "openai": "6.26.0", "partial-json": "^0.1.7", "proxy-agent": "^6.5.0", "undici": "^7.19.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-0GDT2osCfBPYKffaeEzGmrDKGCAF2QQ2eqTGGE5akhBvw1fSA3TYjOCQyLPgzWz3ZHUzZwb6EQ5Yb1Bn3H14CQ=="], - "@mariozechner/pi-coding-agent": ["@mariozechner/pi-coding-agent@0.54.1", "", { "dependencies": { "@mariozechner/jiti": "^2.6.2", "@mariozechner/pi-agent-core": "^0.54.1", "@mariozechner/pi-ai": "^0.54.1", "@mariozechner/pi-tui": "^0.54.1", "@silvia-odwyer/photon-node": "^0.3.4", "chalk": "^5.5.0", "cli-highlight": "^2.1.11", "diff": "^8.0.2", "file-type": "^21.1.1", "glob": "^13.0.1", "hosted-git-info": "^9.0.2", "ignore": "^7.0.5", "marked": "^15.0.12", "minimatch": "^10.1.1", "proper-lockfile": "^4.1.2", "yaml": "^2.8.2" }, "optionalDependencies": { "@mariozechner/clipboard": "^0.3.2" }, "bin": { "pi": "dist/cli.js" } }, "sha512-pPFrdaKZ16oIcdhZVcfWPhCDFx8PWHaACjQS9aFFcMOhLBduyKAGyf8bQtfysekl+gIbBSGDT2rgCxsOwK2bQw=="], + "@mariozechner/pi-coding-agent": ["@mariozechner/pi-coding-agent@0.67.3", "", { "dependencies": { "@mariozechner/jiti": "^2.6.2", "@mariozechner/pi-agent-core": "^0.67.3", "@mariozechner/pi-ai": "^0.67.3", "@mariozechner/pi-tui": "^0.67.3", "@silvia-odwyer/photon-node": "^0.3.4", "ajv": "^8.17.1", "chalk": "^5.5.0", "cli-highlight": "^2.1.11", "diff": "^8.0.2", "extract-zip": "^2.0.1", "file-type": "^21.1.1", "glob": "^13.0.1", "hosted-git-info": "^9.0.2", "ignore": "^7.0.5", "marked": "^15.0.12", "minimatch": "^10.2.3", "proper-lockfile": "^4.1.2", "strip-ansi": "^7.1.0", "undici": "^7.19.1", "uuid": "^11.1.0", "yaml": "^2.8.2" }, "optionalDependencies": { "@mariozechner/clipboard": "^0.3.2" }, "bin": { "pi": "dist/cli.js" } }, "sha512-0bZYeMkQLq2iWobCGoVAF0QrWum5lsnHmMubu4o/NE0OYRyQes3hAN8WaRVuPdZJOA12JAB6wE37F4ZRLiK9CQ=="], - "@mariozechner/pi-tui": ["@mariozechner/pi-tui@0.54.1", "", { "dependencies": { "@types/mime-types": "^2.1.4", "chalk": "^5.5.0", "get-east-asian-width": "^1.3.0", "koffi": "^2.9.0", "marked": "^15.0.12", "mime-types": "^3.0.1" } }, "sha512-FY8QcLlr9T276oZAwMSSPo1drg+J9Y7B+A0S9g8Jh6IFJxymKZZq29/Vit6XDziJfZIgJDraC6lpobtxgTEoFQ=="], + "@mariozechner/pi-tui": ["@mariozechner/pi-tui@0.67.3", "", { "dependencies": { "@types/mime-types": "^2.1.4", "chalk": "^5.5.0", "get-east-asian-width": "^1.3.0", "marked": "^15.0.12", "mime-types": "^3.0.1" }, "optionalDependencies": { "koffi": "^2.9.0" } }, "sha512-Nq6C50kU9DEQdbc/QDXo+m3NkTedU/iyUpABtecNDLpVBrW56m6yO8HNEM0c96HOBfGrjqE7BIv1inWokKVNEw=="], "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], - "@mermaid-js/parser": ["@mermaid-js/parser@0.6.3", "", { "dependencies": { "langium": "3.3.1" } }, "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA=="], + "@mermaid-js/parser": ["@mermaid-js/parser@1.1.0", "", { "dependencies": { "langium": "^4.0.0" } }, "sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw=="], - "@mistralai/mistralai": ["@mistralai/mistralai@1.10.0", "", { "dependencies": { "zod": "^3.20.0", "zod-to-json-schema": "^3.24.1" } }, "sha512-tdIgWs4Le8vpvPiUEWne6tK0qbVc+jMenujnvTqOjogrJUsCSQhus0tHTU1avDDh5//Rq2dFgP9mWRAdIEoBqg=="], + "@mistralai/mistralai": ["@mistralai/mistralai@1.14.1", "", { "dependencies": { "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.1" } }, "sha512-IiLmmZFCCTReQgPAT33r7KQ1nYo5JPdvGkrkZqA8qQ2qB1GHgs5LoP5K2ICyrjnpw2n8oSxMM/VP+liiKcGNlQ=="], "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="], - "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.28.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-gmloF+i+flI8ouQK7MWW4mOwuMh4RePBuPFAEPC6+pdqyWOUMDOixb6qZ69owLJpz6XmyllCouc4t8YWO+E2Nw=="], + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="], + + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], + + "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="], + + "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="], + + "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="], + + "@nodable/entities": ["@nodable/entities@1.1.0", "", {}, "sha512-bidpxmTBP0pOsxULw6XlxzQpTgrAGLDHGBK/JuWhPDL6ZV0GZ/PmN9CA9do6e+A9lYI6qx6ikJUtJYRxup141g=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -630,42 +688,44 @@ "@openai/codex-win32-x64": ["@openai/codex@0.118.0-win32-x64", "", { "os": "win32", "cpu": "x64" }, "sha512-9vKW1ANQxIUsLpiY0VsOM+1avZjqxDZgqgxKABJHWRlLMuaSykwXexf8Hqq+BnYFfmn0d6MjxTuE1UtVvg9caw=="], - "@opencode-ai/plugin": ["@opencode-ai/plugin@1.1.25", "", { "dependencies": { "@opencode-ai/sdk": "1.1.25", "zod": "4.1.8" } }, "sha512-oTUWS446H/j7z3pdzo3cOrB5N87XZ/RKdgPD8yHv/rLX92B4YQHjOqggVQ56Q+1VEnN0jxzhoqRylv/0ZEts/Q=="], + "@opencode-ai/plugin": ["@opencode-ai/plugin@1.4.6", "", { "dependencies": { "@opencode-ai/sdk": "1.4.6", "effect": "4.0.0-beta.48", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.1.99", "@opentui/solid": ">=0.1.99" }, "optionalPeers": ["@opentui/core", "@opentui/solid"] }, "sha512-w+55uE4tCpFoK+MtWeGoPDmpuuabw8m5WVpmucIKoTO5SgD/3EwXVPBqAh44iS72ve1Eu+uhhzeVQ070x9bVjg=="], - "@opencode-ai/sdk": ["@opencode-ai/sdk@1.3.5", "", {}, "sha512-ckKedqONnigSejAm/UVlBuQP0U1Ozn9uC54zLxz/EqQZPWE8y7V+8PT048zC7q6gqI+puj2jns65/+enJSkTEQ=="], + "@opencode-ai/sdk": ["@opencode-ai/sdk@1.4.6", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-sQaVfEfQW3m3DeCVlurSTUjgIYdIk+gIfOys51MVFYzJHw+FyjjuAt7EKKA+LeZU5AiWGlpkIRa1rJo5KWMXCw=="], "@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="], - "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8GvNtMo0NINM7Emk9cNAviCG3teEgr3BUX9be0+GD029zIagx2Sf54jMui1Eu1IpFm7nWHODuLEefGOQNaJ0gQ=="], + "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b6CQgT28Jx7uDwMTcGo7WFqUd1+wWTdp8XyPi/4LRcL/R4deKT7cLx/Q2ZCWAiK6ZU7yexoCaIaKun6azjRLVA=="], - "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-r33eHQOHAwkuiBJIwmkXIyqONQOQMnd1GMTpDzaxx9vf9+svby80LZO9Hcm1ns6KT/TBRFyODC/0loA7FAaffg=="], + "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-//6W21c+GinAMMmxD2hFrFmJH+ZlEwJYbLzAGqp0mLFTli9y74RMtDgI2n9pCupXSpU1Kr1sSylVW9yNbAG9Xg=="], - "@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-p5q3rJk48qhLuLBOFehVc+kqCE03YrswTc6NCxbwsxiwfySXwcAvpF2KWKF/ZZObvvR8hCCvqe1F81b2p5r2dg=="], + "@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-9jKJNOc9ID3BxPBPR4r1Mp1Wqde89Twi5zo2LoEMLMKbqpvEM/WUGdJ0Vv7OX1QPEqVblFO6NMky5yY7rjDI2w=="], - "@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-zkcHPI23QxJ1TdqafhgkXt1NOEN8o5C460sVeNnrhfJ43LwZgtfcvcQE39x/pBedu67fatY8CU0iY00nOh46ZQ=="], + "@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-eTru6tk3K4Ya3SSkUqq/LbdEjwPqLlfINmIhRORrCExBdB1tQbk+WYYflaymO61fkrjnMAjmLTGqk/K37RMIGA=="], - "@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-HKBeUlJdNduRkzJKZ5DXM+pPqntfC50/Hu2X65jVX0Y7hu/6IC8RaUTqpr8FtCZqqmc9wDK0OTL+Mbi9UQIKYQ=="], + "@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-HWIwFzm5fALd9Lli0CgaKb6xOGqODYyHpUTgkn/IHHuS/f3XDCu71+GgkyvfgCYbPoBSgBOfp5TzhRehPcgxow=="], - "@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-n7zhKTSDZS0yOYg5Rq8easZu5Y/o47sv0c7yGr2ciFdcie9uYV55fZ7QMqhWMGK33ezCSikh5EDkUMCIvfWpjA=="], + "@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.12", "", { "os": "linux", "cpu": "x64" }, "sha512-H75bcEn46lMDxd+P+R6Q/jlIKl/YO0ZXaalSyWhQHr7qNmFhQt3rOHurFoCxuwQeqFoToh0JpWVyMVzByZqgBQ=="], - "@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-FeCQyBU62DMuB0nn01vPnf3McXrKOsrK9p7sHaBFYycw0mmoU8kCq/WkBkGMnLuvQljJSyen8QBTx+fXdNupWg=="], + "@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.12", "", { "os": "linux", "cpu": "x64" }, "sha512-0y+lUiQsPvSGsyM/10KtxhVAQ20p6/D+vj01l6vo9gHpYUpyc1L9pSgaPa7SC9TuaiGASlM3Cb62bmSKW0E/3Q=="], - "@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-XkCCHkByYn8BIDvoxnny898znju4xnW2kvFE8FT5+0Y62cWdcBGMZ9RdsEUTeRz16k8hHtJpaSfLcEmNTFIwRQ=="], + "@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.12", "", { "os": "linux", "cpu": "x64" }, "sha512-Zb7T3JxWlArSe44ATO5mtjLCBCt7kenWPl9CYD+zeqq9kHswMv8Cd3h/9uzdv2PA4Flrq57J5XBSuRdStTCXCw=="], - "@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-TJiYC7KCr0XxFTsxgwQOeE7dncrEL/RSyL0EzSL3xRkrxJMWBCvCSjQn7LV1i6T7hFst0+3KoN3VWvD5BinqHA=="], + "@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.12", "", { "os": "linux", "cpu": "x64" }, "sha512-jdsnuFD3H0l4AHtf1nInRHYWIMTWqok0aW8WysjzN5Isn6rBTBGK/ZWX6XjdTgDgcuVbVOYHiLUHHrvT9N6psA=="], - "@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.5", "", { "os": "win32", "cpu": "x64" }, "sha512-T3xkODItb/0ftQPFsZDc7EAX2D6A4TEazQ2YZyofZToO8Q7y8YT8ooWdhd0BQiTCd66uEvgE1DCZetynwg2IoA=="], + "@oven/bun-windows-aarch64": ["@oven/bun-windows-aarch64@1.3.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-Oq0FIcCgL3JWf/4qRuxI5fxsOGyWJ1j904PDx/1TxxSCWWAu0Hh2o8ck4TcaPVv/3BMc1k6UxqQQKBrdP7a+qQ=="], - "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.5", "", { "os": "win32", "cpu": "x64" }, "sha512-rtVQB9/1XK8FWJgFtsOthbPifRMYypgJwxu+pK3NHx8WvFKmq7HcPDqNr8xLzGULjQEO7eAo2aOZfONOwYz+5g=="], + "@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.12", "", { "os": "win32", "cpu": "x64" }, "sha512-veSntY7pDLDh4XmxZMwTqxfoEVp0BDdeqCBoWL46/TigtniPtDFSTIWBxa6l/RcGzklUA/uqLqmsK/9cBZAm8Q=="], - "@pierre/diffs": ["@pierre/diffs@1.1.12", "", { "dependencies": { "@pierre/theme": "0.0.28", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-InssHHM7f0nkazIRkuaiNCy6GkBLfwJlqc7LtTkMD/KSqsuc6bnL2V9sIQoG5PZu9jwinQiXUb/gT7itFa6U9A=="], + "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.12", "", { "os": "win32", "cpu": "x64" }, "sha512-rV21md7QWnu3r/shev7IFMh6hX8BJHwofxESAofUT4yH866oCIbcNbzp6+fxrj4oGD8uisP6WoaTCboijv9yYg=="], - "@pierre/theme": ["@pierre/theme@0.0.28", "", {}, "sha512-1j/H/fECBuc9dEvntdWI+l435HZapw+RCJTlqCA6BboQ5TjlnE005j/ROWutXIs8aq5OAc82JI2Kwk4A1WWBgw=="], + "@pierre/diffs": ["@pierre/diffs@1.1.17", "", { "dependencies": { "@pierre/theme": "0.0.28", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-NtrexN6lSNx0K1JvbCwa91uE/Kc7BGGc8kRC4jfr6iKLJoxR0SZpyi5ldOmpItfepTuJRAhUkao4V+jtciz9bA=="], - "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@pierre/theme": ["@pierre/theme@0.0.28", "", {}, "sha512-1j/H/fECBuc9dEvntdWI+l435HZapw+RCJTlqCA6BboQ5TjlnE005j/ROWutXIs8aq5OAc82JI2Kwk4A1WWBgw=="], "@plannotator/ai": ["@plannotator/ai@workspace:packages/ai"], + "@plannotator/collab-agent": ["@plannotator/collab-agent@workspace:apps/collab-agent"], + "@plannotator/editor": ["@plannotator/editor@workspace:packages/editor"], "@plannotator/hooks": ["@plannotator/hooks@workspace:apps/hook"], @@ -684,6 +744,8 @@ "@plannotator/review-editor": ["@plannotator/review-editor@workspace:packages/review-editor"], + "@plannotator/room-service": ["@plannotator/room-service@workspace:apps/room-service"], + "@plannotator/server": ["@plannotator/server@workspace:packages/server"], "@plannotator/shared": ["@plannotator/shared@workspace:packages/shared"], @@ -692,6 +754,12 @@ "@plannotator/web-highlighter": ["@plannotator/web-highlighter@0.8.1", "", {}, "sha512-FlteNOwRj9iNSY/AhFMtqOnVS4FvsACvTw6IiOM1y8iDyhiU/WeZOgjURENvIY+wuUaiS9DDFmg0PrHMyuMR1Q=="], + "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="], + + "@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="], + + "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], @@ -766,53 +834,59 @@ "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.53", "", {}, "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.54.0", "", { "os": "android", "cpu": "arm" }, "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.54.0", "", { "os": "android", "cpu": "arm64" }, "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.54.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.54.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.54.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.54.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ=="], + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ=="], + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng=="], + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg=="], + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw=="], + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.54.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA=="], + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.54.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="], - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.54.0", "", { "os": "none", "cpu": "arm64" }, "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg=="], + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.54.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw=="], + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.54.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="], - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg=="], + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="], "@secretlint/config-creator": ["@secretlint/config-creator@10.2.2", "", { "dependencies": { "@secretlint/types": "^10.2.2" } }, "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ=="], @@ -838,161 +912,169 @@ "@secretlint/types": ["@secretlint/types@10.2.2", "", {}, "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg=="], - "@shikijs/core": ["@shikijs/core@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA=="], + "@shikijs/core": ["@shikijs/core@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA=="], - "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ=="], + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA=="], - "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ=="], + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g=="], - "@shikijs/langs": ["@shikijs/langs@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0" } }, "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA=="], + "@shikijs/langs": ["@shikijs/langs@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg=="], - "@shikijs/themes": ["@shikijs/themes@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0" } }, "sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw=="], + "@shikijs/themes": ["@shikijs/themes@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA=="], - "@shikijs/transformers": ["@shikijs/transformers@3.21.0", "", { "dependencies": { "@shikijs/core": "3.21.0", "@shikijs/types": "3.21.0" } }, "sha512-CZwvCWWIiRRiFk9/JKzdEooakAP8mQDtBOQ1TKiCaS2E1bYtyBCOkUzS8akO34/7ufICQ29oeSfkb3tT5KtrhA=="], + "@shikijs/transformers": ["@shikijs/transformers@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/types": "3.23.0" } }, "sha512-F9msZVxdF+krQNSdQ4V+Ja5QemeAoTQ2jxt7nJCwhDsdF1JWS3KxIQXA3lQbyKwS3J61oHRUSv4jYWv3CkaKTQ=="], - "@shikijs/types": ["@shikijs/types@3.21.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA=="], + "@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="], "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], "@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="], - "@sinclair/typebox": ["@sinclair/typebox@0.34.48", "", {}, "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA=="], + "@sinclair/typebox": ["@sinclair/typebox@0.34.49", "", {}, "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A=="], + + "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="], - "@smithy/abort-controller": ["@smithy/abort-controller@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw=="], + "@smithy/config-resolver": ["@smithy/config-resolver@4.4.15", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.13", "@smithy/types": "^4.14.0", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.4.0", "@smithy/util-middleware": "^4.2.13", "tslib": "^2.6.2" } }, "sha512-BJdMBY5YO9iHh+lPLYdHv6LbX+J8IcPCYMl1IJdBt2KDWNHwONHrPVHk3ttYBqJd9wxv84wlbN0f7GlQzcQtNQ=="], + + "@smithy/core": ["@smithy/core@3.23.14", "", { "dependencies": { "@smithy/protocol-http": "^5.3.13", "@smithy/types": "^4.14.0", "@smithy/url-parser": "^4.2.13", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.13", "@smithy/util-stream": "^4.5.22", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg=="], - "@smithy/config-resolver": ["@smithy/config-resolver@4.4.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ=="], + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.13", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.13", "@smithy/property-provider": "^4.2.13", "@smithy/types": "^4.14.0", "@smithy/url-parser": "^4.2.13", "tslib": "^2.6.2" } }, "sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ=="], - "@smithy/core": ["@smithy/core@3.23.2", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-HaaH4VbGie4t0+9nY3tNBRSxVTr96wzIqexUa6C2qx3MPePAuz7lIxPxYtt1Wc//SPfJLNoZJzfdt0B6ksj2jA=="], + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.13", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.0", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ=="], - "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw=="], + "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.13", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-wwybfcOX0tLqCcBP378TIU9IqrDuZq/tDV48LlZNydMpCnqnYr+hWBAYbRE+rFFf/p7IkDJySM3bgiMKP2ihPg=="], - "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.8", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw=="], + "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.13", "", { "dependencies": { "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-ied1lO559PtAsMJzg2TKRlctLnEi1PfkNeMMpdwXDImk1zV9uvS/Oxoy/vcy9uv1GKZAjDAB5xT6ziE9fzm5wA=="], - "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.8", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw=="], + "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.13", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-hFyK+ORJrxAN3RYoaD6+gsGDQjeix8HOEkosoajvXYZ4VeqonM3G4jd9IIRm/sWGXUKmudkY9KdYjzosUqdM8A=="], - "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ=="], + "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.13", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-kRrq4EKLGeOxhC2CBEhRNcu1KSzNJzYY7RK3S7CxMPgB5dRrv55WqQOtRwQxQLC04xqORFLUgnDlc6xrNUULaA=="], - "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.8", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A=="], + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.16", "", { "dependencies": { "@smithy/protocol-http": "^5.3.13", "@smithy/querystring-builder": "^4.2.13", "@smithy/types": "^4.14.0", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ=="], - "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.8", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ=="], + "@smithy/hash-node": ["@smithy/hash-node@4.2.13", "", { "dependencies": { "@smithy/types": "^4.14.0", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-4/oy9h0jjmY80a2gOIo75iLl8TOPhmtx4E2Hz+PfMjvx/vLtGY4TMU/35WRyH2JHPfT5CVB38u4JRow7gnmzJA=="], - "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA=="], + "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.13", "", { "dependencies": { "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-jvC0RB/8BLj2SMIkY0Npl425IdnxZJxInpZJbu563zIRnVjpDMXevU3VMCRSabaLB0kf/eFIOusdGstrLJ8IDg=="], - "@smithy/hash-node": ["@smithy/hash-node@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA=="], + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], - "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ=="], + "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.13", "", { "dependencies": { "@smithy/protocol-http": "^5.3.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-IPMLm/LE4AZwu6qiE8Rr8vJsWhs9AtOdySRXrOM7xnvclp77Tyh7hMs/FRrMf26kgIe67vFJXXOSmVxS7oKeig=="], - "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.29", "", { "dependencies": { "@smithy/core": "^3.23.14", "@smithy/middleware-serde": "^4.2.17", "@smithy/node-config-provider": "^4.3.13", "@smithy/shared-ini-file-loader": "^4.4.8", "@smithy/types": "^4.14.0", "@smithy/url-parser": "^4.2.13", "@smithy/util-middleware": "^4.2.13", "tslib": "^2.6.2" } }, "sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw=="], - "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A=="], + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.5.1", "", { "dependencies": { "@smithy/core": "^3.23.14", "@smithy/node-config-provider": "^4.3.13", "@smithy/protocol-http": "^5.3.13", "@smithy/service-error-classification": "^4.2.13", "@smithy/smithy-client": "^4.12.9", "@smithy/types": "^4.14.0", "@smithy/util-middleware": "^4.2.13", "@smithy/util-retry": "^4.3.1", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-/zY+Gp7Qj2D2hVm3irkCyONER7E9MiX3cUUm/k2ZmhkzZkrPgwVS4aJ5NriZUEN/M0D1hhjrgjUmX04HhRwdWA=="], - "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.16", "", { "dependencies": { "@smithy/core": "^3.23.2", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-L5GICFCSsNhbJ5JSKeWFGFy16Q2OhoBizb3X2DrxaJwXSEujVvjG9Jt386dpQn2t7jINglQl0b4K/Su69BdbMA=="], + "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.17", "", { "dependencies": { "@smithy/core": "^3.23.14", "@smithy/protocol-http": "^5.3.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ=="], - "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.33", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-jLqZOdJhtIL4lnA9hXnAG6GgnJlo1sD3FqsTxm9wSfjviqgWesY/TMBVnT84yr4O0Vfe0jWoXlfFbzsBVph3WA=="], + "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.13", "", { "dependencies": { "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw=="], - "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ=="], + "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.13", "", { "dependencies": { "@smithy/property-provider": "^4.2.13", "@smithy/shared-ini-file-loader": "^4.4.8", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw=="], - "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA=="], + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.2", "", { "dependencies": { "@smithy/protocol-http": "^5.3.13", "@smithy/querystring-builder": "^4.2.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA=="], - "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.8", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg=="], + "@smithy/property-provider": ["@smithy/property-provider@4.2.13", "", { "dependencies": { "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ=="], - "@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.10", "", { "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA=="], + "@smithy/protocol-http": ["@smithy/protocol-http@5.3.13", "", { "dependencies": { "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg=="], - "@smithy/property-provider": ["@smithy/property-provider@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w=="], + "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.13", "", { "dependencies": { "@smithy/types": "^4.14.0", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ=="], - "@smithy/protocol-http": ["@smithy/protocol-http@5.3.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ=="], + "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.13", "", { "dependencies": { "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA=="], - "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw=="], + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.13", "", { "dependencies": { "@smithy/types": "^4.14.0" } }, "sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw=="], - "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA=="], + "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.8", "", { "dependencies": { "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw=="], - "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0" } }, "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ=="], + "@smithy/signature-v4": ["@smithy/signature-v4@5.3.13", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.13", "@smithy/types": "^4.14.0", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.13", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg=="], - "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.3", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg=="], + "@smithy/smithy-client": ["@smithy/smithy-client@4.12.9", "", { "dependencies": { "@smithy/core": "^3.23.14", "@smithy/middleware-endpoint": "^4.4.29", "@smithy/middleware-stack": "^4.2.13", "@smithy/protocol-http": "^5.3.13", "@smithy/types": "^4.14.0", "@smithy/util-stream": "^4.5.22", "tslib": "^2.6.2" } }, "sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ=="], - "@smithy/signature-v4": ["@smithy/signature-v4@5.3.8", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg=="], + "@smithy/types": ["@smithy/types@4.14.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ=="], - "@smithy/smithy-client": ["@smithy/smithy-client@4.11.5", "", { "dependencies": { "@smithy/core": "^3.23.2", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" } }, "sha512-xixwBRqoeP2IUgcAl3U9dvJXc+qJum4lzo3maaJxifsZxKUYLfVfCXvhT4/jD01sRrHg5zjd1cw2Zmjr4/SuKQ=="], + "@smithy/url-parser": ["@smithy/url-parser@4.2.13", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw=="], - "@smithy/types": ["@smithy/types@4.12.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw=="], + "@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], - "@smithy/url-parser": ["@smithy/url-parser@4.2.8", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA=="], + "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ=="], - "@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], + "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g=="], - "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], - "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="], + "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ=="], - "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.45", "", { "dependencies": { "@smithy/property-provider": "^4.2.13", "@smithy/smithy-client": "^4.12.9", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-ag9sWc6/nWZAuK3Wm9KlFJUnRkXLrXn33RFjIAmCTFThqLHY+7wCst10BGq56FxslsDrjhSie46c8OULS+BiIw=="], - "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.50", "", { "dependencies": { "@smithy/config-resolver": "^4.4.15", "@smithy/credential-provider-imds": "^4.2.13", "@smithy/node-config-provider": "^4.3.13", "@smithy/property-provider": "^4.2.13", "@smithy/smithy-client": "^4.12.9", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-xpjncL5XozFA3No7WypTsPU1du0fFS8flIyO+Wh2nhCy7bpEapvU7BR55Bg+wrfw+1cRA+8G8UsTjaxgzrMzXg=="], - "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.32", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-092sjYfFMQ/iaPH798LY/OJFBcYu0sSK34Oy9vdixhsU36zlZu8OcYjF3TD4e2ARupyK7xaxPXl+T0VIJTEkkg=="], + "@smithy/util-endpoints": ["@smithy/util-endpoints@3.4.0", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-QQHGPKkw6NPcU6TJ1rNEEa201srPtZiX4k61xL163vvs9sTqW/XKz+UEuJ00uvPqoN+5Rs4Ka1UJ7+Mp03IXJw=="], - "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.35", "", { "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-miz/ggz87M8VuM29y7jJZMYkn7+IErM5p5UgKIf8OtqVs/h2bXr1Bt3uTsREsI/4nK8a0PQERbAPsVPVNIsG7Q=="], + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="], - "@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw=="], + "@smithy/util-middleware": ["@smithy/util-middleware@4.2.13", "", { "dependencies": { "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow=="], - "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + "@smithy/util-retry": ["@smithy/util-retry@4.3.1", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.13", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-FwmicpgWOkP5kZUjN3y+3JIom8NLGqSAJBeoIgK0rIToI817TEBHCrd0A2qGeKQlgDeP+Jzn4i0H/NLAXGy9uQ=="], - "@smithy/util-middleware": ["@smithy/util-middleware@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A=="], + "@smithy/util-stream": ["@smithy/util-stream@4.5.22", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.16", "@smithy/node-http-handler": "^4.5.2", "@smithy/types": "^4.14.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew=="], - "@smithy/util-retry": ["@smithy/util-retry@4.2.8", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg=="], + "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="], - "@smithy/util-stream": ["@smithy/util-stream@4.5.12", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.10", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg=="], + "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + "@smithy/uuid": ["@smithy/uuid@1.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="], - "@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + "@speed-highlight/core": ["@speed-highlight/core@1.2.15", "", {}, "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw=="], - "@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="], + "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="], + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="], - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="], + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="], - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="], + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="], - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="], + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="], - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="], + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="], - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="], + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="], - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="], + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="], - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="], + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="], - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="], + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="], - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="], + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="], - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="], + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="], - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="], + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="], - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="], + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="], - "@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="], + "@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="], "@tanstack/react-table": ["@tanstack/react-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww=="], "@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="], - "@textlint/ast-node-types": ["@textlint/ast-node-types@15.5.2", "", {}, "sha512-fCaOxoup5LIyBEo7R1oYWE7V4bSX0KQeHh66twon9e9usaLE3ijgF8QjYsR6joCssdeCHVd0wHm7ppsEyTr6vg=="], + "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], - "@textlint/linter-formatter": ["@textlint/linter-formatter@15.5.2", "", { "dependencies": { "@azu/format-text": "^1.0.2", "@azu/style-format": "^1.0.1", "@textlint/module-interop": "15.5.2", "@textlint/resolver": "15.5.2", "@textlint/types": "15.5.2", "chalk": "^4.1.2", "debug": "^4.4.3", "js-yaml": "^4.1.1", "lodash": "^4.17.23", "pluralize": "^2.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "table": "^6.9.0", "text-table": "^0.2.0" } }, "sha512-jAw7jWM8+wU9cG6Uu31jGyD1B+PAVePCvnPKC/oov+2iBPKk3ao30zc/Itmi7FvXo4oPaL9PmzPPQhyniPVgVg=="], + "@testing-library/react": ["@testing-library/react@16.3.2", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g=="], - "@textlint/module-interop": ["@textlint/module-interop@15.5.2", "", {}, "sha512-mg6rMQ3+YjwiXCYoQXbyVfDucpTa1q5mhspd/9qHBxUq4uY6W8GU42rmT3GW0V1yOfQ9z/iRrgPtkp71s8JzXg=="], + "@textlint/ast-node-types": ["@textlint/ast-node-types@15.5.4", "", {}, "sha512-bVtB6VEy9U9DpW8cTt25k5T+lz86zV5w6ImePZqY1AXzSuPhqQNT77lkMPxonXzUducEIlSvUu3o7sKw3y9+Sw=="], - "@textlint/resolver": ["@textlint/resolver@15.5.2", "", {}, "sha512-YEITdjRiJaQrGLUWxWXl4TEg+d2C7+TNNjbGPHPH7V7CCnXm+S9GTjGAL7Q2WSGJyFEKt88Jvx6XdJffRv4HEA=="], + "@textlint/linter-formatter": ["@textlint/linter-formatter@15.5.4", "", { "dependencies": { "@azu/format-text": "^1.0.2", "@azu/style-format": "^1.0.1", "@textlint/module-interop": "15.5.4", "@textlint/resolver": "15.5.4", "@textlint/types": "15.5.4", "chalk": "^4.1.2", "debug": "^4.4.3", "js-yaml": "^4.1.1", "lodash": "^4.18.1", "pluralize": "^2.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "table": "^6.9.0", "text-table": "^0.2.0" } }, "sha512-D9qJedKBLmAo+kiudop4UKgSxXMi4O8U86KrCidVXZ9RsK0NSVIw6+r2rlMUOExq79iEY81FRENyzmNVRxDBsg=="], - "@textlint/types": ["@textlint/types@15.5.2", "", { "dependencies": { "@textlint/ast-node-types": "15.5.2" } }, "sha512-sJOrlVLLXp4/EZtiWKWq9y2fWyZlI8GP+24rnU5avtPWBIMm/1w97yzKrAqYF8czx2MqR391z5akhnfhj2f/AQ=="], + "@textlint/module-interop": ["@textlint/module-interop@15.5.4", "", {}, "sha512-JyAUd26ll3IFF87LP0uGoa8Tzw5ZKiYvGs6v8jLlzyND1lUYCI4+2oIAslrODLkf0qwoCaJrBQWM3wsw+asVGQ=="], + + "@textlint/resolver": ["@textlint/resolver@15.5.4", "", {}, "sha512-5GUagtpQuYcmhlOzBGdmVBvDu5lKgVTjwbxtdfoidN4OIqblIxThJHHjazU+ic+/bCIIzI2JcOjHGSaRmE8Gcg=="], + + "@textlint/types": ["@textlint/types@15.5.4", "", { "dependencies": { "@textlint/ast-node-types": "15.5.4" } }, "sha512-mY28j2U7nrWmZbxyKnRvB8vJxJab4AxqOobLfb6iozrLelJbqxcOTvBQednadWPfAk9XWaZVMqUr9Nird3mutg=="], "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="], @@ -1000,6 +1082,8 @@ "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], @@ -1008,7 +1092,7 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], + "@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="], "@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="], @@ -1072,7 +1156,7 @@ "@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="], - "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], "@types/dompurify": ["@types/dompurify@3.2.0", "", { "dependencies": { "dompurify": "*" } }, "sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg=="], @@ -1094,7 +1178,7 @@ "@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="], - "@types/node": ["@types/node@25.5.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg=="], + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], "@types/normalize-package-data": ["@types/normalize-package-data@2.4.4", "", {}, "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="], @@ -1114,17 +1198,25 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - "@types/vscode": ["@types/vscode@1.109.0", "", {}, "sha512-0Pf95rnwEIwDbmXGC08r0B4TQhAbsHQ5UyTIgVgoieDe4cOnf92usuR5dEczb6bTKEp7ziZH4TV1TRGPPCExtw=="], + "@types/vscode": ["@types/vscode@1.116.0", "", {}, "sha512-sYHp4MO6BqJ2PD7Hjt0hlIS3tMaYsVPJrd0RUjDJ8HtOYnyJIEej0bLSccM8rE77WrC+Xox/kdBwEFDO8MsxNA=="], - "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.3", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA=="], + "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], + + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.5", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], - "@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.2", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.53", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ=="], + "@upsetjs/venn.js": ["@upsetjs/venn.js@2.0.0", "", { "optionalDependencies": { "d3-selection": "^3.0.0", "d3-transition": "^3.0.1" } }, "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw=="], - "@viz-js/viz": ["@viz-js/viz@3.25.0", "", {}, "sha512-dM7zAYMdf7mcRz5Kdb+YJb6+qv5Rjk0rPZ18gROdpMrP/3S7RFOp8uxybeiz5RypHrE1zo1vccA8Twh4mIcLZw=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="], - "@vscode/vsce": ["@vscode/vsce@3.7.1", "", { "dependencies": { "@azure/identity": "^4.1.0", "@secretlint/node": "^10.1.2", "@secretlint/secretlint-formatter-sarif": "^10.1.2", "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", "@vscode/vsce-sign": "^2.0.0", "azure-devops-node-api": "^12.5.0", "chalk": "^4.1.2", "cheerio": "^1.0.0-rc.9", "cockatiel": "^3.1.2", "commander": "^12.1.0", "form-data": "^4.0.0", "glob": "^11.0.0", "hosted-git-info": "^4.0.2", "jsonc-parser": "^3.2.0", "leven": "^3.1.0", "markdown-it": "^14.1.0", "mime": "^1.3.4", "minimatch": "^3.0.3", "parse-semver": "^1.1.1", "read": "^1.0.7", "secretlint": "^10.1.2", "semver": "^7.5.2", "tmp": "^0.2.3", "typed-rest-client": "^1.8.4", "url-join": "^4.0.1", "xml2js": "^0.5.0", "yauzl": "^2.3.1", "yazl": "^2.2.2" }, "optionalDependencies": { "keytar": "^7.7.0" }, "bin": { "vsce": "vsce" } }, "sha512-OTm2XdMt2YkpSn2Nx7z2EJtSuhRHsTPYsSK59hr3v8jRArK+2UEoju4Jumn1CmpgoBLGI6ReHLJ/czYltNUW3g=="], + "@viz-js/viz": ["@viz-js/viz@3.26.0", "", {}, "sha512-q/m3rj4aTs38yezZsWABRSsI1auZIWXk5X2aOdz9B7oPR1eYIn9JMEBXY/HbTJwhgS1vEOESC+m0eO6p3imFyQ=="], + + "@vscode/vsce": ["@vscode/vsce@3.9.0", "", { "dependencies": { "@azure/identity": "^4.1.0", "@secretlint/node": "^10.1.2", "@secretlint/secretlint-formatter-sarif": "^10.1.2", "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", "@vscode/vsce-sign": "^2.0.0", "azure-devops-node-api": "^12.5.0", "chalk": "^4.1.2", "cheerio": "^1.0.0-rc.9", "cockatiel": "^3.1.2", "commander": "^12.1.0", "form-data": "^4.0.0", "glob": "^11.0.0", "hosted-git-info": "^4.0.2", "jsonc-parser": "^3.2.0", "leven": "^3.1.0", "markdown-it": "^14.1.0", "mime": "^1.3.4", "minimatch": "^3.0.3", "parse-semver": "^1.1.1", "read": "^1.0.7", "secretlint": "^10.1.2", "semver": "^7.5.2", "tmp": "^0.2.3", "typed-rest-client": "^1.8.4", "url-join": "^4.0.1", "xml2js": "^0.5.0", "yauzl": "^3.2.1", "yazl": "^2.2.2" }, "optionalDependencies": { "keytar": "^7.7.0" }, "bin": { "vsce": "vsce" } }, "sha512-Dfql2kgPHpTKS+G4wTqYBKzK1KqX2gMxTC9dpnYge+aIZL+thh1Z6GXs4zOtNwo7DCPmS7BCfaHUAmNLxKiXkw=="], "@vscode/vsce-sign": ["@vscode/vsce-sign@2.0.9", "", { "optionalDependencies": { "@vscode/vsce-sign-alpine-arm64": "2.0.6", "@vscode/vsce-sign-alpine-x64": "2.0.6", "@vscode/vsce-sign-darwin-arm64": "2.0.6", "@vscode/vsce-sign-darwin-x64": "2.0.6", "@vscode/vsce-sign-linux-arm": "2.0.6", "@vscode/vsce-sign-linux-arm64": "2.0.6", "@vscode/vsce-sign-linux-x64": "2.0.6", "@vscode/vsce-sign-win32-arm64": "2.0.6", "@vscode/vsce-sign-win32-x64": "2.0.6" } }, "sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g=="], @@ -1148,7 +1240,7 @@ "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], - "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -1190,7 +1282,7 @@ "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], - "astro": ["astro@5.17.2", "", { "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.5", "@astrojs/markdown-remark": "6.3.10", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "acorn": "^8.15.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.3.1", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.1.1", "cssesc": "^3.0.0", "debug": "^4.4.3", "deterministic-object-hash": "^2.0.2", "devalue": "^5.6.2", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.27.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.4.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.1", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.1", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.3", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "svgo": "^4.0.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.3", "unist-util-visit": "^5.0.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^6.4.1", "vitefu": "^1.1.1", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.3", "zod": "^3.25.76", "zod-to-json-schema": "^3.25.1", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "astro.js" } }, "sha512-7jnMqGo53hOQNwo1N/wqeOvUp8wwW/p+DeerSjSkHNx8L/1mhy6P7rVo7EhdmF8DpKqw0tl/B5Fx1WcIzg1ysA=="], + "astro": ["astro@5.18.1", "", { "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.6", "@astrojs/markdown-remark": "6.3.11", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "acorn": "^8.15.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.3.1", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.1.1", "cssesc": "^3.0.0", "debug": "^4.4.3", "deterministic-object-hash": "^2.0.2", "devalue": "^5.6.2", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.27.3", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.4.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.1", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.1", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.3", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "svgo": "^4.0.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.3", "unist-util-visit": "^5.0.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^6.4.1", "vitefu": "^1.1.1", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.3", "zod": "^3.25.76", "zod-to-json-schema": "^3.25.1", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "astro.js" } }, "sha512-m4VWilWZ+Xt6NPoYzC4CgGZim/zQUO7WFL0RHCH0AiEavF1153iC3+me2atDvXpf/yX4PyGUeD8wZLq1cirT3g=="], "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], @@ -1206,9 +1298,9 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.9.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g=="], - "basic-ftp": ["basic-ftp@5.1.0", "", {}, "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw=="], + "basic-ftp": ["basic-ftp@5.3.0", "", {}, "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w=="], "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], @@ -1228,11 +1320,11 @@ "boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="], - "brace-expansion": ["brace-expansion@5.0.3", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA=="], + "brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], @@ -1240,9 +1332,9 @@ "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], - "bun": ["bun@1.3.5", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.5", "@oven/bun-darwin-x64": "1.3.5", "@oven/bun-darwin-x64-baseline": "1.3.5", "@oven/bun-linux-aarch64": "1.3.5", "@oven/bun-linux-aarch64-musl": "1.3.5", "@oven/bun-linux-x64": "1.3.5", "@oven/bun-linux-x64-baseline": "1.3.5", "@oven/bun-linux-x64-musl": "1.3.5", "@oven/bun-linux-x64-musl-baseline": "1.3.5", "@oven/bun-windows-x64": "1.3.5", "@oven/bun-windows-x64-baseline": "1.3.5" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-c1YHIGUfgvYPJmLug5QiLzNWlX2Dg7X/67JWu1Va+AmMXNXzC/KQn2lgQ7rD+n1u1UqDpJMowVGGxTNpbPydNw=="], + "bun": ["bun@1.3.12", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.12", "@oven/bun-darwin-x64": "1.3.12", "@oven/bun-darwin-x64-baseline": "1.3.12", "@oven/bun-linux-aarch64": "1.3.12", "@oven/bun-linux-aarch64-musl": "1.3.12", "@oven/bun-linux-x64": "1.3.12", "@oven/bun-linux-x64-baseline": "1.3.12", "@oven/bun-linux-x64-musl": "1.3.12", "@oven/bun-linux-x64-musl-baseline": "1.3.12", "@oven/bun-windows-aarch64": "1.3.12", "@oven/bun-windows-x64": "1.3.12", "@oven/bun-windows-x64-baseline": "1.3.12" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-KLwDUqs5WIny/94F4xZ4QfaAE6YWyjR+s79pt/ItQhk2CG+PJQ5xL6VuOWhiyN2eP3fryZK95vog9CTLCaYV2Q=="], - "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + "bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], @@ -1254,7 +1346,7 @@ "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], - "caniuse-lite": ["caniuse-lite@1.0.30001761", "", {}, "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g=="], + "caniuse-lite": ["caniuse-lite@1.0.30001788", "", {}, "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], @@ -1272,9 +1364,9 @@ "cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="], - "chevrotain": ["chevrotain@11.0.3", "", { "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", "@chevrotain/regexp-to-ast": "11.0.3", "@chevrotain/types": "11.0.3", "@chevrotain/utils": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw=="], + "chevrotain": ["chevrotain@12.0.0", "", { "dependencies": { "@chevrotain/cst-dts-gen": "12.0.0", "@chevrotain/gast": "12.0.0", "@chevrotain/regexp-to-ast": "12.0.0", "@chevrotain/types": "12.0.0", "@chevrotain/utils": "12.0.0" } }, "sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ=="], - "chevrotain-allstar": ["chevrotain-allstar@0.3.1", "", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^11.0.0" } }, "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw=="], + "chevrotain-allstar": ["chevrotain-allstar@0.4.1", "", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^12.0.0" } }, "sha512-PvVJm3oGqrveUVW2Vt/eZGeiAIsJszYweUcYwcskg9e+IubNYKKD+rHHem7A6XVO22eDAL+inxNIGAzZ/VIWlA=="], "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], @@ -1314,7 +1406,7 @@ "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], - "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], + "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -1322,7 +1414,7 @@ "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], - "cookie-es": ["cookie-es@1.2.2", "", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="], + "cookie-es": ["cookie-es@1.2.3", "", {}, "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw=="], "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], @@ -1336,7 +1428,7 @@ "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], - "css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="], + "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], @@ -1346,7 +1438,7 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - "cytoscape": ["cytoscape@3.33.1", "", {}, "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ=="], + "cytoscape": ["cytoscape@3.33.2", "", {}, "sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw=="], "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], @@ -1416,11 +1508,11 @@ "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], - "dagre-d3-es": ["dagre-d3-es@7.0.13", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q=="], + "dagre-d3-es": ["dagre-d3-es@7.0.14", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg=="], "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], - "dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="], + "dayjs": ["dayjs@1.11.20", "", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], @@ -1436,11 +1528,11 @@ "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], - "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], - "delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="], + "delaunator": ["delaunator@5.1.0", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ=="], "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], @@ -1456,7 +1548,7 @@ "deterministic-object-hash": ["deterministic-object-hash@2.0.2", "", { "dependencies": { "base-64": "^1.0.0" } }, "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ=="], - "devalue": ["devalue@5.6.2", "", {}, "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg=="], + "devalue": ["devalue@5.7.1", "", {}, "sha512-MUbZ586EgQqdRnC4yDrlod3BEdyvE4TapGYHMW2CiaW+KkkFmWEFqBUaLltEZCGi0iFXCEjRF0OjF0DV2QHjOA=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], @@ -1470,13 +1562,15 @@ "dockview-react": ["dockview-react@5.2.0", "", { "dependencies": { "dockview": "^5.2.0" } }, "sha512-xJU5EiViiYYoP0ez5KxN8I+5CWSiPC27KVgVJBpRYRYJN6wKjMUpUqqSHwTlN1PGw5OzCu7UGQlUl1RQew74ag=="], + "dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], - "dompurify": ["dompurify@3.3.3", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA=="], + "dompurify": ["dompurify@3.4.0", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg=="], "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], @@ -1484,15 +1578,15 @@ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], "editions": ["editions@6.22.0", "", { "dependencies": { "version-range": "^4.15.0" } }, "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ=="], "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="], + "effect": ["effect@4.0.0-beta.48", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-MMAM/ZabuNdNmgXiin+BAanQXK7qM8mlt7nfXDoJ/Gn9V8i89JlCq+2N0AiWmqFLXjGLA0u3FjiOjSOYQk5uMw=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.338", "", {}, "sha512-KVQQ3xko9/coDX3qXLUEEbqkKT8L+1DyAovrtu0Khtrt9wjSZ+7CZV4GVzxFy9Oe1NbrIU1oVXCwHJruIA1PNg=="], "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], @@ -1502,12 +1596,14 @@ "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], - "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], + "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], - "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], @@ -1566,19 +1662,25 @@ "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], - "express-rate-limit": ["express-rate-limit@8.3.1", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw=="], + "express-rate-limit": ["express-rate-limit@8.3.2", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg=="], "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], + + "fast-check": ["fast-check@4.6.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-h7H6Dm0Fy+H4ciQYFxFjXnXkzR2kr9Fb22c0UBpHnm59K2zpr2t13aPTHlltFiNT6zuxp6HMPAVVvgur4BLdpA=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], - "fast-xml-parser": ["fast-xml-parser@5.3.5", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-JeaA2Vm9ffQKp9VjvfzObuMCjUYAp5WDYhRYL5LrBPY/jUDlUtOvDfot0vKSkB9tuX885BDHjtw4fZadD95wnA=="], + "fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="], + + "fast-xml-parser": ["fast-xml-parser@5.6.0", "", { "dependencies": { "@nodable/entities": "^1.1.0", "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-5G+uaEBbOm9M4dgMOV3K/rBzfUNGqGqoUTaYJM3hBwM8t71w07gxLQZoTsjkY8FtfjabqgQHEkeIySBDYeBmJw=="], "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], @@ -1588,17 +1690,19 @@ "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], - "file-type": ["file-type@21.3.0", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="], + "file-type": ["file-type@21.3.4", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], + "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], + "flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="], "fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="], - "fontkitten": ["fontkitten@1.0.2", "", { "dependencies": { "tiny-inflate": "^1.0.3" } }, "sha512-piJxbLnkD9Xcyi7dWJRnqszEURixe7CrF/efBfbffe2DPyabmuIuqraruY8cXTs19QoM8VJzx47BDRVNXETM7Q=="], + "fontkitten": ["fontkitten@1.0.3", "", { "dependencies": { "tiny-inflate": "^1.0.3" } }, "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw=="], "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], @@ -1620,7 +1724,7 @@ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - "gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], + "gaxios": ["gaxios@7.1.4", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA=="], "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], @@ -1628,7 +1732,7 @@ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], + "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], @@ -1638,6 +1742,8 @@ "get-source": ["get-source@2.0.12", "", { "dependencies": { "data-uri-to-buffer": "^2.0.0", "source-map": "^0.6.1" } }, "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w=="], + "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], + "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="], "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], @@ -1652,7 +1758,7 @@ "globby": ["globby@14.1.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.3", "ignore": "^7.0.3", "path-type": "^6.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.3.0" } }, "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA=="], - "google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="], + "google-auth-library": ["google-auth-library@10.6.2", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw=="], "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], @@ -1660,12 +1766,12 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - "gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="], - - "h3": ["h3@1.15.5", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg=="], + "h3": ["h3@1.15.11", "", { "dependencies": { "cookie-es": "^1.2.3", "crossws": "^0.3.5", "defu": "^6.1.6", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg=="], "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], + "happy-dom": ["happy-dom@20.9.0", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], @@ -1700,7 +1806,7 @@ "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="], - "hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="], + "hono": ["hono@4.12.14", "", {}, "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w=="], "hosted-git-info": ["hosted-git-info@9.0.2", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg=="], @@ -1730,11 +1836,11 @@ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + "ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], - "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + "internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], @@ -1768,7 +1874,7 @@ "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], - "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], @@ -1806,7 +1912,7 @@ "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], - "katex": ["katex@0.16.28", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg=="], + "katex": ["katex@0.16.45", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA=="], "keytar": ["keytar@7.9.0", "", { "dependencies": { "node-addon-api": "^4.3.0", "prebuild-install": "^7.0.1" } }, "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ=="], @@ -1814,43 +1920,45 @@ "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], - "koffi": ["koffi@2.15.1", "", {}, "sha512-mnc0C0crx/xMSljb5s9QbnLrlFHprioFO1hkXyuSuO/QtbpLDa0l/uM21944UfQunMKmp3/r789DTDxVyyH6aA=="], + "koffi": ["koffi@2.16.0", "", {}, "sha512-h/2NJueOKWd0YYycEOWDspomizgNfuOKf/V7ZE2fytvuRtHoY9Tb+y4x6GJ6pFqaVndWn9dLK+sCI14eWtu5rA=="], + + "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], - "langium": ["langium@3.3.1", "", { "dependencies": { "chevrotain": "~11.0.3", "chevrotain-allstar": "~0.3.0", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.0.8" } }, "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w=="], + "langium": ["langium@4.2.2", "", { "dependencies": { "@chevrotain/regexp-to-ast": "~12.0.0", "chevrotain": "~12.0.0", "chevrotain-allstar": "~0.4.1", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.1.0" } }, "sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ=="], "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], - "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], - "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], - "lodash-es": ["lodash-es@4.17.23", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="], + "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], @@ -1872,10 +1980,12 @@ "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], - "lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="], + "lru-cache": ["lru-cache@11.3.5", "", {}, "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw=="], "lru_map": ["lru_map@0.4.1", "", {}, "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg=="], + "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "magicast": ["magicast@0.5.2", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ=="], @@ -1894,7 +2004,7 @@ "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], - "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], @@ -1924,7 +2034,7 @@ "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], - "mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="], + "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="], @@ -1934,7 +2044,7 @@ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - "mermaid": ["mermaid@11.12.2", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", "@mermaid-js/parser": "^0.6.3", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.13", "dayjs": "^1.11.18", "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", "lodash-es": "^4.17.21", "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0" } }, "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w=="], + "mermaid": ["mermaid@11.14.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.1.0", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.1", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.19", "dompurify": "^3.3.1", "katex": "^0.16.25", "khroma": "^2.1.0", "lodash-es": "^4.17.23", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0" } }, "sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g=="], "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], @@ -2016,9 +2126,9 @@ "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], - "miniflare": ["miniflare@3.20250718.3", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250718.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-JuPrDJhwLrNLEJiNLWO7ZzJrv/Vv9kZuwMYCfv0LskQDM6Eonw4OvywO3CH/wCGjgHzha/qyjUh8JQ068TjDgQ=="], + "miniflare": ["miniflare@4.20260415.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.8", "workerd": "1.20260415.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-JoExRWN4YBI2luA5BoSMFEgi8rQWXUGzo3mtE+58VXCLV3jj/Xnk5Yeqs/IXWz8Es5GJIaq6BtsixDvAxXSIng=="], - "minimatch": ["minimatch@10.2.2", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw=="], + "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], @@ -2026,7 +2136,7 @@ "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], - "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], + "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], "motion": ["motion@12.38.0", "", { "dependencies": { "framer-motion": "^12.38.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w=="], @@ -2038,6 +2148,12 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "msgpackr": ["msgpackr@1.11.9", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-FkoAAyyA6HM8wL882EcEyFZ9s7hVADSwG9xrVx3dxxNQAtgADTrJoEWivID82Iv1zWDsv/OtbrrcZAzGzOMdNw=="], + + "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="], + + "multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="], + "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], "mute-stream": ["mute-stream@0.0.8", "", {}, "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="], @@ -2052,11 +2168,11 @@ "neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="], - "netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="], + "netmask": ["netmask@2.1.1", "", {}, "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="], "nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="], - "node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="], + "node-abi": ["node-abi@3.89.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA=="], "node-addon-api": ["node-addon-api@4.3.0", "", {}, "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="], @@ -2066,9 +2182,11 @@ "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], + "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], + "node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="], - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], + "node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="], "node-sarif-builder": ["node-sarif-builder@3.4.0", "", { "dependencies": { "@types/sarif": "^2.1.7", "fs-extra": "^11.1.1" } }, "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg=="], @@ -2092,13 +2210,13 @@ "oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="], - "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="], + "oniguruma-to-es": ["oniguruma-to-es@4.3.5", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ=="], "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], - "openai": ["openai@6.10.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-ITxOGo7rO3XRMiKA5l7tQ43iNNu+iXGFAcf2t+aWVzzqRaS0i7m1K2BhxNdaveB+5eENhO0VY1FkiZzhBk4v3A=="], + "openai": ["openai@6.26.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA=="], - "overlayscrollbars": ["overlayscrollbars@2.14.0", "", {}, "sha512-RjV0pqc79kYhQLC3vTcLRb5GLpI1n6qh0Oua3g+bGH4EgNOJHVBGP7u0zZtxoAa0dkHlAqTTSYRb9MMmxNLjig=="], + "overlayscrollbars": ["overlayscrollbars@2.15.1", "", {}, "sha512-glX26JwjL+Tkzv0JNOWdW4VozP5dGXO+Wx8+TPrdTEJTSYT/8eJS8yXM+fewjU0nFq/JeCa+X+BqABNjC4YZSA=="], "overlayscrollbars-react": ["overlayscrollbars-react@0.5.6", "", { "peerDependencies": { "overlayscrollbars": "^2.0.0", "react": ">=16.8.0" } }, "sha512-E5To04bL5brn9GVCZ36SnfGanxa2I2MDkWoa4Cjo5wol7l+diAgi4DBc983V7l2nOk/OLJ6Feg4kySspQEGDBw=="], @@ -2140,6 +2258,8 @@ "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], + "path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], @@ -2152,13 +2272,13 @@ "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], - "perfect-freehand": ["perfect-freehand@1.2.2", "", {}, "sha512-eh31l019WICQ03pkF3FSzHxB8n07ItqIQ++G5UV8JX0zVOXzgTGCqnRR0jJ2h9U8/2uW4W4mtGJELt9kEV0CFQ=="], + "perfect-freehand": ["perfect-freehand@1.2.3", "", {}, "sha512-bHZSfqDHGNlPpgH2yxXgPHlQSPpEbo+qg7li0M78J9vNAi2yjwLeA4x79BEQhX44lEWpCLSFCeRZwpw0niiXPA=="], "piccolore": ["piccolore@0.1.3", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], @@ -2172,10 +2292,12 @@ "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "postcss": ["postcss@8.5.10", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ=="], "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], + "printable-characters": ["printable-characters@1.0.42", "", {}, "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ=="], "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], @@ -2186,7 +2308,7 @@ "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], - "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "protobufjs": ["protobufjs@7.5.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg=="], "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], @@ -2198,7 +2320,9 @@ "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], - "qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="], + "pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="], + + "qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="], "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], @@ -2212,9 +2336,11 @@ "rc-config-loader": ["rc-config-loader@4.1.4", "", { "dependencies": { "debug": "^4.4.3", "js-yaml": "^4.1.1", "json5": "^2.2.3", "require-from-string": "^2.0.2" } }, "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ=="], - "react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + "react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="], + + "react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="], - "react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], + "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], @@ -2284,11 +2410,9 @@ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], + "robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="], - "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], - - "rollup": ["rollup@4.54.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.54.0", "@rollup/rollup-android-arm64": "4.54.0", "@rollup/rollup-darwin-arm64": "4.54.0", "@rollup/rollup-darwin-x64": "4.54.0", "@rollup/rollup-freebsd-arm64": "4.54.0", "@rollup/rollup-freebsd-x64": "4.54.0", "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", "@rollup/rollup-linux-arm-musleabihf": "4.54.0", "@rollup/rollup-linux-arm64-gnu": "4.54.0", "@rollup/rollup-linux-arm64-musl": "4.54.0", "@rollup/rollup-linux-loong64-gnu": "4.54.0", "@rollup/rollup-linux-ppc64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-musl": "4.54.0", "@rollup/rollup-linux-s390x-gnu": "4.54.0", "@rollup/rollup-linux-x64-gnu": "4.54.0", "@rollup/rollup-linux-x64-musl": "4.54.0", "@rollup/rollup-openharmony-arm64": "4.54.0", "@rollup/rollup-win32-arm64-msvc": "4.54.0", "@rollup/rollup-win32-ia32-msvc": "4.54.0", "@rollup/rollup-win32-x64-gnu": "4.54.0", "@rollup/rollup-win32-x64-msvc": "4.54.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw=="], + "rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="], "rollup-plugin-inject": ["rollup-plugin-inject@3.0.2", "", { "dependencies": { "estree-walker": "^0.6.1", "magic-string": "^0.25.3", "rollup-pluginutils": "^2.8.1" } }, "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w=="], @@ -2310,7 +2434,7 @@ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - "sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], + "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], @@ -2330,11 +2454,11 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - "shiki": ["shiki@3.21.0", "", { "dependencies": { "@shikijs/core": "3.21.0", "@shikijs/engine-javascript": "3.21.0", "@shikijs/engine-oniguruma": "3.21.0", "@shikijs/langs": "3.21.0", "@shikijs/themes": "3.21.0", "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w=="], + "shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="], "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], - "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="], "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], @@ -2350,7 +2474,7 @@ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], - "sitemap": ["sitemap@8.0.2", "", { "dependencies": { "@types/node": "^17.0.5", "@types/sax": "^1.2.1", "arg": "^5.0.0", "sax": "^1.4.1" }, "bin": { "sitemap": "dist/cli.js" } }, "sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ=="], + "sitemap": ["sitemap@9.0.1", "", { "dependencies": { "@types/node": "^24.9.2", "@types/sax": "^1.2.1", "arg": "^5.0.0", "sax": "^1.4.1" }, "bin": { "sitemap": "dist/esm/cli.js" } }, "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ=="], "slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], @@ -2358,7 +2482,7 @@ "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], - "smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="], + "smol-toml": ["smol-toml@1.6.1", "", {}, "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg=="], "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], @@ -2380,7 +2504,7 @@ "spdx-license-ids": ["spdx-license-ids@3.0.23", "", {}, "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw=="], - "stacktracey": ["stacktracey@2.1.8", "", { "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" } }, "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw=="], + "stacktracey": ["stacktracey@2.2.0", "", { "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" } }, "sha512-ETyQEz+CzXiLjEbyJqpbp+/T79RQD/6wqFucRBIlVNZfYq2Ay7wbretD4cxpbymZlaPWx58aIhPEY1Cr8DlVvg=="], "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], @@ -2392,21 +2516,17 @@ "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], - "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], - "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], - - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], - "strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "strnum": ["strnum@2.2.3", "", {}, "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg=="], - "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], + "strtok3": ["strtok3@10.3.5", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA=="], "structured-source": ["structured-source@4.0.0", "", { "dependencies": { "boundary": "^2.0.0" } }, "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA=="], @@ -2420,13 +2540,13 @@ "supports-hyperlinks": ["supports-hyperlinks@3.2.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig=="], - "svgo": ["svgo@4.0.0", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.4.1" }, "bin": "./bin/svgo.js" }, "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw=="], + "svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="], "table": ["table@6.9.0", "", { "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } }, "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A=="], - "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], + "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="], - "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="], "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], @@ -2444,9 +2564,9 @@ "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], - "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], + "tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="], - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], @@ -2456,6 +2576,8 @@ "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="], + "toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="], + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], @@ -2494,17 +2616,17 @@ "underscore": ["underscore@1.13.8", "", {}, "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ=="], - "undici": ["undici@7.22.0", "", {}, "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg=="], + "undici": ["undici@7.25.0", "", {}, "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ=="], - "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], - "unenv": ["unenv@2.0.0-rc.14", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.1", "ohash": "^2.0.10", "pathe": "^2.0.3", "ufo": "^1.5.4" } }, "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q=="], + "unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="], "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], - "unifont": ["unifont@0.7.3", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-b0GtQzKCyuSHGsfj5vyN8st7muZ6VCI4XD4vFlr7Uy1rlWVYxC3npnfk8MyreHxJYrz1ooLDqDzFe9XqQTlAhA=="], + "unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="], "unique-username-generator": ["unique-username-generator@1.5.1", "", { "bin": { "usernamegen": "dist/cli.js", "usergen": "dist/cli.js", "unique-username": "dist/cli.js", "uuname": "dist/cli.js" } }, "sha512-Q0pSKPyij4L7Tm6Bo3XsWeFG9qbyWTtwb3jTN+XgGhCFdlvQn6Fj0DCfYElw0kp/Xp7Jv1Q+CL+aA8S07RMChA=="], @@ -2522,7 +2644,7 @@ "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], - "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], "unist-util-visit-children": ["unist-util-visit-children@3.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA=="], @@ -2532,7 +2654,7 @@ "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], - "unstorage": ["unstorage@1.17.4", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw=="], + "unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="], "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], @@ -2558,11 +2680,11 @@ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - "vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], + "vite": ["vite@6.4.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ=="], - "vite-plugin-singlefile": ["vite-plugin-singlefile@2.3.0", "", { "dependencies": { "micromatch": "^4.0.8" }, "peerDependencies": { "rollup": "^4.44.1", "vite": "^5.4.11 || ^6.0.0 || ^7.0.0" } }, "sha512-DAcHzYypM0CasNLSz/WG0VdKOCxGHErfrjOoyIPiNxTPTGmO6rRD/te93n1YL/s+miXq66ipF1brMBikf99c6A=="], + "vite-plugin-singlefile": ["vite-plugin-singlefile@2.3.2", "", { "dependencies": { "micromatch": "^4.0.8" }, "peerDependencies": { "rollup": "^4.59.0", "vite": "^5.4.11 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-b8SxCi/gG7K298oJDcKOuZeU6gf6wIcCJAaEqUmmZXdjfuONlkyNyWZC3tEbN6QockRCNUd3it9eGTtpHGoYmg=="], - "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], + "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="], "vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="], @@ -2574,7 +2696,7 @@ "vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], - "vscode-uri": ["vscode-uri@3.0.8", "", {}, "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="], + "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], @@ -2582,7 +2704,7 @@ "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], - "whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], @@ -2590,17 +2712,15 @@ "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], - "workerd": ["workerd@1.20250718.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250718.0", "@cloudflare/workerd-darwin-arm64": "1.20250718.0", "@cloudflare/workerd-linux-64": "1.20250718.0", "@cloudflare/workerd-linux-arm64": "1.20250718.0", "@cloudflare/workerd-windows-64": "1.20250718.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-kqkIJP/eOfDlUyBzU7joBg+tl8aB25gEAGqDap+nFWb+WHhnooxjGHgxPBy3ipw2hnShPFNOQt5lFRxbwALirg=="], + "workerd": ["workerd@1.20260415.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260415.1", "@cloudflare/workerd-darwin-arm64": "1.20260415.1", "@cloudflare/workerd-linux-64": "1.20260415.1", "@cloudflare/workerd-linux-arm64": "1.20260415.1", "@cloudflare/workerd-windows-64": "1.20260415.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-phyPjRnx+mQDfkhN9ENPioL1L0SdhYs4S0YmJK/xF9Oga+ykNfdSy1MHnsOj8yqnOV96zcVQMx32dJ0r3pq0jQ=="], - "wrangler": ["wrangler@3.114.17", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@cloudflare/unenv-preset": "2.0.2", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-modules-polyfill": "0.2.2", "blake3-wasm": "2.1.5", "esbuild": "0.17.19", "miniflare": "3.20250718.3", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.14", "workerd": "1.20250718.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250408.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-tAvf7ly+tB+zwwrmjsCyJ2pJnnc7SZhbnNwXbH+OIdVas3zTSmjcZOjmLKcGGptssAA3RyTKhcF9BvKZzMUycA=="], + "wrangler": ["wrangler@4.83.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.16.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260415.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260415.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260415.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-gw5g3LCiuAqVWxaoKY6+quE0HzAUEFb/FV3oAlNkE1ttd4XP3FiV91XDkkzUCcdqxS4WjhQvPhIDBNdhEi8P0A=="], "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], + "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], @@ -2614,13 +2734,13 @@ "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], + "yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], "yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], + "yauzl": ["yauzl@3.3.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "pend": "~1.2.0" } }, "sha512-PtGEvEP30p7sbIBJKUBjUnqgTVOyMURc4dLo9iNyAJnNIEz9pm88cCXF21w94Kg3k6RXkeZh5DHOGS0qEONvNQ=="], "yazl": ["yazl@2.5.1", "", { "dependencies": { "buffer-crc32": "~0.2.3" } }, "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw=="], @@ -2630,11 +2750,13 @@ "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], - "youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="], + "youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="], + + "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], - "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], + "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], "zod-to-ts": ["zod-to-ts@1.2.0", "", { "peerDependencies": { "typescript": "^4.9.4 || ^5.0.2", "zod": "^3" } }, "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA=="], @@ -2642,78 +2764,66 @@ "@astrojs/react/@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], - "@astrojs/sitemap/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - "@aws-sdk/client-sso/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], - - "@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iOq86f2H67924kQUIPOAvlmMaOAvOLoDOIb66I2YqSUpMYB6ufiuJW3RlREgskxv86S5qKzMnfy/X6CqMjK6XQ=="], + "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1026.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.27", "@aws-sdk/nested-clients": "^3.996.19", "@aws-sdk/types": "^3.973.7", "@smithy/property-provider": "^4.2.13", "@smithy/shared-ini-file-loader": "^4.4.8", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-Ieq/HiRrbEtrYP387Nes0XlR7H1pJiJOZKv+QyQzMYpvTiDs0VKy2ZB3E2Zf+aFovWmeE7lRE4lXyF7dYM6GgA=="], - "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iOq86f2H67924kQUIPOAvlmMaOAvOLoDOIb66I2YqSUpMYB6ufiuJW3RlREgskxv86S5qKzMnfy/X6CqMjK6XQ=="], - - "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.993.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-+35g4c+8r7sB9Sjp1KPdM8qxGn6B/shBjJtEUN4e+Edw9UEQlZKIzioOGu3UAbyE0a/s450LdLZr4wbJChtmww=="], - - "@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iOq86f2H67924kQUIPOAvlmMaOAvOLoDOIb66I2YqSUpMYB6ufiuJW3RlREgskxv86S5qKzMnfy/X6CqMjK6XQ=="], - - "@aws-sdk/middleware-user-agent/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], - - "@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], + "@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="], "@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@chevrotain/cst-dts-gen/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], - - "@chevrotain/gast/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + "@jridgewell/gen-mapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@cloudflare/kv-asset-handler/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], - - "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], - - "@google/genai/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], + "@jridgewell/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], "@mariozechner/pi-ai/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.73.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw=="], - "@mariozechner/pi-coding-agent/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="], - "@mariozechner/pi-coding-agent/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], "@mariozechner/pi-tui/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], - "@mistralai/mistralai/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@opencode-ai/plugin/@opencode-ai/sdk": ["@opencode-ai/sdk@1.1.25", "", {}, "sha512-mWUX489ArEF2ICg3iZsx2VQaGS3Z2j/dwAJDacao9t7dGDzjOIaacPw2weZ10zld7XmT9V9C0PM/A5lDZ52J+w=="], + "@opencode-ai/plugin/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], "@pierre/diffs/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="], - "@plannotator/hooks/@types/node": ["@types/node@22.19.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA=="], + "@plannotator/hooks/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], + + "@plannotator/paste-service/wrangler": ["wrangler@3.114.17", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@cloudflare/unenv-preset": "2.0.2", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-modules-polyfill": "0.2.2", "blake3-wasm": "2.1.5", "esbuild": "0.17.19", "miniflare": "3.20250718.3", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.14", "workerd": "1.20250718.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250408.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-tAvf7ly+tB+zwwrmjsCyJ2pJnnc7SZhbnNwXbH+OIdVas3zTSmjcZOjmLKcGGptssAA3RyTKhcF9BvKZzMUycA=="], + + "@plannotator/portal/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], - "@plannotator/portal/@types/node": ["@types/node@22.19.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA=="], + "@plannotator/review/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], - "@plannotator/review/@types/node": ["@types/node@22.19.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA=="], + "@poppinss/colors/kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + + "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.0", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA=="], + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], + "@textlint/linter-formatter/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "@textlint/linter-formatter/pluralize": ["pluralize@2.0.0", "", {}, "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw=="], @@ -2722,8 +2832,6 @@ "@textlint/linter-formatter/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@types/sax/@types/node": ["@types/node@22.19.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA=="], - "@vscode/vsce/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "@vscode/vsce/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], @@ -2734,21 +2842,17 @@ "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - "astro/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="], - - "astro/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], + "astro/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], "astro/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "bun-types/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], - "cheerio/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], "cheerio/parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], - "chevrotain/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + "cheerio/whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -2772,12 +2876,18 @@ "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], + "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "effect/uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], + "encoding-sniffer/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + "extract-zip/yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], + "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], @@ -2790,27 +2900,19 @@ "hast-util-raw/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], - "htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], - "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], - "magicast/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], - - "magicast/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "markdown-it/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], - "mermaid/dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="], - "mermaid/marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - "miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], + "miniflare/undici": ["undici@7.24.8", "", {}, "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ=="], - "miniflare/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], - - "miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], + "miniflare/ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], "node-fetch/data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], @@ -2826,13 +2928,15 @@ "parse5-parser-stream/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], - "protobufjs/@types/node": ["@types/node@22.19.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA=="], + "pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], "proxy-agent/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], - "read-pkg/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + "rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], - "rimraf/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "read-pkg/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], "rollup-plugin-inject/estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="], @@ -2840,15 +2944,9 @@ "rollup-pluginutils/estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="], - "router/path-to-regexp": ["path-to-regexp@8.4.0", "", {}, "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg=="], - - "sitemap/@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="], - - "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], - "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "sitemap/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], "svgo/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], @@ -2860,22 +2958,14 @@ "whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], - "wrangler/esbuild": ["esbuild@0.17.19", "", { "optionalDependencies": { "@esbuild/android-arm": "0.17.19", "@esbuild/android-arm64": "0.17.19", "@esbuild/android-x64": "0.17.19", "@esbuild/darwin-arm64": "0.17.19", "@esbuild/darwin-x64": "0.17.19", "@esbuild/freebsd-arm64": "0.17.19", "@esbuild/freebsd-x64": "0.17.19", "@esbuild/linux-arm": "0.17.19", "@esbuild/linux-arm64": "0.17.19", "@esbuild/linux-ia32": "0.17.19", "@esbuild/linux-loong64": "0.17.19", "@esbuild/linux-mips64el": "0.17.19", "@esbuild/linux-ppc64": "0.17.19", "@esbuild/linux-riscv64": "0.17.19", "@esbuild/linux-s390x": "0.17.19", "@esbuild/linux-x64": "0.17.19", "@esbuild/netbsd-x64": "0.17.19", "@esbuild/openbsd-x64": "0.17.19", "@esbuild/sunos-x64": "0.17.19", "@esbuild/win32-arm64": "0.17.19", "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw=="], - - "wrangler/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], + "wrangler/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], - "youch/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - "zod-to-ts/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@astrojs/react/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], @@ -2886,17 +2976,23 @@ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], - "@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], + "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], + "@plannotator/hooks/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iOq86f2H67924kQUIPOAvlmMaOAvOLoDOIb66I2YqSUpMYB6ufiuJW3RlREgskxv86S5qKzMnfy/X6CqMjK6XQ=="], + "@plannotator/paste-service/wrangler/@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="], - "@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], + "@plannotator/paste-service/wrangler/@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.0.2", "", { "peerDependencies": { "unenv": "2.0.0-rc.14", "workerd": "^1.20250124.0" }, "optionalPeers": ["workerd"] }, "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg=="], - "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "@plannotator/paste-service/wrangler/esbuild": ["esbuild@0.17.19", "", { "optionalDependencies": { "@esbuild/android-arm": "0.17.19", "@esbuild/android-arm64": "0.17.19", "@esbuild/android-x64": "0.17.19", "@esbuild/darwin-arm64": "0.17.19", "@esbuild/darwin-x64": "0.17.19", "@esbuild/freebsd-arm64": "0.17.19", "@esbuild/freebsd-x64": "0.17.19", "@esbuild/linux-arm": "0.17.19", "@esbuild/linux-arm64": "0.17.19", "@esbuild/linux-ia32": "0.17.19", "@esbuild/linux-loong64": "0.17.19", "@esbuild/linux-mips64el": "0.17.19", "@esbuild/linux-ppc64": "0.17.19", "@esbuild/linux-riscv64": "0.17.19", "@esbuild/linux-s390x": "0.17.19", "@esbuild/linux-x64": "0.17.19", "@esbuild/netbsd-x64": "0.17.19", "@esbuild/openbsd-x64": "0.17.19", "@esbuild/sunos-x64": "0.17.19", "@esbuild/win32-arm64": "0.17.19", "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw=="], - "@plannotator/hooks/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "@plannotator/paste-service/wrangler/miniflare": ["miniflare@3.20250718.3", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250718.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-JuPrDJhwLrNLEJiNLWO7ZzJrv/Vv9kZuwMYCfv0LskQDM6Eonw4OvywO3CH/wCGjgHzha/qyjUh8JQ068TjDgQ=="], + + "@plannotator/paste-service/wrangler/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], + + "@plannotator/paste-service/wrangler/unenv": ["unenv@2.0.0-rc.14", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.1", "ohash": "^2.0.10", "pathe": "^2.0.3", "ufo": "^1.5.4" } }, "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q=="], + + "@plannotator/paste-service/wrangler/workerd": ["workerd@1.20250718.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250718.0", "@cloudflare/workerd-darwin-arm64": "1.20250718.0", "@cloudflare/workerd-linux-64": "1.20250718.0", "@cloudflare/workerd-linux-arm64": "1.20250718.0", "@cloudflare/workerd-windows-64": "1.20250718.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-kqkIJP/eOfDlUyBzU7joBg+tl8aB25gEAGqDap+nFWb+WHhnooxjGHgxPBy3ipw2hnShPFNOQt5lFRxbwALirg=="], "@plannotator/portal/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], @@ -2906,69 +3002,67 @@ "@textlint/linter-formatter/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@types/sax/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - - "@vscode/vsce/glob/minimatch": ["minimatch@10.2.2", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw=="], + "@vscode/vsce/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "@vscode/vsce/hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - "@vscode/vsce/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "@vscode/vsce/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "astro/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], + "astro/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], - "astro/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], + "astro/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], - "astro/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], + "astro/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], - "astro/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], + "astro/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], - "astro/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], + "astro/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], - "astro/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], + "astro/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], - "astro/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], + "astro/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], - "astro/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], + "astro/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], - "astro/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], + "astro/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], - "astro/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], + "astro/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], - "astro/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], + "astro/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], - "astro/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], + "astro/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], - "astro/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], + "astro/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], - "astro/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], + "astro/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], - "astro/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], + "astro/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], - "astro/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], + "astro/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], - "astro/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], + "astro/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], - "astro/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], + "astro/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], - "astro/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], + "astro/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], - "astro/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], + "astro/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], - "astro/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], + "astro/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], - "astro/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], + "astro/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], - "astro/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], + "astro/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], - "astro/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], + "astro/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], - "astro/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], + "astro/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], - "astro/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], + "astro/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], "cheerio/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], @@ -2980,8 +3074,6 @@ "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], - "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], - "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], @@ -2994,15 +3086,7 @@ "parse5-parser-stream/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - "protobufjs/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - - "rimraf/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "rimraf/glob/minimatch": ["minimatch@9.0.6", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ=="], - - "rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - - "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "sitemap/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "table/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -3050,6 +3134,8 @@ "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + "vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], @@ -3058,118 +3144,174 @@ "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], - "wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.17.19", "", { "os": "android", "cpu": "arm" }, "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A=="], + "wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], - "wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.17.19", "", { "os": "android", "cpu": "arm64" }, "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA=="], + "wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], - "wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.17.19", "", { "os": "android", "cpu": "x64" }, "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww=="], + "wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], - "wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.17.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg=="], + "wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], - "wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.17.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw=="], + "wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], - "wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.17.19", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ=="], + "wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], - "wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.17.19", "", { "os": "freebsd", "cpu": "x64" }, "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ=="], + "wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], - "wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.17.19", "", { "os": "linux", "cpu": "arm" }, "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA=="], + "wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], - "wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.17.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg=="], + "wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], - "wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.17.19", "", { "os": "linux", "cpu": "ia32" }, "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ=="], + "wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], - "wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ=="], + "wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], - "wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A=="], + "wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], - "wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.17.19", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg=="], + "wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], - "wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA=="], + "wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], - "wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.17.19", "", { "os": "linux", "cpu": "s390x" }, "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q=="], + "wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], - "wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.17.19", "", { "os": "linux", "cpu": "x64" }, "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw=="], + "wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], - "wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.17.19", "", { "os": "none", "cpu": "x64" }, "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q=="], + "wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], - "wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.17.19", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g=="], + "wrangler/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], - "wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.17.19", "", { "os": "sunos", "cpu": "x64" }, "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg=="], + "wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], - "wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.17.19", "", { "os": "win32", "cpu": "arm64" }, "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag=="], + "wrangler/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], - "wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.17.19", "", { "os": "win32", "cpu": "ia32" }, "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw=="], + "wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], - "wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.17.19", "", { "os": "win32", "cpu": "x64" }, "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA=="], + "wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], - "wrangler/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], + "wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], - "wrangler/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], + "wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], - "wrangler/sharp/@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="], + "wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], - "wrangler/sharp/@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="], + "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "wrangler/sharp/@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="], + "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "wrangler/sharp/@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - "wrangler/sharp/@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="], + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - "wrangler/sharp/@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="], + "@plannotator/paste-service/wrangler/@cloudflare/kv-asset-handler/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], - "wrangler/sharp/@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.17.19", "", { "os": "android", "cpu": "arm" }, "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A=="], - "wrangler/sharp/@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.17.19", "", { "os": "android", "cpu": "arm64" }, "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA=="], - "wrangler/sharp/@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.17.19", "", { "os": "android", "cpu": "x64" }, "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww=="], - "wrangler/sharp/@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.17.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg=="], - "wrangler/sharp/@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.17.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw=="], - "wrangler/sharp/@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.17.19", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ=="], - "wrangler/sharp/@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.17.19", "", { "os": "freebsd", "cpu": "x64" }, "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ=="], - "wrangler/sharp/@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.17.19", "", { "os": "linux", "cpu": "arm" }, "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA=="], - "wrangler/sharp/@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.17.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg=="], - "wrangler/sharp/@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.17.19", "", { "os": "linux", "cpu": "ia32" }, "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ=="], - "wrangler/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ=="], - "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A=="], - "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.17.19", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg=="], - "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA=="], - "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.17.19", "", { "os": "linux", "cpu": "s390x" }, "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q=="], - "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.17.19", "", { "os": "linux", "cpu": "x64" }, "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw=="], - "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.17.19", "", { "os": "none", "cpu": "x64" }, "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q=="], - "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.17.19", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g=="], - "@vscode/vsce/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.17.19", "", { "os": "sunos", "cpu": "x64" }, "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg=="], - "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.17.19", "", { "os": "win32", "cpu": "arm64" }, "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag=="], - "rimraf/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.17.19", "", { "os": "win32", "cpu": "ia32" }, "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw=="], - "rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "@plannotator/paste-service/wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.17.19", "", { "os": "win32", "cpu": "x64" }, "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA=="], - "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@plannotator/paste-service/wrangler/miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], + + "@plannotator/paste-service/wrangler/miniflare/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + + "@plannotator/paste-service/wrangler/miniflare/ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], + + "@plannotator/paste-service/wrangler/miniflare/youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="], + + "@plannotator/paste-service/wrangler/miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="], - "rimraf/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + "@plannotator/paste-service/wrangler/sharp/@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="], - "rimraf/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + "@plannotator/paste-service/wrangler/sharp/@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="], - "rimraf/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "@plannotator/paste-service/wrangler/sharp/@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="], + + "@plannotator/paste-service/wrangler/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], + + "@plannotator/paste-service/wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250718.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-FHf4t7zbVN8yyXgQ/r/GqLPaYZSGUVzeR7RnL28Mwj2djyw2ZergvytVc7fdGcczl6PQh+VKGfZCfUqpJlbi9g=="], + + "@plannotator/paste-service/wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250718.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fUiyUJYyqqp4NqJ0YgGtp4WJh/II/YZsUnEb6vVy5Oeas8lUOxnN+ZOJ8N/6/5LQCVAtYCChRiIrBbfhTn5Z8Q=="], + + "@plannotator/paste-service/wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250718.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5+eb3rtJMiEwp08Kryqzzu8d1rUcK+gdE442auo5eniMpT170Dz0QxBrqkg2Z48SFUPYbj+6uknuA5tzdRSUSg=="], + + "@plannotator/paste-service/wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250718.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Aa2M/DVBEBQDdATMbn217zCSFKE+ud/teS+fFS+OQqKABLn0azO2qq6ANAHYOIE6Q3Sq4CxDIQr8lGdaJHwUog=="], + + "@plannotator/paste-service/wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250718.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dY16RXKffmugnc67LTbyjdDHZn5NoTF1yHEf2fN4+OaOnoGSp3N1x77QubTDwqZ9zECWxgQfDLjddcH8dWeFhg=="], + + "@vscode/vsce/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "rimraf/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "@plannotator/paste-service/wrangler/miniflare/youch/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], } } diff --git a/package.json b/package.json index 17e747af..1601534a 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,9 @@ "dev:portal": "bun run --cwd apps/portal dev", "dev:marketing": "bun run --cwd apps/marketing dev", "dev:review": "bun run --cwd apps/review dev", + "dev:room": "bun run --cwd apps/room-service dev", + "dev:live-room": "scripts/dev-live-room-local.sh", + "agent:run": "bun run apps/collab-agent/index.ts", "build:hook": "bun run --cwd apps/hook build", "build:portal": "bun run --cwd apps/portal build", "build:marketing": "bun run --cwd apps/marketing build", @@ -29,8 +32,8 @@ "dev:vscode": "bun run --cwd apps/vscode-extension watch", "build:vscode": "bun run --cwd apps/vscode-extension build", "package:vscode": "bun run --cwd apps/vscode-extension package", - "test": "bun test", - "typecheck": "bash apps/pi-extension/vendor.sh && tsc --noEmit -p packages/shared/tsconfig.json && tsc --noEmit -p packages/ai/tsconfig.json && tsc --noEmit -p packages/server/tsconfig.json && tsc --noEmit -p packages/ui/tsconfig.json && tsc --noEmit -p apps/pi-extension/tsconfig.json" + "test": "bun test --path-ignore-patterns 'packages/ui/**' --path-ignore-patterns 'packages/editor/**' && bun test --cwd packages/ui && bun test --cwd packages/editor", + "typecheck": "bash apps/pi-extension/vendor.sh && bunx tsc --noEmit -p packages/shared/tsconfig.json && bunx tsc --noEmit -p packages/ai/tsconfig.json && bunx tsc --noEmit -p packages/server/tsconfig.json && bunx tsc --noEmit -p packages/ui/tsconfig.slice5.json && bunx tsc --noEmit -p packages/editor/tsconfig.json && bunx tsc --noEmit -p apps/pi-extension/tsconfig.json" }, "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.92", @@ -46,6 +49,7 @@ "@types/dompurify": "^3.2.0", "@types/node": "^25.5.2", "@types/turndown": "^5.0.6", - "bun-types": "^1.3.11" + "bun-types": "^1.3.11", + "wrangler": "^4.81.1" } } diff --git a/packages/editor/App.tsx b/packages/editor/App.tsx index 5ade5dca..52cc0b5a 100644 --- a/packages/editor/App.tsx +++ b/packages/editor/App.tsx @@ -20,6 +20,10 @@ import { getCallbackConfig, CallbackAction, executeCallback, type ToastPayload } import { useAgents } from '@plannotator/ui/hooks/useAgents'; import { useActiveSection } from '@plannotator/ui/hooks/useActiveSection'; import { storage } from '@plannotator/ui/utils/storage'; +import { + getIdentity, + getPresenceColor, +} from '@plannotator/ui/utils/identity'; import { configStore } from '@plannotator/ui/config'; import { CompletionOverlay } from '@plannotator/ui/components/CompletionOverlay'; import { UpdateBanner } from '@plannotator/ui/components/UpdateBanner'; @@ -57,7 +61,13 @@ import { useArchive } from '@plannotator/ui/hooks/useArchive'; import { useEditorAnnotations } from '@plannotator/ui/hooks/useEditorAnnotations'; import { useExternalAnnotations } from '@plannotator/ui/hooks/useExternalAnnotations'; import { useExternalAnnotationHighlights } from '@plannotator/ui/hooks/useExternalAnnotationHighlights'; +import { useAnnotationHighlightReconciler } from '@plannotator/ui/hooks/useAnnotationHighlightReconciler'; +import { useRoomAdminActions } from '@plannotator/ui/hooks/collab/useRoomAdminActions'; +import { RoomHeaderControls } from '@plannotator/ui/components/collab/RoomHeaderControls'; +import { RoomAdminErrorToast } from '@plannotator/ui/components/collab/RoomAdminErrorToast'; +import { ImageStripNotice } from '@plannotator/ui/components/collab/ImageStripNotice'; import { buildPlanAgentInstructions } from '@plannotator/ui/utils/planAgentInstructions'; +import { buildRoomAgentInstructions } from '@plannotator/ui/utils/roomAgentInstructions'; import { hasNewSettings, markNewSettingsSeen } from '@plannotator/ui/utils/newSettingsHint'; import { useFileBrowser } from '@plannotator/ui/hooks/useFileBrowser'; import { isVaultBrowserEnabled } from '@plannotator/ui/utils/obsidian'; @@ -80,7 +90,10 @@ const USE_DIFF_DEMO = const DEMO_PLAN_CONTENT = USE_DIFF_DEMO ? DIFF_DEMO_PLAN_CONTENT : DEFAULT_DEMO_PLAN_CONTENT; -import { useCheckboxOverrides } from './hooks/useCheckboxOverrides'; +import { useCheckboxOverrides, derivePendingCheckboxBlockIds } from './hooks/useCheckboxOverrides'; +import { useAnnotationController } from '@plannotator/ui/hooks/useAnnotationController'; +import { StartRoomModal } from '@plannotator/ui/components/collab/StartRoomModal'; +import { useStartLiveRoom } from './hooks/collab/useStartLiveRoom'; type NoteAutoSaveResults = { obsidian?: boolean; @@ -88,9 +101,85 @@ type NoteAutoSaveResults = { octarine?: boolean; }; -const App: React.FC = () => { - const [markdown, setMarkdown] = useState(DEMO_PLAN_CONTENT); - const [annotations, setAnnotations] = useState([]); +export interface AppProps { + /** + * When provided, the editor runs in room mode: annotation mutations + * route through the room client and the `/api/plan` fetch is + * skipped (the plan arrives from the encrypted room snapshot + * instead). Approve and Deny are local-only — they are never + * offered in room mode — so passing this prop does not change the + * approve/deny flow. + * + * AppRoot is the only caller that should pass this; plain + * consumers mounting `` get local mode unchanged. + */ + roomSession?: import('@plannotator/ui/hooks/collab/useCollabRoomSession').UseCollabRoomSessionReturn; +} + +const App: React.FC = ({ roomSession }) => { + const roomModeActive = !!roomSession?.room; + const [markdown, setMarkdown] = useState( + roomModeActive ? '' : DEMO_PLAN_CONTENT, + ); + + // Room admin action (delete). Pending + error state live here (not + // in `RoomApp`) because the control renders in the editor header + // alongside everything else App owns. The error slot is surfaced + // via a toast at the bottom of the layout. + const roomAdmin = useRoomAdminActions(roomSession?.room); + + // Stripped-image handoff count from the creator-origin fragment + // (`&stripped=N`). AppRoot reads and strips the fragment param on + // mount, stashing the number on `window.__PLANNOTATOR_STRIPPED_IMAGES__`. + // The initializer is PURE; the consume-once effect below clears the + // global so a later App remount doesn't re-show the notice. Pattern + // matches the previous RoomApp implementation — see its commit + // history for the StrictMode double-run trap this avoids. + const [strippedImagesCount, setStrippedImagesCount] = useState(() => { + if (!roomModeActive || typeof window === 'undefined') return 0; + const w = window as { __PLANNOTATOR_STRIPPED_IMAGES__?: number }; + return w.__PLANNOTATOR_STRIPPED_IMAGES__ ?? 0; + }); + useEffect(() => { + if (typeof window === 'undefined') return; + const w = window as { __PLANNOTATOR_STRIPPED_IMAGES__?: number }; + if (w.__PLANNOTATOR_STRIPPED_IMAGES__ !== undefined) { + delete w.__PLANNOTATOR_STRIPPED_IMAGES__; + } + }, []); + // Annotation state lives behind a uniform controller. In local mode it + // wraps useState; in room mode it delegates to useCollabRoom, with + // `pending` tracking sends awaiting echo and `failed` holding ids whose + // last send rejected. `setAnnotations` is retained as a name pointing at + // `controller.setAll` — undefined in room mode, so the few downstream + // consumers that ATOMIC-replace (useSharing import, draft restore, linked + // doc switch) degrade to no-ops in room mode. Those flows are not + // supported inside a live room. + const annotationController = useAnnotationController({ + initial: [], + room: roomSession?.room, + }); + const { annotations } = annotationController; + const setAnnotations: React.Dispatch> = + annotationController.setAll ?? + ((_: React.SetStateAction) => { + // In room mode the controller has no replace-all primitive. Silently + // drop — every caller that relies on atomic replace is already + // guarded upstream (share import is disabled in room mode; draft + // restore triggers only in local mode; linked doc switch exits room + // mode first). + }); + + // Sync the plan markdown from the encrypted room snapshot. `room.planMarkdown` + // is empty string until the snapshot decrypts; we only overwrite local + // markdown when we have non-empty room plan content, so a late snapshot + // doesn't clobber the initial empty string render noisily. + useEffect(() => { + if (!roomSession?.room) return; + const plan = roomSession.room.planMarkdown; + if (plan && plan !== markdown) setMarkdown(plan); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [roomSession?.room?.planMarkdown]); const [selectedAnnotationId, setSelectedAnnotationId] = useState(null); const [blocks, setBlocks] = useState([]); const [frontmatter, setFrontmatter] = useState(null); @@ -142,6 +231,15 @@ const App: React.FC = () => { return () => ro.disconnect(); }, []); const [isApiMode, setIsApiMode] = useState(false); + // Approve / Deny are a LOCAL (same-origin) capability. In room mode + // the editor is served from room.plannotator.ai, which has no + // path to the blocked agent hook — so the buttons aren't offered + // there, even for admins. Decisions are made from the creator's + // localhost tab; room-side feedback flows back via the existing + // import paths (share hash / paste short URL / "Copy consolidated + // feedback" → paste). If a future change wants a room-origin + // approve path, it has to change THIS line. + const approveDenyAvailable = isApiMode; const [origin, setOrigin] = useState(null); const [gitUser, setGitUser] = useState(); const [isWSL, setIsWSL] = useState(false); @@ -155,6 +253,8 @@ const App: React.FC = () => { const [isSubmitting, setIsSubmitting] = useState(false); const [isExiting, setIsExiting] = useState(false); const [submitted, setSubmitted] = useState<'approved' | 'denied' | 'exited' | null>(null); + /** Visible error message for failed approve/deny. Cleared on next attempt. */ + const [submitError, setSubmitError] = useState(null); const [pendingPasteImage, setPendingPasteImage] = useState<{ file: File; blobUrl: string; initialName: string } | null>(null); const [showPermissionModeSetup, setShowPermissionModeSetup] = useState(false); const [permissionMode, setPermissionMode] = useState('bypassPermissions'); @@ -180,6 +280,21 @@ const App: React.FC = () => { const isMobile = useIsMobile(); const viewerRef = useRef(null); + // Monotonic generation driven by Viewer's highlight-surface lifecycle + // (init + `clearAllHighlights`). Reconcilers that track which annotation + // IDs are already materialized as DOM marks watch this value so they + // repaint from scratch when the underlying surface is reset out from + // under them (e.g. share-import wiping the DOM via clearAllHighlights). + // + // The counter is parent-owned on purpose: if the Viewer remounts (key + // change, conditional render) and emitted its own first-mount number + // again, React's setState bailout would silently drop the update and + // the reconciler's applied map would stay stale. The Viewer just emits + // "I reset" and we bump here. + const [highlightSurfaceGeneration, setHighlightSurfaceGeneration] = useState(0); + const bumpHighlightSurfaceGeneration = useCallback(() => { + setHighlightSurfaceGeneration(g => g + 1); + }, []); // containerRef + scrollViewport both point at the OverlayScrollbars // viewport element (the node that actually scrolls), not the
// host. Consumers: useActiveSection (IntersectionObserver root) and @@ -190,6 +305,29 @@ const App: React.FC = () => { onViewportReady: handleViewportReady, } = useOverlayViewport(); + // Expose the scroll viewport to sibling components outside App's + // React tree (LocalPresenceEmitter, RemoteCursorLayer — both live + // in RoomApp as React-siblings of App, so they can't consume + // `ScrollViewportContext`). They use content coordinates for + // remote cursor presence: "pointer at pixel (x, y) inside the + // scrolling content" is the one coordinate space that stays + // consistent across participants regardless of scroll / window + // size / zoom. Tagging the element with a data attribute lets + // those components `document.querySelector` for it — ugly + // coupling, but the alternative (hoisting presence components + // into App, or threading the ref back up through `renderEditor`) + // would be a bigger refactor for a localized win. Exactly one + // element carries the attribute (one App instance per page). + useEffect(() => { + if (!scrollViewport) return; + scrollViewport.dataset.planScrollViewport = ''; + return () => { + // Clean up on unmount so a later App mount (e.g. HMR) doesn't + // have two elements briefly claiming the role. + delete scrollViewport.dataset.planScrollViewport; + }; + }, [scrollViewport]); + usePrintMode(); // Resizable panels @@ -232,7 +370,7 @@ const App: React.FC = () => { } }, [wideModeType, sidebar.close, sidebar.open]); - const openSidebarTab = useCallback((tab: SidebarTab) => { + const openSidebarTab = useCallback((tab?: SidebarTab) => { if (wideModeType !== null) { exitWideMode({ restore: false, sidebarTab: tab, panelOpen: false }); return; @@ -477,7 +615,7 @@ const App: React.FC = () => { // Flash highlight for annotated files in the sidebar const [highlightedFiles, setHighlightedFiles] = useState | undefined>(); - const flashTimerRef = React.useRef>(); + const flashTimerRef = React.useRef | undefined>(undefined); const handleFlashAnnotatedFiles = React.useCallback(() => { const filePaths = new Set(allAnnotationCounts.keys()); if (filePaths.size === 0) return; @@ -511,18 +649,27 @@ const App: React.FC = () => { // Drive DOM highlights for SSE-delivered external annotations. Disabled // while a linked doc overlay is open (Viewer DOM is hidden) and while the // plan diff view is active (diff view has its own annotation surface). - const { reset: resetExternalHighlights } = useExternalAnnotationHighlights({ + useExternalAnnotationHighlights({ viewerRef, externalAnnotations, enabled: isApiMode && !linkedDocHook.isActive && !isPlanDiffActive, planKey: markdown, + surfaceGeneration: highlightSurfaceGeneration, }); // Merge local + SSE annotations, deduping draft-restored externals against // live SSE versions. Prefer the SSE version when both exist (same source, // type, and originalText). This avoids the timing issues of an effect-based // cleanup — draft-restored externals persist until SSE actually re-delivers them. + // + // Room-mode exclusion: when a live room is active, external annotations are + // NOT merged. An SSE-sourced annotation visible only to the creator would + // diverge from other participants' views and could be accidentally + // consolidated into approve/deny payloads. A future pass can forward + // the SSE stream to encrypted room ops so external annotations become + // shared. const allAnnotations = useMemo(() => { + if (roomSession?.room) return annotations; if (externalAnnotations.length === 0) return annotations; const local = annotations.filter(a => { @@ -535,7 +682,7 @@ const App: React.FC = () => { }); return [...local, ...externalAnnotations]; - }, [annotations, externalAnnotations]); + }, [annotations, externalAnnotations, roomSession?.room]); // Plan diff state — memoize filtered annotation lists to avoid new references per render const diffAnnotations = useMemo(() => allAnnotations.filter(a => !!a.diffContext), [allAnnotations]); @@ -569,7 +716,11 @@ const App: React.FC = () => { setIsLoading(false); }, shareBaseUrl, - pasteApiUrl + pasteApiUrl, + // Room mode disables static sharing entirely. The URL fragment + // (#key=) is a room credential, not a deflated share + // payload, and must not be interpreted as a static share. + roomModeActive, ); // Auto-save annotation drafts @@ -582,6 +733,12 @@ const App: React.FC = () => { }); const handleRestoreDraft = React.useCallback(() => { + // Draft restore is a replace-all flow; in room mode the room-backed + // controller has no replace-all primitive (annotations are + // server-authoritative). We intentionally skip restore in room mode — + // the user's local draft is still on disk; they can apply it after + // leaving the room. + if (roomModeActive) return; const { annotations: restored, globalAttachments: restoredGlobal } = restoreDraft(); if (restored.length > 0) { setAnnotations(restored); @@ -591,7 +748,7 @@ const App: React.FC = () => { viewerRef.current?.applySharedAnnotations(restored.filter(a => !a.diffContext)); }, 100); } - }, [restoreDraft]); + }, [restoreDraft, roomModeActive]); // Fetch available agents for OpenCode (for validation on approve) const { agents: availableAgents, validateAgent, getAgentWarning } = useAgents(origin); @@ -601,17 +758,33 @@ const App: React.FC = () => { if (pendingSharedAnnotations && pendingSharedAnnotations.length > 0) { // Small delay to ensure DOM is rendered const timer = setTimeout(() => { - // Clear existing highlights first (important when loading new share URL) + // Clear existing highlights first (important when loading new share URL). + // Viewer fires `onHighlightSurfaceReset`, which bumps the parent-owned + // generation and cascades into both reconcilers (external SSE + room) + // so their applied maps invalidate and repaint on the next tick. viewerRef.current?.clearAllHighlights(); viewerRef.current?.applySharedAnnotations(pendingSharedAnnotations.filter(a => !a.diffContext)); clearPendingSharedAnnotations(); - // `clearAllHighlights` wiped live external SSE highlights too; - // tell the external-highlight bookkeeper to re-apply them. - resetExternalHighlights(); }, 100); return () => clearTimeout(timer); } - }, [pendingSharedAnnotations, clearPendingSharedAnnotations, resetExternalHighlights]); + }, [pendingSharedAnnotations, clearPendingSharedAnnotations]); + + // Room-mode annotation → DOM mark reconciliation. Delegates to the + // shared `useAnnotationHighlightReconciler` (also used by external + // SSE). Room fingerprint includes comment `text` because peers can + // edit an annotation's comment without changing `originalText` — + // that case must trigger a remove+reapply so the mark's visible + // content matches the canonical annotation. + useAnnotationHighlightReconciler({ + viewerRef, + annotations, + enabled: roomModeActive, + planKey: markdown, + surfaceGeneration: highlightSurfaceGeneration, + eligibleFilter: a => !a.diffContext && !!a.originalText, + fingerprint: roomAnnFingerprint, + }); const handleTaterModeChange = (enabled: boolean) => { setTaterMode(enabled); @@ -636,6 +809,7 @@ const App: React.FC = () => { useEffect(() => { if (isLoadingShared) return; // Wait for share check to complete if (isSharedSession) return; // Already loaded from share + if (roomModeActive) return; // Room mode loads plan from the encrypted snapshot fetch('/api/plan') .then(res => { @@ -816,6 +990,11 @@ const App: React.FC = () => { // Global paste listener for image attachments useEffect(() => { const handlePaste = (e: ClipboardEvent) => { + // Room mode: images are not supported. Block the paste flow so + // the user doesn't get an upload modal that silently fails (the + // room Worker has no /api/upload endpoint). + if (roomModeActive) return; + const items = e.clipboardData?.items; if (!items) return; @@ -836,7 +1015,7 @@ const App: React.FC = () => { document.addEventListener('paste', handlePaste); return () => document.removeEventListener('paste', handlePaste); - }, [globalAttachments]); + }, [globalAttachments, roomModeActive]); // Handle paste annotator accept — name comes from ImageAnnotator const handlePasteAnnotatorAccept = async (blob: Blob, hasDrawings: boolean, name: string) => { @@ -872,6 +1051,7 @@ const App: React.FC = () => { // API mode handlers const handleApprove = async () => { setIsSubmitting(true); + setSubmitError(null); try { const obsidianSettings = getObsidianSettings(); const bearSettings = getBearSettings(); @@ -939,22 +1119,29 @@ const App: React.FC = () => { body.feedback = annotationsOutput; } - await fetch('/api/approve', { + const approveRes = await fetch('/api/approve', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); + if (!approveRes.ok) { + throw new Error(`Approve failed: ${approveRes.status}`); + } + setSubmitted('approved'); - } catch { + setSubmitError(null); + } catch (err) { setIsSubmitting(false); + setSubmitError(err instanceof Error ? err.message : 'Approve failed'); } }; const handleDeny = async () => { setIsSubmitting(true); + setSubmitError(null); try { const planSaveSettings = getPlanSaveSettings(); - await fetch('/api/deny', { + const denyRes = await fetch('/api/deny', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -965,12 +1152,67 @@ const App: React.FC = () => { }, }) }); + if (!denyRes.ok) { + throw new Error(`Deny failed: ${denyRes.status}`); + } setSubmitted('denied'); - } catch { + setSubmitError(null); + } catch (err) { setIsSubmitting(false); + setSubmitError(err instanceof Error ? err.message : 'Deny failed'); } }; + /** + * Stable `pendingIds` derivation for AnnotationPanel. The controller + * exposes pending as `Map` but the panel only needs + * "is this id pending?" — we project to a Set with the controller's + * pending Map as the memo dependency so a new Set is built ONLY when + * pending actually changes. + */ + const pendingAnnotationIds = useMemo | undefined>( + () => (roomModeActive ? new Set(annotationController.pending.keys()) : undefined), + [roomModeActive, annotationController.pending], + ); + + // Live Rooms is a local-creator-only flow: it needs a running + // Plannotator hook (isApiMode) to host the creator's original tab so + // the blocked agent hook has an approve/deny surface after the room + // opens in a new tab. Hosted portals (share.plannotator.ai, the + // marketing demo) have no such host, and the room-service CORS policy + // intentionally doesn't whitelist them — offering the button there + // would surface an "unreachable room service" error on click. Gate + // the menu + export-modal affordances on this single flag so both + // surfaces stay consistent. + const canStartLiveRoom = isApiMode && !roomModeActive; + + // Creator-side start-room flow. State, handlers, URL construction, + // placeholder-tab pop-open, and the image-strip memo all live in + // the hook; App.tsx consumes the return object and renders the + // modal below. + const { + showStartRoomModal, + startRoomInFlight, + startRoomError, + imageAnnotationsToStrip, + handleStartLiveRoom, + handleCancelStartRoom, + handleConfirmStartRoom, + } = useStartLiveRoom({ + annotations, + markdown, + globalAttachments, + canStartLiveRoom, + }); + + // Note: the room admin action (delete) lives only in the Room + // menu (RoomMenu / RoomHeaderControls). The PlanHeaderMenu used to + // duplicate it via a `runHeaderRoomAdmin` wrapper, but that path + // swallowed errors without a visible surface — the Room menu owns + // the pending-state chrome and the error toast, so routing admin + // clicks through a single focal point gives the user a consistent + // recovery path. + // Annotate mode handler — sends feedback via /api/feedback const handleAnnotateFeedback = async () => { setIsSubmitting(true); @@ -1066,7 +1308,7 @@ const App: React.FC = () => { ]); const handleAddAnnotation = (ann: Annotation) => { - setAnnotations(prev => [...prev, ann]); + annotationController.add(ann); setSelectedAnnotationId(ann.id); if (wideModeType === null) { setIsPanelOpen(true); @@ -1079,19 +1321,45 @@ const App: React.FC = () => { if (id && isMobile && wideModeType === null) setIsPanelOpen(true); }, [isMobile, wideModeType]); - // Core annotation removal — highlight cleanup + state filter + selection clear + // Core annotation removal — highlight cleanup + controller filter + selection clear. + // + // Local mode: remove the highlight immediately (optimistic). The user + // sees instant feedback and there's no server to reject. + // + // Room mode: do NOT remove the highlight before the server echo. The + // room annotation reconciliation effect (above) removes marks when + // they disappear from canonical state. Removing early would desync + // the DOM if the server rejects: the annotation would stay canonical + // but lose its visible mark. const removeAnnotation = (id: string) => { - viewerRef.current?.removeHighlight(id); - setAnnotations(prev => prev.filter(a => a.id !== id)); + if (!roomModeActive) { + viewerRef.current?.removeHighlight(id); + } + annotationController.remove(id); if (selectedAnnotationId === id) setSelectedAnnotationId(null); }; + // Room-mode only. Delegates to `derivePendingCheckboxBlockIds` + // (same file as `useCheckboxOverrides`) — busy-gate + revert-gate + // semantics documented on the helper. + const pendingCheckboxBlockIds = useMemo | undefined>( + () => (roomModeActive ? derivePendingCheckboxBlockIds(annotationController) : undefined), + [ + roomModeActive, + annotationController.pending, + annotationController.failed, + annotationController.pendingAdditions, + annotationController.annotations, + ], + ); + // Interactive checkbox toggling with annotation tracking const checkbox = useCheckboxOverrides({ blocks, annotations, addAnnotation: handleAddAnnotation, removeAnnotation, + pendingBlockIds: pendingCheckboxBlockIds, }); const handleDeleteAnnotation = (id: string) => { @@ -1104,8 +1372,21 @@ const App: React.FC = () => { if (selectedAnnotationId === id) setSelectedAnnotationId(null); return; } - // If this is a checkbox annotation, revert the visual override - if (id.startsWith('ann-checkbox-')) { + // If this is a checkbox annotation, clear the visual override. + // + // Local mode: synchronous — there's no server to reject the remove, + // so revert optimistically in lockstep with the annotation removal. + // + // Room mode: DO NOT revert here. The override must stay until the + // canonical checkbox annotation actually disappears from the room + // state (echoed remove). Otherwise a remove that later fails + // (disconnect, server rejection) leaves the annotation canonical + // but the checkbox visually reverted — inconsistent. + // `useCheckboxOverrides` runs a reconciliation effect that clears + // overrides once the backing annotation is gone from BOTH canonical + // and pending/failed state, which is exactly when it's safe to + // revert in room mode. + if (id.startsWith('ann-checkbox-') && !roomModeActive) { if (ann) { checkbox.revertOverride(ann.blockId); } @@ -1119,15 +1400,27 @@ const App: React.FC = () => { updateExternalAnnotation(id, updates); return; } - setAnnotations(prev => prev.map(a => - a.id === id ? { ...a, ...updates } : a - )); + annotationController.update(id, updates); }; const handleIdentityChange = (oldIdentity: string, newIdentity: string) => { - setAnnotations(prev => prev.map(ann => - ann.author === oldIdentity ? { ...ann, author: newIdentity } : ann - )); + // In room mode the joined identity — which stamps new annotations, + // labels remote cursors, and keyed presence — is owned by `RoomApp` + // and was confirmed at the join gate. Rewriting old annotations + // from here while that live identity stays on the prior name + // produces a "split" participant: old rows now say "Alice" but + // future rows / the cursor flag still say "Bob." Skip rewrites + // inside a room. Updating the Settings display name still takes + // effect locally and on subsequent rooms; a deliberate live + // rename feature (update presence + rename server-side) would be + // a RoomApp-owned feature, not a half-rewrite from here. + if (roomModeActive) return; + // Identity-rename is a bulk update across all matching annotations. + for (const ann of annotations) { + if (ann.author === oldIdentity) { + annotationController.update(ann.id, { author: newIdentity }); + } + } }; const handleAddGlobalAttachment = (image: ImageAttachment) => { @@ -1149,7 +1442,12 @@ const App: React.FC = () => { const hasDocAnnotations = Array.from(docAnnotations.values()).some( (d) => d.annotations.length > 0 || d.globalAttachments.length > 0 ); - const hasPlanAnnotations = allAnnotations.length > 0 || globalAttachments.length > 0; + // Room mode: global attachments are local-only (rooms carry no + // image payloads), so they must NOT be included in consolidated + // feedback — approving with local-only image refs would ship paths + // collaborators never saw. Out-of-room keeps existing behavior. + const effectiveGlobalAttachments = roomModeActive ? [] : globalAttachments; + const hasPlanAnnotations = allAnnotations.length > 0 || effectiveGlobalAttachments.length > 0; const hasEditorAnnotations = editorAnnotations.length > 0; if (!hasPlanAnnotations && !hasDocAnnotations && !hasEditorAnnotations) { @@ -1157,7 +1455,7 @@ const App: React.FC = () => { } let output = hasPlanAnnotations - ? exportAnnotations(blocks, allAnnotations, globalAttachments, annotateSource === 'message' ? 'Message Feedback' : annotateSource === 'folder' ? 'Folder Feedback' : annotateSource === 'file' ? 'File Feedback' : 'Plan Feedback', annotateSource ?? 'plan') + ? exportAnnotations(blocks, allAnnotations, effectiveGlobalAttachments, annotateSource === 'message' ? 'Message Feedback' : annotateSource === 'folder' ? 'Folder Feedback' : annotateSource === 'file' ? 'File Feedback' : 'Plan Feedback', annotateSource ?? 'plan') : ''; if (hasDocAnnotations) { @@ -1169,7 +1467,7 @@ const App: React.FC = () => { } return output; - }, [blocks, allAnnotations, globalAttachments, linkedDocHook.getDocAnnotations, editorAnnotations]); + }, [blocks, allAnnotations, globalAttachments, roomModeActive, linkedDocHook.getDocAnnotations, editorAnnotations]); // Bot callback config — read once from URL search params (?cb=&ct=) const callbackConfig = React.useMemo(() => getCallbackConfig(), []); @@ -1285,6 +1583,76 @@ const App: React.FC = () => { setTimeout(() => setNoteSaveToast(null), 3000); }; + // Room-mode copy handlers. Share a single clipboard + toast helper so + // the three call sites can't drift (success-vs-error copy, timeout, + // toast slot reuse). The `noteSaveToast` slot is reused for both + // flows — small grief that two kinds of message share one slot, but + // avoids a second toast system for a handful of rare clicks. + const copyToClipboardWithToast = React.useCallback( + async (text: string, successMessage: string, errorMessage: string) => { + try { + await navigator.clipboard.writeText(text); + setNoteSaveToast({ type: 'success', message: successMessage }); + } catch { + setNoteSaveToast({ type: 'error', message: errorMessage }); + } + setTimeout(() => setNoteSaveToast(null), 2500); + }, + [], + ); + const handleCopyParticipantUrl = React.useCallback(async () => { + const url = roomSession?.joinUrl; + if (!url) return; + await copyToClipboardWithToast( + url, + 'Participant link copied', + 'Failed to copy link', + ); + }, [roomSession?.joinUrl, copyToClipboardWithToast]); + const handleCopyAdminUrl = React.useCallback(async () => { + const url = roomSession?.adminUrl; + if (!url) return; + await copyToClipboardWithToast( + url, + 'Admin link copied', + 'Failed to copy admin link', + ); + }, [roomSession?.adminUrl, copyToClipboardWithToast]); + const handleCopyConsolidatedFeedback = React.useCallback(async () => { + // Exclude diff-context annotations from exported feedback — same + // filter the prior RoomPanel path used. Global attachments are + // empty inside a room (images are stripped at create time). + const text = exportAnnotations( + blocks, + allAnnotations.filter(a => !a.diffContext), + [], + ); + await copyToClipboardWithToast( + text, + 'Feedback copied', + 'Failed to copy feedback', + ); + }, [blocks, allAnnotations, copyToClipboardWithToast]); + + const handleCopyRoomAgentInstructions = React.useCallback(async () => { + // Prefer the participant URL (joinUrl) so an agent dropped into + // the clipboard payload doesn't accidentally end up with admin + // capability. The CLI also strips `#admin=` defensively, but + // pairing the strip with a deliberate participant-URL copy here + // makes the guard layered rather than single-point. + const joinUrl = roomSession?.joinUrl; + if (!joinUrl) return; + const payload = buildRoomAgentInstructions({ + joinUrl, + userIdentity: getIdentity(), + }); + await copyToClipboardWithToast( + payload, + 'Agent instructions copied', + 'Failed to copy instructions', + ); + }, [roomSession?.joinUrl, copyToClipboardWithToast]); + // Cmd/Ctrl+S keyboard shortcut — save to default notes app useEffect(() => { const handleSaveShortcut = (e: KeyboardEvent) => { @@ -1419,7 +1787,13 @@ const App: React.FC = () => { )} - {isApiMode && (!linkedDocHook.isActive || annotateMode) && !archive.archiveMode && ( + {submitError && ( +
+ {submitError} +
+ )} + + {approveDenyAvailable && (!linkedDocHook.isActive || annotateMode) && !archive.archiveMode && ( <> {annotateMode ? ( // Annotate mode: Close always visible, Send Annotations when annotations exist @@ -1494,6 +1868,39 @@ const App: React.FC = () => { )} + {/* + Room mode cluster: conditional status pill (only when + state is non-default — reconnecting / offline / deleted), + peer avatar bubbles, and a Room actions dropdown. Sits + between the approve/deny area (hidden in room mode) and + the annotations-panel toggle. Replaces the previous + floating `RoomPanel` aside — one visually-grouped + header cluster instead of a separate fixed card. + */} + {roomModeActive && roomSession?.room && ( + { + // Flash a toast confirming the intent BEFORE dispatching + // the delete. The admin command tears the socket down on + // success, which transitions this tab to the terminal + // screen — the toast is the last UX before that swap, + // so it has to fire first. + setNoteSaveToast({ type: 'success', message: 'Room deleted' }); + setTimeout(() => setNoteSaveToast(null), 3000); + void roomAdmin.run('delete'); + }} + /> + )} + {/* Annotations panel toggle — top-level header button */} + + + )} + {isPending && !failure && ( +
+ Sending… +
+ )} + + ); + })} {editorAnnotations && editorAnnotations.length > 0 && ( <> {sortedAnnotations.length > 0 && ( @@ -231,10 +340,19 @@ function formatTimestamp(ts: number): string { const AnnotationCard: React.FC<{ annotation: Annotation; isSelected: boolean; + /** + * "Is this annotation authored by the current user?" — passed in + * from AnnotationPanel so the helper can bake in the room-mode + * override (the joined display name instead of the cookie Tater). + * Taking it as a prop rather than closing over the parent's local + * helper keeps AnnotationCard module-scoped. + */ + isMe: (author: string | undefined) => boolean; onSelect: () => void; - onDelete: () => void; + /** Undefined = hide delete button (e.g. row is pending or failed). */ + onDelete?: () => void; onEdit?: (updates: Partial) => void; -}> = ({ annotation, isSelected, onSelect, onDelete, onEdit }) => { +}> = ({ annotation, isSelected, isMe, onSelect, onDelete, onEdit }) => { const [isEditing, setIsEditing] = useState(false); const [editText, setEditText] = useState(annotation.text || ''); const textareaRef = useRef(null); @@ -253,6 +371,17 @@ const AnnotationCard: React.FC<{ } }, [annotation.text, isEditing]); + // Cancel in-progress edits when `onEdit` disappears — happens when + // the row flips into pending/failed room-sync state mid-edit. + // Without this the textarea would persist visually; Save would + // silently no-op because onEdit is now undefined, confusing the + // user. + useEffect(() => { + if (!onEdit && isEditing) { + setIsEditing(false); + } + }, [onEdit, isEditing]); + const handleStartEdit = (e: React.MouseEvent) => { e.stopPropagation(); setEditText(annotation.text || ''); @@ -340,11 +469,11 @@ const AnnotationCard: React.FC<{ > {/* Author */} {annotation.author && ( -
+
- {annotation.author}{isCurrentUser(annotation.author) && ' (me)'} + {annotation.author}{isMe(annotation.author) && ' (me)'}
)} @@ -380,15 +509,17 @@ const AnnotationCard: React.FC<{ )} - + {onDelete && ( + + )}
diff --git a/packages/ui/components/BlockRenderer.tsx b/packages/ui/components/BlockRenderer.tsx index c4bd74e7..c933fe75 100644 --- a/packages/ui/components/BlockRenderer.tsx +++ b/packages/ui/components/BlockRenderer.tsx @@ -18,7 +18,9 @@ export const BlockRenderer: React.FC<{ orderedIndex?: number | null; githubRepo?: string; headingAnchorId?: string; -}> = ({ block, onOpenLinkedDoc, imageBaseDir, onImageClick, onToggleCheckbox, checkboxOverrides, orderedIndex, githubRepo, headingAnchorId }) => { + /** Mirrors the Viewer-level prop; forwarded verbatim to every nested InlineMarkdown. */ + localDocLinksEnabled?: boolean; +}> = ({ block, onOpenLinkedDoc, imageBaseDir, onImageClick, onToggleCheckbox, checkboxOverrides, orderedIndex, githubRepo, headingAnchorId, localDocLinksEnabled }) => { switch (block.type) { case 'heading': { const Tag = `h${block.level || 1}` as React.ElementType; @@ -27,7 +29,8 @@ export const BlockRenderer: React.FC<{ 2: 'text-xl font-semibold mb-3 mt-8 text-foreground/90', 3: 'text-base font-semibold mb-2 mt-6 text-foreground/80', }[block.level || 1] || 'text-base font-semibold mb-2 mt-4'; - return ; + return ; } case 'blockquote': { @@ -38,6 +41,7 @@ export const BlockRenderer: React.FC<{ kind={block.alertKind} body={block.content} onOpenLinkedDoc={onOpenLinkedDoc} + localDocLinksEnabled={localDocLinksEnabled} imageBaseDir={imageBaseDir} onImageClick={onImageClick} githubRepo={githubRepo} @@ -54,7 +58,8 @@ export const BlockRenderer: React.FC<{ > {paragraphs.map((para, i) => (

0 ? 'mt-2' : ''}> - +

))} @@ -83,7 +88,8 @@ export const BlockRenderer: React.FC<{ onToggle={isInteractive ? () => onToggleCheckbox!(block.id, !isChecked) : undefined} /> - + ); @@ -99,6 +105,7 @@ export const BlockRenderer: React.FC<{ imageBaseDir={imageBaseDir} onImageClick={onImageClick} onOpenLinkedDoc={onOpenLinkedDoc} + localDocLinksEnabled={localDocLinksEnabled} githubRepo={githubRepo} /> ); @@ -107,6 +114,9 @@ export const BlockRenderer: React.FC<{ return
; case 'html': + // HtmlBlock renders raw HTML directly — it bypasses InlineMarkdown + // entirely, so `localDocLinksEnabled` has no effect here and + // isn't part of the HtmlBlock prop surface. return ; case 'directive': { @@ -120,6 +130,7 @@ export const BlockRenderer: React.FC<{ blockType="directive" kindAttribute={kind} onOpenLinkedDoc={onOpenLinkedDoc} + localDocLinksEnabled={localDocLinksEnabled} imageBaseDir={imageBaseDir} onImageClick={onImageClick} githubRepo={githubRepo} @@ -133,7 +144,8 @@ export const BlockRenderer: React.FC<{ className="mb-4 leading-relaxed text-foreground/90 text-[15px]" data-block-id={block.id} > - +

); } diff --git a/packages/ui/components/CommentPopover.tsx b/packages/ui/components/CommentPopover.tsx index 16f8350e..950a32c6 100644 --- a/packages/ui/components/CommentPopover.tsx +++ b/packages/ui/components/CommentPopover.tsx @@ -18,6 +18,14 @@ interface CommentPopoverProps { onSubmit: (text: string, images?: ImageAttachment[]) => void; /** Called when popover is closed/cancelled */ onClose: () => void; + /** + * Default true. Set false in room mode: Live Rooms V1 strips image + * attachments at room-create time and doesn't carry them over the + * wire for new annotations either, so the attachments UI would + * either silently drop images or fail validation at send. Hiding + * the affordance is the honest surface. + */ + attachmentsEnabled?: boolean; } const MAX_POPOVER_WIDTH = 384; @@ -45,6 +53,7 @@ export const CommentPopover: React.FC = ({ initialText = '', onSubmit, onClose, + attachmentsEnabled = true, }) => { const [mode, setMode] = useState<'popover' | 'dialog'>('popover'); const [text, setText] = useState(initialText); @@ -196,12 +205,14 @@ export const CommentPopover: React.FC = ({ {/* Footer */}
- setImages((prev) => [...prev, img])} - onRemove={(path) => setImages((prev) => prev.filter((i) => i.path !== path))} - variant="inline" - /> + {attachmentsEnabled && ( + setImages((prev) => [...prev, img])} + onRemove={(path) => setImages((prev) => prev.filter((i) => i.path !== path))} + variant="inline" + /> + )}
{submitHint} @@ -292,12 +303,14 @@ export const CommentPopover: React.FC = ({ {/* Footer */}
- setImages((prev) => [...prev, img])} - onRemove={(path) => setImages((prev) => prev.filter((i) => i.path !== path))} - variant="inline" - /> + {attachmentsEnabled && ( + setImages((prev) => [...prev, img])} + onRemove={(path) => setImages((prev) => prev.filter((i) => i.path !== path))} + variant="inline" + /> + )}
{submitHint} diff --git a/packages/ui/components/ExportModal.tsx b/packages/ui/components/ExportModal.tsx index 99ca856b..9086688a 100644 --- a/packages/ui/components/ExportModal.tsx +++ b/packages/ui/components/ExportModal.tsx @@ -33,6 +33,13 @@ interface ExportModalProps { markdown?: string; isApiMode?: boolean; initialTab?: Tab; + /** + * Optional. When present, the Share tab surfaces a primary + * "Start live room" CTA above the existing static-share links. Absent + * → the existing UI is unchanged (local mode with no room backend + * configured). + */ + onStartLiveRoom?: () => void; } type Tab = 'share' | 'annotations' | 'notes'; @@ -56,6 +63,7 @@ export const ExportModal: React.FC = ({ markdown, isApiMode = false, initialTab, + onStartLiveRoom, }) => { const defaultTab = initialTab || (sharingEnabled ? 'share' : 'annotations'); const [activeTab, setActiveTab] = useState(defaultTab); @@ -252,6 +260,27 @@ export const ExportModal: React.FC = ({ {/* Tab content */} {activeTab === 'share' && sharingEnabled ? (
+ {onStartLiveRoom && ( +
+
Start a live room
+

+ Real-time collaborative review. The link you share is participant-only; + admin controls stay with you. +

+ +
+ )} + {onStartLiveRoom && ( +
+ Static share +
+ )} {/* Short URL — primary copy target when available */} {shortShareUrl ? (
diff --git a/packages/ui/components/InlineMarkdown.tsx b/packages/ui/components/InlineMarkdown.tsx index 797ff3e3..fc52ed43 100644 --- a/packages/ui/components/InlineMarkdown.tsx +++ b/packages/ui/components/InlineMarkdown.tsx @@ -88,7 +88,15 @@ export const InlineMarkdown: React.FC<{ imageBaseDir?: string; onImageClick?: (src: string, alt: string) => void; githubRepo?: string; -}> = ({ text, onOpenLinkedDoc, imageBaseDir, onImageClick, githubRepo }) => { + /** + * When false, local-doc links (wikilinks `[[foo]]` and markdown + * links to `*.md`/`*.mdx`/`*.html` files) render as plain text + * instead of clickable anchors. Room mode uses this because the + * room origin has no file server. Non-local links (http/https) + * are unaffected. Default true. + */ + localDocLinksEnabled?: boolean; +}> = ({ text, onOpenLinkedDoc, imageBaseDir, onImageClick, githubRepo, localDocLinksEnabled = true }) => { const parts: React.ReactNode[] = []; let remaining = text; let key = 0; @@ -179,6 +187,7 @@ export const InlineMarkdown: React.FC<{ onImageClick={onImageClick} text={match[1]} onOpenLinkedDoc={onOpenLinkedDoc} + localDocLinksEnabled={localDocLinksEnabled} githubRepo={githubRepo} /> , @@ -199,6 +208,7 @@ export const InlineMarkdown: React.FC<{ onImageClick={onImageClick} text={match[1]} onOpenLinkedDoc={onOpenLinkedDoc} + localDocLinksEnabled={localDocLinksEnabled} githubRepo={githubRepo} /> @@ -219,6 +229,7 @@ export const InlineMarkdown: React.FC<{ onImageClick={onImageClick} text={match[1]} onOpenLinkedDoc={onOpenLinkedDoc} + localDocLinksEnabled={localDocLinksEnabled} githubRepo={githubRepo} /> , @@ -238,6 +249,7 @@ export const InlineMarkdown: React.FC<{ onImageClick={onImageClick} text={match[1]} onOpenLinkedDoc={onOpenLinkedDoc} + localDocLinksEnabled={localDocLinksEnabled} githubRepo={githubRepo} /> , @@ -258,6 +270,7 @@ export const InlineMarkdown: React.FC<{ onImageClick={onImageClick} text={match[1]} onOpenLinkedDoc={onOpenLinkedDoc} + localDocLinksEnabled={localDocLinksEnabled} githubRepo={githubRepo} /> , @@ -380,7 +393,11 @@ export const InlineMarkdown: React.FC<{ ? target : `${target}.md`; - if (onOpenLinkedDoc) { + // `localDocLinksEnabled === false` pins the wikilink to plain + // text regardless of handler presence — room mode uses this so + // a click doesn't attempt to resolve a local path on the room + // origin (which has no `/api/doc` or Obsidian endpoint). + if (onOpenLinkedDoc && localDocLinksEnabled) { parts.push( , ); + } else if (isLocalDoc && !localDocLinksEnabled) { + // Room mode: the room origin has no file server, so a click + // would navigate to a non-existent room-origin path. Render + // as plain text so participants see the label without an + // affordance to click. + parts.push( + {linkText}, + ); } else if (isLocalDoc) { - // No handler — render as plain link (e.g., in shared/portal views) + // No handler (e.g. shared/portal views) — render as a plain + // ``. Clicking navigates to `/`, + // which may or may not resolve depending on deployment. parts.push( void; onCopyShareLink: () => void; onOpenImport: () => void; + /** + * Opens the Start-live-room modal. Optional so existing consumers + * (build-time and tests) don't need to thread the prop; when omitted + * the menu item is hidden. + */ + onStartLiveRoom?: () => void; onSaveToObsidian: () => void; onSaveToBear: () => void; onSaveToOctarine: () => void; @@ -39,6 +45,7 @@ export const PlanHeaderMenu: React.FC = ({ onPrint, onCopyShareLink, onOpenImport, + onStartLiveRoom, onSaveToObsidian, onSaveToBear, onSaveToOctarine, @@ -164,6 +171,19 @@ export const PlanHeaderMenu: React.FC = ({ label="Copy Share Link" /> )} + {onStartLiveRoom && ( + { + closeMenu(); + onStartLiveRoom(); + }} + // 👥 (people) distinguishes "Start live room" from the + // visually-similar "Copy Share Link" entry above, which + // keeps its chain-link icon for static sharing. + icon={👥} + label="Start live room…" + /> + )} {sharingEnabled && ( { @@ -302,3 +322,4 @@ const NoteIcon = () => ( ); + diff --git a/packages/ui/components/Settings.tsx b/packages/ui/components/Settings.tsx index fbb62092..c77dc5c9 100644 --- a/packages/ui/components/Settings.tsx +++ b/packages/ui/components/Settings.tsx @@ -4,7 +4,8 @@ import type { Origin } from '@plannotator/shared/agents'; import { configStore, useConfigValue } from '../config'; import { loadDiffFont } from '../utils/diffFonts'; import { TaterSpritePullup } from './TaterSpritePullup'; -import { getIdentity, regenerateIdentity, setCustomIdentity } from '../utils/identity'; +import { getIdentity, regenerateIdentity, setCustomIdentity, getPresenceColor, setPresenceColor } from '../utils/identity'; +import { PRESENCE_SWATCHES } from '../utils/presenceColor'; import { GitUser } from '../icons/GitUser'; import { getObsidianSettings, @@ -546,6 +547,7 @@ export const Settings: React.FC = ({ taterMode, onTaterModeChange const [showDialog, setShowDialog] = useState(false); const [activeTab, setActiveTab] = useState('general'); const [identity, setIdentity] = useState(''); + const [presenceColor, setPresenceColorState] = useState(PRESENCE_SWATCHES[0]); const [obsidian, setObsidian] = useState({ enabled: false, vaultPath: '', @@ -621,6 +623,7 @@ export const Settings: React.FC = ({ taterMode, onTaterModeChange if (showDialog) { if (showNewHints) markNewSettingsSeen(); setIdentity(getIdentity()) + setPresenceColorState(getPresenceColor()); setObsidian(getObsidianSettings()); setBear(getBearSettings()); setOctarine(getOctarineSettings()); @@ -768,6 +771,12 @@ export const Settings: React.FC = ({ taterMode, onTaterModeChange handleIdentitySave(gitUser); }; + const handlePresenceColorChange = (color: string) => { + if (color === presenceColor) return; + const saved = setPresenceColor(color); + setPresenceColorState(saved); + }; + return ( <>
+ {/* + Presence color lives next to the name as part + of identity. The Live Rooms create/join gates + read the same preference, so editing here + updates what peers see on the next room join. + */} +
+ {PRESENCE_SWATCHES.map(s => ( +
{/* Permission Mode (Claude Code only) */} diff --git a/packages/ui/components/Viewer.tsx b/packages/ui/components/Viewer.tsx index 601feef4..655fd535 100644 --- a/packages/ui/components/Viewer.tsx +++ b/packages/ui/components/Viewer.tsx @@ -88,6 +88,43 @@ interface ViewerProps { // Checkbox toggle props onToggleCheckbox?: (blockId: string, checked: boolean) => void; checkboxOverrides?: Map; + /** + * When set, newly-created annotations stamp `author` with this + * value instead of the cookie-backed `getIdentity()`. Threaded + * from App in room mode so annotations carry the display name + * the participant typed into the JoinRoomGate — matches the + * name peers see on remote cursors/avatars. + */ + authorOverride?: string; + /** + * Default true. Passed to CommentPopover to hide the attachments + * UI when false. Used by App in room mode: Live Rooms V1 strips + * image attachments at room-create time and doesn't carry new + * attachments over the wire, so offering the affordance would + * silently drop the user's image. + */ + attachmentsEnabled?: boolean; + /** + * When false, links to local documents (wikilinks like `[[foo]]` + * and markdown links to `*.md`/`*.mdx`/`*.html`) render as plain + * text instead of clickable anchors. Used by room mode because + * `room.plannotator.ai` has no `/api/doc` or Obsidian endpoint — + * clicking such a link would either trigger a broken fetch or + * navigate the room tab to a non-existent room-origin path. + * Non-local links (http/https) are unaffected. + */ + localDocLinksEnabled?: boolean; + /** + * Notifies the parent that the internal highlight surface has been + * (re)initialized or cleared. Fires once on initial highlighter + * construction and on each `clearAllHighlights()` call. + * + * The callback is a bare event — it carries no number. The parent + * owns the monotonic generation counter so a Viewer remount (which + * resets any Viewer-local state) still produces a fresh value that + * `setState` won't dedupe as a no-op. + */ + onHighlightSurfaceReset?: () => void; } export interface ViewerHandle { @@ -160,6 +197,10 @@ export const Viewer = forwardRef(({ sourceInfo, onToggleCheckbox, checkboxOverrides, + authorOverride, + attachmentsEnabled = true, + localDocLinksEnabled = true, + onHighlightSurfaceReset, }, ref) => { const [copied, setCopied] = useState(false); const [lightbox, setLightbox] = useState<{ src: string; alt: string } | null>(null); @@ -226,6 +267,8 @@ export const Viewer = forwardRef(({ onSelectAnnotation, selectedAnnotationId, mode, + authorOverride, + onSurfaceReset: onHighlightSurfaceReset, }); // Refs for code block annotation path @@ -377,7 +420,7 @@ export const Viewer = forwardRef(({ text, originalText: codeText, createdA: Date.now(), - author: getIdentity(), + author: authorOverride ?? getIdentity(), images, ...(isQuickLabel ? { isQuickLabel: true } : {}), ...(quickLabelTip ? { quickLabelTip } : {}), @@ -438,7 +481,7 @@ export const Viewer = forwardRef(({ text: text.trim(), originalText: '', createdA: Date.now(), - author: getIdentity(), + author: authorOverride ?? getIdentity(), images, }; onAddAnnotation(newAnnotation); @@ -559,6 +602,7 @@ export const Viewer = forwardRef(({ block={block} orderedIndex={indices[i]} onOpenLinkedDoc={onOpenLinkedDoc} + localDocLinksEnabled={localDocLinksEnabled} onToggleCheckbox={onToggleCheckbox} checkboxOverrides={checkboxOverrides} githubRepo={repoInfo?.display} @@ -631,7 +675,7 @@ export const Viewer = forwardRef(({ isHovered={inputMethod !== 'pinpoint' && hoveredCodeBlock?.block.id === group.block.id} /> ) : ( - setLightbox({ src, alt })} key={group.block.id} block={group.block} onOpenLinkedDoc={onOpenLinkedDoc} onToggleCheckbox={onToggleCheckbox} checkboxOverrides={checkboxOverrides} githubRepo={repoInfo?.display} headingAnchorId={headingSlugMap.get(group.block.id)} /> + setLightbox({ src, alt })} key={group.block.id} block={group.block} onOpenLinkedDoc={onOpenLinkedDoc} localDocLinksEnabled={localDocLinksEnabled} onToggleCheckbox={onToggleCheckbox} checkboxOverrides={checkboxOverrides} githubRepo={repoInfo?.display} headingAnchorId={headingSlugMap.get(group.block.id)} /> ) )} @@ -728,6 +772,7 @@ export const Viewer = forwardRef(({ onImageClick={(src, alt) => setLightbox({ src, alt })} onOpenLinkedDoc={onOpenLinkedDoc} githubRepo={repoInfo?.display} + localDocLinksEnabled={localDocLinksEnabled} /> )} @@ -743,6 +788,7 @@ export const Viewer = forwardRef(({ contextText={hookCommentPopover.contextText} isGlobal={false} initialText={hookCommentPopover.initialText} + attachmentsEnabled={attachmentsEnabled} onSubmit={hookCommentSubmit} onClose={hookCommentClose} /> @@ -753,6 +799,7 @@ export const Viewer = forwardRef(({ contextText={viewerCommentPopover.contextText} isGlobal={viewerCommentPopover.isGlobal} initialText={viewerCommentPopover.initialText} + attachmentsEnabled={attachmentsEnabled} onSubmit={handleViewerCommentSubmit} onClose={handleViewerCommentClose} /> diff --git a/packages/ui/components/blocks/AlertBlock.tsx b/packages/ui/components/blocks/AlertBlock.tsx index acf1a0ce..b106b2e9 100644 --- a/packages/ui/components/blocks/AlertBlock.tsx +++ b/packages/ui/components/blocks/AlertBlock.tsx @@ -10,6 +10,7 @@ interface AlertBlockProps { imageBaseDir?: string; onImageClick?: (src: string, alt: string) => void; githubRepo?: string; + localDocLinksEnabled?: boolean; } const TITLE: Record = { @@ -37,7 +38,7 @@ const Icon: React.FC<{ kind: AlertKind }> = ({ kind }) => { }; export const AlertBlock: React.FC = ({ - blockId, kind, body, onOpenLinkedDoc, imageBaseDir, onImageClick, githubRepo, + blockId, kind, body, onOpenLinkedDoc, imageBaseDir, onImageClick, githubRepo, localDocLinksEnabled, }) => { return (
= ({ {TITLE[kind]}
- {renderProseBody({ body, imageBaseDir, onImageClick, onOpenLinkedDoc, githubRepo })} + {renderProseBody({ body, imageBaseDir, onImageClick, onOpenLinkedDoc, githubRepo, localDocLinksEnabled })}
); }; diff --git a/packages/ui/components/blocks/Callout.tsx b/packages/ui/components/blocks/Callout.tsx index 93d5c14e..e5994dcd 100644 --- a/packages/ui/components/blocks/Callout.tsx +++ b/packages/ui/components/blocks/Callout.tsx @@ -12,6 +12,7 @@ interface CalloutProps { imageBaseDir?: string; onImageClick?: (src: string, alt: string) => void; githubRepo?: string; + localDocLinksEnabled?: boolean; } export const Callout: React.FC = ({ @@ -25,6 +26,7 @@ export const Callout: React.FC = ({ imageBaseDir, onImageClick, githubRepo, + localDocLinksEnabled, }) => { const kindAttr = blockType === 'alert' ? { 'data-alert-kind': kindAttribute } : { 'data-directive-kind': kindAttribute }; @@ -48,6 +50,7 @@ export const Callout: React.FC = ({ onImageClick, onOpenLinkedDoc, githubRepo, + localDocLinksEnabled, })}
); diff --git a/packages/ui/components/blocks/TableBlock.tsx b/packages/ui/components/blocks/TableBlock.tsx index 646b5682..f9e58de4 100644 --- a/packages/ui/components/blocks/TableBlock.tsx +++ b/packages/ui/components/blocks/TableBlock.tsx @@ -10,6 +10,7 @@ interface TableBlockProps { imageBaseDir?: string; onImageClick?: (src: string, alt: string) => void; githubRepo?: string; + localDocLinksEnabled?: boolean; } // Parse pipe-delimited markdown table content into headers + rows. @@ -83,6 +84,7 @@ export const TableBlock: React.FC = ({ imageBaseDir, onImageClick, githubRepo, + localDocLinksEnabled, }) => { const containerRef = useRef(null); const { headers, rows } = parseTableContent(block.content); @@ -110,6 +112,7 @@ export const TableBlock: React.FC = ({ text={header} onOpenLinkedDoc={onOpenLinkedDoc} githubRepo={githubRepo} + localDocLinksEnabled={localDocLinksEnabled} /> ))} @@ -126,6 +129,7 @@ export const TableBlock: React.FC = ({ text={cell} onOpenLinkedDoc={onOpenLinkedDoc} githubRepo={githubRepo} + localDocLinksEnabled={localDocLinksEnabled} /> ))} diff --git a/packages/ui/components/blocks/TablePopout.tsx b/packages/ui/components/blocks/TablePopout.tsx index 6d84e6a6..b91d5c07 100644 --- a/packages/ui/components/blocks/TablePopout.tsx +++ b/packages/ui/components/blocks/TablePopout.tsx @@ -25,6 +25,7 @@ interface TablePopoutProps { imageBaseDir?: string; onImageClick?: (src: string, alt: string) => void; githubRepo?: string; + localDocLinksEnabled?: boolean; } // A row is a dict of columnId → cell text. Column ids are derived from the @@ -41,6 +42,7 @@ const TablePopoutImpl: React.FC = ({ imageBaseDir, onImageClick, githubRepo, + localDocLinksEnabled, }) => { const { headers, rows } = useMemo(() => parseTableContent(block.content), [block.content]); @@ -81,11 +83,12 @@ const TablePopoutImpl: React.FC = ({ text={info.getValue()} onOpenLinkedDoc={onOpenLinkedDoc} githubRepo={githubRepo} + localDocLinksEnabled={localDocLinksEnabled} /> ), }), ); - }, [columnIds, headers, imageBaseDir, onImageClick, onOpenLinkedDoc, githubRepo]); + }, [columnIds, headers, imageBaseDir, onImageClick, onOpenLinkedDoc, githubRepo, localDocLinksEnabled]); const [sorting, setSorting] = useState([]); const [globalFilter, setGlobalFilter] = useState(''); diff --git a/packages/ui/components/blocks/proseBody.tsx b/packages/ui/components/blocks/proseBody.tsx index 50e52b5a..cbd6dea6 100644 --- a/packages/ui/components/blocks/proseBody.tsx +++ b/packages/ui/components/blocks/proseBody.tsx @@ -17,6 +17,7 @@ export function renderProseBody(args: { onImageClick?: (src: string, alt: string) => void; onOpenLinkedDoc?: (path: string) => void; githubRepo?: string; + localDocLinksEnabled?: boolean; }): React.ReactNode { const { body, @@ -26,6 +27,7 @@ export function renderProseBody(args: { onImageClick, onOpenLinkedDoc, githubRepo, + localDocLinksEnabled, } = args; const inline = (text: string) => ( @@ -35,6 +37,7 @@ export function renderProseBody(args: { text={text} onOpenLinkedDoc={onOpenLinkedDoc} githubRepo={githubRepo} + localDocLinksEnabled={localDocLinksEnabled} /> ); diff --git a/packages/ui/components/collab/ImageStripNotice.tsx b/packages/ui/components/collab/ImageStripNotice.tsx new file mode 100644 index 00000000..e234a5e8 --- /dev/null +++ b/packages/ui/components/collab/ImageStripNotice.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +/** + * Dismissible banner shown after room creation when one or more local + * annotations carried images that were stripped for the shared snapshot. + * Text annotations are preserved; only the image attachments are dropped. + * + * Presentational only — parent controls mount/unmount and dismissal. + */ + +export interface ImageStripNoticeProps { + strippedCount: number; + onDismiss(): void; + className?: string; +} + +export function ImageStripNotice({ + strippedCount, + onDismiss, + className = '', +}: ImageStripNoticeProps): React.ReactElement | null { + if (strippedCount <= 0) return null; + return ( +
+
+ Images stripped.{' '} + {strippedCount} item{strippedCount === 1 ? '' : 's'} with image attachments {strippedCount === 1 ? 'was' : 'were'} removed before sharing — text comments are preserved. Your local copies are unchanged. +
+ +
+ ); +} diff --git a/packages/ui/components/collab/JoinRoomGate.tsx b/packages/ui/components/collab/JoinRoomGate.tsx new file mode 100644 index 00000000..89277814 --- /dev/null +++ b/packages/ui/components/collab/JoinRoomGate.tsx @@ -0,0 +1,122 @@ +import React, { useState } from 'react'; +import type { ConnectionStatus } from '@plannotator/shared/collab/client'; +import { PRESENCE_SWATCHES } from '@plannotator/ui/utils/presenceColor'; + +/** + * Pre-connect identity gate for room participants. Parent mounts this + * BEFORE connecting — captures a display name + color, then calls + * `onJoin` with the settled identity. While connecting, this same + * component also surfaces status messages (connecting / authenticating) + * so the user has constant feedback. + * + * Both `initialDisplayName` and `initialColor` should come from the + * user's Plannotator preferences (`getIdentity()` / `getPresenceColor()`). + * Parent persists edits back via the corresponding setters after the + * user submits; the gate itself is pure presentation. + * + * Fatal failure states (malformed URL / access denied / room deleted) + * are rendered by the parent as a full-screen replacement — this gate + * handles only the happy-path and the in-flight connection states. + */ + +export interface JoinRoomSubmit { + displayName: string; + color: string; +} + +export interface JoinRoomGateProps { + initialDisplayName?: string; + initialColor?: string; + connectionStatus: ConnectionStatus; + onJoin(submit: JoinRoomSubmit): void; +} + +function statusMessage(s: ConnectionStatus): string | null { + switch (s) { + case 'connecting': return 'Connecting to room…'; + case 'authenticating': return 'Verifying access…'; + case 'reconnecting': return 'Reconnecting…'; + default: return null; + } +} + +export function JoinRoomGate({ + initialDisplayName = '', + initialColor = PRESENCE_SWATCHES[0], + connectionStatus, + onJoin, +}: JoinRoomGateProps): React.ReactElement { + const [displayName, setDisplayName] = useState(initialDisplayName); + const [color, setColor] = useState(initialColor); + const [submitted, setSubmitted] = useState(false); + + const showStatus = submitted && statusMessage(connectionStatus); + + function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + const trimmed = displayName.trim(); + if (!trimmed) return; + setSubmitted(true); + onJoin({ displayName: trimmed, color }); + } + + return ( +
+
+

Join live review

+ +
+ + setDisplayName(e.target.value)} + disabled={submitted} + className="w-full px-2 py-1 border rounded text-sm" + placeholder="Your name" + autoFocus + /> +
+ +
+ +
+ {PRESENCE_SWATCHES.map(s => ( +
+
+ + {showStatus && ( +
+ {showStatus} +
+ )} + +
+ +
+
+
+ ); +} diff --git a/packages/ui/components/collab/ParticipantAvatars.test.tsx b/packages/ui/components/collab/ParticipantAvatars.test.tsx new file mode 100644 index 00000000..37a8d5f9 --- /dev/null +++ b/packages/ui/components/collab/ParticipantAvatars.test.tsx @@ -0,0 +1,85 @@ +import { describe, expect, test } from 'bun:test'; +import { render } from '@testing-library/react'; +import { ParticipantAvatars } from './ParticipantAvatars'; +import type { PresenceState } from '@plannotator/shared/collab'; + +function peer(name: string, color = '#abc'): PresenceState { + return { + user: { id: name.toLowerCase(), name, color }, + cursor: null, + }; +} + +describe('ParticipantAvatars', () => { + test('returns null with no peers', () => { + const { container } = render(); + expect(container.querySelector('[data-testid="participant-avatars"]')).toBeNull(); + }); + + test('renders one avatar per peer with correct initial', () => { + const { container } = render( + , + ); + const avatars = container.querySelectorAll('[data-participant-id]'); + expect(avatars.length).toBe(2); + const initials = Array.from(avatars).map(a => a.textContent); + expect(initials).toEqual(['A', 'B']); // sorted by name + }); + + test('collapses extras above maxVisible into "+N"', () => { + const presence: Record = {}; + for (let i = 0; i < 6; i++) { + presence[`c${i}`] = peer(String.fromCharCode(65 + i)); + } + const { container } = render( + , + ); + expect(container.querySelectorAll('[data-participant-id]').length).toBe(3); + const overflow = container.querySelector('[data-testid="participant-overflow"]'); + expect(overflow?.textContent).toBe('+3'); + }); + + test('overflow title lists names not shown', () => { + const presence = { + c1: peer('Alice'), c2: peer('Bob'), c3: peer('Charlie'), c4: peer('Dana'), c5: peer('Eve'), + }; + const { container } = render( + , + ); + const overflow = container.querySelector('[data-testid="participant-overflow"]'); + expect(overflow?.getAttribute('title')).toBe('Charlie, Dana, Eve'); + }); + + test('falls back to "?" initial when name is blank', () => { + const { container } = render( + , + ); + const avatars = container.querySelectorAll('[data-participant-id]'); + expect(avatars[0].textContent).toBe('G'); // falls through to "Guest" → "G" + }); + + test('marks agent peers with the agent indicator', () => { + const { container } = render( + , + ); + const avatars = container.querySelectorAll('[data-participant-id]'); + const kinds = Array.from(avatars).map(a => a.dataset.participantKind); + // deriveParticipants sorts by name; 'Alice' < 'alice-agent-claude' (case-insensitive localeCompare) + expect(kinds).toEqual(['human', 'agent']); + + // The agent avatar has the marker; the human avatar does not. + const humanMarker = avatars[0].querySelector('[data-testid="participant-agent-marker"]'); + const agentMarker = avatars[1].querySelector('[data-testid="participant-agent-marker"]'); + expect(humanMarker).toBeNull(); + expect(agentMarker).not.toBeNull(); + + // Agent tooltip includes the type. + expect(avatars[1].getAttribute('title')).toContain('agent'); + expect(avatars[1].getAttribute('title')).toContain('claude'); + }); +}); diff --git a/packages/ui/components/collab/ParticipantAvatars.tsx b/packages/ui/components/collab/ParticipantAvatars.tsx new file mode 100644 index 00000000..e3619981 --- /dev/null +++ b/packages/ui/components/collab/ParticipantAvatars.tsx @@ -0,0 +1,105 @@ +import React, { useMemo } from 'react'; +import type { PresenceState } from '@plannotator/shared/collab'; +import { isAgentIdentity, getAgentType } from '@plannotator/ui/utils/agentIdentity'; + +/** + * Pure avatar stack for room participants. Reads from `remotePresence` + * (keyed by clientId) and renders one colored initial per peer. Does NOT + * include the local user — callers render their own user elsewhere. + * + * Overflow: show at most `maxVisible` avatars; the rest are summarized + * as "+N" with a tooltip listing the extra names. + * + * Agent peers (identity ending in `-agent-`) render with a small + * marker overlay so observers can tell them apart from human peers. + * Detection is purely identity-based via `isAgentIdentity` — no other + * protocol fields are consulted. + */ + +export interface ParticipantAvatarsProps { + remotePresence: Record; + maxVisible?: number; + className?: string; +} + +interface Participant { + clientId: string; + name: string; + color: string; + initial: string; + isAgent: boolean; + agentType: string | undefined; +} + +function deriveParticipants( + remotePresence: Record, +): Participant[] { + const out: Participant[] = []; + for (const [clientId, p] of Object.entries(remotePresence)) { + const name = (p.user?.name ?? '').trim() || 'Guest'; + const color = p.user?.color ?? '#888'; + const initial = name.charAt(0).toUpperCase() || '?'; + const isAgent = isAgentIdentity(name); + const agentType = getAgentType(name); + out.push({ clientId, name, color, initial, isAgent, agentType }); + } + // Stable sort by name so order doesn't thrash when presence maps rehydrate. + out.sort((a, b) => a.name.localeCompare(b.name)); + return out; +} + +export function ParticipantAvatars({ + remotePresence, + maxVisible = 4, + className = '', +}: ParticipantAvatarsProps): React.ReactElement | null { + const participants = useMemo(() => deriveParticipants(remotePresence), [remotePresence]); + if (participants.length === 0) return null; + + const visible = participants.slice(0, maxVisible); + const overflow = participants.slice(maxVisible); + const overflowTitle = overflow.map(p => p.name).join(', '); + + return ( +
+ {visible.map(p => ( + + {p.initial} + {p.isAgent && ( + + ⚙ + + )} + + ))} + {overflow.length > 0 && ( + + +{overflow.length} + + )} +
+ ); +} diff --git a/packages/ui/components/collab/RemoteCursorLayer.tsx b/packages/ui/components/collab/RemoteCursorLayer.tsx new file mode 100644 index 00000000..30a738c7 --- /dev/null +++ b/packages/ui/components/collab/RemoteCursorLayer.tsx @@ -0,0 +1,460 @@ +import React, { useEffect, useMemo, useRef } from 'react'; +import type { PresenceState, CursorState } from '@plannotator/shared/collab'; +import { isAgentIdentity } from '@plannotator/ui/utils/agentIdentity'; + +/** + * Absolute-positioned overlay that renders remote cursor flags. Parent + * mounts this as a sibling of the Viewer inside the scroll viewport so + * cursors scroll with content without any extra math. + * + * Rendering model: + * - One `
` per remote client; mount/unmount is React-driven so + * adds/removes during a session update cleanly. + * - Position is NOT React state. A single `requestAnimationFrame` + * loop reads the latest target from `remotePresence`/`containerRect` + * refs, lerps each cursor's current position toward its target, and + * mutates `transform` on the DOM node directly. This matches the + * industry pattern used by Figma-style / Liveblocks-style cursor + * systems — avoids React reconciliation on every frame (~60Hz * N + * cursors would otherwise churn the scheduler for nothing) and + * leans on the GPU compositor for `translate3d`. + * + * Smoothing: + * - Latest-wins target per clientId. On each frame: lerp toward target + * with a fixed alpha (~0.3 feels responsive without overshoot). + * - Snap (bypass lerp) when: + * 1. First frame for a clientId — avoid sliding from (0,0). + * 2. Cursor reappears after going null/idle — treat like first. + * 3. Single-frame distance > SNAP_THRESHOLD — usually a + * coordinate-space flip (block ↔ viewport) or scroll jump, + * where animating the "swoosh" would look worse than snapping. + * + * Offscreen indicators: + * - When a cursor's resolved position falls outside the overlay + * container rect, the same element is repurposed as a small edge + * label (`↑ Alice` / `↓ Alice`) pinned to the nearest edge and + * clamped horizontally. Tells the reader "they're somewhere else + * in the doc" instead of letting the cursor vanish. + * + * Coordinate model (matches the protocol's `CursorState`): + * - `coordinateSpace: 'document'` — (x, y) in scroll-document coords. + * Render at (x - scrollX, y - scrollY) within the viewport. + * - `coordinateSpace: 'viewport'` — (x, y) in viewport coords. + * Rendered as-is minus the container offset. + * - `coordinateSpace: 'block'` — relative to the block's bounding + * rect, identified by `blockId`. Resolved via `[data-block-id=…]`. + * + * Local cursor is NOT rendered — that cursor is the browser's own caret. + */ + +export interface RemoteCursorLayerProps { + remotePresence: Record; + /** + * Bounding rect of the overlay container in viewport coords. Used to + * translate viewport-space cursor coords into overlay-local coords + * and to decide whether a cursor is onscreen vs. pinned to an edge. + */ + containerRect: DOMRect | null; + /** ParentNode to search within for block elements. Defaults to document. */ + root?: ParentNode; + className?: string; +} + +interface CursorRenderState { + displayX: number; + displayY: number; + /** True once we've ever painted this cursor; toggled off on idle. */ + everRendered: boolean; +} + +function findBlockRect(blockId: string, root: ParentNode): DOMRect | null { + // `blockId` arrives as decrypted remote presence. The bundled UI only + // emits real `data-block-id` values, but anything holding the room URL + + // key (direct WebSocket client, modified console, agent) can send an + // arbitrary string. A newline or other CSS-invalid character makes the + // selector throw `SyntaxError` during render, taking the whole cursor + // layer down for every participant. Escape safely and swallow any + // residual selector failures so bad remote input just drops the cursor. + try { + const escaped = + typeof CSS !== 'undefined' && typeof CSS.escape === 'function' + ? CSS.escape(blockId) + : blockId.replace(/["\\]/g, '\\$&'); + const el = root.querySelector(`[data-block-id="${escaped}"]`) as HTMLElement | null; + return el ? el.getBoundingClientRect() : null; + } catch { + return null; + } +} + +/** + * Find the plan's scroll viewport element. App tags it with + * `data-plan-scroll-viewport` when the OverlayScrollbars instance + * settles. Used to resolve `document`-space cursors (protocol supports + * all three coordinate spaces) — the bundled UI's LocalPresenceEmitter + * emits `block`-space with a sticky anchor, but a direct-agent or + * future client could still use `document`, and the layer handles + * both uniformly via `resolveCursor` below. + * + * Fall-through to `null` is safe — the caller skips rendering when + * `resolveCursor` returns null, so the cursor waits for the scroll + * area to mount instead of rendering at a garbage position. + */ +function findScrollViewport(): HTMLElement | null { + return typeof document !== 'undefined' + ? document.querySelector('[data-plan-scroll-viewport]') + : null; +} + +function resolveCursor( + cursor: CursorState, + root: ParentNode, +): { viewportX: number; viewportY: number } | null { + switch (cursor.coordinateSpace) { + case 'viewport': + return { viewportX: cursor.x, viewportY: cursor.y }; + case 'document': { + // Content-space: cursor.(x, y) is relative to the scroll + // container's inner content origin. Map to this viewer's + // viewport by re-applying their scroll container rect and + // current scroll position. + const vp = findScrollViewport(); + if (!vp) return null; + const rect = vp.getBoundingClientRect(); + return { + viewportX: rect.left + cursor.x - vp.scrollLeft, + viewportY: rect.top + cursor.y - vp.scrollTop, + }; + } + case 'block': { + // The bundled UI's LocalPresenceEmitter writes block-space with + // a sticky anchor (same block until the pointer crosses into a + // new one), so this is the hot path for same-app peers. Also + // honors direct-agent clients that send block coords. + if (!cursor.blockId) return null; + const blockRect = findBlockRect(cursor.blockId, root); + if (!blockRect) return null; + return { + viewportX: blockRect.left + cursor.x, + viewportY: blockRect.top + cursor.y, + }; + } + default: + return null; + } +} + +// Line-height fallback for cursor caret — we don't know the remote +// user's line-height at the cursor, and resolving per-block metrics on +// every update would be expensive. 18px covers standard body copy. +const CURSOR_HEIGHT_PX = 18; + +// Smoothing tuning. See component docstring. +const LERP_ALPHA = 0.3; +const SNAP_THRESHOLD_PX = 600; + +/** + * Inset applied when clamping an offscreen cursor to the container + * edge. Keeps the pinned glyph fully visible instead of clipping half + * of it against the edge. + */ +const EDGE_INSET_PX = 8; + +export function RemoteCursorLayer({ + remotePresence, + containerRect, + root = typeof document !== 'undefined' ? document : undefined as unknown as ParentNode, + className = '', +}: RemoteCursorLayerProps): React.ReactElement | null { + // Refs the rAF loop reads. React updates these on every prop change; + // the loop picks up the latest values on its next frame without + // depending on React render cycles for motion. + const presenceRef = useRef(remotePresence); + presenceRef.current = remotePresence; + const containerRectRef = useRef(containerRect); + containerRectRef.current = containerRect; + const rootRef = useRef(root); + rootRef.current = root; + + const renderStatesRef = useRef>(new Map()); + const nodeRefsRef = useRef>(new Map()); + + // Gate the animation loop on actually having remote cursors to draw. + // Solo rooms (the common case) would otherwise run a 60Hz no-op loop + // for every session. Effect restarts only when this boolean flips + // empty↔non-empty, so continuous cursor updates during a busy session + // don't retear the loop down. + const hasRemoteCursors = Object.keys(remotePresence).length > 0; + + useEffect(() => { + if (!hasRemoteCursors) return; + let rafId = 0; + + const tick = () => { + const presence = presenceRef.current; + const rect = containerRectRef.current; + const rootEl = rootRef.current ?? (typeof document !== 'undefined' ? document : null); + if (!rootEl) { + rafId = requestAnimationFrame(tick); + return; + } + + const states = renderStatesRef.current; + const nodes = nodeRefsRef.current; + + // Drop render state for cursors no longer in presence. The node + // itself is unmounted by React on the next render — we just + // release our tracking so a rejoin starts fresh (snap). + for (const id of Array.from(states.keys())) { + if (!(id in presence)) { + states.delete(id); + } + } + + for (const [clientId, p] of Object.entries(presence)) { + const node = nodes.get(clientId); + if (!node) continue; // React hasn't committed the element yet. + + const resolved = p.cursor ? resolveCursor(p.cursor, rootEl) : null; + if (!resolved) { + // Null / unresolvable cursor — mark idle and hide. Next + // non-null packet snaps back in from the new position instead + // of sliding from wherever the ghost was left. + node.style.display = 'none'; + const prev = states.get(clientId); + if (prev) prev.everRendered = false; + continue; + } + + // Target in overlay-local space. + const targetX = resolved.viewportX - (rect?.left ?? 0); + const targetY = resolved.viewportY - (rect?.top ?? 0); + + let state = states.get(clientId); + if (!state || !state.everRendered) { + // First paint for this clientId (or just came back from + // idle): snap so we don't see a slide from (0,0) or the + // previous stale position. + state = { displayX: targetX, displayY: targetY, everRendered: true }; + states.set(clientId, state); + } else { + const dx = targetX - state.displayX; + const dy = targetY - state.displayY; + if (Math.hypot(dx, dy) > SNAP_THRESHOLD_PX) { + // Huge single-frame jump — usually a block↔viewport + // coordinate flip or a scroll that moved the block rect + // hundreds of pixels. Animating it looks like a + // full-screen swoosh; snap instead. + state.displayX = targetX; + state.displayY = targetY; + } else { + state.displayX += dx * LERP_ALPHA; + state.displayY += dy * LERP_ALPHA; + } + } + + // Onscreen check against the overlay container bounds. The + // container IS the editor viewport in our current layout, so + // "outside container" == "outside visible editor" == pin to + // the nearest edge. + const containerWidth = rect?.width ?? (typeof window !== 'undefined' ? window.innerWidth : 0); + const containerHeight = rect?.height ?? (typeof window !== 'undefined' ? window.innerHeight : 0); + const above = state.displayY < 0; + const below = state.displayY > containerHeight; + const leftOf = state.displayX < 0; + const rightOf = state.displayX > containerWidth; + const offscreen = above || below || leftOf || rightOf; + + let renderX = state.displayX; + let renderY = state.displayY; + let edgeDirection: 'none' | 'above' | 'below' | 'left' | 'right' = 'none'; + if (offscreen) { + // Clamp to the nearest edge with a small inset so the glyph + // stays fully visible. Direction picks vertical over + // horizontal because most scrolling is vertical; a corner- + // case cursor gets the vertical indicator with horizontal + // clamping applied for position. + renderX = Math.max(EDGE_INSET_PX, Math.min(containerWidth - EDGE_INSET_PX, state.displayX)); + renderY = Math.max(EDGE_INSET_PX, Math.min(containerHeight - EDGE_INSET_PX, state.displayY)); + edgeDirection = above ? 'above' : below ? 'below' : leftOf ? 'left' : 'right'; + } + + node.style.display = ''; + node.style.transform = `translate3d(${renderX}px, ${renderY}px, 0)`; + + // Toggle the visual via dataset — CSS (below) swaps caret vs. + // edge indicator based on `data-edge-direction`. + if (node.dataset.edgeDirection !== edgeDirection) { + node.dataset.edgeDirection = edgeDirection; + } + } + + rafId = requestAnimationFrame(tick); + }; + + rafId = requestAnimationFrame(tick); + return () => cancelAnimationFrame(rafId); + }, [hasRemoteCursors]); + + // React owns the set of mounted cursor nodes (keyed by clientId). + // The rAF loop positions them. Recomputed each render — trivial cost + // and keeps the list stable in key order so React doesn't reorder + // DOM nodes when presence iteration order shifts. + const clientIds = useMemo( + () => Object.keys(remotePresence).sort(), + // `remotePresence` is a new object each state emit, but its key + // set changes rarely; depending on the whole object is fine given + // the sort is O(n log n) on tiny n. + [remotePresence], + ); + + if (clientIds.length === 0) return null; + + return ( +
+ {/* + Self-contained style block. Keeps the swap between onscreen + caret and offscreen edge-pin pure CSS, driven by the + `data-edge-direction` attribute the rAF loop mutates on each + cursor node. + .remote-cursor-offscreen is default-hidden via CSS here + (NOT via inline `style={{ display: 'flex' }}` on the element) + because inline styles beat stylesheet rules — with an inline + default of flex, the `data-edge-direction="none"` rule that + tries to hide the pill would lose and both variants would + paint on every cursor. + */} + + {clientIds.map(clientId => { + const p = remotePresence[clientId]; + const name = p?.user?.name ?? 'Guest'; + const color = p?.user?.color ?? '#888'; + const isAgent = isAgentIdentity(p?.user?.name); + return ( + + ); + })} +
+ ); +} + +/** + * Single cursor glyph. Position is NEVER set here — the parent's rAF + * loop mutates `transform` and `data-edge-direction` on the node + * directly via the shared ref map. This component only owns the + * static-per-client bits: color, name, and the SVG/label markup. + * + * Contains both the normal caret+label and the offscreen edge-pin + * variants in the DOM; CSS selectors on the parent's `data-edge-*` + * dataset decide which is visible. Keeps motion allocation-free + * since the DOM structure never changes during animation. + */ +function RemoteCursor({ + clientId, + name, + color, + isAgent, + nodeRefsRef, +}: { + clientId: string; + name: string; + color: string; + isAgent: boolean; + nodeRefsRef: React.RefObject>; +}): React.ReactElement { + // Callback ref keyed by clientId. Each mount/unmount registers or + // releases in the shared ref map that the rAF loop reads. + const setRef = (el: HTMLDivElement | null) => { + const map = nodeRefsRef.current; + if (!map) return; + if (el) map.set(clientId, el); + else map.delete(clientId); + }; + + return ( +