diff --git a/apps/docs/changelog/overview.mdx b/apps/docs/changelog/overview.mdx index ac565e10..77ca603d 100644 --- a/apps/docs/changelog/overview.mdx +++ b/apps/docs/changelog/overview.mdx @@ -1,5 +1,6 @@ --- title: "Changelog" +sidebarTitle: "Supermemory" description: "New updates and improvements to Supermemory" --- diff --git a/apps/docs/changelog/plugins.mdx b/apps/docs/changelog/plugins.mdx new file mode 100644 index 00000000..f11aaefb --- /dev/null +++ b/apps/docs/changelog/plugins.mdx @@ -0,0 +1,81 @@ +--- +title: "Plugin changelog" +sidebarTitle: "Plugins" +description: "Recent updates and improvements to Supermemory plugins" +--- + + + +### OpenCode entity context + +OpenCode now sends entity context with memory operations, so saved context can stay tied to the active project and conversation. The entity-context prompt was also moved out of the API client for cleaner reuse across capture and compaction flows. + +### Cursor session auth + +Cursor now starts the auth flow from the session hook when needed, and the OAuth success screen uses the Cursor-branded callback path. + + + + + +### Claude Code update notices + +Claude Code now surfaces plugin update notices during sessions and includes the latest package/version metadata. + +### OpenCode context prompt + +OpenCode gained an entity-context prompt so memory recall and capture can carry more precise source context. + + + + + +### Claude Code marketplace polish + +The Claude Code plugin manifest was polished for the official marketplace listing, including refreshed metadata and naming. + +### Codex update notices + +Codex now checks for plugin updates during session start and shows a user-visible notice when a newer version is available. + + + + + +### Claude Code rename migration + +Claude Code completed the rename to the `supermemory` plugin while keeping migration safe for users already on the new plugin name. Configuration also supports custom `baseUrl` values for local or self-hosted Supermemory installs. + +### Cursor web OAuth + +Cursor OAuth now routes through the Supermemory web app, keeping the plugin auth flow consistent with the rest of the integrations. + + + + + +### Codex auth and status tooling + +Codex added status, logout, and web-auth flows, plus Windows-safe auth URL opening and entity context for saved memories. The installer now includes a `supermemory-status` skill so Codex can report connection, hook, config, and installed-skill health from inside a session. + +### OAuth status refinements + +Codex and OpenCode integration status now renders more clearly in the Supermemory app during OAuth connection and setup. + + + + + +### Claude Code recall reasoning + +Claude Code gained reasoned per-turn memory recall with auto-approve support, refreshed bundled scripts, and updated skill names for `supermemory-save` and `supermemory-search`. + +### Cursor session hooks + +Cursor session hooks now load reliably and persist real project sessions into the correct container. + +### OpenClaw and Hermes memory attribution + +Saved plugin memories now parse source attribution more accurately, and the dashboard shows the correct plugin logos and recent-memory rows for OpenClaw and Hermes. + + diff --git a/apps/docs/docs.json b/apps/docs/docs.json index 6be467ff..71ff23c0 100644 --- a/apps/docs/docs.json +++ b/apps/docs/docs.json @@ -104,7 +104,11 @@ { "group": "Manage Content", "icon": "folder-cog", - "pages": ["document-operations", "memory-operations"] + "pages": [ + "document-operations", + "memory-operations", + "memory-review" + ] }, "overview/use-cases" ] @@ -306,7 +310,7 @@ "anchors": [ { "anchor": "Changelog", - "pages": ["changelog/overview"] + "pages": ["changelog/overview", "changelog/plugins"] } ], "tab": "Changelog" diff --git a/apps/docs/integrations/codex.mdx b/apps/docs/integrations/codex.mdx index 6307d996..0b86c82c 100644 --- a/apps/docs/integrations/codex.mdx +++ b/apps/docs/integrations/codex.mdx @@ -49,7 +49,7 @@ This command: - Copies hook and skill scripts to `~/.codex/supermemory/` - Enables `codex_hooks = true` in `~/.codex/config.toml` - Registers `UserPromptSubmit` (recall) and `Stop` (capture) hooks in `~/.codex/hooks.json` -- Installs `supermemory-search`, `supermemory-save`, and `supermemory-forget` skills to `~/.codex/skills/` +- Installs `supermemory-search`, `supermemory-save`, `supermemory-forget`, and `supermemory-status` skills to `~/.codex/skills/` Restart Codex CLI after installing. @@ -81,13 +81,14 @@ Tags are generated automatically — no configuration needed. You can override t ## Explicit Memory Skills -The installer includes three skills that Codex auto-discovers from `~/.codex/skills/`. They use the same `SUPERMEMORY_CODEX_API_KEY` as the hooks — no separate login needed. +The installer includes four skills that Codex auto-discovers from `~/.codex/skills/`. They use the same `SUPERMEMORY_CODEX_API_KEY` as the hooks — no separate login needed. | Skill | Description | |-------|-------------| | `supermemory-search` | Search your memories by natural-language query | | `supermemory-save` | Save important project knowledge to memory | | `supermemory-forget` | Remove outdated or incorrect memories | +| `supermemory-status` | Check Supermemory connection, hook, config, and skill status | These skills let you interact with memory explicitly — for example: @@ -95,6 +96,7 @@ These skills let you interact with memory explicitly — for example: > Remember that this project uses Vitest for unit tests and Playwright for E2E. > What do you remember about our database schema? > Forget the memory about the old API endpoint. +> Is Supermemory connected? ``` ## Verify Installation @@ -111,7 +113,7 @@ codex-supermemory status: API key: ✓ set (SUPERMEMORY_CODEX_API_KEY) Hook scripts: ✓ installed at ~/.codex/supermemory hooks.json: ✓ registered (implicit memory) - Skills: ✓ installed (supermemory-search, supermemory-save, supermemory-forget) + Skills: ✓ installed (supermemory-search, supermemory-save, supermemory-forget, supermemory-status) config.toml: ✓ exists All good! Memory is active. diff --git a/apps/docs/memory-operations.mdx b/apps/docs/memory-operations.mdx index baf3b36e..f70a9b16 100644 --- a/apps/docs/memory-operations.mdx +++ b/apps/docs/memory-operations.mdx @@ -121,28 +121,163 @@ Use **Create Memories** when you already know the exact facts to store (user pre ## Forget Memory -Soft-delete a memory — excluded from search results but preserved in the system. Use this when you might want to restore later. +Soft-delete a single memory — excluded from search results but preserved in the database. Identify it by `id` or by exact `content`, scoped to its `containerTag`. ```typescript - await fetch("https://api.supermemory.ai/v4/memories/mem_abc123/forget", { - method: "POST", + await fetch("https://api.supermemory.ai/v4/memories", { + method: "DELETE", headers: { - "Authorization": `Bearer ${API_KEY}` - } + "Authorization": `Bearer ${API_KEY}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + // Identify by ID or by exact content + id: "mem_abc123", + // content: "John prefers dark mode", + containerTag: "user_123", + reason: "outdated information" + }) }); ``` ```bash - curl -X POST "https://api.supermemory.ai/v4/memories/mem_abc123/forget" \ - -H "Authorization: Bearer $SUPERMEMORY_API_KEY" + curl -X DELETE "https://api.supermemory.ai/v4/memories" \ + -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "id": "mem_abc123", + "containerTag": "user_123", + "reason": "outdated information" + }' ``` -The memory will no longer appear in search results but remains in the database. +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `id` | string | * | Memory ID to forget | +| `content` | string | * | Exact content match to forget (alternative to ID) | +| `containerTag` | string | yes | Container tag / space the memory belongs to | +| `reason` | string | no | Optional reason recorded as `forgetReason` | + +\* Either `id` or `content` must be provided. + +The memory will no longer appear in search results but remains in the database (`isForgotten=true`). + +--- + +## Forget Matching + +Forget **everything** about a topic in one call. You give a prompt or a query; the service semantically searches the container's memories, an LLM decides which ones are genuinely about your target, and those are soft-deleted. Use this for "forget everything about X" rather than deleting memories one by one. + + +This is a bulk, destructive operation. Always **`dryRun` first** to review what would be forgotten, then re-run with `dryRun: false`. The match is semantic, so a too-broad query can select more than you intend — `threshold` and `maxForget` bound the blast radius. + + + + + ```typescript + // 1) Preview + const preview = await fetch("https://api.supermemory.ai/v4/memories/forget-matching", { + method: "POST", + headers: { + "Authorization": `Bearer ${API_KEY}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + query: "forget everything about Project Titan", + containerTag: "user_123", + dryRun: true + }) + }).then((r) => r.json()); + // preview.candidates → [{ id, memory, score }, ...] + + // 2) Apply + const result = await fetch("https://api.supermemory.ai/v4/memories/forget-matching", { + method: "POST", + headers: { + "Authorization": `Bearer ${API_KEY}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + query: "forget everything about Project Titan", + containerTag: "user_123", + dryRun: false, + reason: "project cancelled" + }) + }).then((r) => r.json()); + // result.forgotten → [{ id, memory, score }, ...] + // result.forgetBatchId → tagged on every forgotten memory for traceability + ``` + + + ```bash + # Preview + curl -X POST "https://api.supermemory.ai/v4/memories/forget-matching" \ + -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "query": "forget everything about Project Titan", + "containerTag": "user_123", + "dryRun": true + }' + + # Apply + curl -X POST "https://api.supermemory.ai/v4/memories/forget-matching" \ + -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "query": "forget everything about Project Titan", + "containerTag": "user_123", + "dryRun": false, + "reason": "project cancelled" + }' + ``` + + + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `query` | string | yes | What to forget — a natural-language instruction ("forget everything about Project Titan") or a bare topic ("Project Titan") | +| `containerTag` | string | yes | Container tag / space to scope the operation to | +| `dryRun` | boolean | no | When `true`, returns what *would* be forgotten without changing anything. Defaults to `false` | +| `threshold` | number | no | Similarity floor (0–1) for candidate memories. Lower casts a wider net. Defaults to `0.5` | +| `maxForget` | number | no | Safety cap on how many memories may be forgotten in one call (1–500). Defaults to `100` | +| `reason` | string | no | Reason recorded as `forgetReason` on each forgotten memory | + +### Response + +```json +{ + "dryRun": false, + "count": 3, + "forgetBatchId": "VcuQoGRz4hA4ak5Xu6DRUN", + "summary": "Forgot 3 memories about \"Project Titan\".", + "forgotten": [ + { "id": "mem_1", "memory": "Project Titan ships in Q3", "score": 0.82 } + ] +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `dryRun` | boolean | Whether this was a preview or a real forget | +| `count` | number | Number of memories selected (dryRun) or forgotten (apply) | +| `forgetBatchId` | string \| null | ID tagged on every memory forgotten in this call; `null` on dryRun | +| `summary` | string | One-line summary of the operation (e.g. `Forgot 3 memories about "Project Titan".`) | +| `candidates` | array | On `dryRun`: the memories that **would** be forgotten (`{ id, memory, score }`) | +| `forgotten` | array | On apply: the memories that **were** forgotten (`{ id, memory, score }`) | + + +Identity is server-owned: the LLM only ever references opaque handles for the memories a search returned, so it can never forget a memory outside the results it reviewed, and every operation is scoped to the `containerTag` you pass. + --- @@ -201,6 +336,7 @@ Update a memory by creating a new version. The original is preserved with `isLat ## Next Steps +- [Review Inferred Memories](/memory-review) — Approve or decline low-confidence memories - [Document Operations](/document-operations) — Manage documents (SDK supported) - [Search](/search) — Query your memories - [Ingesting Content](/add-memories) — Add new content diff --git a/apps/docs/memory-review.mdx b/apps/docs/memory-review.mdx new file mode 100644 index 00000000..84fda2fa --- /dev/null +++ b/apps/docs/memory-review.mdx @@ -0,0 +1,288 @@ +--- +title: "Review Inferred Memories" +sidebarTitle: "Memory Review" +description: "List and act on low-confidence inferred memories — approve, decline, or undo" +icon: "list-checks" +--- + +Supermemory's graph automatically **derives** new facts from patterns across your +existing memories (see [Graph Memory](/concepts/graph-memory)). These derived facts +are guesses — the engine wasn't told them directly — so they are flagged as +**inferred** (`isInference: true`) and **down-weighted in search** until confirmed. + +These two endpoints let you build a review experience on top of that queue: list the +inferred memories awaiting review, then **approve**, **decline**, or **undo** a +decision on each one. + + +These endpoints are scoped to a single [container tag](/concepts/container-tags) +(space), under `/v3/container-tags/{containerTag}`. + + +## How review affects ranking + +While a memory is unreviewed and inferred it is down-weighted in search, so the +engine's guesses rank below facts you stated explicitly. Reviewing it resolves that +either way: + +| Action | Result | Effect on search | +|--------|--------|------------------| +| **Approve** | `isInference` cleared | Ranks like a stated fact — no longer down-weighted | +| **Decline** | `isForgotten` set | Removed from search entirely — a rejected guess is forgotten | +| **Undo** | back to unreviewed | Returns to the queue; inferred and down-weighted again | + +A reviewed memory is stamped with `reviewStatus` in its metadata so it drops out of the +review queue (declined memories also leave search, since they're forgotten). **Undo** +clears that stamp — and un-forgets a declined memory — bringing it back. + +--- + +## List Inferred Memories + +Return the inferred memories for a container tag that are still awaiting review (the +review queue). Reviewed memories are excluded. + +``` +GET /v3/container-tags/{containerTag}/inferred +``` + + + + ```typescript + const res = await fetch( + "https://api.supermemory.ai/v3/container-tags/user_123/inferred", + { headers: { "Authorization": `Bearer ${API_KEY}` } } + ); + + const { memories, total } = await res.json(); + ``` + + + ```bash + curl "https://api.supermemory.ai/v3/container-tags/user_123/inferred" \ + -H "Authorization: Bearer $SUPERMEMORY_API_KEY" + ``` + + + +### Path parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `containerTag` | string | The container tag / space to read the review queue for | + +### Response + +```json +{ + "memories": [ + { + "id": "mem_abc123", + "memory": "Alex likely works on Stripe's core payments product", + "parentCount": 3, + "createdAt": "2025-01-15T10:30:00.000Z", + "updatedAt": "2025-01-15T10:30:00.000Z", + "metadata": { "source": "derive" } + } + ], + "total": 1 +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `memories[].id` | string | Memory entry ID — pass to the review endpoint | +| `memories[].memory` | string | The inferred memory text | +| `memories[].parentCount` | number | How many source memories this was derived from. Higher = stronger signal | +| `memories[].createdAt` | string | ISO 8601 timestamp | +| `memories[].updatedAt` | string | ISO 8601 timestamp | +| `memories[].metadata` | object \| null | Arbitrary metadata stored on the memory | +| `total` | number | Count of unreviewed inferred memories returned | + + +The queue returns up to **50** memories, ordered by `parentCount` descending (most +strongly supported first), then by `createdAt` descending. It excludes anything that +is forgotten, expired, or already reviewed. An unknown or empty container tag returns +`{ "memories": [], "total": 0 }`. + + +--- + +## Review an Inferred Memory + +Record a decision on a single inferred memory. + +``` +POST /v3/container-tags/{containerTag}/inferred/{memoryId}/review +``` + + + + ```typescript + const res = await fetch( + "https://api.supermemory.ai/v3/container-tags/user_123/inferred/mem_abc123/review", + { + method: "POST", + headers: { + "Authorization": `Bearer ${API_KEY}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ action: "approve" }) + } + ); + + const result = await res.json(); + // { id: "mem_abc123", isInference: false, isForgotten: false, reviewStatus: "approved" } + ``` + + + ```bash + curl -X POST \ + "https://api.supermemory.ai/v3/container-tags/user_123/inferred/mem_abc123/review" \ + -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action": "approve"}' + ``` + + + +### Path parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `containerTag` | string | The container tag / space the memory belongs to | +| `memoryId` | string | The memory entry ID from the list endpoint | + +### Body parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `action` | string | yes | One of `approve`, `decline`, or `undo` | + + +The reject action is named **`decline`**. There is no `reject` value. + + +**Action semantics:** + +- **`approve`** — Promote the memory: clears `isInference`, so it ranks like a stated + fact instead of a down-weighted guess. Stamps `reviewStatus: "approved"`. +- **`decline`** — Reject the suggestion: the memory is **forgotten** + (`isForgotten: true`) and stamped `reviewStatus: "declined"`, so it leaves both + search and the review queue. +- **`undo`** — Revert a prior `approve`/`decline` back to the unreviewed inferred + state: restores `isInference: true`, un-forgets the memory (`isForgotten: false`), + and clears the review stamp, so it returns to the queue. + +### Response + +```json +{ + "id": "mem_abc123", + "isInference": false, + "isForgotten": false, + "reviewStatus": "approved" +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `id` | string | The reviewed memory ID | +| `isInference` | boolean | `false` after approve; `true` after decline or undo | +| `isForgotten` | boolean | `true` after decline (the memory is forgotten); `false` otherwise | +| `reviewStatus` | `"approved"` \| `"declined"` \| `null` | The new status; `null` after an undo | + +### Errors + +| Status | When | +|--------|------| +| `401` | Missing or invalid authentication | +| `404` | The container tag or memory was not found in your organization | +| `409` | The memory isn't reviewable for this action — it's not an inferred memory, or there's no prior review to undo | + +--- + +## Building a review experience + +The endpoints are designed for an optimistic, one-at-a-time review UI (swipe to keep / +decline, with undo). A typical client fetches the queue once, then pops each card off +locally as the user decides — `undo` re-adds it. + + +```typescript +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + +const BASE = "https://api.supermemory.ai/v3"; +const key = (tag: string) => ["inferred-memories", tag] as const; + +export type InferredMemory = { + id: string; + memory: string; + parentCount: number; + createdAt: string; + updatedAt: string; + metadata: Record | null; +}; + +export type ReviewAction = "approve" | "decline" | "undo"; + +export function useInferredMemories(containerTag: string) { + return useQuery({ + queryKey: key(containerTag), + queryFn: async (): Promise => { + const res = await fetch(`${BASE}/container-tags/${containerTag}/inferred`, { + headers: { Authorization: `Bearer ${API_KEY}` }, + }); + if (!res.ok) throw new Error("Failed to load review queue"); + const data = await res.json(); + return data.memories ?? []; + }, + staleTime: 60_000, + }); +} + +export function useReviewInferredMemory(containerTag: string) { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (vars: { memoryId: string; action: ReviewAction }) => { + const res = await fetch( + `${BASE}/container-tags/${containerTag}/inferred/${vars.memoryId}/review`, + { + method: "POST", + headers: { + Authorization: `Bearer ${API_KEY}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ action: vars.action }), + }, + ); + if (!res.ok) throw new Error("Review failed"); + return res.json(); + }, + onSuccess: (_data, { memoryId, action }) => { + // approve/decline remove the card; undo brings it back, so refetch. + if (action === "undo") { + queryClient.invalidateQueries({ queryKey: key(containerTag) }); + return; + } + queryClient.setQueryData(key(containerTag), (prev) => + prev?.filter((m) => m.id !== memoryId), + ); + }, + }); +} +``` + + + +There is no separate "skip" action. A swipe-to-skip is purely client-side — don't send +a request and the memory simply stays in the queue for a later session. + + +--- + +## Next Steps + +- [Graph Memory](/concepts/graph-memory) — How inferred (`derive`) memories are created +- [Memory Operations](/memory-operations) — Create, forget, and update memories +- [Search](/search) — How inferred memories are ranked in results diff --git a/apps/docs/user-profiles.mdx b/apps/docs/user-profiles.mdx index e2b3ae46..cb5c7cd1 100644 --- a/apps/docs/user-profiles.mdx +++ b/apps/docs/user-profiles.mdx @@ -121,6 +121,9 @@ Get profile and search results in one call by adding the `q` parameter: | `containerTag` | string | Yes | User/project identifier | | `q` | string | No | Search query (includes search results in response) | | `threshold` | 0-1 | No | Filter search results by relevance score | +| `filters` | object | No | Metadata filters applied to profile and search results | +| `include` | string[] | No | Sections to return — any of `"static"`, `"dynamic"`, `"buckets"`. Omit to return all | +| `buckets` | string[] | No | Restrict the `buckets` section to specific keys. Omit for all configured buckets | --- @@ -180,6 +183,142 @@ ${result.searchResults?.results.map(m => m.memory).join('\n') || 'None'} --- +## Profile Buckets + +Buckets are **custom topical categories** for a profile — an axis that sits alongside +`static` and `dynamic`. Where static/dynamic split facts by how long-lived they are, +buckets group them by subject (e.g. `preferences`, `goals`, `work`). As content is +ingested, a classifier assigns each memory to the buckets it matches, so you can pull +just the slice of context a given surface needs. + +Every org starts with a built-in `preferences` bucket. You can define your own at the +organization or space level in your console settings; space-level buckets are +**add-only** — a container tag inherits all org buckets and may add more, but cannot +disable them. + +### Requesting buckets + +Pass `include: ["buckets"]` to return bucket-organized memories, and optionally +`buckets` to limit the response to specific keys. `include` also lets you skip +sections you don't need — `["buckets"]` alone omits `static` and `dynamic`. + + + + ```typescript + const res = await fetch("https://api.supermemory.ai/v4/profile", { + method: "POST", + headers: { + "Authorization": `Bearer ${API_KEY}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + containerTag: "user_123", + include: ["buckets"], + buckets: ["preferences", "goals"] // optional — omit for all buckets + }) + }); + + const { profile } = await res.json(); + console.log(profile.buckets.preferences); + console.log(profile.buckets.goals); + ``` + + + ```bash + curl -X POST "https://api.supermemory.ai/v4/profile" \ + -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "containerTag": "user_123", + "include": ["buckets"], + "buckets": ["preferences", "goals"] + }' + ``` + + + +**Response:** +```json +{ + "profile": { + "buckets": { + "preferences": [ + "[Summary] Prefers concise, technical answers and dark-mode tooling", + "[Recent] Switched their editor to Zed" + ], + "goals": [ + "[Recent] Wants to ship the billing revamp this quarter" + ] + } + } +} +``` + + +**`[Recent]` and `[Summary]` labels.** To keep profiles dense, an entity's older +memories are periodically aggregated into a short synthesis. Entries prefixed +`[Summary]` are that aggregated context; entries prefixed `[Recent]` were ingested +since the last aggregation and aren't summarized yet. The `dynamic` section uses the +same `[Recent]` prefix (plus a `[YYYY-MM-DD]` date). Strip the prefixes if you only +want raw text, or keep them to signal recency to your model. + + +### List bucket definitions + +To see which buckets are configured for a container tag (org buckets merged with any +space-level additions), call `/v4/profile/buckets`: + + + + ```typescript + const res = await fetch("https://api.supermemory.ai/v4/profile/buckets", { + method: "POST", + headers: { + "Authorization": `Bearer ${API_KEY}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ containerTag: "user_123" }) + }); + + const { buckets } = await res.json(); + // [{ key: "preferences", description: "..." }, ...] + ``` + + + ```bash + curl -X POST "https://api.supermemory.ai/v4/profile/buckets" \ + -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"containerTag": "user_123"}' + ``` + + + +**Response:** +```json +{ + "buckets": [ + { + "key": "preferences", + "description": "Explicit first-person preferences the person directly stated." + } + ] +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `buckets[].key` | string | Stable slug, also stored on each memory. Lowercase alphanumeric with `-`/`_`, 1–64 chars | +| `buckets[].description` | string | What belongs in the bucket — guides the ingestion classifier | + + +Bucket descriptions steer classification. A precise description ("Explicit +first-person preferences only — exclude inferred traits") yields cleaner buckets than +a vague one. `static` and `dynamic` are reserved and can't be used as bucket keys. + + +--- + ## Framework Examples @@ -249,8 +388,9 @@ ${result.searchResults?.results.map(m => m.memory).join('\n') || 'None'} ```typescript interface ProfileResponse { profile: { - static: string[]; // Long-term facts - dynamic: string[]; // Recent context + static?: string[]; // Long-term facts + dynamic?: string[]; // Recent context + buckets?: Record; // Topical buckets, keyed by bucket key }; searchResults?: { // Only if q parameter provided results: SearchResult[]; diff --git a/apps/docs/vibe-coding.mdx b/apps/docs/vibe-coding.mdx index 0899692c..6a5c3279 100644 --- a/apps/docs/vibe-coding.mdx +++ b/apps/docs/vibe-coding.mdx @@ -47,7 +47,7 @@ Replace `claude` with: `cursor`, `opencode`, or `vscode` After adding the MCP, paste this in your agent session: -``` +```` You are integrating Supermemory into my application. Supermemory provides user memory, semantic search, and automatic knowledge extraction for AI applications. Note: You can always reference the documentation by using the **SearchSupermemoryDocs MCP** or running a web search tool for content on **supermemory.ai/docs**. @@ -386,7 +386,7 @@ NOW: 3. Include installation, settings config, and full integration DOCS: https://supermemory.ai/docs -``` +```` diff --git a/apps/mcp/pnpm-lock.yaml b/apps/mcp/pnpm-lock.yaml new file mode 100644 index 00000000..f7587960 --- /dev/null +++ b/apps/mcp/pnpm-lock.yaml @@ -0,0 +1,3480 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@cloudflare/workers-oauth-provider': + specifier: ^0.2.2 + version: 0.2.4 + '@modelcontextprotocol/ext-apps': + specifier: ^1.0.0 + version: 1.7.4(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@3.25.76))(react@19.2.7)(zod@3.25.76) + '@modelcontextprotocol/sdk': + specifier: ^1.25.2 + version: 1.29.0(@cfworker/json-schema@4.1.1)(zod@3.25.76) + agents: + specifier: ^0.3.5 + version: 0.3.10(@cloudflare/ai-chat@0.0.6)(@cloudflare/codemode@0.0.6)(@cloudflare/workers-types@4.20260620.1)(ai@6.0.208(zod@3.25.76))(hono@4.12.26)(react@19.2.7)(zod@3.25.76) + hono: + specifier: ^4.11.1 + version: 4.12.26 + posthog-node: + specifier: ^5.18.0 + version: 5.38.2 + supermemory: + specifier: ^4.0.0 + version: 4.24.12 + zod: + specifier: ^3.25.76 + version: 3.25.76 + devDependencies: + '@cloudflare/workers-types': + specifier: ^4.20250620.0 + version: 4.20260620.1 + d3-force-3d: + specifier: ^3.0.5 + version: 3.0.6 + force-graph: + specifier: ^1.49.0 + version: 1.51.4 + typescript: + specifier: ^5.8.3 + version: 5.9.3 + vite: + specifier: ^6.0.0 + version: 6.4.3 + vite-plugin-singlefile: + specifier: ^2.3.0 + version: 2.3.3(rollup@4.62.2)(vite@6.4.3) + vitest: + specifier: ^3.2.4 + version: 3.2.6 + wrangler: + specifier: ^4.4.0 + version: 4.103.0(@cloudflare/workers-types@4.20260620.1) + +packages: + + '@ai-sdk/gateway@3.0.133': + resolution: {integrity: sha512-Ebs+7iS9zUgJu5B0RlxM2JmDWzq79Cpd6YdiqcCzB5qFdpfQJPUDiXutqlQP89F2XGjOdDeidulBTXUdXWzOxw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@4.0.30': + resolution: {integrity: sha512-VO7I+vPffqI5sMnPoUq5DCSqKIgQIk/naJWRdQVpz2ma2zoprC/lqiJiUEl2s6DfvTD76TbhD3q39ROjlA6rGw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@3.0.10': + resolution: {integrity: sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw==} + engines: {node: '>=18'} + + '@apidevtools/json-schema-ref-parser@11.9.3': + resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} + engines: {node: '>= 16'} + + '@babel/runtime-corejs3@7.29.7': + resolution: {integrity: sha512-ppj9ouYku+RX0ljtgZd+KMO5mkM2bCqg8H2PYAFWnLsHEIKIdRojqbJ2i3eVHrisuxy7nOFCmngTDdWtUCdXUQ==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@cfworker/json-schema@4.1.1': + resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} + + '@cloudflare/ai-chat@0.0.6': + resolution: {integrity: sha512-XDJP7ywORzQd17f09hMY1n3+bQsLw+BmXh7HzuWGb7gBBbD23OlQXccuQQqFNBYHi+IfIpe9YdGbHaUGsfCUwA==} + peerDependencies: + agents: ^0.3.10 + ai: ^6.0.0 + react: ^19.0.0 + zod: ^3.25.0 || ^4.0.0 + + '@cloudflare/codemode@0.0.6': + resolution: {integrity: sha512-P8ba7fgyeOOEODgU+lyUj82P89VfslKExsdF6nPGRebIbgiCbbpoLHBmBtdRRsIasVHUhceSazJxIS6dg70yRA==} + peerDependencies: + agents: ^0.3.7 + ai: ^6.0.0 + zod: ^3.25.0 || ^4.0.0 + + '@cloudflare/kv-asset-handler@0.5.0': + resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==} + engines: {node: '>=22.0.0'} + + '@cloudflare/unenv-preset@2.16.1': + resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: '>1.20260305.0 <2.0.0-0' + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/workerd-darwin-64@1.20260617.1': + resolution: {integrity: sha512-jWwmgEVVWbsHNrLSNXzwjJaH90VzRxq1cWkQFUidxyeUPnMxemeNE8I9qFAfrpzGgE11e9sKDcE3ettJW08swQ==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20260617.1': + resolution: {integrity: sha512-LHH7b565g9znfCUOkwbec6FG2rmRbsgCy6aJiU9KN662mNheWl5sw/iKleiFSiljPKQQP3HkjnC/NSkdgi/aSA==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-linux-64@1.20260617.1': + resolution: {integrity: sha512-FMnaAKXe4Cfd8TQurCVd9fs2XQVBFRCsP+Id/SRdUv89MlwYu9zXfoyx6BxM+brPTIUK38SHbo8iaxiwzLi9JQ==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20260617.1': + resolution: {integrity: sha512-MRoifFYcqbxxIIQy7PqO5tFY/qPFSnjXzakWl0sO93l+HLyG35jRAgOi6jfqa4kBxc7gKKtH861DcewjxUfkjA==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-windows-64@1.20260617.1': + resolution: {integrity: sha512-rgBV9wQrv0OSKgCTTbhFUFY3sLGNANZ88aqaLvtmEn2gmbFVb1J4PDGochVUdB7NSEp4D/ghHva6/8SZmbONpw==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + + '@cloudflare/workers-oauth-provider@0.2.4': + resolution: {integrity: sha512-xovAzPPj0QT+HeSTgRIMlxlHciaUOOrojwra9/sRoIWsHRFbN04cNvXnXQEFlpc0pDENE23OZS2Do8wswySR2g==} + + '@cloudflare/workers-types@4.20260620.1': + resolution: {integrity: sha512-WB81w9u1bAS7KcekpC7/nYhLpIXAEtgybso7XgGJV8CQKNkNPYcyjvICLdghOlDBi/9Ivk+f7NRckV2Bkq1bDg==} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@jsdevtools/ono@7.1.3': + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + + '@modelcontextprotocol/ext-apps@1.7.4': + resolution: {integrity: sha512-QQqysE549cf/Y0VabBmAACXhj92EhB3t8yVct2BHbkWiPTFA1S91EqTVjYXXcZEefXU0pmHcdObhsNMcomJIOQ==} + engines: {node: '>=20'} + peerDependencies: + '@modelcontextprotocol/sdk': ^1.29.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + + '@modelcontextprotocol/sdk@1.25.2': + resolution: {integrity: sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@poppinss/colors@4.1.6': + resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} + + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} + + '@poppinss/exception@1.2.3': + resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} + + '@posthog/core@1.35.3': + resolution: {integrity: sha512-EsGPbSLl39Jgo2KZ+kI9UAxFnh5nddaN5bNm2rXvUwF+vGmam9eN1EXeNbxhRU7ulEeIiGdm7XjoU7pzavkgIQ==} + + '@posthog/types@1.390.2': + resolution: {integrity: sha512-WcfKz2GNn2vfDX8vXmJYbKxegPxVWHuDQ/pHdAn0HoZDXDFnEp/+x3qBQA+fEvtbPjjtjgAt2wIgJMlM7asx7g==} + + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + cpu: [x64] + os: [win32] + + '@sindresorhus/is@7.2.0': + resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} + engines: {node: '>=18'} + + '@speed-highlight/core@1.2.17': + resolution: {integrity: sha512-Z92FwKpCtfaW1V0jTU/fh3QzYEZN8wDwrzRIBoADCJfn4mJCNcJN/XegifX7BDrQ8/h9Xh/JnbyMchL0FqXrkg==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tweenjs/tween.js@25.0.0': + resolution: {integrity: sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/lodash@4.17.24': + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + + '@vercel/oidc@3.2.0': + resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} + engines: {node: '>= 20'} + + '@vitest/expect@3.2.6': + resolution: {integrity: sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==} + + '@vitest/mocker@3.2.6': + resolution: {integrity: sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.6': + resolution: {integrity: sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==} + + '@vitest/runner@3.2.6': + resolution: {integrity: sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==} + + '@vitest/snapshot@3.2.6': + resolution: {integrity: sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==} + + '@vitest/spy@3.2.6': + resolution: {integrity: sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==} + + '@vitest/utils@3.2.6': + resolution: {integrity: sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + accessor-fn@1.5.3: + resolution: {integrity: sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==} + engines: {node: '>=12'} + + agents@0.3.10: + resolution: {integrity: sha512-hKj3nbej14GA2SgE6/stQheJF35LCS7DxMuHG0QDl1/npnnECYyG0Yf5DXl6AvJAZOFNDwT3iEzKi8yLZngPDA==} + hasBin: true + peerDependencies: + '@ai-sdk/openai': ^3.0.0 + '@ai-sdk/react': ^3.0.0 + '@cloudflare/ai-chat': ^0.0.6 + '@cloudflare/codemode': ^0.0.6 + ai: ^6.0.0 + react: ^19.0.0 + viem: '>=2.0.0' + x402: ^0.7.1 + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + '@ai-sdk/openai': + optional: true + '@ai-sdk/react': + optional: true + viem: + optional: true + x402: + optional: true + + ai@6.0.208: + resolution: {integrity: sha512-STz+AaZqJ4ZjH7UkpXkbHx+bjgIDOsE8fIUoZjkZ2whoZcfVmG9K/TqEKouJZ03SuZuD7lagntlU3zBhAEkRpQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + bezier-js@6.1.4: + resolution: {integrity: sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==} + + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + canvas-color-tracker@1.3.2: + resolution: {integrity: sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==} + engines: {node: '>=12'} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + core-js-pure@3.49.0: + resolution: {integrity: sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cron-schedule@6.0.0: + resolution: {integrity: sha512-BoZaseYGXOo5j5HUwTaegIog3JJbuH4BbrY9A1ArLjXpy+RWb3mV28F/9Gv1dDA7E2L8kngWva4NWisnLTyfgQ==} + engines: {node: '>=20'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-binarytree@1.0.2: + resolution: {integrity: sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-force-3d@3.0.6: + resolution: {integrity: sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-octree@1.1.0: + resolution: {integrity: sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + event-target-polyfill@0.0.4: + resolution: {integrity: sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==} + + eventsource-parser@3.1.0: + resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + express-rate-limit@7.5.1: + resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express-rate-limit@8.5.2: + resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + float-tooltip@1.7.5: + resolution: {integrity: sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==} + engines: {node: '>=12'} + + force-graph@1.51.4: + resolution: {integrity: sha512-TdJ2KbkoiDQ7NIRx8IPGD0mAXXpLhamS7c+b7W98b0MHG7lphnda1VOQX/98UDTsttIAdH4TcP0l0MauSnLK8w==} + engines: {node: '>=12'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hono@4.12.26: + resolution: {integrity: sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw==} + engines: {node: '>=16.9.0'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + index-array-by@1.4.2: + resolution: {integrity: sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==} + engines: {node: '>=12'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + + js-base64@3.7.8: + resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + hasBin: true + + json-schema-to-typescript@15.0.4: + resolution: {integrity: sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==} + engines: {node: '>=16.0.0'} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + + kapsule@1.16.3: + resolution: {integrity: sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==} + engines: {node: '>=12'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mimetext@3.0.28: + resolution: {integrity: sha512-eQXpbNrtxLCjUtiVbR/qR09dbPgZ2o+KR1uA7QKqGhbn8QV7HIL16mXXsobBL4/8TqoYh1us31kfz+dNfCev9g==} + + miniflare@4.20260617.1: + resolution: {integrity: sha512-Go3/gzStm99QHptsSgU+q1S+xDfLoRgwjJNY80kaTVi0ENhTyqKq+sc4xZiWBSbM7uUcJwmzm8+QFKtcYLJ9nw==} + engines: {node: '>=22.0.0'} + hasBin: true + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.14: + resolution: {integrity: sha512-U9kYi5bpVMEI31yC8iw4bJJp0avcHXA0W8/wNfLfnvJYzihQo2ZRPYPvpAAd570HAcCBjCTN7vnr+v4StKl1IQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanoid@5.1.15: + resolution: {integrity: sha512-kBg3RpGtIe+RpTbyXwoI6pk5yD7KUiI3sygUqgeBMRst42KmhB4RZC7eiO9Wa1HIpaCCtpE2DJ6OI4Wi5ebwFw==} + engines: {node: ^18 || >=20} + hasBin: true + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + partyserver@0.1.5: + resolution: {integrity: sha512-kaE3GYaYWFc70EJQDQEhyYbO2Wczz/NgsFXerfjRo0t2s7ZxL1XggWT+HkMrdEyqbZOv3b66CV93WG0Lcg/ThQ==} + peerDependencies: + '@cloudflare/workers-types': ^4.20240729.0 + + partysocket@1.1.11: + resolution: {integrity: sha512-P0EtOQiAwvLriqLgdThcSaREfz3bP77LkLSdmXq680BosPKvGSoGTh/d0g3S+UNmaqcw89Ad7JXHHKyRx3xU9Q==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + posthog-node@5.38.2: + resolution: {integrity: sha512-eiKpU+vX4hVuHbO/EosvPHsmh2AVIdoVmWss/uUOs1t4b0ViCblw2o8OIFqHxKj3mYRnSOBlX0Dw3wBvcCaYpA==} + engines: {node: ^20.20.0 || >=22.22.0} + peerDependencies: + rxjs: ^7.0.0 + peerDependenciesMeta: + rxjs: + optional: true + + preact@10.29.2: + resolution: {integrity: sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==} + + prettier@3.8.4: + resolution: {integrity: sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==} + engines: {node: '>=14'} + hasBin: true + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.15.2: + resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + supermemory@4.24.12: + resolution: {integrity: sha512-xAFextuqk4JuoW33jJaFGqT1oMppN2IgfWUrV18Fv3qAAZ6M1SR1tb+7EBq8vrEQIx4iY2MQh5p+qnfL6lI8Yw==} + hasBin: true + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici@7.28.0: + resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + engines: {node: '>=20.18.1'} + + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite-plugin-singlefile@2.3.3: + resolution: {integrity: sha512-XVnGH0QzbOa8fxRSsHdCarVN1BSBXNi7uLMQYlrGRN5apdHkk62XQWRJhVever0lnfuyBkwn+kvVChdm/OoOUg==} + engines: {node: '>18.0.0'} + peerDependencies: + rollup: ^4.59.0 + vite: ^5.4.21 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + rollup: + optional: true + + vite@6.4.3: + resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + 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 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.6: + resolution: {integrity: sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.6 + '@vitest/ui': 3.2.6 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + workerd@1.20260617.1: + resolution: {integrity: sha512-Re5pl6pdowt3ZmWUzGlOuB7jbRIIPetgKalmo4cYmucQnVhpo7/3e4MfpekbhLi2EhZZz5EY9NWRu8zFzuEZew==} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.103.0: + resolution: {integrity: sha512-3Lv1P5t2xcSEkSTKtG+Lz+3JFryuU7YPLkaCUj7gNe+CJsjZJLtUwqsh1x595QBxkIbCE0GAvDx2DCJUU4+oqw==} + engines: {node: '>=22.0.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^4.20260617.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + youch-core@0.3.3: + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} + + youch@4.1.0-beta.10: + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod-to-ts@2.1.0: + resolution: {integrity: sha512-jZP1GokTqR99FLmtGU+B9acjD9u/R++b++Vxe1sWpBQDd82/9/j5LyLZZ7/Oy3h2nxcz5NihikgX4D0hhdu3+g==} + peerDependencies: + typescript: ^5 || ^6 + zod: ^3.25.0 || ^4.0.0 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@ai-sdk/gateway@3.0.133(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 3.0.10 + '@ai-sdk/provider-utils': 4.0.30(zod@3.25.76) + '@vercel/oidc': 3.2.0 + zod: 3.25.76 + + '@ai-sdk/provider-utils@4.0.30(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 3.0.10 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.1.0 + zod: 3.25.76 + + '@ai-sdk/provider@3.0.10': + dependencies: + json-schema: 0.4.0 + + '@apidevtools/json-schema-ref-parser@11.9.3': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.2.0 + + '@babel/runtime-corejs3@7.29.7': + dependencies: + core-js-pure: 3.49.0 + + '@babel/runtime@7.29.7': {} + + '@cfworker/json-schema@4.1.1': {} + + '@cloudflare/ai-chat@0.0.6(agents@0.3.10)(ai@6.0.208(zod@3.25.76))(react@19.2.7)(zod@3.25.76)': + dependencies: + agents: 0.3.10(@cloudflare/ai-chat@0.0.6)(@cloudflare/codemode@0.0.6)(@cloudflare/workers-types@4.20260620.1)(ai@6.0.208(zod@3.25.76))(hono@4.12.26)(react@19.2.7)(zod@3.25.76) + ai: 6.0.208(zod@3.25.76) + react: 19.2.7 + zod: 3.25.76 + + '@cloudflare/codemode@0.0.6(agents@0.3.10)(ai@6.0.208(zod@3.25.76))(typescript@5.9.3)(zod@3.25.76)': + dependencies: + agents: 0.3.10(@cloudflare/ai-chat@0.0.6)(@cloudflare/codemode@0.0.6)(@cloudflare/workers-types@4.20260620.1)(ai@6.0.208(zod@3.25.76))(hono@4.12.26)(react@19.2.7)(zod@3.25.76) + ai: 6.0.208(zod@3.25.76) + zod: 3.25.76 + zod-to-ts: 2.1.0(typescript@5.9.3)(zod@3.25.76) + transitivePeerDependencies: + - typescript + + '@cloudflare/kv-asset-handler@0.5.0': {} + + '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260617.1)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20260617.1 + + '@cloudflare/workerd-darwin-64@1.20260617.1': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20260617.1': + optional: true + + '@cloudflare/workerd-linux-64@1.20260617.1': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20260617.1': + optional: true + + '@cloudflare/workerd-windows-64@1.20260617.1': + optional: true + + '@cloudflare/workers-oauth-provider@0.2.4': {} + + '@cloudflare/workers-types@4.20260620.1': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true + + '@hono/node-server@1.19.14(hono@4.12.26)': + dependencies: + hono: 4.12.26 + + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.11.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jsdevtools/ono@7.1.3': {} + + '@modelcontextprotocol/ext-apps@1.7.4(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@3.25.76))(react@19.2.7)(zod@3.25.76)': + dependencies: + '@modelcontextprotocol/sdk': 1.29.0(@cfworker/json-schema@4.1.1)(zod@3.25.76) + '@standard-schema/spec': 1.1.0 + zod: 3.25.76 + optionalDependencies: + react: 19.2.7 + + '@modelcontextprotocol/sdk@1.25.2(@cfworker/json-schema@4.1.1)(hono@4.12.26)(zod@3.25.76)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.26) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.0 + express: 5.2.1 + express-rate-limit: 7.5.1(express@5.2.1) + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + optionalDependencies: + '@cfworker/json-schema': 4.1.1 + transitivePeerDependencies: + - hono + - supports-color + + '@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@3.25.76)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.26) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.0 + express: 5.2.1 + express-rate-limit: 8.5.2(express@5.2.1) + hono: 4.12.26 + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + optionalDependencies: + '@cfworker/json-schema': 4.1.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/api@1.9.1': {} + + '@poppinss/colors@4.1.6': + dependencies: + kleur: 4.1.5 + + '@poppinss/dumper@0.6.5': + dependencies: + '@poppinss/colors': 4.1.6 + '@sindresorhus/is': 7.2.0 + supports-color: 10.2.2 + + '@poppinss/exception@1.2.3': {} + + '@posthog/core@1.35.3': + dependencies: + '@posthog/types': 1.390.2 + + '@posthog/types@1.390.2': {} + + '@rollup/rollup-android-arm-eabi@4.62.2': + optional: true + + '@rollup/rollup-android-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-x64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.2': + optional: true + + '@sindresorhus/is@7.2.0': {} + + '@speed-highlight/core@1.2.17': {} + + '@standard-schema/spec@1.1.0': {} + + '@tweenjs/tween.js@25.0.0': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/lodash@4.17.24': {} + + '@vercel/oidc@3.2.0': {} + + '@vitest/expect@3.2.6': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.6 + '@vitest/utils': 3.2.6 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.6(vite@6.4.3)': + dependencies: + '@vitest/spy': 3.2.6 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.3 + + '@vitest/pretty-format@3.2.6': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.6': + dependencies: + '@vitest/utils': 3.2.6 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.6': + dependencies: + '@vitest/pretty-format': 3.2.6 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.6': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.6': + dependencies: + '@vitest/pretty-format': 3.2.6 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + accessor-fn@1.5.3: {} + + agents@0.3.10(@cloudflare/ai-chat@0.0.6)(@cloudflare/codemode@0.0.6)(@cloudflare/workers-types@4.20260620.1)(ai@6.0.208(zod@3.25.76))(hono@4.12.26)(react@19.2.7)(zod@3.25.76): + dependencies: + '@cfworker/json-schema': 4.1.1 + '@cloudflare/ai-chat': 0.0.6(agents@0.3.10)(ai@6.0.208(zod@3.25.76))(react@19.2.7)(zod@3.25.76) + '@cloudflare/codemode': 0.0.6(agents@0.3.10)(ai@6.0.208(zod@3.25.76))(typescript@5.9.3)(zod@3.25.76) + '@modelcontextprotocol/sdk': 1.25.2(@cfworker/json-schema@4.1.1)(hono@4.12.26)(zod@3.25.76) + ai: 6.0.208(zod@3.25.76) + cron-schedule: 6.0.0 + escape-html: 1.0.3 + json-schema: 0.4.0 + json-schema-to-typescript: 15.0.4 + mimetext: 3.0.28 + nanoid: 5.1.15 + partyserver: 0.1.5(@cloudflare/workers-types@4.20260620.1) + partysocket: 1.1.11 + react: 19.2.7 + yargs: 18.0.0 + zod: 3.25.76 + transitivePeerDependencies: + - '@cloudflare/workers-types' + - hono + - supports-color + + ai@6.0.208(zod@3.25.76): + dependencies: + '@ai-sdk/gateway': 3.0.133(zod@3.25.76) + '@ai-sdk/provider': 3.0.10 + '@ai-sdk/provider-utils': 4.0.30(zod@3.25.76) + '@opentelemetry/api': 1.9.1 + zod: 3.25.76 + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@6.2.2: {} + + ansi-styles@6.2.3: {} + + argparse@2.0.1: {} + + assertion-error@2.0.1: {} + + bezier-js@6.1.4: {} + + blake3-wasm@2.1.5: {} + + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.0.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.2 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + bytes@3.1.2: {} + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + canvas-color-tracker@1.3.2: + dependencies: + tinycolor2: 1.6.0 + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + check-error@2.1.3: {} + + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.0.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cookie@1.1.1: {} + + core-js-pure@3.49.0: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cron-schedule@6.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-binarytree@1.0.2: {} + + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-ease@3.0.1: {} + + d3-force-3d@3.0.6: + dependencies: + d3-binarytree: 1.0.2 + d3-dispatch: 3.0.1 + d3-octree: 1.1.0 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-octree@1.1.0: {} + + d3-quadtree@3.0.1: {} + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + depd@2.0.0: {} + + detect-libc@2.1.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + emoji-regex@10.6.0: {} + + encodeurl@2.0.0: {} + + error-stack-parser-es@1.0.5: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + etag@1.8.1: {} + + event-target-polyfill@0.0.4: {} + + eventsource-parser@3.1.0: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.0 + + expect-type@1.3.0: {} + + express-rate-limit@7.5.1(express@5.2.1): + dependencies: + express: 5.2.1 + + express-rate-limit@8.5.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.2.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.2 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.2: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + float-tooltip@1.7.5: + dependencies: + d3-selection: 3.0.0 + kapsule: 1.16.3 + preact: 10.29.2 + + force-graph@1.51.4: + dependencies: + '@tweenjs/tween.js': 25.0.0 + accessor-fn: 1.5.3 + bezier-js: 6.1.4 + canvas-color-tracker: 1.3.2 + d3-array: 3.2.4 + d3-drag: 3.0.0 + d3-force-3d: 3.0.6 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + float-tooltip: 1.7.5 + index-array-by: 1.4.2 + kapsule: 1.16.3 + lodash-es: 4.18.1 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.6.0: {} + + 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.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hono@4.12.26: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + index-array-by@1.4.2: {} + + inherits@2.0.4: {} + + internmap@2.0.3: {} + + ip-address@10.2.0: {} + + ipaddr.js@1.9.1: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-promise@4.0.0: {} + + isexe@2.0.0: {} + + jose@6.2.3: {} + + js-base64@3.7.8: {} + + js-tokens@9.0.1: {} + + js-yaml@4.2.0: + dependencies: + argparse: 2.0.1 + + json-schema-to-typescript@15.0.4: + dependencies: + '@apidevtools/json-schema-ref-parser': 11.9.3 + '@types/json-schema': 7.0.15 + '@types/lodash': 4.17.24 + is-glob: 4.0.3 + js-yaml: 4.2.0 + lodash: 4.18.1 + minimist: 1.2.8 + prettier: 3.8.4 + tinyglobby: 0.2.17 + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + json-schema@0.4.0: {} + + kapsule@1.16.3: + dependencies: + lodash-es: 4.18.1 + + kleur@4.1.5: {} + + lodash-es@4.18.1: {} + + lodash@4.18.1: {} + + loupe@3.2.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mimetext@3.0.28: + dependencies: + '@babel/runtime': 7.29.7 + '@babel/runtime-corejs3': 7.29.7 + js-base64: 3.7.8 + mime-types: 2.1.35 + + miniflare@4.20260617.1: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.34.5 + undici: 7.28.0 + workerd: 1.20260617.1 + ws: 8.21.0 + youch: 4.1.0-beta.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + minimist@1.2.8: {} + + ms@2.1.3: {} + + nanoid@3.3.14: {} + + nanoid@5.1.15: {} + + negotiator@1.0.0: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + parseurl@1.3.3: {} + + partyserver@0.1.5(@cloudflare/workers-types@4.20260620.1): + dependencies: + '@cloudflare/workers-types': 4.20260620.1 + nanoid: 5.1.15 + + partysocket@1.1.11: + dependencies: + event-target-polyfill: 0.0.4 + + path-key@3.1.1: {} + + path-to-regexp@6.3.0: {} + + path-to-regexp@8.4.2: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + pkce-challenge@5.0.1: {} + + postcss@8.5.15: + dependencies: + nanoid: 3.3.14 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + posthog-node@5.38.2: + dependencies: + '@posthog/core': 1.35.3 + + preact@10.29.2: {} + + prettier@3.8.4: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.15.2: + dependencies: + side-channel: 1.1.1 + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + react@19.2.7: {} + + require-from-string@2.0.2: {} + + rollup@4.62.2: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 + fsevents: 2.3.3 + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + safer-buffer@2.1.2: {} + + semver@7.8.5: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + statuses@2.0.2: {} + + std-env@3.10.0: {} + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + supermemory@4.24.12: {} + + supports-color@10.2.2: {} + + tinybench@2.9.0: {} + + tinycolor2@1.6.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + tslib@2.8.1: + optional: true + + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typescript@5.9.3: {} + + undici@7.28.0: {} + + unenv@2.0.0-rc.24: + dependencies: + pathe: 2.0.3 + + unpipe@1.0.0: {} + + vary@1.1.2: {} + + vite-node@3.2.4: + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.4.3 + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite-plugin-singlefile@2.3.3(rollup@4.62.2)(vite@6.4.3): + dependencies: + micromatch: 4.0.8 + vite: 6.4.3 + optionalDependencies: + rollup: 4.62.2 + + vite@6.4.3: + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.15 + rollup: 4.62.2 + tinyglobby: 0.2.17 + optionalDependencies: + fsevents: 2.3.3 + + vitest@3.2.6: + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.6 + '@vitest/mocker': 3.2.6(vite@6.4.3) + '@vitest/pretty-format': 3.2.6 + '@vitest/runner': 3.2.6 + '@vitest/snapshot': 3.2.6 + '@vitest/spy': 3.2.6 + '@vitest/utils': 3.2.6 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.4.3 + vite-node: 3.2.4 + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + workerd@1.20260617.1: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20260617.1 + '@cloudflare/workerd-darwin-arm64': 1.20260617.1 + '@cloudflare/workerd-linux-64': 1.20260617.1 + '@cloudflare/workerd-linux-arm64': 1.20260617.1 + '@cloudflare/workerd-windows-64': 1.20260617.1 + + wrangler@4.103.0(@cloudflare/workers-types@4.20260620.1): + dependencies: + '@cloudflare/kv-asset-handler': 0.5.0 + '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260617.1) + blake3-wasm: 2.1.5 + esbuild: 0.28.1 + miniflare: 4.20260617.1 + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260617.1 + optionalDependencies: + '@cloudflare/workers-types': 4.20260620.1 + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + ws@8.21.0: {} + + y18n@5.0.8: {} + + yargs-parser@22.0.0: {} + + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + + youch-core@0.3.3: + dependencies: + '@poppinss/exception': 1.2.3 + error-stack-parser-es: 1.0.5 + + youch@4.1.0-beta.10: + dependencies: + '@poppinss/colors': 4.1.6 + '@poppinss/dumper': 0.6.5 + '@speed-highlight/core': 1.2.17 + cookie: 1.1.1 + youch-core: 0.3.3 + + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod-to-ts@2.1.0(typescript@5.9.3)(zod@3.25.76): + dependencies: + typescript: 5.9.3 + zod: 3.25.76 + + zod@3.25.76: {} diff --git a/apps/mcp/src/server/auth/index.ts b/apps/mcp/src/server/auth/index.ts index a7fc5b24..268c5f84 100644 --- a/apps/mcp/src/server/auth/index.ts +++ b/apps/mcp/src/server/auth/index.ts @@ -6,6 +6,8 @@ import type { ContainerTagAccess } from "../../shared/types" +const FETCH_TIMEOUT_MS = 30_000 + export type { ContainerTagAccess } export interface AuthUser { @@ -30,6 +32,7 @@ export async function validateApiKey( const response = await fetch(`${apiUrl}/v3/session`, { method: "GET", headers: { Authorization: `Bearer ${apiKey}` }, + signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), }) if (!response.ok) { @@ -82,6 +85,7 @@ export async function validateOAuthToken( const response = await fetch(`${apiUrl}/v3/mcp/session-with-key`, { method: "GET", headers: { Authorization: `Bearer ${token}` }, + signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), }) if (!response.ok) { @@ -124,6 +128,7 @@ export async function validateOAuthToken( const rbacResponse = await fetch(`${apiUrl}/v3/session`, { method: "GET", headers: { Authorization: `Bearer ${data.apiKey}` }, + signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), }) if (!rbacResponse.ok) { console.error("RBAC fetch returned non-OK:", rbacResponse.status) diff --git a/apps/mcp/src/server/client/index.ts b/apps/mcp/src/server/client/index.ts index 995ddc36..0beb9f8d 100644 --- a/apps/mcp/src/server/client/index.ts +++ b/apps/mcp/src/server/client/index.ts @@ -8,6 +8,7 @@ import type { const MAX_CHARS = 200000 const DEFAULT_PROJECT_ID = "sm_project_default" +const FETCH_TIMEOUT_MS = 30_000 export type { ContainerTag, @@ -82,6 +83,7 @@ export class SupermemoryClient { this.client = new Supermemory({ apiKey: bearerToken, baseURL: apiUrl, + timeout: FETCH_TIMEOUT_MS, }) this.containerTag = containerTag || DEFAULT_PROJECT_ID } @@ -242,12 +244,14 @@ export class SupermemoryClient { async listContainerTags(): Promise { try { + const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS) const response = await fetch(`${this.apiUrl}/v3/container-tags/list`, { method: "GET", headers: { Authorization: `Bearer ${this.bearerToken}`, "Content-Type": "application/json", }, + signal, }) if (!response.ok) { @@ -270,8 +274,10 @@ export class SupermemoryClient { containerTags?: string[], page = 1, limit = 200, + options?: { signal?: AbortSignal }, ): Promise { try { + const signal = options?.signal ?? AbortSignal.timeout(FETCH_TIMEOUT_MS) const response = await fetch(`${this.apiUrl}/v3/documents/documents`, { method: "POST", headers: { @@ -285,6 +291,7 @@ export class SupermemoryClient { order: "desc", containerTags, }), + signal, }) if (!response.ok) { throw Object.assign(new Error("Failed to fetch documents"), { @@ -338,6 +345,15 @@ export class SupermemoryClient { } private handleError(error: unknown): never { + // Handle request timeout (AbortSignal.timeout or explicit abort) + if ( + error instanceof Error && + (error.name === "AbortError" || error.name === "TimeoutError") + ) { + throw new Error("Request to Supermemory API timed out") + } + + // Handle network/fetch errors if (error instanceof TypeError) { if ( error.message.includes("fetch") || diff --git a/apps/web/app/(app)/integrations/[card]/page.tsx b/apps/web/app/(app)/integrations/[card]/page.tsx new file mode 100644 index 00000000..b8d28cf1 --- /dev/null +++ b/apps/web/app/(app)/integrations/[card]/page.tsx @@ -0,0 +1,13 @@ +import { notFound } from "next/navigation" +import { AppExperience } from "@/components/app-experience" +import { isIntegrationCard } from "@/lib/integration-routes" + +export default async function IntegrationCardPage({ + params, +}: { + params: Promise<{ card: string }> +}) { + const { card } = await params + if (!isIntegrationCard(card)) notFound() + return +} diff --git a/apps/web/app/(app)/integrations/page.tsx b/apps/web/app/(app)/integrations/page.tsx new file mode 100644 index 00000000..350ef97a --- /dev/null +++ b/apps/web/app/(app)/integrations/page.tsx @@ -0,0 +1,5 @@ +import { AppExperience } from "@/components/app-experience" + +export default function IntegrationsPage() { + return +} diff --git a/apps/web/app/(app)/onboarding/page.tsx b/apps/web/app/(app)/onboarding/page.tsx index 64ee0299..c819d0f8 100644 --- a/apps/web/app/(app)/onboarding/page.tsx +++ b/apps/web/app/(app)/onboarding/page.tsx @@ -5,6 +5,7 @@ import { useRouter, useSearchParams } from "next/navigation" import { toast } from "sonner" import { useAuth } from "@lib/auth-context" import { authClient } from "@lib/auth" +import { analytics } from "@/lib/analytics" import { BrainShell } from "@/components/onboarding-brain/shell" import { StepAbout, @@ -15,6 +16,7 @@ import { type SourcesValues, } from "@/components/onboarding-brain/step-sources" import { StepIngest } from "@/components/onboarding-brain/step-ingest" +import { useFeatureFlagEnabled } from "posthog-js/react" import { StepTeam, type TeamValues, @@ -34,12 +36,29 @@ import { const STORAGE_KEY = "supermemory-brain-onboarding-v1" +const countsAsConnectedSource = (state: unknown) => + state === "connected" || state === "waitlist" + +const getErrorMessage = (error: unknown, fallback: string) => { + if (error instanceof Error && error.message) return error.message + if (typeof error === "string" && error.trim()) return error + if (typeof error === "object" && error !== null && "message" in error) { + const message = (error as { message?: unknown }).message + if (typeof message === "string" && message.trim()) return message + } + return fallback +} + export default function BrainOnboardingPage() { const router = useRouter() const params = useSearchParams() const { user, org, organizations, setActiveOrg, refetchOrganizations } = useAuth() + // `?new=1` forces creating an additional org even when the user already has one. + const forceCreate = params?.get("new") === "1" + const nameParam = params?.get("name")?.trim() || "" + const stepFromUrl = (params?.get("step") as BrainStep | null) ?? "about" const initialStep: BrainStep = BRAIN_STEPS.includes(stepFromUrl) ? stepFromUrl @@ -60,11 +79,13 @@ export default function BrainOnboardingPage() { [user?.email], ) + // Team (Company Brain) onboarding is gated behind a private-beta flag. + const allowTeam = useFeatureFlagEnabled("company-brain-beta") ?? false const [mode, setMode] = useState(detectedMode) const [about, setAbout] = useState({ name: user?.name ?? "", about: "", - workspaceName: suggestedWorkspaceName, + workspaceName: nameParam || suggestedWorkspaceName, workspaceDomain: domain ?? "", }) const [sources, setSources] = useState({ @@ -78,6 +99,7 @@ export default function BrainOnboardingPage() { }) useEffect(() => { + if (forceCreate) return try { const raw = localStorage.getItem(STORAGE_KEY) if (!raw) return @@ -92,7 +114,7 @@ export default function BrainOnboardingPage() { if (cached.sources) setSources((s) => ({ ...s, ...cached.sources })) if (cached.team) setTeam((t) => ({ ...t, ...cached.team })) } catch {} - }, []) + }, [forceCreate]) useEffect(() => { try { @@ -103,8 +125,40 @@ export default function BrainOnboardingPage() { } catch {} }, [mode, about, sources, team]) + const navTrigger = useRef<"user" | "auto">("auto") + const startedRef = useRef(false) + useEffect(() => { + if (startedRef.current) return + startedRef.current = true + analytics.onboardingStarted({ + mode: detectedMode, + entry_step: initialStep, + }) + analytics.onboardingStepViewed({ + step: initialStep, + index: BRAIN_STEPS.indexOf(initialStep), + trigger: "auto", + }) + }, [detectedMode, initialStep]) + + const firstStepRender = useRef(true) + useEffect(() => { + // Skip the mount run — the gated effect above fires the initial view. + if (firstStepRender.current) { + firstStepRender.current = false + return + } + analytics.onboardingStepViewed({ + step, + index: BRAIN_STEPS.indexOf(step), + trigger: navTrigger.current, + }) + navTrigger.current = "auto" + }, [step]) + const setStepAndUrl = useCallback( (next: BrainStep) => { + navTrigger.current = "user" setStep(next) const url = new URL(window.location.href) url.searchParams.set("step", next) @@ -127,37 +181,67 @@ export default function BrainOnboardingPage() { return plan === "scale" || plan === "scale_yearly" }, [org]) + // Personal onboarding has no team step — drop it from the flow + stepper. + const steps = useMemo( + () => + mode === "team" ? BRAIN_STEPS : BRAIN_STEPS.filter((s) => s !== "team"), + [mode], + ) + const finish = useCallback(async () => { + analytics.onboardingCompleted({ + mode, + steps_completed: BRAIN_STEPS.length, + sources_connected: Object.values(sources.connected).filter( + countsAsConnectedSource, + ).length, + invites_sent: team.invites.filter((i) => i.email.trim()).length, + }) try { localStorage.removeItem(STORAGE_KEY) } catch {} + // Extra org from settings: hard-reload so org-scoped caches don't show the previous org's data. + if (forceCreate) { + window.location.href = "/?onboarded=1" + return + } router.push("/?onboarded=1") - }, [router]) + }, [router, mode, sources, team, forceCreate]) const goNext = useCallback(() => { - const idx = BRAIN_STEPS.indexOf(step) - const next = BRAIN_STEPS[idx + 1] + const idx = steps.indexOf(step) + analytics.onboardingStepCompleted({ step, index: idx }) + const next = steps[idx + 1] if (!next) { finish() return } setStepAndUrl(next) - }, [step, setStepAndUrl, finish]) + }, [step, steps, setStepAndUrl, finish]) + + // If the current step isn't valid for the mode (e.g. switched to personal), + // fall back to the last valid step. + useEffect(() => { + if (!steps.includes(step)) { + setStepAndUrl(steps[steps.length - 1] ?? "about") + } + }, [steps, step, setStepAndUrl]) const [creatingOrg, setCreatingOrg] = useState(false) const creatingOrgRef = useRef(false) const ensureOrg = useCallback(async () => { - if (organizations && organizations.length > 0) return + if (!forceCreate && organizations && organizations.length > 0) return const name = (about.workspaceName || suggestedWorkspaceName).trim() const slug = generateOrgSlug(name) + const effectiveMode = allowTeam ? mode : "personal" const metadata: BrainMetadata & { signupSource: string } = { signupSource: "consumer", brainOnboardingVersion: "v1", - brainMode: mode, + brainMode: effectiveMode, brainWorkspaceName: name, brainWorkspaceDomain: - mode === "team" ? about.workspaceDomain || domain : null, + effectiveMode === "team" ? about.workspaceDomain || domain : null, brainContainerTag: containerTag, ...(about.about.trim() ? { brainAbout: about.about.trim() } : {}), } @@ -166,7 +250,12 @@ export default function BrainOnboardingPage() { slug, metadata, }) - await setActiveOrg(result.data?.slug ?? slug) + if (result.error || !result.data?.slug) { + throw new Error( + getErrorMessage(result.error, "Organization was not created."), + ) + } + await setActiveOrg(result.data.slug) if (about.name.trim()) { await authClient.updateUser({ name: about.name.trim(), @@ -175,15 +264,30 @@ export default function BrainOnboardingPage() { }) } await refetchOrganizations() + analytics.onboardingWorkspaceCreated({ + mode, + has_about: Boolean(about.about.trim()), + has_domain: Boolean(mode === "team" && (about.workspaceDomain || domain)), + }) + // Drop new=1 so a reload or back+Continue reuses this org instead of creating a duplicate. + if (forceCreate) { + const url = new URL(window.location.href) + url.searchParams.delete("new") + url.searchParams.delete("name") + router.replace(url.pathname + url.search, { scroll: false }) + } }, [ organizations, about, suggestedWorkspaceName, mode, + allowTeam, domain, containerTag, setActiveOrg, refetchOrganizations, + forceCreate, + router, ]) const handleAboutContinue = useCallback(async () => { @@ -194,13 +298,22 @@ export default function BrainOnboardingPage() { await ensureOrg() goNext() } catch (e) { + const message = getErrorMessage(e, "Organization was not created.") console.error("Failed to create organization:", e) - toast.error("Couldn't create your workspace. Please try again.") + analytics.onboardingWorkspaceCreateFailed({ + error: message, + }) + toast.error("Organization was not created", { + description: "Please try again from Settings.", + }) + if (forceCreate && (organizations?.length ?? 0) > 0) { + router.replace("/") + } } finally { creatingOrgRef.current = false setCreatingOrg(false) } - }, [ensureOrg, goNext]) + }, [ensureOrg, goNext, forceCreate, organizations, router]) const [sendingInvites, setSendingInvites] = useState(false) const sendingInvitesRef = useRef(false) @@ -209,6 +322,7 @@ export default function BrainOnboardingPage() { if (sendingInvitesRef.current) return const pending = team.invites.filter((i) => i.email.trim()) if (pending.length === 0) { + analytics.onboardingTeamSkipped() goNext() return } @@ -231,6 +345,10 @@ export default function BrainOnboardingPage() { r.status === "rejected" || (r.status === "fulfilled" && Boolean(r.value?.error)), ).length + analytics.onboardingInvitesSent({ + sent: pending.length - failed, + failed, + }) if (failed > 0) { toast.error( `${failed} of ${pending.length} invite${pending.length === 1 ? "" : "s"} couldn't be sent.`, @@ -255,12 +373,17 @@ export default function BrainOnboardingPage() { return ( {step === "about" && ( { + analytics.onboardingModeSelected({ mode: m }) + setMode(m) + }} + allowTeam={allowTeam} domain={domain} suggestedWorkspaceName={suggestedWorkspaceName} defaultName={user?.name ?? ""} @@ -281,7 +404,13 @@ export default function BrainOnboardingPage() { onContinue={goNext} /> )} - {step === "ingest" && } + {step === "ingest" && ( + + )} {step === "team" && ( { + analytics.onboardingTeamSkipped() + goNext() + }} submitting={sendingInvites} onUpgrade={() => router.push("/settings/billing")} /> diff --git a/apps/web/app/(app)/page.tsx b/apps/web/app/(app)/page.tsx index 020cad49..240d355e 100644 --- a/apps/web/app/(app)/page.tsx +++ b/apps/web/app/(app)/page.tsx @@ -1,850 +1,5 @@ -"use client" +import { AppExperience } from "@/components/app-experience" -import { - useState, - useCallback, - useEffect, - useMemo, - useRef, - useSyncExternalStore, -} from "react" -import { AnimatePresence, motion } from "motion/react" -import { useQueryState } from "nuqs" -import { Header, PublicHeader } from "@/components/header" -import { MobileBottomNav } from "@/components/bottom-nav" -import { ChatSidebar, HomeChatComposer } from "@/components/chat" -import type { ChatAttachmentDraft } from "@/components/chat/attachments" -import { DashboardView } from "@/components/dashboard-view" -import { MemoriesGrid } from "@/components/memories-grid" -import { GraphLayoutView } from "@/components/graph-layout-view" -import { IntegrationsView, DetailWrapper } from "@/components/integrations-view" -import { MCPDetailView } from "@/components/mcp-modal/mcp-detail-view" -import { XBookmarksDetailView } from "@/components/onboarding/x-bookmarks-detail-view" -import { ChromeDetail } from "@/components/integrations/chrome-detail" -import { ShortcutsDetail } from "@/components/integrations/shortcuts-detail" -import { RaycastDetail } from "@/components/integrations/raycast-detail" -import { PluginsDetail } from "@/components/integrations/plugins-detail" -import { AnimatedGradientBackground } from "@/components/animated-gradient-background" -import { OnboardingConfetti } from "@/components/onboarding-brain/onboarding-confetti" -import { AddDocumentModal } from "@/components/add-document" -import { DocumentModal } from "@/components/document-modal" -import { DocumentsCommandPalette } from "@/components/documents-command-palette" -import { FullscreenNoteModal } from "@/components/fullscreen-note-modal" -import type { HighlightItem } from "@/components/highlights-card" -import { DigestsView } from "@/components/digests-view" -import { HotkeysProvider } from "react-hotkeys-hook" -import { useHotkeys } from "react-hotkeys-hook" -import { useIsMobile } from "@hooks/use-mobile" -import { useAuth } from "@lib/auth-context" -import { useProject } from "@/stores" -import { useContainerTags } from "@/hooks/use-container-tags" -import { DEFAULT_PROJECT_ID } from "@lib/constants" -import { - useQuickNoteDraftReset, - useQuickNoteDraft, -} from "@/stores/quick-note-draft" -import { analytics } from "@/lib/analytics" -import type { ModelId, ReasoningEffort } from "@/lib/models" -import { useDocumentMutations } from "@/hooks/use-document-mutations" -import { useQuery, useQueryClient } from "@tanstack/react-query" -import { toast } from "sonner" -import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" -import type { z } from "zod" -import { useViewMode } from "@/lib/view-mode-context" -import type { MemoryOfDay } from "@/components/dashboard-view" -import { ErrorBoundary } from "@/components/error-boundary" -import { cn } from "@lib/utils" -import { - addDocumentParam, - searchParam, - qParam, - docParam, - fullscreenParam, - threadParam, - type IntegrationParamValue, -} from "@/lib/search-params" -import { getChatSpaceDisplayLabel } from "@/lib/chat-space-label" -import { getToolDocumentSpace } from "@/lib/plugin-space" - -type DocumentsResponse = z.infer -type DocumentWithMemories = DocumentsResponse["documents"][0] - -function subscribeViewportWidth(cb: () => void) { - window.addEventListener("resize", cb) - return () => window.removeEventListener("resize", cb) -} - -function getViewportWidth() { - return window.innerWidth -} - -const GRADIENT_TOP_WIDTH_MAX = 1440 - -function gradientTopPositionForWidth(width: number) { - const minW = 320 - const pctWide = 15 - const pctNarrow = 55 - const w = Math.min(GRADIENT_TOP_WIDTH_MAX, Math.max(minW, width)) - const t = (w - minW) / (GRADIENT_TOP_WIDTH_MAX - minW) - const eased = t * t - return `${Math.round(pctNarrow + eased * (pctWide - pctNarrow))}%` -} - -function ViewErrorFallback() { - return ( -
-

- Something went wrong.{" "} - -

-
- ) -} - -export default function NewPage() { - const isMobile = useIsMobile() - const { user, session, isSessionPending } = useAuth() - - const { selectedProject, selectedProjects, setSelectedProject } = useProject() - const selectedProjectTag = selectedProjects[0] - const { allProjects } = useContainerTags() - const dashboardSpaceLabel = useMemo( - () => - getChatSpaceDisplayLabel({ - selectedProject, - allProjects, - }), - [selectedProject, allProjects], - ) - const emptyStateSpaceName = selectedProjectTag - ? selectedProjectTag === DEFAULT_PROJECT_ID - ? "My Space" - : (allProjects.find((p) => p.containerTag === selectedProjectTag)?.name ?? - selectedProjectTag) - : undefined - - const { viewMode, setViewMode } = useViewMode() - const queryClient = useQueryClient() - const [highlightsForceAt, setHighlightsForceAt] = useState(0) - - // Chrome extension auth: send session token via postMessage so the content script can store it - useEffect(() => { - const url = new URL(window.location.href) - if (!url.searchParams.get("extension-auth-success")) return - const sessionToken = session?.token - const userData = { email: user?.email, name: user?.name, userId: user?.id } - if (sessionToken && userData.email) { - window.postMessage( - { token: encodeURIComponent(sessionToken), userData }, - window.location.origin, - ) - url.searchParams.delete("extension-auth-success") - window.history.replaceState({}, "", url.toString()) - } - }, [user, session]) - - // URL-driven modal states - const [addDoc, setAddDoc] = useQueryState("add", addDocumentParam) - const [isSearchOpen, setIsSearchOpen] = useQueryState("search", searchParam) - const [searchPrefill, setSearchPrefill] = useQueryState("q", qParam) - const [docId, setDocId] = useQueryState("doc", docParam) - const [isFullscreen, setIsFullscreen] = useQueryState( - "fullscreen", - fullscreenParam, - ) - const [, setThreadIdUrl] = useQueryState("thread", threadParam) - - // Ephemeral local state (not worth URL-encoding) - const [fullscreenInitialContent, setFullscreenInitialContent] = useState("") - const [queuedChatSeed, setQueuedChatSeed] = useState(null) - const [queuedChatModel, setQueuedChatModel] = useState(null) - const [queuedChatReasoningEffort, setQueuedChatReasoningEffort] = - useState(null) - const [queuedChatProject, setQueuedChatProject] = useState( - null, - ) - const [queuedChatAttachments, setQueuedChatAttachments] = useState< - ChatAttachmentDraft[] | null - >(null) - const [queuedHighlightContent, setQueuedHighlightContent] = useState< - string | null - >(null) - const [queuedMessageSource, setQueuedMessageSource] = useState< - "highlight" | "home" - >("highlight") - const [selectedDocument, setSelectedDocument] = - useState(null) - - // Clear document when docId is removed (e.g. back button) - useEffect(() => { - if (!docId) setSelectedDocument(null) - }, [docId]) - - useEffect(() => { - if (viewMode === "dashboard") void setThreadIdUrl(null) - }, [viewMode, setThreadIdUrl]) - - // Resolve document from cache when loading with ?doc= (deep link / refresh) - useEffect(() => { - if (!docId || selectedDocument) return - - const tryResolve = () => { - const queries = queryClient.getQueriesData<{ - pages: DocumentsResponse[] - }>({ queryKey: ["documents-with-memories"] }) - for (const [, data] of queries) { - if (!data?.pages) continue - for (const page of data.pages) { - const doc = page.documents?.find((d) => d.id === docId) - if (doc) { - setSelectedDocument(doc) - return true - } - } - } - return false - } - - if (tryResolve()) return - - const unsubscribe = queryClient.getQueryCache().subscribe(() => { - if (tryResolve()) unsubscribe() - }) - return unsubscribe - }, [docId, selectedDocument, queryClient]) - - const resetDraft = useQuickNoteDraftReset(selectedProject) - const { draft: quickNoteDraft } = useQuickNoteDraft(selectedProject || "") - const quickNoteDraftRef = useRef(quickNoteDraft) - quickNoteDraftRef.current = quickNoteDraft - - const { noteMutation, bulkDeleteMutation } = useDocumentMutations({ - onClose: () => { - resetDraft() - setIsFullscreen(false) - }, - }) - - const [selectedDocumentIds, setSelectedDocumentIds] = useState>( - new Set(), - ) - const [isSelectionMode, setIsSelectionMode] = useState(false) - - const handleToggleSelection = useCallback((documentId: string) => { - setSelectedDocumentIds((prev) => { - const next = new Set(prev) - if (next.has(documentId)) { - next.delete(documentId) - } else { - next.add(documentId) - } - return next - }) - }, []) - - const handleClearSelection = useCallback(() => { - setSelectedDocumentIds(new Set()) - setIsSelectionMode(false) - }, []) - - const handleEnterSelectionMode = useCallback(() => { - setIsSelectionMode(true) - }, []) - - const handleSelectAllVisible = useCallback((visibleIds: string[]) => { - setSelectedDocumentIds((prev) => { - const next = new Set(prev) - for (const id of visibleIds) { - next.add(id) - } - return next - }) - }, []) - - const handleBulkDelete = useCallback(() => { - const ids = Array.from(selectedDocumentIds) - if (ids.length === 0) return - bulkDeleteMutation.mutate( - { documentIds: ids }, - { - onSuccess: () => { - setSelectedDocumentIds(new Set()) - setIsSelectionMode(false) - if (selectedDocument && ids.includes(selectedDocument.id ?? "")) { - setDocId(null) - } - }, - }, - ) - }, [selectedDocumentIds, bulkDeleteMutation, selectedDocument, setDocId]) - - type SpaceHighlightsResponse = { - highlights: HighlightItem[] - questions: string[] - generatedAt: string - } - - const HIGHLIGHTS_CACHE_NAME = "space-highlights-v1" - const HIGHLIGHTS_MAX_AGE = 4 * 60 * 60 * 1000 // 4 hours - - const handleResetHighlights = useCallback(async () => { - toast.success("Refreshing daily brief…") - try { - await caches.delete(HIGHLIGHTS_CACHE_NAME) - } catch {} - setHighlightsForceAt(Date.now()) - }, []) - - const { data: highlightsData, isLoading: isLoadingHighlights } = - useQuery({ - queryKey: ["space-highlights", selectedProject, highlightsForceAt], - queryFn: async (): Promise => { - const spaceId = selectedProject || "sm_project_default" - const forceRefresh = highlightsForceAt > 0 - const cacheKey = `${process.env.NEXT_PUBLIC_BACKEND_URL}/v3/space-highlights?spaceId=${spaceId}` - - if (!forceRefresh) { - const cache = await caches.open(HIGHLIGHTS_CACHE_NAME) - const cached = await cache.match(cacheKey) - if (cached) { - const age = - Date.now() - Number(cached.headers.get("x-cached-at") || 0) - if (age < HIGHLIGHTS_MAX_AGE) { - return cached.json() - } - } - } - - const response = await fetch( - `${process.env.NEXT_PUBLIC_BACKEND_URL}/v3/space-highlights`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ - spaceId, - highlightsCount: 3, - questionsCount: 4, - includeHighlights: true, - includeQuestions: true, - forceRefresh, - }), - }, - ) - - if (!response.ok) { - throw new Error("Failed to fetch space highlights") - } - - const data = await response.json() - - // Update browser cache with fresh data (works for both normal and forced refresh) - try { - const freshCache = await caches.open(HIGHLIGHTS_CACHE_NAME) - const cacheResponse = new Response(JSON.stringify(data), { - headers: { - "Content-Type": "application/json", - "x-cached-at": String(Date.now()), - }, - }) - await freshCache.put(cacheKey, cacheResponse) - } catch {} - - // Reset force flag after the forced fetch completes so future project-switches - // use the normal cache path instead of always bypassing it. - if (forceRefresh) setHighlightsForceAt(0) - - return data - }, - staleTime: HIGHLIGHTS_MAX_AGE, - refetchOnWindowFocus: false, - }) - - const { data: memoryOfDay = null } = useQuery({ - queryKey: [ - "memory-of-day", - user?.id, - new Date().toISOString().slice(0, 10), - ], - queryFn: async (): Promise => { - const cacheKey = `memory-of-day:v2:${user?.id}:${new Date().toISOString().slice(0, 10)}` - try { - const stored = localStorage.getItem(cacheKey) - if (stored) return JSON.parse(stored) as MemoryOfDay - } catch {} - - const response = await fetch( - `${process.env.NEXT_PUBLIC_BACKEND_URL}/v3/memory-of-day`, - { credentials: "include" }, - ) - if (!response.ok) return null - const data = (await response.json()) as MemoryOfDay | null - if (data) { - try { - localStorage.setItem(cacheKey, JSON.stringify(data)) - } catch {} - } - return data - }, - staleTime: 24 * 60 * 60 * 1000, - refetchOnWindowFocus: false, - enabled: !!user, - }) - - useHotkeys("c", () => { - analytics.addDocumentModalOpened() - setAddDoc("note") - }) - useHotkeys("mod+k", (e) => { - e.preventDefault() - analytics.searchOpened({ source: "hotkey" }) - setIsSearchOpen(true) - }) - - const handleOpenDocument = useCallback( - (document: DocumentWithMemories) => { - if (document.id) { - analytics.documentModalOpened({ document_id: document.id }) - setSelectedDocument(document) - setDocId(document.id) - } - }, - [setDocId], - ) - - const handleOpenToolDocument = useCallback( - (document: DocumentWithMemories, pluginClientId: string) => { - const documentSpace = getToolDocumentSpace(document, pluginClientId) - if (documentSpace) { - setSelectedProject(documentSpace) - } - handleOpenDocument(document) - void setViewMode("list") - }, - [handleOpenDocument, setSelectedProject, setViewMode], - ) - - // Separate from handleOpenDocument because the graph view only has a document ID, - // not the full document object. The modal will fetch the document via the docId - // query param, so there may be a brief loading state (unlike handleOpenDocument - // which pre-populates via setSelectedDocument). - const handleOpenDocumentById = useCallback( - (documentId: string) => { - analytics.documentModalOpened({ document_id: documentId }) - setDocId(documentId) - }, - [setDocId], - ) - - const handleQuickNoteSave = useCallback( - (content: string) => { - if (content.trim()) { - const hadPreviousContent = quickNoteDraftRef.current.trim().length > 0 - noteMutation.mutate( - { content, project: selectedProject }, - { - onSuccess: () => { - if (hadPreviousContent) { - analytics.quickNoteEdited() - } else { - analytics.quickNoteCreated() - } - }, - }, - ) - } - }, - [selectedProject, noteMutation], - ) - - const handleFullScreenSave = useCallback( - (content: string) => { - if (content.trim()) { - const hadInitialContent = fullscreenInitialContent.trim().length > 0 - noteMutation.mutate( - { content, project: selectedProject }, - { - onSuccess: () => { - if (hadInitialContent) { - analytics.quickNoteEdited() - } else { - analytics.quickNoteCreated() - } - }, - }, - ) - } - }, - [selectedProject, noteMutation, fullscreenInitialContent], - ) - - const handleMaximize = useCallback( - (content: string) => { - analytics.fullscreenNoteModalOpened() - setFullscreenInitialContent(content) - setIsFullscreen(true) - }, - [setIsFullscreen], - ) - - const handleHighlightsChat = useCallback( - (highlightContent: string, userReply: string) => { - setQueuedHighlightContent(highlightContent) - setQueuedChatSeed(userReply) - setQueuedChatModel(null) - setQueuedChatReasoningEffort(null) - setQueuedChatProject(null) - setQueuedChatAttachments(null) - setQueuedMessageSource("highlight") - void setViewMode("chat") - }, - [setViewMode], - ) - - const handleHomeChatStart = useCallback( - ( - message: string, - model: ModelId, - projectId: string, - reasoningEffort: ReasoningEffort, - attachments?: ChatAttachmentDraft[], - ) => { - setQueuedHighlightContent(null) - setQueuedChatSeed(message) - setQueuedChatModel(model) - setQueuedChatReasoningEffort(reasoningEffort) - setQueuedChatProject(projectId) - setQueuedChatAttachments(attachments ?? null) - setQueuedMessageSource("home") - void setViewMode("chat") - }, - [setViewMode], - ) - - const consumeQueuedChat = useCallback(() => { - setQueuedChatSeed(null) - setQueuedChatModel(null) - setQueuedChatReasoningEffort(null) - setQueuedChatProject(null) - setQueuedChatAttachments(null) - setQueuedHighlightContent(null) - setQueuedMessageSource("highlight") - }, []) - - const handleHighlightsShowRelated = useCallback( - (query: string) => { - analytics.searchOpened({ source: "highlight_related" }) - setSearchPrefill(query) - setIsSearchOpen(true) - }, - [setSearchPrefill, setIsSearchOpen], - ) - - const handleOpenIntegrations = useCallback( - (integration?: IntegrationParamValue) => { - if (integration === "notion" || integration === "google-drive") { - void setAddDoc("connect") - return - } - void setViewMode(integration ?? "integrations") - }, - [setViewMode, setAddDoc], - ) - - const handleOpenPlugins = useCallback(() => { - void setViewMode("plugins") - }, [setViewMode]) - - const handleAddMemory = useCallback( - (tab: "note" | "link") => { - analytics.addDocumentModalOpened() - setAddDoc(tab) - }, - [setAddDoc], - ) - - const viewportWidth = useSyncExternalStore( - subscribeViewportWidth, - getViewportWidth, - () => GRADIENT_TOP_WIDTH_MAX, - ) - const gradientTopPosition = gradientTopPositionForWidth(viewportWidth) - - const isChatView = viewMode === "chat" - const showNovaBackdrop = - viewMode === "graph" || - viewMode === "list" || - viewMode === "dashboard" || - viewMode === "digests" - const isDashboardShell = - viewMode === "dashboard" || (viewMode === "graph" && isMobile) - const isGraphMode = viewMode === "graph" - const showBottomNav = isMobile && !!session && !isChatView - const isPublicIntegrations = - !session && !isSessionPending && viewMode === "integrations" - - return ( - - -
- {showNovaBackdrop && ( -
- -
-
-
- )} - {isPublicIntegrations ? ( - - ) : !session && viewMode === "mcp" ? ( - - ) : ( -
{ - analytics.addDocumentModalOpened() - setAddDoc("note") - }} - onOpenSearch={() => { - analytics.searchOpened({ source: "header" }) - setIsSearchOpen(true) - }} - /> - )} - - -
- }> - {isChatView ? ( -
- { - if (!open) void setViewMode("dashboard") - }} - queuedMessage={queuedChatSeed} - queuedHighlightContent={queuedHighlightContent} - onConsumeQueuedMessage={consumeQueuedChat} - queuedMessageSource={queuedMessageSource} - queuedAttachments={queuedChatAttachments} - initialSelectedModel={queuedChatModel} - initialReasoningEffort={queuedChatReasoningEffort} - initialChatProject={queuedChatProject} - /> -
- ) : viewMode === "integrations" ? ( -
- -
- ) : viewMode === "mcp" ? ( - void setViewMode("integrations")} - /> - ) : viewMode === "plugins" ? ( - void setViewMode("integrations")} - > - - - ) : viewMode === "chrome" ? ( - void setViewMode("integrations")} - > - - - ) : viewMode === "shortcuts" ? ( - void setViewMode("integrations")} - > - - - ) : viewMode === "raycast" ? ( - void setViewMode("integrations")} - > - - - ) : viewMode === "import" ? ( - void setViewMode("integrations")} - /> - ) : viewMode === "digests" ? ( -
- -
- ) : viewMode === "graph" ? ( -
- -
- ) : viewMode === "list" ? ( -
- -
- ) : ( - { - analytics.searchOpened({ source: "header" }) - setIsSearchOpen(true) - }} - onOpenIntegrations={handleOpenIntegrations} - onOpenPlugins={handleOpenPlugins} - onNavigateToMemories={() => void setViewMode("list")} - onNavigateToGraph={() => void setViewMode("graph")} - onOpenDocument={handleOpenDocument} - onOpenToolDocument={handleOpenToolDocument} - onHighlightsChat={handleHighlightsChat} - onHighlightsShowRelated={handleHighlightsShowRelated} - onResetHighlights={handleResetHighlights} - onOpenDigests={() => void setViewMode("digests")} - memoryOfDay={memoryOfDay} - /> - )} -
-
-
-
- - {isDashboardShell && showBottomNav && ( -
- )} - {isDashboardShell && ( -
-
- -
-
- )} - - {showBottomNav && ( - { - analytics.addDocumentModalOpened() - setAddDoc("note") - }} - onOpenSearch={() => { - analytics.searchOpened({ source: "header" }) - setIsSearchOpen(true) - }} - /> - )} - - setAddDoc(null)} - /> - { - setIsSearchOpen(open) - if (!open) setSearchPrefill("") - }} - projectId={selectedProject} - onOpenDocument={handleOpenDocument} - onAddMemory={() => { - analytics.addDocumentModalOpened() - setAddDoc("note") - }} - onOpenIntegrations={() => setViewMode("integrations")} - initialSearch={searchPrefill} - /> - setDocId(null)} - /> - setIsFullscreen(false)} - initialContent={fullscreenInitialContent} - onSave={handleFullScreenSave} - isSaving={noteMutation.isPending} - /> -
- - ) +export default function Page() { + return } diff --git a/apps/web/app/(app)/settings/integrations/page.tsx b/apps/web/app/(app)/settings/integrations/page.tsx index aa8a456b..b128db96 100644 --- a/apps/web/app/(app)/settings/integrations/page.tsx +++ b/apps/web/app/(app)/settings/integrations/page.tsx @@ -1,17 +1,5 @@ -"use client" - -import { useEffect } from "react" -import { useRouter, useSearchParams } from "next/navigation" +import { redirect } from "next/navigation" export default function SettingsIntegrationsPage() { - const router = useRouter() - const searchParams = useSearchParams() - - useEffect(() => { - const params = new URLSearchParams(searchParams.toString()) - params.set("view", "integrations") - router.replace(`/?${params.toString()}`) - }, [router, searchParams]) - - return null + redirect("/integrations") } diff --git a/apps/web/app/(app)/settings/page.tsx b/apps/web/app/(app)/settings/page.tsx index 32b74126..657a4f60 100644 --- a/apps/web/app/(app)/settings/page.tsx +++ b/apps/web/app/(app)/settings/page.tsx @@ -15,7 +15,7 @@ export default function SettingsRedirect() { const hash = typeof window !== "undefined" ? window.location.hash : "" const tab = parseHashToTab(hash) router.replace( - tab === "integrations" ? "/?view=integrations" : `/?settings=${tab}`, + tab === "integrations" ? "/integrations" : `/?settings=${tab}`, ) }, [router]) diff --git a/apps/web/app/org/invite/[invitationId]/page.tsx b/apps/web/app/org/invite/[invitationId]/page.tsx new file mode 100644 index 00000000..e6c6d9da --- /dev/null +++ b/apps/web/app/org/invite/[invitationId]/page.tsx @@ -0,0 +1,388 @@ +"use client" + +import { authClient, useSession } from "@lib/auth" +import { useAuth } from "@lib/auth-context" +import { cn } from "@lib/utils" +import { Loader, Users, XCircle } from "lucide-react" +import { useParams, useRouter } from "next/navigation" +import { useCallback, useEffect, useState } from "react" +import { toast } from "sonner" +import { dmSans125ClassName } from "@/lib/fonts" + +type InvitationData = { + id: string + email: string + role: string + status: string + expiresAt: string + organizationName: string + organizationSlug: string + organizationId: string + inviterEmail?: string +} + +type InviteState = + | "loading" + | "no_session" + | "ready" + | "not_found" + | "expired" + | "already_accepted" + | "wrong_account" + +const pageWrapperClass = + "flex items-center justify-center min-h-screen bg-background p-4" +const cardClass = cn( + "bg-[#14161A] rounded-[14px] p-6 w-full max-w-[400px]", + "shadow-[inset_2.42px_2.42px_4.263px_rgba(11,15,21,0.7)]", +) + +function FullPageSpinner() { + return ( +
+
+
+ ) +} + +function PrimaryButton({ + children, + onClick, + disabled, +}: { + children: React.ReactNode + onClick: () => void + disabled?: boolean +}) { + return ( + + ) +} + +function SecondaryButton({ + children, + onClick, + disabled, +}: { + children: React.ReactNode + onClick: () => void + disabled?: boolean +}) { + return ( + + ) +} + +function IconTile({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ) +} + +function Title({ children }: { children: React.ReactNode }) { + return ( +

+ {children} +

+ ) +} + +function Subtitle({ children }: { children: React.ReactNode }) { + return ( +

+ {children} +

+ ) +} + +function StatusCard({ + icon, + title, + description, + actionLabel, + onAction, +}: { + icon: React.ReactNode + title: string + description: string + actionLabel: string + onAction: () => void +}) { + return ( +
+
+
+ {icon} +
+ {title} + {description} +
+ {actionLabel} +
+
+
+ ) +} + +export default function InvitePage() { + const params = useParams<{ invitationId: string }>() + const invitationId = params.invitationId + const { data: session, isPending: sessionPending } = useSession() + const { setActiveOrg, refetchOrganizations } = useAuth() + const router = useRouter() + + const [state, setState] = useState("loading") + const [invitation, setInvitation] = useState(null) + const [accepting, setAccepting] = useState(false) + const [declining, setDeclining] = useState(false) + + useEffect(() => { + if (sessionPending) return + if (!session) { + setState("no_session") + return + } + let cancelled = false + ;(async () => { + const { data, error } = await authClient.organization.getInvitation({ + query: { id: invitationId }, + }) + if (cancelled) return + if (error) { + setState(error.status === 403 ? "wrong_account" : "not_found") + return + } + if (!data) { + setState("not_found") + return + } + const inv = data as unknown as InvitationData + if (inv.status === "accepted") setState("already_accepted") + else if (inv.status === "canceled" || inv.status === "rejected") + setState("not_found") + else if (new Date(inv.expiresAt) < new Date()) setState("expired") + else { + setInvitation(inv) + setState("ready") + } + })() + return () => { + cancelled = true + } + }, [session, sessionPending, invitationId]) + + const handleAccept = useCallback(async () => { + setAccepting(true) + try { + const { error } = await authClient.organization.acceptInvitation({ + invitationId, + }) + if (error) { + toast.error(error.message ?? "Failed to accept invitation") + return + } + if (invitation?.organizationSlug) { + await setActiveOrg(invitation.organizationSlug) + } + await refetchOrganizations() + toast.success( + `You've joined ${invitation?.organizationName ?? "the team"}`, + ) + router.push("/") + } finally { + setAccepting(false) + } + }, [invitationId, invitation, setActiveOrg, refetchOrganizations, router]) + + const handleDecline = useCallback(async () => { + setDeclining(true) + try { + const { error } = await authClient.organization.rejectInvitation({ + invitationId, + }) + if (error) { + toast.error(error.message ?? "Failed to decline invitation") + return + } + toast.success("Invitation declined") + router.push("/") + } finally { + setDeclining(false) + } + }, [invitationId, router]) + + if (state === "loading") return + + if (state === "no_session") { + const loginHref = `/login?redirect=${encodeURIComponent( + typeof window !== "undefined" ? window.location.href : "", + )}` + return ( +
+
+
+ + + +
+ You're not logged in + Log in to view and accept this invitation. +
+ router.push(loginHref)}> + Log in + +
+
+
+ ) + } + + if (state === "wrong_account") { + return ( + } + title="This invitation isn't for you" + description={`It was sent to a different email${ + session?.user?.email ? ` than ${session.user.email}` : "" + }.`} + actionLabel="Go to dashboard" + onAction={() => router.push("/")} + /> + ) + } + + if ( + state === "not_found" || + state === "expired" || + state === "already_accepted" + ) { + const copy = { + not_found: { + title: "Invitation not found", + body: "This invitation doesn't exist or has been revoked.", + }, + expired: { + title: "Invitation expired", + body: "Ask your team admin to send a new one.", + }, + already_accepted: { + title: "Already joined", + body: "You've already accepted this invitation.", + }, + }[state] + return ( + } + title={copy.title} + description={copy.body} + actionLabel="Go to dashboard" + onAction={() => router.push("/")} + /> + ) + } + + return ( +
+
+
+ + + +
+ {invitation?.organizationName} + + You've been invited to join{" "} + + {invitation?.organizationName} + {" "} + as {invitation?.role}. + + {invitation?.inviterEmail && ( +

+ Invited by {invitation.inviterEmail} +

+ )} + {session?.user?.email && ( +

+ Signed in as {session.user.email} +

+ )} +
+
+ + {accepting ? ( + <> + + Accepting… + + ) : ( + "Accept invitation" + )} + + + {declining ? ( + <> + + Declining… + + ) : ( + "Decline" + )} + +
+
+
+
+ ) +} diff --git a/apps/web/components/app-experience.tsx b/apps/web/components/app-experience.tsx new file mode 100644 index 00000000..4f017ca4 --- /dev/null +++ b/apps/web/components/app-experience.tsx @@ -0,0 +1,879 @@ +"use client" + +import { + useState, + useCallback, + useEffect, + useMemo, + useRef, + useSyncExternalStore, +} from "react" +import { AnimatePresence, motion } from "motion/react" +import { useQueryState } from "nuqs" +import { Header, PublicHeader } from "@/components/header" +import { MobileBottomNav } from "@/components/bottom-nav" +import { ChatSidebar, HomeChatComposer } from "@/components/chat" +import type { ChatAttachmentDraft } from "@/components/chat/attachments" +import { DashboardView } from "@/components/dashboard-view" +import { BrainHomeView } from "@/components/brain-home/brain-home-view" +import { useHasCompanyBrain } from "@/hooks/use-company-brain" +import { MemoriesGrid } from "@/components/memories-grid" +import { GraphLayoutView } from "@/components/graph-layout-view" +import { IntegrationsView, DetailWrapper } from "@/components/integrations-view" +import { MCPDetailView } from "@/components/mcp-modal/mcp-detail-view" +import { XBookmarksDetailView } from "@/components/onboarding/x-bookmarks-detail-view" +import { ChromeDetail } from "@/components/integrations/chrome-detail" +import { ShortcutsDetail } from "@/components/integrations/shortcuts-detail" +import { RaycastDetail } from "@/components/integrations/raycast-detail" +import { PluginsDetail } from "@/components/integrations/plugins-detail" +import { AnimatedGradientBackground } from "@/components/animated-gradient-background" +import { OnboardingConfetti } from "@/components/onboarding-brain/onboarding-confetti" +import { AddDocumentModal } from "@/components/add-document" +import { DocumentModal } from "@/components/document-modal" +import { DocumentsCommandPalette } from "@/components/documents-command-palette" +import { FullscreenNoteModal } from "@/components/fullscreen-note-modal" +import type { HighlightItem } from "@/components/highlights-card" +import { DigestsView } from "@/components/digests-view" +import { HotkeysProvider } from "react-hotkeys-hook" +import { useHotkeys } from "react-hotkeys-hook" +import { useIsMobile } from "@hooks/use-mobile" +import { useAuth } from "@lib/auth-context" +import { useProject } from "@/stores" +import { useContainerTags } from "@/hooks/use-container-tags" +import { DEFAULT_PROJECT_ID } from "@lib/constants" +import { + useQuickNoteDraftReset, + useQuickNoteDraft, +} from "@/stores/quick-note-draft" +import { analytics } from "@/lib/analytics" +import type { ModelId, ReasoningEffort } from "@/lib/models" +import { useDocumentMutations } from "@/hooks/use-document-mutations" +import { useQuery, useQueryClient } from "@tanstack/react-query" +import { toast } from "sonner" +import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" +import type { z } from "zod" +import { useViewMode, useLegacyViewRedirect } from "@/lib/view-mode-context" +import type { MemoryOfDay } from "@/components/dashboard-view" +import { ErrorBoundary } from "@/components/error-boundary" +import { cn } from "@lib/utils" +import { + addDocumentParam, + searchParam, + qParam, + docParam, + fullscreenParam, + threadParam, + type IntegrationParamValue, +} from "@/lib/search-params" +import { getChatSpaceDisplayLabel } from "@/lib/chat-space-label" +import { getToolDocumentSpace } from "@/lib/plugin-space" + +type DocumentsResponse = z.infer +type DocumentWithMemories = DocumentsResponse["documents"][0] + +function subscribeViewportWidth(cb: () => void) { + window.addEventListener("resize", cb) + return () => window.removeEventListener("resize", cb) +} + +function getViewportWidth() { + return window.innerWidth +} + +const GRADIENT_TOP_WIDTH_MAX = 1440 + +function gradientTopPositionForWidth(width: number) { + const minW = 320 + const pctWide = 15 + const pctNarrow = 55 + const w = Math.min(GRADIENT_TOP_WIDTH_MAX, Math.max(minW, width)) + const t = (w - minW) / (GRADIENT_TOP_WIDTH_MAX - minW) + const eased = t * t + return `${Math.round(pctNarrow + eased * (pctWide - pctNarrow))}%` +} + +function ViewErrorFallback() { + return ( +
+

+ Something went wrong.{" "} + +

+
+ ) +} + +export function AppExperience() { + const isMobile = useIsMobile() + const { user, session, isSessionPending, org } = useAuth() + + const { selectedProject, selectedProjects, setSelectedProject } = useProject() + const selectedProjectTag = selectedProjects[0] + const { allProjects } = useContainerTags() + const dashboardSpaceLabel = useMemo( + () => + getChatSpaceDisplayLabel({ + selectedProject, + allProjects, + }), + [selectedProject, allProjects], + ) + const emptyStateSpaceName = selectedProjectTag + ? selectedProjectTag === DEFAULT_PROJECT_ID + ? "My Space" + : (allProjects.find((p) => p.containerTag === selectedProjectTag)?.name ?? + selectedProjectTag) + : undefined + + const { viewMode, setViewMode } = useViewMode() + useLegacyViewRedirect() + const isCompanyBrain = useHasCompanyBrain() + + // Slack OAuth redirects back here with ?slack=connected — toast then clean up. + useEffect(() => { + const sp = new URLSearchParams(window.location.search) + if (sp.get("slack") !== "connected") return + const team = sp.get("team") + toast.success( + team + ? `Supermemory added to ${team} on Slack` + : "Supermemory added to your Slack", + ) + sp.delete("slack") + sp.delete("team") + const qs = sp.toString() + window.history.replaceState( + null, + "", + window.location.pathname + (qs ? `?${qs}` : ""), + ) + }, []) + const queryClient = useQueryClient() + const [highlightsForceAt, setHighlightsForceAt] = useState(0) + + // Chrome extension auth: send session token via postMessage so the content script can store it + useEffect(() => { + const url = new URL(window.location.href) + if (!url.searchParams.get("extension-auth-success")) return + const sessionToken = session?.token + const userData = { email: user?.email, name: user?.name, userId: user?.id } + if (sessionToken && userData.email) { + window.postMessage( + { token: encodeURIComponent(sessionToken), userData }, + window.location.origin, + ) + url.searchParams.delete("extension-auth-success") + window.history.replaceState({}, "", url.toString()) + } + }, [user, session]) + + // URL-driven modal states + const [addDoc, setAddDoc] = useQueryState("add", addDocumentParam) + const [isSearchOpen, setIsSearchOpen] = useQueryState("search", searchParam) + const [searchPrefill, setSearchPrefill] = useQueryState("q", qParam) + const [docId, setDocId] = useQueryState("doc", docParam) + const [isFullscreen, setIsFullscreen] = useQueryState( + "fullscreen", + fullscreenParam, + ) + const [, setThreadIdUrl] = useQueryState("thread", threadParam) + + // Ephemeral local state (not worth URL-encoding) + const [fullscreenInitialContent, setFullscreenInitialContent] = useState("") + const [queuedChatSeed, setQueuedChatSeed] = useState(null) + const [queuedChatModel, setQueuedChatModel] = useState(null) + const [queuedChatReasoningEffort, setQueuedChatReasoningEffort] = + useState(null) + const [queuedChatProject, setQueuedChatProject] = useState( + null, + ) + const [queuedChatAttachments, setQueuedChatAttachments] = useState< + ChatAttachmentDraft[] | null + >(null) + const [queuedHighlightContent, setQueuedHighlightContent] = useState< + string | null + >(null) + const [queuedMessageSource, setQueuedMessageSource] = useState< + "highlight" | "home" + >("highlight") + const [selectedDocument, setSelectedDocument] = + useState(null) + + // Clear document when docId is removed (e.g. back button) + useEffect(() => { + if (!docId) setSelectedDocument(null) + }, [docId]) + + useEffect(() => { + if (viewMode === "dashboard") void setThreadIdUrl(null) + }, [viewMode, setThreadIdUrl]) + + // Resolve document from cache when loading with ?doc= (deep link / refresh) + useEffect(() => { + if (!docId || selectedDocument) return + + const tryResolve = () => { + const queries = queryClient.getQueriesData<{ + pages: DocumentsResponse[] + }>({ queryKey: ["documents-with-memories"] }) + for (const [, data] of queries) { + if (!data?.pages) continue + for (const page of data.pages) { + const doc = page.documents?.find((d) => d.id === docId) + if (doc) { + setSelectedDocument(doc) + return true + } + } + } + return false + } + + if (tryResolve()) return + + const unsubscribe = queryClient.getQueryCache().subscribe(() => { + if (tryResolve()) unsubscribe() + }) + return unsubscribe + }, [docId, selectedDocument, queryClient]) + + const resetDraft = useQuickNoteDraftReset(selectedProject) + const { draft: quickNoteDraft } = useQuickNoteDraft(selectedProject || "") + const quickNoteDraftRef = useRef(quickNoteDraft) + quickNoteDraftRef.current = quickNoteDraft + + const { noteMutation, bulkDeleteMutation } = useDocumentMutations({ + onClose: () => { + resetDraft() + setIsFullscreen(false) + }, + }) + + const [selectedDocumentIds, setSelectedDocumentIds] = useState>( + new Set(), + ) + const [isSelectionMode, setIsSelectionMode] = useState(false) + + const handleToggleSelection = useCallback((documentId: string) => { + setSelectedDocumentIds((prev) => { + const next = new Set(prev) + if (next.has(documentId)) { + next.delete(documentId) + } else { + next.add(documentId) + } + return next + }) + }, []) + + const handleClearSelection = useCallback(() => { + setSelectedDocumentIds(new Set()) + setIsSelectionMode(false) + }, []) + + const handleEnterSelectionMode = useCallback(() => { + setIsSelectionMode(true) + }, []) + + const handleSelectAllVisible = useCallback((visibleIds: string[]) => { + setSelectedDocumentIds((prev) => { + const next = new Set(prev) + for (const id of visibleIds) { + next.add(id) + } + return next + }) + }, []) + + const handleBulkDelete = useCallback(() => { + const ids = Array.from(selectedDocumentIds) + if (ids.length === 0) return + bulkDeleteMutation.mutate( + { documentIds: ids }, + { + onSuccess: () => { + setSelectedDocumentIds(new Set()) + setIsSelectionMode(false) + if (selectedDocument && ids.includes(selectedDocument.id ?? "")) { + setDocId(null) + } + }, + }, + ) + }, [selectedDocumentIds, bulkDeleteMutation, selectedDocument, setDocId]) + + type SpaceHighlightsResponse = { + highlights: HighlightItem[] + questions: string[] + generatedAt: string + } + + const HIGHLIGHTS_CACHE_NAME = "space-highlights-v1" + const HIGHLIGHTS_MAX_AGE = 4 * 60 * 60 * 1000 // 4 hours + + const handleResetHighlights = useCallback(async () => { + toast.success("Refreshing daily brief…") + try { + await caches.delete(HIGHLIGHTS_CACHE_NAME) + } catch {} + setHighlightsForceAt(Date.now()) + }, []) + + const { data: highlightsData, isLoading: isLoadingHighlights } = + useQuery({ + queryKey: ["space-highlights", selectedProject, highlightsForceAt], + queryFn: async (): Promise => { + const spaceId = selectedProject || "sm_project_default" + const forceRefresh = highlightsForceAt > 0 + const cacheKey = `${process.env.NEXT_PUBLIC_BACKEND_URL}/v3/space-highlights?spaceId=${spaceId}` + + if (!forceRefresh) { + const cache = await caches.open(HIGHLIGHTS_CACHE_NAME) + const cached = await cache.match(cacheKey) + if (cached) { + const age = + Date.now() - Number(cached.headers.get("x-cached-at") || 0) + if (age < HIGHLIGHTS_MAX_AGE) { + return cached.json() + } + } + } + + const response = await fetch( + `${process.env.NEXT_PUBLIC_BACKEND_URL}/v3/space-highlights`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "include", + body: JSON.stringify({ + spaceId, + highlightsCount: 3, + questionsCount: 4, + includeHighlights: true, + includeQuestions: true, + forceRefresh, + }), + }, + ) + + if (!response.ok) { + throw new Error("Failed to fetch space highlights") + } + + const data = await response.json() + + // Update browser cache with fresh data (works for both normal and forced refresh) + try { + const freshCache = await caches.open(HIGHLIGHTS_CACHE_NAME) + const cacheResponse = new Response(JSON.stringify(data), { + headers: { + "Content-Type": "application/json", + "x-cached-at": String(Date.now()), + }, + }) + await freshCache.put(cacheKey, cacheResponse) + } catch {} + + // Reset force flag after the forced fetch completes so future project-switches + // use the normal cache path instead of always bypassing it. + if (forceRefresh) setHighlightsForceAt(0) + + return data + }, + staleTime: HIGHLIGHTS_MAX_AGE, + refetchOnWindowFocus: false, + }) + + const { data: memoryOfDay = null } = useQuery({ + queryKey: [ + "memory-of-day", + user?.id, + org?.id, + new Date().toISOString().slice(0, 10), + ], + queryFn: async (): Promise => { + const cacheKey = `memory-of-day:v2:${user?.id}:${org?.id}:${new Date().toISOString().slice(0, 10)}` + try { + const stored = localStorage.getItem(cacheKey) + if (stored) return JSON.parse(stored) as MemoryOfDay + } catch {} + + const response = await fetch( + `${process.env.NEXT_PUBLIC_BACKEND_URL}/v3/memory-of-day`, + { credentials: "include" }, + ) + if (!response.ok) return null + const data = (await response.json()) as MemoryOfDay | null + if (data) { + try { + localStorage.setItem(cacheKey, JSON.stringify(data)) + } catch {} + } + return data + }, + staleTime: 24 * 60 * 60 * 1000, + refetchOnWindowFocus: false, + enabled: !!user && !!org, + }) + + useHotkeys("c", () => { + analytics.addDocumentModalOpened() + setAddDoc("note") + }) + useHotkeys("mod+k", (e) => { + e.preventDefault() + analytics.searchOpened({ source: "hotkey" }) + setIsSearchOpen(true) + }) + + const handleOpenDocument = useCallback( + (document: DocumentWithMemories) => { + if (document.id) { + analytics.documentModalOpened({ document_id: document.id }) + setSelectedDocument(document) + setDocId(document.id) + } + }, + [setDocId], + ) + + const handleOpenToolDocument = useCallback( + (document: DocumentWithMemories, pluginClientId: string) => { + const documentSpace = getToolDocumentSpace(document, pluginClientId) + if (documentSpace) { + setSelectedProject(documentSpace) + } + handleOpenDocument(document) + void setViewMode("list") + }, + [handleOpenDocument, setSelectedProject, setViewMode], + ) + + // Separate from handleOpenDocument because the graph view only has a document ID, + // not the full document object. The modal will fetch the document via the docId + // query param, so there may be a brief loading state (unlike handleOpenDocument + // which pre-populates via setSelectedDocument). + const handleOpenDocumentById = useCallback( + (documentId: string) => { + analytics.documentModalOpened({ document_id: documentId }) + setDocId(documentId) + }, + [setDocId], + ) + + const handleQuickNoteSave = useCallback( + (content: string) => { + if (content.trim()) { + const hadPreviousContent = quickNoteDraftRef.current.trim().length > 0 + noteMutation.mutate( + { content, project: selectedProject }, + { + onSuccess: () => { + if (hadPreviousContent) { + analytics.quickNoteEdited() + } else { + analytics.quickNoteCreated() + } + }, + }, + ) + } + }, + [selectedProject, noteMutation], + ) + + const handleFullScreenSave = useCallback( + (content: string) => { + if (content.trim()) { + const hadInitialContent = fullscreenInitialContent.trim().length > 0 + noteMutation.mutate( + { content, project: selectedProject }, + { + onSuccess: () => { + if (hadInitialContent) { + analytics.quickNoteEdited() + } else { + analytics.quickNoteCreated() + } + }, + }, + ) + } + }, + [selectedProject, noteMutation, fullscreenInitialContent], + ) + + const handleMaximize = useCallback( + (content: string) => { + analytics.fullscreenNoteModalOpened() + setFullscreenInitialContent(content) + setIsFullscreen(true) + }, + [setIsFullscreen], + ) + + const handleHighlightsChat = useCallback( + (highlightContent: string, userReply: string) => { + setQueuedHighlightContent(highlightContent) + setQueuedChatSeed(userReply) + setQueuedChatModel(null) + setQueuedChatReasoningEffort(null) + setQueuedChatProject(null) + setQueuedChatAttachments(null) + setQueuedMessageSource("highlight") + void setViewMode("chat") + }, + [setViewMode], + ) + + const handleHomeChatStart = useCallback( + ( + message: string, + model: ModelId, + projectId: string, + reasoningEffort: ReasoningEffort, + attachments?: ChatAttachmentDraft[], + ) => { + setQueuedHighlightContent(null) + setQueuedChatSeed(message) + setQueuedChatModel(model) + setQueuedChatReasoningEffort(reasoningEffort) + setQueuedChatProject(projectId) + setQueuedChatAttachments(attachments ?? null) + setQueuedMessageSource("home") + void setViewMode("chat") + }, + [setViewMode], + ) + + const consumeQueuedChat = useCallback(() => { + setQueuedChatSeed(null) + setQueuedChatModel(null) + setQueuedChatReasoningEffort(null) + setQueuedChatProject(null) + setQueuedChatAttachments(null) + setQueuedHighlightContent(null) + setQueuedMessageSource("highlight") + }, []) + + const handleHighlightsShowRelated = useCallback( + (query: string) => { + analytics.searchOpened({ source: "highlight_related" }) + setSearchPrefill(query) + setIsSearchOpen(true) + }, + [setSearchPrefill, setIsSearchOpen], + ) + + const handleOpenIntegrations = useCallback( + (integration?: IntegrationParamValue) => { + if (integration === "notion" || integration === "google-drive") { + void setAddDoc("connect") + return + } + void setViewMode(integration ?? "integrations") + }, + [setViewMode, setAddDoc], + ) + + const handleOpenPlugins = useCallback(() => { + void setViewMode("plugins") + }, [setViewMode]) + + const handleAddMemory = useCallback( + (tab: "note" | "link") => { + analytics.addDocumentModalOpened() + setAddDoc(tab) + }, + [setAddDoc], + ) + + const viewportWidth = useSyncExternalStore( + subscribeViewportWidth, + getViewportWidth, + () => GRADIENT_TOP_WIDTH_MAX, + ) + const gradientTopPosition = gradientTopPositionForWidth(viewportWidth) + + const isChatView = viewMode === "chat" + const showNovaBackdrop = + viewMode === "graph" || + viewMode === "list" || + viewMode === "dashboard" || + viewMode === "digests" + const isDashboardShell = + viewMode === "dashboard" || (viewMode === "graph" && isMobile) + const isGraphMode = viewMode === "graph" + const showBottomNav = isMobile && !!session && !isChatView + const isPublicIntegrations = + !session && !isSessionPending && viewMode === "integrations" + + return ( + + +
+ {showNovaBackdrop && ( +
+ +
+
+
+ )} + {isPublicIntegrations ? ( + + ) : !session && viewMode === "mcp" ? ( + + ) : ( +
{ + analytics.addDocumentModalOpened() + setAddDoc("note") + }} + onOpenSearch={() => { + analytics.searchOpened({ source: "header" }) + setIsSearchOpen(true) + }} + /> + )} + + +
+ }> + {isChatView ? ( +
+ { + if (!open) void setViewMode("dashboard") + }} + queuedMessage={queuedChatSeed} + queuedHighlightContent={queuedHighlightContent} + onConsumeQueuedMessage={consumeQueuedChat} + queuedMessageSource={queuedMessageSource} + queuedAttachments={queuedChatAttachments} + initialSelectedModel={queuedChatModel} + initialReasoningEffort={queuedChatReasoningEffort} + initialChatProject={queuedChatProject} + /> +
+ ) : viewMode === "integrations" ? ( +
+ +
+ ) : viewMode === "mcp" ? ( + void setViewMode("integrations")} + /> + ) : viewMode === "plugins" ? ( + void setViewMode("integrations")} + > + + + ) : viewMode === "chrome" ? ( + void setViewMode("integrations")} + > + + + ) : viewMode === "shortcuts" ? ( + void setViewMode("integrations")} + > + + + ) : viewMode === "raycast" ? ( + void setViewMode("integrations")} + > + + + ) : viewMode === "import" ? ( + void setViewMode("integrations")} + /> + ) : viewMode === "digests" ? ( +
+ +
+ ) : viewMode === "graph" ? ( +
+ +
+ ) : viewMode === "list" ? ( +
+ +
+ ) : isCompanyBrain ? ( +
+ +
+ ) : ( + { + analytics.searchOpened({ source: "header" }) + setIsSearchOpen(true) + }} + onOpenIntegrations={handleOpenIntegrations} + onOpenPlugins={handleOpenPlugins} + onNavigateToMemories={() => void setViewMode("list")} + onNavigateToGraph={() => void setViewMode("graph")} + onOpenDocument={handleOpenDocument} + onOpenToolDocument={handleOpenToolDocument} + onHighlightsChat={handleHighlightsChat} + onHighlightsShowRelated={handleHighlightsShowRelated} + onResetHighlights={handleResetHighlights} + onOpenDigests={() => void setViewMode("digests")} + memoryOfDay={memoryOfDay} + /> + )} +
+
+
+
+ + {isDashboardShell && showBottomNav && ( +
+ )} + {isDashboardShell && ( +
+
+ +
+
+ )} + + {showBottomNav && ( + { + analytics.addDocumentModalOpened() + setAddDoc("note") + }} + onOpenSearch={() => { + analytics.searchOpened({ source: "header" }) + setIsSearchOpen(true) + }} + /> + )} + + setAddDoc(null)} + /> + { + setIsSearchOpen(open) + if (!open) setSearchPrefill("") + }} + projectId={selectedProject} + onOpenDocument={handleOpenDocument} + onAddMemory={() => { + analytics.addDocumentModalOpened() + setAddDoc("note") + }} + onOpenIntegrations={() => setViewMode("integrations")} + initialSearch={searchPrefill} + /> + setDocId(null)} + /> + setIsFullscreen(false)} + initialContent={fullscreenInitialContent} + onSave={handleFullScreenSave} + isSaving={noteMutation.isPending} + /> +
+ + ) +} diff --git a/apps/web/components/brain-home/brain-home-view.tsx b/apps/web/components/brain-home/brain-home-view.tsx new file mode 100644 index 00000000..bab6b4c8 --- /dev/null +++ b/apps/web/components/brain-home/brain-home-view.tsx @@ -0,0 +1,360 @@ +"use client" + +import { $fetch } from "@lib/api" +import { useAuth } from "@lib/auth-context" +import { cn } from "@lib/utils" +import { useQuery } from "@tanstack/react-query" +import { ArrowRight, Check, FileText, Loader2 } from "lucide-react" +import Link from "next/link" +import { dmSans125ClassName } from "@/lib/fonts" +import { ConnectionsBoard } from "./connections-board" + +const BACKEND = + process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai" + +const cardStyle = { + boxShadow: + "0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset", +} + +type RecentDoc = { + id?: string + title?: string | null + createdAt?: string | Date | null +} + +function useBrainOverview() { + const { user, org } = useAuth() + const enabled = !!user && !!org?.id + + const docs = useQuery({ + queryKey: ["brain-recents", org?.id], + queryFn: async () => { + const res = await $fetch("@post/documents/documents", { + body: { + page: 1, + limit: 6, + sort: "createdAt", + order: "desc", + containerTags: [], + }, + disableValidation: true, + }) + if (res.error) throw new Error(res.error?.message) + return res.data as unknown as { + documents?: RecentDoc[] + pagination?: { totalItems?: number } + } + }, + staleTime: 60_000, + enabled, + }) + + const connectors = useQuery({ + queryKey: ["brain-home", "connectors"], + queryFn: async () => { + const res = await $fetch("@post/connections/list", { + body: { containerTags: [] }, + }) + if (res.error) return [] as Array<{ provider?: string }> + return (res.data ?? []) as Array<{ provider?: string }> + }, + staleTime: 30_000, + enabled, + }) + + const brain = useQuery({ + queryKey: ["brain-connections"], + queryFn: async () => { + const [c, s] = await Promise.all([ + fetch(`${BACKEND}/brain/connections`, { credentials: "include" }), + fetch(`${BACKEND}/brain/slack/status`, { credentials: "include" }), + ]) + const toolkits = c.ok + ? ((await c.json()) as { toolkits: { org: boolean; user: boolean }[] }) + .toolkits + : [] + const slack = s.ok + ? ((await s.json()) as { connected: boolean }).connected + : false + return { + activeCount: toolkits.filter((t) => t.org || t.user).length, + slack, + } + }, + staleTime: 30_000, + enabled, + }) + + const mcp = useQuery({ + queryKey: ["mcp-status"], + queryFn: async () => { + const res = await $fetch("@get/mcp/has-login") + if (res.error) return false + return Boolean((res.data as { previousLogin?: boolean })?.previousLogin) + }, + staleTime: 60_000, + enabled, + }) + + const memoriesCount = docs.data?.pagination?.totalItems ?? 0 + const connectedCount = + (brain.data?.activeCount ?? 0) + + (brain.data?.slack ? 1 : 0) + + (connectors.data?.length ?? 0) + + return { + loading: docs.isPending, + recentDocs: docs.data?.documents ?? [], + memoriesCount, + connectedCount, + hasSource: connectedCount > 0, + hasAgent: mcp.data ?? false, + hasMemory: memoriesCount > 0, + } +} + +export function BrainHomeView() { + const o = useBrainOverview() + const stepsDone = [o.hasSource, o.hasAgent, o.hasMemory].filter( + Boolean, + ).length + + return ( +
+ + +
+ + +
+
+ ) +} + +function StatsRow({ + memories, + connected, + setupDone, +}: { + memories: number + connected: number + setupDone: number +}) { + const tiles = [ + { label: "Memories", value: memories.toLocaleString() }, + { label: "Connected sources", value: String(connected) }, + { label: "Setup", value: `${setupDone}/3` }, + ] + return ( +
+ {tiles.map((t) => ( +
+

+ {t.label} +

+

+ {t.value} +

+
+ ))} +
+ ) +} + +function RecentMemories({ + docs, + loading, +}: { + docs: RecentDoc[] + loading: boolean +}) { + return ( +
+

+ Recent memories +

+ + {loading ? ( +
+ + Loading… +
+ ) : docs.length === 0 ? ( +
+
+ +
+
+

+ No memories yet +

+

+ Connect a source or ask your brain below — what you save shows up + here. +

+
+
+ ) : ( +
    + {docs.map((doc, i) => ( +
  • +
    + +
    +

    + {doc.title?.trim() || "Untitled memory"} +

    + + {formatWhen(doc.createdAt)} + +
  • + ))} +
+ )} +
+ ) +} + +function GettingStarted({ + hasSource, + hasAgent, + hasMemory, +}: { + hasSource: boolean + hasAgent: boolean + hasMemory: boolean +}) { + const steps = [ + { + done: hasSource, + title: "Connect a source", + hint: "GitHub, Linear, Drive or Slack.", + href: "/settings/integrations", + }, + { + done: hasAgent, + title: "Install a coding agent", + hint: "Claude Code, Codex or Cursor.", + href: "/settings/integrations", + }, + { + done: hasMemory, + title: "Add your first memory", + hint: "Save a doc, or ask your brain below.", + }, + ] + return ( +
+
+

+ Getting started +

+

+ A few steps to make your brain useful. +

+ +
    + {steps.map((step) => ( +
  • + + {step.done && } + +
    +
    +

    + {step.title} +

    + {!step.done && step.href && ( + + Set up + + + )} +
    + {!step.done && ( +

    + {step.hint} +

    + )} +
    +
  • + ))} +
+
+ ) +} + +function formatWhen(value?: string | Date | null): string { + if (!value) return "" + const d = new Date(value) + if (Number.isNaN(d.getTime())) return "" + const min = Math.round((Date.now() - d.getTime()) / 60000) + if (min < 1) return "just now" + if (min < 60) return `${min}m` + const hr = Math.round(min / 60) + if (hr < 24) return `${hr}h` + const day = Math.round(hr / 24) + if (day < 7) return `${day}d` + return d.toLocaleDateString() +} diff --git a/apps/web/components/brain-home/connections-board.tsx b/apps/web/components/brain-home/connections-board.tsx new file mode 100644 index 00000000..6886d449 --- /dev/null +++ b/apps/web/components/brain-home/connections-board.tsx @@ -0,0 +1,393 @@ +"use client" + +import { $fetch } from "@lib/api" +import { cn } from "@lib/utils" +import { useQuery } from "@tanstack/react-query" +import { GoogleDrive, Notion } from "@ui/assets/icons" +import { Cloud, ExternalLink, Loader2 } from "lucide-react" +import Link from "next/link" +import { useCallback, useEffect, useState } from "react" +import { toast } from "sonner" +import { dmSans125ClassName } from "@/lib/fonts" + +const BACKEND = + process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai" + +const cardStyle = { + boxShadow: + "0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset", +} +const tileStyle = { + boxShadow: + "0px 1px 2px 0px rgba(0,43,87,0.1), inset 0px 0px 0px 1px rgba(43,49,67,0.08), inset 0px 1px 1px 0px rgba(0,0,0,0.08), inset 0px 2px 4px 0px rgba(0,0,0,0.02)", +} + +type ConnRow = { toolkit: string; org: boolean; user: boolean } + +export function ConnectionsBoard() { + const [brainRows, setBrainRows] = useState(null) + const [slack, setSlack] = useState<{ + connected: boolean + teamName: string | null + } | null>(null) + const [busy, setBusy] = useState(null) + + const loadBrain = useCallback(async () => { + try { + const [c, s] = await Promise.all([ + fetch(`${BACKEND}/brain/connections`, { credentials: "include" }), + fetch(`${BACKEND}/brain/slack/status`, { credentials: "include" }), + ]) + if (c.ok) + setBrainRows(((await c.json()) as { toolkits: ConnRow[] }).toolkits) + if (s.ok) setSlack(await s.json()) + } catch {} + }, []) + + useEffect(() => { + void loadBrain() + const onFocus = () => void loadBrain() + window.addEventListener("focus", onFocus) + return () => window.removeEventListener("focus", onFocus) + }, [loadBrain]) + + const { data: connectors } = useQuery({ + queryKey: ["brain-home", "connectors"], + queryFn: async () => { + const res = await $fetch("@post/connections/list", { + body: { containerTags: [] }, + }) + if (res.error) return [] as Array<{ provider?: string }> + return (res.data ?? []) as Array<{ provider?: string }> + }, + staleTime: 30_000, + }) + + const connectorConnected = (provider: string) => + Boolean(connectors?.some((c) => c.provider === provider)) + + const connectBrain = async (toolkit: string) => { + setBusy(`brain:${toolkit}`) + try { + const res = await fetch( + `${BACKEND}/brain/connections/${toolkit}/link?scope=user`, + { method: "POST", credentials: "include" }, + ) + if (!res.ok) { + toast.error("Couldn't start the connection.") + return + } + const data = (await res.json()) as { url?: string } + if (data.url) window.open(data.url, "_blank", "noopener") + else toast.error("Couldn't start the connection.") + } catch { + toast.error("Couldn't start the connection.") + } finally { + setBusy(null) + } + } + + const connectConnector = async ( + provider: "google-drive" | "notion" | "onedrive", + ) => { + setBusy(`conn:${provider}`) + try { + const res = await $fetch("@post/connections/:provider", { + params: { provider }, + body: { redirectUrl: window.location.href, containerTags: [] }, + }) + const data = res.data as { authLink?: string } | undefined + if (data?.authLink) window.location.href = data.authLink + else toast.error("Couldn't start the connection.") + } catch { + toast.error("Couldn't start the connection.") + } finally { + setBusy(null) + } + } + + const brainConnected = (toolkit: string) => + Boolean(brainRows?.find((r) => r.toolkit === toolkit)?.org) || + Boolean(brainRows?.find((r) => r.toolkit === toolkit)?.user) + + return ( +
+ {slack && !slack.connected && } + +
+ + } + name="GitHub" + subtitle="Repos, pull requests and issues." + connected={brainConnected("github")} + busy={busy === "brain:github"} + onConnect={() => connectBrain("github")} + /> + } + name="Linear" + subtitle="Issues, projects and cycles." + connected={brainConnected("linear")} + busy={busy === "brain:linear"} + onConnect={() => connectBrain("linear")} + /> + + + + All connectors + + + } + > + } + name="Google Drive" + subtitle="Docs, sheets and slides." + connected={connectorConnected("google-drive")} + busy={busy === "conn:google-drive"} + onConnect={() => connectConnector("google-drive")} + /> + } + name="Notion" + subtitle="Pages, databases and blocks." + connected={connectorConnected("notion")} + busy={busy === "conn:notion"} + onConnect={() => connectConnector("notion")} + /> + } + name="OneDrive" + subtitle="Files from Microsoft 365." + connected={connectorConnected("onedrive")} + busy={busy === "conn:onedrive"} + onConnect={() => connectConnector("onedrive")} + /> + +
+
+ ) +} + +function SlackBanner() { + return ( +
+
+
+
+
+ +
+
+

+ Company Brain in Slack +

+

+ Install Supermemory so your team can{" "} + @supermemory in any + channel. +

+
+
+ + + + Add to Slack + +
+
+ ) +} + +function Group({ + title, + subtitle, + accent, + cta, + children, +}: { + title: string + subtitle: string + accent?: boolean + cta?: React.ReactNode + children: React.ReactNode +}) { + return ( +
+ {accent && ( +
+ )} +
+
+

+ {title} +

+

+ {subtitle} +

+
+ {cta} +
+ {children} +
+ ) +} + +function AppCard({ + icon, + name, + subtitle, + connected, + busy, + onConnect, +}: { + icon: React.ReactNode + name: string + subtitle: string + connected: boolean + busy: boolean + onConnect: () => void +}) { + return ( +
+
+ {icon} +
+
+

+ {name} +

+

+ {subtitle} +

+
+ {connected ? ( + + + Connected + + ) : ( + + )} +
+ ) +} + +function GithubMark({ className }: { className?: string }) { + return ( + + GitHub + + + ) +} + +function LinearMark({ className }: { className?: string }) { + return ( + + Linear + + + ) +} + +function SlackMark({ className }: { className?: string }) { + return ( + + ) +} diff --git a/apps/web/components/chat/index.tsx b/apps/web/components/chat/index.tsx index a2d8e305..aae5920b 100644 --- a/apps/web/components/chat/index.tsx +++ b/apps/web/components/chat/index.tsx @@ -48,7 +48,10 @@ import { import { SpaceSelector } from "@/components/space-selector" import { SuperLoader } from "../superloader" import { UserMessage } from "./message/user-message" -import { AgentMessage } from "./message/agent-message" +import { + AgentMessage, + isChatToolDisplayPartType, +} from "./message/agent-message" import { ChatGraphContextRail } from "./chat-graph-context-rail" import { ChainOfThought } from "./input/chain-of-thought" import { useIsMobile } from "@hooks/use-mobile" @@ -1147,14 +1150,14 @@ export function ChatSidebar({ }) => ({ id: m.id, role: m.role, - // Strip tool parts (they break convertToModelMessages with tool_use/tool_result - // mismatches); keep text/reasoning + source parts so citations survive reload. + // Keep chat tool outputs that are meaningful to render after thread reload. parts: (m.parts || []).filter( (p) => p.type === "text" || p.type === "reasoning" || p.type === "source-url" || - p.type === "source-document", + p.type === "source-document" || + isChatToolDisplayPartType(p.type), ), metadata: m.metadata, createdAt: new Date(m.createdAt), diff --git a/apps/web/components/chat/message/agent-message.tsx b/apps/web/components/chat/message/agent-message.tsx index 1b7f69a8..eac0f7b9 100644 --- a/apps/web/components/chat/message/agent-message.tsx +++ b/apps/web/components/chat/message/agent-message.tsx @@ -2,6 +2,7 @@ import { type ReactNode, useEffect, useMemo, useRef, useState } from "react" import type { UIMessage } from "@ai-sdk/react" +import { useQuery } from "@tanstack/react-query" import { Streamdown } from "streamdown" import { BookOpenIcon, @@ -19,18 +20,37 @@ import { } from "lucide-react" import { cn } from "@lib/utils" import { isWebSearchToolName } from "@/lib/chat-web-search-tools" +import { + buildCitationIndex, + fetchDocumentsByIds, + getDocumentSourceUrl, + isMemoryToolOutputReady, + mapDocumentsByKnownIds, + type CitationTarget, + type DocumentWithMemories, + extractMemoryToolOutputs, +} from "@/lib/chat-memory-tools" +import { + parseSourceAnnotatedMarkdown, + stripSourceMarkup, +} from "@/lib/source-annotations" import { modelNames, type ModelId } from "@/lib/models" import { RelatedMemories } from "./related-memories" import { MessageActions } from "./message-actions" const TOOL_META: Record = { bash: { label: "Memory", icon: TerminalIcon }, + recallContext: { label: "Recall Memories", icon: BookOpenIcon }, + discoverSpaces: { label: "Discover Spaces", icon: SearchIcon }, web_search: { label: "Web search", icon: GlobeIcon }, google_search: { label: "Google search", icon: GlobeIcon }, // legacy tool names kept for existing persisted messages searchMemories: { label: "Search Memories", icon: SearchIcon }, addMemory: { label: "Add Memory", icon: PlusIcon }, fetchMemory: { label: "Fetch Memory", icon: BookOpenIcon }, + forgetMemory: { label: "Forget Memory", icon: XCircleIcon }, + updateMemory: { label: "Update Memory", icon: BookOpenIcon }, + forgetDocument: { label: "Forget Document", icon: XCircleIcon }, scheduleTask: { label: "Schedule Task", icon: ClockIcon }, listSchedules: { label: "List Schedules", icon: ListIcon }, cancelSchedule: { label: "Cancel Schedule", icon: XCircleIcon }, @@ -38,7 +58,7 @@ const TOOL_META: Record = { type ToolCallDisplayPart = { type: string - state: string + state?: string input?: unknown output?: unknown toolCallId?: string @@ -64,6 +84,20 @@ function faviconUrl(host: string): string { return `https://www.google.com/s2/favicons?sz=64&domain=${host}` } +function safeExternalUrl(url: string | null | undefined): string | null { + if (!url) return null + if (url.startsWith("/") && !url.startsWith("//")) return url + if (url.startsWith("#") && !url.startsWith("#sm-source:")) return url + try { + const parsed = new URL(url) + return parsed.protocol === "http:" || parsed.protocol === "https:" + ? url + : null + } catch { + return null + } +} + function isWebSearchPart(part: { type: string; toolName?: string }): boolean { if (part.type === "dynamic-tool") { return isWebSearchToolName(part.toolName ?? "") @@ -74,6 +108,25 @@ function isWebSearchPart(part: { type: string; toolName?: string }): boolean { return false } +function isMemoryRetrievalToolName(toolName: string): boolean { + return ( + toolName === "searchMemories" || + toolName === "recallContext" || + toolName === "discoverSpaces" + ) +} + +export function isChatToolDisplayPartType(type: string): boolean { + return ( + type === "tool-searchMemories" || + type === "tool-recallContext" || + type === "tool-discoverSpaces" || + type === "tool-forgetMemory" || + type === "tool-updateMemory" || + type === "tool-forgetDocument" + ) +} + function CitationLink({ href, label, @@ -83,7 +136,8 @@ function CitationLink({ label: string source?: SourceUrlPart }) { - const url = source?.url ?? href + const url = safeExternalUrl(source?.url ?? href) ?? "" + if (!url) return <>{label} const host = sourceHost(url) const rawTitle = source?.title?.trim() const hasTitle = @@ -136,9 +190,138 @@ function CitationLink({ ) } -function makeMarkdownComponents(sources: SourceUrlPart[]) { +function sourceTitle( + target: CitationTarget, + document?: DocumentWithMemories, +): string { + return ( + document?.title?.trim() || + target.title?.trim() || + document?.customId || + target.customId || + target.documentId || + target.sourceId + ) +} + +function sourceSummary( + target: CitationTarget, + document?: DocumentWithMemories, +): string | null { + const summary = + document?.summary || + target.summary || + (document as { content?: string } | undefined)?.content || + null + return summary ? summary.trim() : null +} + +function sourceKind( + target: CitationTarget, + document?: DocumentWithMemories, +): string { + return (document?.type || target.type || "memory").replaceAll("_", " ") +} + +function SourceCitationLink({ + sourceId, + children, + citationIndex, + documentByKnownId, +}: { + sourceId: string + children: ReactNode + citationIndex: Map + documentByKnownId: Map +}) { + const target = citationIndex.get(sourceId) + if (!target) return <>{children} + + const document = + (target.documentId + ? documentByKnownId.get(target.documentId) + : undefined) ?? + (target.customId ? documentByKnownId.get(target.customId) : undefined) + const url = safeExternalUrl( + document ? getDocumentSourceUrl(document) : target.url, + ) + const title = sourceTitle(target, document) + const summary = sourceSummary(target, document) + + return ( + + {url ? ( + + {children} + + ) : ( + + )} + + {sourceId} + + + + + + {title} + + + {sourceKind(target, document)} + + + {summary ? ( + + {summary} + + ) : null} + {url ? ( + + Open source + + ) : null} + + + + ) +} + +function makeMarkdownComponents( + sources: SourceUrlPart[], + citationIndex: Map, + documentByKnownId: Map, +) { return { a: ({ href, children }: { href?: string; children?: ReactNode }) => { + if (href?.startsWith("#sm-source:")) { + const sourceId = (() => { + try { + return decodeURIComponent(href.slice("#sm-source:".length)) + } catch { + return null + } + })() + if (!sourceId) return <>{children} + return ( + + {children} + + ) + } const label = typeof children === "string" ? children @@ -146,14 +329,16 @@ function makeMarkdownComponents(sources: SourceUrlPart[]) { ? children.join("") : "" const match = label.match(/^\[?(\d+)\]?$/) - if (match && href) { + const safeHref = safeExternalUrl(href) + if (match && safeHref) { const n = Number(match[1]) - const source = sources.find((s) => s.url === href) ?? sources[n - 1] - return + const source = sources.find((s) => s.url === safeHref) ?? sources[n - 1] + return } + if (!safeHref) return <>{children} return ( @@ -516,7 +704,38 @@ export function AgentMessage({ .filter((part) => part.type === "text") .map((part) => part.text) .join(" ") - const webSources = (() => { + const copyText = stripSourceMarkup(messageText) + const memoryOutputs = useMemo( + () => extractMemoryToolOutputs(message), + [message], + ) + const citationIndex = useMemo( + () => buildCitationIndex(memoryOutputs), + [memoryOutputs], + ) + const allowedSourceIds = useMemo( + () => new Set(citationIndex.keys()), + [citationIndex], + ) + const sourceDocumentIds = useMemo(() => { + const ids = new Set() + for (const target of citationIndex.values()) { + if (target.documentId) ids.add(target.documentId) + if (target.customId) ids.add(target.customId) + } + return [...ids].sort() + }, [citationIndex]) + const { data: sourceDocuments = [] } = useQuery({ + queryKey: ["chat-source-documents", sourceDocumentIds], + queryFn: () => fetchDocumentsByIds(sourceDocumentIds), + enabled: sourceDocumentIds.length > 0, + staleTime: 5 * 60 * 1000, + }) + const documentByKnownId = useMemo( + () => mapDocumentsByKnownIds(sourceDocuments), + [sourceDocuments], + ) + const webSources = useMemo(() => { const seen = new Set() const out: SourceUrlPart[] = [] for (const part of message.parts) { @@ -527,15 +746,13 @@ export function AgentMessage({ out.push(source) } return out - })() + }, [message.parts]) const hasAssistantText = message.parts.some( (p) => p.type === "text" && (p as { text?: string }).text?.trim(), ) - const sourceKey = webSources.map((s) => s.url).join("|") - // biome-ignore lint/correctness/useExhaustiveDependencies: keyed by stable source urls const markdownComponents = useMemo( - () => makeMarkdownComponents(webSources), - [sourceKey], + () => makeMarkdownComponents(webSources, citationIndex, documentByKnownId), + [webSources, citationIndex, documentByKnownId], ) const responseModelLabel = responseModel ? `${modelNames[responseModel].name} ${modelNames[responseModel].version}` @@ -603,7 +820,10 @@ export function AgentMessage({ className="text-sm text-white/90 chat-markdown-content" > - {runText} + { + parseSourceAnnotatedMarkdown(runText, allowedSourceIds) + .markdown + }
) @@ -613,7 +833,7 @@ export function AgentMessage({ type: "dynamic-tool" toolName: string toolCallId: string - state: string + state?: string input?: unknown output?: unknown errorText?: string @@ -651,7 +871,7 @@ export function AgentMessage({
+ {headerNotice ?
{headerNotice}
: null} {/* Header */} diff --git a/apps/web/components/ensure-workspace.tsx b/apps/web/components/ensure-workspace.tsx index 73218af2..675edff4 100644 --- a/apps/web/components/ensure-workspace.tsx +++ b/apps/web/components/ensure-workspace.tsx @@ -20,12 +20,14 @@ export function EnsureWorkspace({ children }: { children: React.ReactNode }) { const pathname = usePathname() const router = useRouter() const searchParams = useSearchParams() - const { session, organizations, isRestoring } = useAuth() + const { session, organizations, isRestoring, isSessionPending } = useAuth() const isPublicAppPage = - pathname === "/" && - ["integrations", "mcp"].includes(searchParams.get("view") ?? "") - const isGuestPublicAppPage = isPublicAppPage && !session + pathname === "/integrations" || + pathname === "/integrations/mcp" || + (pathname === "/" && + ["integrations", "mcp"].includes(searchParams.get("view") ?? "")) + const isGuestPublicAppPage = isPublicAppPage && !session && !isSessionPending const isOnboarding = pathname.startsWith("/onboarding") useEffect(() => { diff --git a/apps/web/components/header.tsx b/apps/web/components/header.tsx index 8abe42ab..3d977209 100644 --- a/apps/web/components/header.tsx +++ b/apps/web/components/header.tsx @@ -514,7 +514,7 @@ export function PublicHeader({ return (
@@ -523,7 +523,7 @@ export function PublicHeader({

- + ) case "import": return ( @@ -3409,7 +3684,7 @@ export function IntegrationsView({ ] return ( -
+
{shortcutsConnect.dialog}
+ + ) : null + } + > {items.map((item) => (
+ onOpenChange={(open) => { setNewKey((s) => ({ open, key: open ? s.key : "", pluginId: open ? s.pluginId : null, + loading: open ? s.loading : false, })) - } + if (!open) void setConnectTarget(null) + }} >

- Copy your key and run these steps to finish. + {newKey.loading + ? "Generating your key…" + : "Copy your key and run these steps to finish."}

@@ -3599,15 +3889,28 @@ export function IntegrationsView({ INSET, )} > - + {newKey.loading ? ( +
+ + Generating your key… +
+ ) : ( + + )}
) diff --git a/apps/web/components/integrations/plugins-detail.tsx b/apps/web/components/integrations/plugins-detail.tsx index 0bd044ae..2f28e791 100644 --- a/apps/web/components/integrations/plugins-detail.tsx +++ b/apps/web/components/integrations/plugins-detail.tsx @@ -613,7 +613,7 @@ export function PluginsDetail() { try { const result = await autumn.attach({ planId: "api_pro", - successUrl: `${window.location.origin}/?view=integrations`, + successUrl: `${window.location.origin}/integrations`, }) if (result?.paymentUrl) { window.open(result.paymentUrl, "_self") diff --git a/apps/web/components/login-tools-panel.tsx b/apps/web/components/login-tools-panel.tsx index 8b6f1da1..40c3b6d5 100644 --- a/apps/web/components/login-tools-panel.tsx +++ b/apps/web/components/login-tools-panel.tsx @@ -1,367 +1,8 @@ "use client" -import Image from "next/image" -import { useEffect, useState } from "react" -import { motion } from "motion/react" import { cn } from "@lib/utils" import { dmSansClassName } from "@/lib/fonts" -import { ChromeIcon, RaycastIcon } from "@/components/integration-icons" -import { - ClaudeDesktopIcon, - GoogleDrive, - MCPIcon, - Notion, -} from "@ui/assets/icons" -import { Logo } from "@ui/assets/Logo" -import NovaOrb from "@/components/nova/nova-orb" - -type ToolNode = { - id: string - name: string - x: number - y: number - icon?: React.ComponentType<{ className?: string }> - iconSrc?: string -} - -type ContextConnection = { - from: ToolNode - to: ToolNode -} - -type ContextPhase = "idle" | "capture" | "hold" | "recall" - -const CENTER = { x: 50, y: 50 } -const IN_MS = 1100 -const HOLD_MS = 700 -const OUT_MS = 1100 -const TOTAL_MS = IN_MS + HOLD_MS + OUT_MS - -const TOOL_NODES: ToolNode[] = [ - { id: "chrome", name: "Chrome", x: 14, y: 20, icon: ChromeIcon }, - { id: "notion", name: "Notion", x: 84, y: 16, icon: Notion }, - { id: "drive", name: "Google Drive", x: 10, y: 52, icon: GoogleDrive }, - { id: "claude", name: "Claude", x: 90, y: 44, icon: ClaudeDesktopIcon }, - { id: "raycast", name: "Raycast", x: 76, y: 76, icon: RaycastIcon }, - { id: "mcp", name: "MCP", x: 22, y: 84, icon: MCPIcon }, - { - id: "claude-code", - name: "Claude Code", - x: 20, - y: 36, - iconSrc: "/images/plugins/claude-code.svg", - }, - { - id: "codex", - name: "Codex", - x: 92, - y: 28, - iconSrc: "/images/plugins/codex.png", - }, - { - id: "opencode", - name: "OpenCode", - x: 58, - y: 10, - iconSrc: "/images/plugins/opencode.svg", - }, - { - id: "hermes", - name: "Hermes", - x: 36, - y: 90, - iconSrc: "/images/plugins/hermes.svg", - }, - { - id: "openclaw", - name: "OpenClaw", - x: 68, - y: 68, - iconSrc: "/images/plugins/openclaw.svg", - }, -] - -const CONTEXT_FLOWS: [string, string][] = [ - ["chrome", "claude"], - ["notion", "raycast"], - ["drive", "claude-code"], - ["opencode", "codex"], - ["claude-code", "mcp"], - ["hermes", "openclaw"], - ["claude", "mcp"], - ["notion", "claude"], -] - -function nodeById(id: string) { - return TOOL_NODES.find((node) => node.id === id) -} - -function pickContextFlow(): ContextConnection | null { - const flow = CONTEXT_FLOWS[Math.floor(Math.random() * CONTEXT_FLOWS.length)] - if (!flow) return null - const [fromId, toId] = flow - const from = nodeById(fromId) - const to = nodeById(toId) - if (!from || !to) return null - return { from, to } -} - -function ToolNodeIcon({ - node, - role, -}: { - node: ToolNode - role?: "source" | "destination" -}) { - const Icon = node.icon - - return ( -
-
- {Icon ? ( - - ) : node.iconSrc ? ( - - ) : null} -
- - {node.name} - -
- ) -} - -function MemoryChip() { - return ( -
-
- - - -
-
- ) -} - -function AnimatedContextFlow({ - connection, - phase, -}: { - connection: ContextConnection - phase: ContextPhase -}) { - const { from, to } = connection - const dIn = `M ${from.x} ${from.y} L ${CENTER.x} ${CENTER.y}` - const dOut = `M ${CENTER.x} ${CENTER.y} L ${to.x} ${to.y}` - - const chipLeft = - phase === "recall" - ? [`${CENTER.x}%`, `${to.x}%`] - : phase === "hold" - ? `${CENTER.x}%` - : [`${from.x}%`, `${CENTER.x}%`] - - const chipTop = - phase === "recall" - ? [`${CENTER.y}%`, `${to.y}%`] - : phase === "hold" - ? `${CENTER.y}%` - : [`${from.y}%`, `${CENTER.y}%`] - - return ( -
- - - {phase !== "idle" && ( - - - - )} -
- ) -} - -function ToolsContextNetwork() { - const [connection, setConnection] = useState(null) - const [pulseId, setPulseId] = useState(0) - const [phase, setPhase] = useState("idle") - - useEffect(() => { - let cancelled = false - let pulseTimeout: ReturnType - - const runPulse = () => { - if (cancelled) return - const next = pickContextFlow() - if (!next) return - setConnection(next) - setPulseId((id) => id + 1) - pulseTimeout = setTimeout(runPulse, TOTAL_MS + 900 + Math.random() * 500) - } - - runPulse() - return () => { - cancelled = true - clearTimeout(pulseTimeout) - } - }, []) - - useEffect(() => { - if (!connection) return - - setPhase("capture") - const holdTimer = setTimeout(() => setPhase("hold"), IN_MS) - const recallTimer = setTimeout(() => setPhase("recall"), IN_MS + HOLD_MS) - const idleTimer = setTimeout(() => setPhase("idle"), TOTAL_MS) - - return () => { - clearTimeout(holdTimer) - clearTimeout(recallTimer) - clearTimeout(idleTimer) - } - }, [connection]) - - const sourceRole = (nodeId: string) => - connection && - connection.from.id === nodeId && - (phase === "capture" || phase === "hold") - ? ("source" as const) - : undefined - - const destRole = (nodeId: string) => - connection && connection.to.id === nodeId && phase === "recall" - ? ("destination" as const) - : undefined - - return ( -
- {connection && phase !== "idle" ? ( - - ) : null} - - {TOOL_NODES.map((node) => ( - - ))} - -
- - -
- -
-
-
-
- ) -} +import OrbitMemory from "@/components/orbit-memory" function LoginPanelBackground() { return ( @@ -373,6 +14,15 @@ function LoginPanelBackground() {
+
) } @@ -382,8 +32,16 @@ export function LoginToolsPanel() {