From 844d5b81c2d987ebb7a8cb5cf912ba285cb20529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Emre=20Kabakc=C4=B1?= Date: Fri, 24 Apr 2026 17:21:34 +0100 Subject: [PATCH 01/19] feat(owletto-backend): ClientSDK + sandbox scaffolding for execute/search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lands PR-1 of docs/plans/mcp-multi-org-and-execute.md: the in-process SDK surface, per-namespace delegations, membership cache, and isolated-vm runner skeleton. No new MCP tools yet — execute and search land in PR-2. - `src/sandbox/client-sdk.ts` — buildClientSDK + .org() accessor with per-call membership re-validation. AccessDenied/OrgNotFound errors surface the exact org and reason. - `src/sandbox/membership-cache.ts` — small LRU (cap 128, TTL 30s) keyed on (userId, slug-or-id). Catches revocations within the TTL while keeping cross-org walks cheap. - `src/sandbox/namespaces/*.ts` — 11 namespaces (entities, entitySchema, connections, feeds, authProfiles, operations, watchers, classifiers, viewTemplates, knowledge, organizations). Thin delegations over existing admin handlers; per-call authz runs inside the handlers. - `src/sandbox/method-metadata.ts` — summary/access/example keyed by SDK path. Seeded for PR-1; PR-2 adds CI coverage check. - `src/sandbox/typebox-to-signature.ts` — TypeBox → inline TS signature formatter used by the `search` tool. - `src/sandbox/run-script.ts` — isolated-vm loader + limits shape. Native module is an optionalDependency so bun install succeeds on platforms without a prebuild; Dockerfile adds python3 + build-essential so production builds work. Tests: 25 unit tests (bun:test) cover membership cache semantics, metadata shape, typebox formatter, and run-script loader fallback. Integration test (vitest) for .org() exercises real org/member tables with AccessDenied/OrgNotFound/public-org/LRU-shortcircuit/revocation paths. --- bun.lock | 553 +----------------- docker/app/Dockerfile | 2 + packages/owletto-backend/package.json | 3 + .../sandbox/client-sdk-org.test.ts | 223 +++++++ .../unit/sandbox/membership-cache.test.ts | 109 ++++ .../unit/sandbox/method-metadata.test.ts | 48 ++ .../__tests__/unit/sandbox/run-script.test.ts | 42 ++ .../unit/sandbox/typebox-to-signature.test.ts | 72 +++ .../owletto-backend/src/sandbox/client-sdk.ts | 253 ++++++++ .../src/sandbox/membership-cache.ts | 74 +++ .../src/sandbox/method-metadata.ts | 291 +++++++++ .../src/sandbox/namespaces/auth-profiles.ts | 43 ++ .../src/sandbox/namespaces/classifiers.ts | 54 ++ .../src/sandbox/namespaces/connections.ts | 69 +++ .../src/sandbox/namespaces/entities.ts | 170 ++++++ .../src/sandbox/namespaces/entity-schema.ts | 90 +++ .../src/sandbox/namespaces/feeds.ts | 38 ++ .../src/sandbox/namespaces/index.ts | 15 + .../src/sandbox/namespaces/knowledge.ts | 70 +++ .../src/sandbox/namespaces/operations.ts | 46 ++ .../src/sandbox/namespaces/organizations.ts | 80 +++ .../src/sandbox/namespaces/view-templates.ts | 47 ++ .../src/sandbox/namespaces/watchers.ts | 107 ++++ .../owletto-backend/src/sandbox/run-script.ts | 141 +++++ .../src/sandbox/typebox-to-signature.ts | 107 ++++ 25 files changed, 2202 insertions(+), 545 deletions(-) create mode 100644 packages/owletto-backend/src/__tests__/integration/sandbox/client-sdk-org.test.ts create mode 100644 packages/owletto-backend/src/__tests__/unit/sandbox/membership-cache.test.ts create mode 100644 packages/owletto-backend/src/__tests__/unit/sandbox/method-metadata.test.ts create mode 100644 packages/owletto-backend/src/__tests__/unit/sandbox/run-script.test.ts create mode 100644 packages/owletto-backend/src/__tests__/unit/sandbox/typebox-to-signature.test.ts create mode 100644 packages/owletto-backend/src/sandbox/client-sdk.ts create mode 100644 packages/owletto-backend/src/sandbox/membership-cache.ts create mode 100644 packages/owletto-backend/src/sandbox/method-metadata.ts create mode 100644 packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts create mode 100644 packages/owletto-backend/src/sandbox/namespaces/classifiers.ts create mode 100644 packages/owletto-backend/src/sandbox/namespaces/connections.ts create mode 100644 packages/owletto-backend/src/sandbox/namespaces/entities.ts create mode 100644 packages/owletto-backend/src/sandbox/namespaces/entity-schema.ts create mode 100644 packages/owletto-backend/src/sandbox/namespaces/feeds.ts create mode 100644 packages/owletto-backend/src/sandbox/namespaces/index.ts create mode 100644 packages/owletto-backend/src/sandbox/namespaces/knowledge.ts create mode 100644 packages/owletto-backend/src/sandbox/namespaces/operations.ts create mode 100644 packages/owletto-backend/src/sandbox/namespaces/organizations.ts create mode 100644 packages/owletto-backend/src/sandbox/namespaces/view-templates.ts create mode 100644 packages/owletto-backend/src/sandbox/namespaces/watchers.ts create mode 100644 packages/owletto-backend/src/sandbox/run-script.ts create mode 100644 packages/owletto-backend/src/sandbox/typebox-to-signature.ts diff --git a/bun.lock b/bun.lock index b3263acc8..bd8eb0fc2 100644 --- a/bun.lock +++ b/bun.lock @@ -161,6 +161,9 @@ "vite": "^6.0.0", "vitest": "^2.1.8", }, + "optionalDependencies": { + "isolated-vm": "^5.0.4", + }, }, "packages/owletto-cli": { "name": "owletto", @@ -252,70 +255,6 @@ "playwright", ], }, - "packages/owletto-web": { - "name": "@owletto/web", - "version": "1.6.0", - "dependencies": { - "@codemirror/autocomplete": "^6.20.0", - "@codemirror/commands": "^6.10.2", - "@codemirror/lang-json": "^6.0.2", - "@codemirror/language": "^6.12.2", - "@codemirror/lint": "^6.9.4", - "@codemirror/search": "^6.6.0", - "@codemirror/state": "^6.5.4", - "@codemirror/view": "^6.39.15", - "@daveyplate/better-auth-ui": "^3.3.15", - "@jsonforms/core": "^3.7.0", - "@jsonforms/react": "^3.7.0", - "@lobu/owletto-sdk": "workspace:*", - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-collapsible": "^1.1.12", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slider": "^1.3.6", - "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-tabs": "^1.1.13", - "@radix-ui/react-tooltip": "^1.2.8", - "@tanstack/react-query": "^5.64.0", - "@tanstack/react-router": "^1.95.0", - "@tanstack/react-table": "^8.21.3", - "@tanstack/router-devtools": "^1.95.0", - "better-auth": "^1.4.10", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cmdk": "^1.1.1", - "date-fns": "^4.1.0", - "echarts": "^5.5.0", - "framer-motion": "^12.31.0", - "lucide-react": "^0.469.0", - "qrcode.react": "^4.2.0", - "react": "^19.0.0", - "react-day-picker": "^9.13.0", - "react-dom": "^19.0.0", - "react-markdown": "^10.1.0", - "simple-icons": "^16.15.0", - "sonner": "^2.0.7", - "tailwind-merge": "^3.4.0", - "zod": "^3.24.0", - }, - "devDependencies": { - "@tailwindcss/postcss": "^4.1.17", - "@tanstack/router-plugin": "^1.95.0", - "@types/react": "^19.0.0", - "@types/react-dom": "^19.0.0", - "@vitejs/plugin-react": "^4.3.4", - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.17", - "typescript": "^5.7.2", - "vite": "^6.0.0", - "vitest": "^2.1.8", - }, - }, "packages/owletto-worker": { "name": "@lobu/owletto-worker", "version": "1.6.0", @@ -366,8 +305,6 @@ "@types/node": "20.19.9", }, "packages": { - "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.90.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-MzZtPabJF1b0FTDl6Z6H5ljphPwACLGP13lu8MTiB8jXaW/YXlpOp+Po2cVou3MPM5+f5toyLnul9whKCy7fBg=="], @@ -504,8 +441,6 @@ "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="], - "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="], - "@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-jsx": "^7.28.6", "@babel/types": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow=="], "@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.27.1", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q=="], @@ -526,8 +461,6 @@ "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], - "@better-auth/api-key": ["@better-auth/api-key@1.6.8", "", { "dependencies": { "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/core": "^1.6.8", "@better-auth/utils": "0.4.0", "better-auth": "^1.6.8" } }, "sha512-Sm2NawGFP+bhGjwUorasITjvqII/bxP+Zcjw7tdga2IspeI2IxSQ6sq2jymqGeE5cN5lzC+w9zwOUDsyHeeTLg=="], - "@better-auth/core": ["@better-auth/core@1.6.8", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types", "@opentelemetry/api"] }, "sha512-YVLkvMvUDeP3W0Wl9PHgwF2etWxKL9rntP0NjZ3YXsiuf05O5Gx0BNvWSeG9fcUaAFjjkiVdFOEvpYtDPpehog=="], "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.6.8", "", { "peerDependencies": { "@better-auth/core": "^1.6.8", "@better-auth/utils": "0.4.0", "drizzle-orm": "^0.45.2" }, "optionalPeers": ["drizzle-orm"] }, "sha512-iAQ8KWPfGppcakZXStbX7aWGeK0pLlDlzkQN6NhFM7Qm2KwBcc0X1EyRGIiLSIiGw8hTNppGp8igliMm2ti0zw=="], @@ -538,8 +471,6 @@ "@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.6.8", "", { "peerDependencies": { "@better-auth/core": "^1.6.8", "@better-auth/utils": "0.4.0", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-z2NxSf890g24co33av3SqBKilP8QesNkIp27zIKRE3AqXjii4zQhN7M17DtAsGWJMH2Z336e/mypR2pYYn9DpQ=="], - "@better-auth/passkey": ["@better-auth/passkey@1.6.8", "", { "dependencies": { "@simplewebauthn/browser": "^13.2.2", "@simplewebauthn/server": "^13.2.3", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/core": "^1.6.8", "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "better-auth": "^1.6.8", "better-call": "1.3.5", "nanostores": "^1.0.1" } }, "sha512-7nOyao3YcH8HUCU48SaKBbT0AsnrFqlwoHTq+jW5zLN/zXKeu5VYN7Eumpgep+eY3bb+nBuf/jGMAldQvmx82w=="], - "@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.6.8", "", { "peerDependencies": { "@better-auth/core": "^1.6.8", "@better-auth/utils": "0.4.0", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-eyPCQwFXFgGo82acjvQpL5iPNi4h3SWhpTG6Ck0mROkqYU9H0v3lpf9vnO/EraOkB6o7VGDPmOirJNYGcFd4lQ=="], "@better-auth/telemetry": ["@better-auth/telemetry@1.6.8", "", { "peerDependencies": { "@better-auth/core": "^1.6.8", "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21" } }, "sha512-EkYehns4SH04kGOjVJ++THnGtpjvmktF5+bGbDAanMd6IjP4O7sCaDGrT62xraj4lgj3UO1vQXpR6/M1Xlmixg=="], @@ -570,10 +501,6 @@ "@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="], - "@captchafox/react": ["@captchafox/react@1.12.0", "", { "dependencies": { "@captchafox/types": "^1.4.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-ALJWVvSBL3H16KMrGg3exO6EufI32zY63LBB0UC0l7jW+Ynz832tG7wwg0PbDWNle0CiN35DAfxDzH/29sDp3g=="], - - "@captchafox/types": ["@captchafox/types@1.5.0", "", {}, "sha512-uReDnNoAarRjhbxC0w4hEueKLCx8w22dv+XqzDjR4rUFo3fCj8Dh3A6D27OFAYlfSr/nwomDatAxz7ixwJ5y5w=="], - "@chat-adapter/discord": ["@chat-adapter/discord@4.26.0", "", { "dependencies": { "@chat-adapter/shared": "4.26.0", "chat": "4.26.0", "discord-api-types": "^0.37.119", "discord-interactions": "^4.4.0", "discord.js": "^14.25.1" } }, "sha512-p0Xm/aPjHP9z87/tXtHz0KFmbUcu7SGPQAAlT8mrKRX49jO77Tognj/5poSmV1XQ1C4BLfxpPvTQlLNK/5omkw=="], "@chat-adapter/gchat": ["@chat-adapter/gchat@4.26.0", "", { "dependencies": { "@chat-adapter/shared": "4.26.0", "@googleapis/chat": "^44.6.0", "@googleapis/workspaceevents": "^9.1.0", "chat": "4.26.0" } }, "sha512-GOS3hBrjGx/m1NE1HDtwA8st/aeW3W1TaINeXqX7IjCMnO0re1Q31DwhpaPkTD7CraqrSJRUwV37Gz/hCTmWNw=="], @@ -594,34 +521,12 @@ "@clack/prompts": ["@clack/prompts@1.2.0", "", { "dependencies": { "@clack/core": "1.2.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w=="], - "@codemirror/autocomplete": ["@codemirror/autocomplete@6.20.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A=="], - - "@codemirror/commands": ["@codemirror/commands@6.10.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q=="], - - "@codemirror/lang-json": ["@codemirror/lang-json@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/json": "^1.0.0" } }, "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ=="], - - "@codemirror/language": ["@codemirror/language@6.12.3", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA=="], - - "@codemirror/lint": ["@codemirror/lint@6.9.5", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA=="], - - "@codemirror/search": ["@codemirror/search@6.7.0", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.37.0", "crelt": "^1.0.5" } }, "sha512-ZvGm99wc/s2cITtMT15LFdn8aH/aS+V+DqyGq/N5ZlV5vWtH+nILvC2nw0zX7ByNoHHDZ2IxxdW38O0tc5nVHg=="], - - "@codemirror/state": ["@codemirror/state@6.6.0", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ=="], - - "@codemirror/view": ["@codemirror/view@6.41.1", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-ToDnWKbBnke+ZLrP6vgTTDScGi5H37YYuZGniQaBzxMVdtCxMrslsmtnOvbPZk4RX9bvkQqnWR/WS/35tJA0qg=="], - "@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="], "@ctrl/tinycolor": ["@ctrl/tinycolor@4.2.0", "", {}, "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A=="], "@dabh/diagnostics": ["@dabh/diagnostics@2.0.8", "", { "dependencies": { "@so-ric/colorspace": "^1.1.6", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q=="], - "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="], - - "@daveyplate/better-auth-tanstack": ["@daveyplate/better-auth-tanstack@1.3.6", "", { "peerDependencies": { "@tanstack/query-core": ">=5.65.0", "@tanstack/react-query": ">=5.65.0", "better-auth": ">=1.2.8", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-GvIGdbjRMZCEfAffU7LeWpGpie4vSli8V9jmNFCQOziZZMEFbO4cd53HBFmAushC9oEYIyhSNZBgV2ADzW94Ww=="], - - "@daveyplate/better-auth-ui": ["@daveyplate/better-auth-ui@3.4.0", "", { "dependencies": { "@better-auth/api-key": "^1.5.6", "@better-fetch/fetch": "^1.1.21", "@hcaptcha/react-hcaptcha": "^2.0.2", "@noble/hashes": "^2.0.1", "@react-email/components": "^1.0.10", "@wojtekmaj/react-recaptcha-v3": "^0.1.4", "better-call": "2.0.2", "bowser": "^2.11.0", "react-google-recaptcha": "^3.1.0", "react-qr-code": "^2.0.18", "vaul": "^1.1.2" }, "peerDependencies": { "@better-auth/passkey": ">=1.4.6", "@captchafox/react": "^1.10.0", "@daveyplate/better-auth-tanstack": "^1.3.6", "@hookform/resolvers": ">=5.2.0", "@instantdb/react": ">=0.18.0", "@marsidev/react-turnstile": ">=1.1.0", "@radix-ui/react-avatar": ">=1.1.0", "@radix-ui/react-checkbox": ">=1.1.0", "@radix-ui/react-context": ">=1.1.0", "@radix-ui/react-dialog": ">=1.1.0", "@radix-ui/react-dropdown-menu": ">=2.1.0", "@radix-ui/react-label": ">=2.1.0", "@radix-ui/react-primitive": ">=2.0.0", "@radix-ui/react-select": ">=2.2.0", "@radix-ui/react-separator": ">=1.1.0", "@radix-ui/react-slot": ">=1.1.0", "@radix-ui/react-tabs": ">=1.1.0", "@radix-ui/react-tooltip": ">=1.2.0", "@radix-ui/react-use-callback-ref": ">=1.1.0", "@radix-ui/react-use-layout-effect": ">=1.1.0", "@tanstack/react-query": ">=5.66.0", "@triplit/client": ">=1.0.0", "@triplit/react": ">=1.0.0", "better-auth": "^1.4.6", "class-variance-authority": ">=0.7.0", "clsx": ">=2.1.0", "input-otp": ">=1.4.0", "lucide-react": ">=0.469.0", "react": ">=18.0.0", "react-dom": ">=18.0.0", "react-hook-form": ">=7.55.0", "sonner": ">=1.7.0", "tailwind-merge": ">=2.6.0", "tailwindcss": ">=3.0.0", "zod": ">=3.0.0" } }, "sha512-mGA0cKsAk0AcoKkxjff2xQOviMrUDDN+9SsRusURP/kiTmoLvWAv8utaPex0GFLHKm1w3BrLBdJRg9B2LkT7Bw=="], - "@discordjs/builders": ["@discordjs/builders@1.14.1", "", { "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.40", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-gSKkhXLqs96TCzk66VZuHHl8z2bQMJFGwrXC0f33ngK+FLNau4hU1PYny3DNJfNdSH+gVMzE85/d5FQ2BpcNwQ=="], "@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="], @@ -706,14 +611,6 @@ "@fastify/otel": ["@fastify/otel@0.18.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.212.0", "@opentelemetry/semantic-conventions": "^1.28.0", "minimatch": "^10.2.4" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-3TASCATfw+ctICSb4ymrv7iCm0qJ0N9CarB+CZ7zIJ7KqNbwI5JjyDL1/sxoC0ccTO1Zyd1iQ+oqncPg5FJXaA=="], - "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], - - "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], - - "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="], - - "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], - "@google/genai": ["@google/genai@1.34.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.24.0" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-vu53UMPvjmb7PGzlYu6Tzxso8Dfhn+a7eQFaS2uNemVtDZKwzSpJ5+ikqBbXplF7RGB1STcVDqCkPvquiwb2sw=="], "@googleapis/chat": ["@googleapis/chat@44.6.0", "", { "dependencies": { "googleapis-common": "^8.0.0" } }, "sha512-Bnqzev/bSTXSbE0/N2WS4Stnleo8j9bJJ1LkCBk1fXQnehcArVMv7q543rzPYU6MJql4D34On6diNGAuYtI9xQ=="], @@ -724,18 +621,12 @@ "@grpc/proto-loader": ["@grpc/proto-loader@0.7.15", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ=="], - "@hcaptcha/react-hcaptcha": ["@hcaptcha/react-hcaptcha@2.0.2", "", {}, "sha512-VbuH6VJ6m3BHmVBHs0fL9t+suZd7PQEqCzqL2BiUbBvbHI3XfvSgdiug2QiEPN8zskbPTIV/FfGPF53JCckrow=="], - - "@hexagon/base64": ["@hexagon/base64@1.1.28", "", {}, "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw=="], - "@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], "@hono/zod-openapi": ["@hono/zod-openapi@1.3.0", "", { "dependencies": { "@asteasolutions/zod-to-openapi": "^8.5.0", "@hono/zod-validator": "^0.7.6", "openapi3-ts": "^4.5.0" }, "peerDependencies": { "hono": ">=4.3.6", "zod": "^4.0.0" } }, "sha512-loDVevfMaaNa0slskhpMcqjSdidVXba2QJwNVmnS5Dp6L8AqSgtjJxWGJfRZtosyzYOb5gx4ZzXNCe+QhwY7xw=="], "@hono/zod-validator": ["@hono/zod-validator@0.7.6", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw=="], - "@hookform/resolvers": ["@hookform/resolvers@5.2.2", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA=="], - "@huggingface/jinja": ["@huggingface/jinja@0.2.2", "", {}, "sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA=="], "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], @@ -792,14 +683,6 @@ "@inquirer/figures": ["@inquirer/figures@1.0.15", "", {}, "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g=="], - "@instantdb/core": ["@instantdb/core@1.0.15", "", { "dependencies": { "@instantdb/version": "1.0.15", "mutative": "^1.0.10", "uuid": "^11.1.0" } }, "sha512-1A4n47U0YLHKhvl0G+CiPGfBynq1cj+NqIVRhUMc/yHYT6rePeRWesxvevNiEJU2sLxOKML6Htcbtdh7jUjSQA=="], - - "@instantdb/react": ["@instantdb/react@1.0.15", "", { "dependencies": { "@instantdb/core": "1.0.15", "@instantdb/react-common": "1.0.15", "@instantdb/version": "1.0.15", "eventsource": "^4.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-d6Jbl10DaUh9Ni8+C0RLoTTHpB0UP9/8wAznimb6Z5dDhFiQ3gQyXpnz81OPXQEpQmEJUscqdGJul0DL17s8/g=="], - - "@instantdb/react-common": ["@instantdb/react-common@1.0.15", "", { "dependencies": { "@instantdb/core": "1.0.15", "@instantdb/version": "1.0.15" }, "peerDependencies": { "react": ">=16" } }, "sha512-EuDG9DfAx+XLW9UQoGe4lwW7Xos8njIuJp/grm0v7uWvnxlgAqKG+rTKjTDP0P6GfPpRbcZS0Houg0951tQe2g=="], - - "@instantdb/version": ["@instantdb/version@1.0.15", "", {}, "sha512-xHDT23QK0tKAdxC2Z98mBx+znwnVShYWDsp0juY4xxzTGjrb37qNqRYb+6IIJc1J3GZ+xW/fCIexVK3b0B6fog=="], - "@ioredis/commands": ["@ioredis/commands@1.5.1", "", {}, "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw=="], "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], @@ -842,10 +725,6 @@ "@jscpd/tokenizer": ["@jscpd/tokenizer@4.0.5", "", { "dependencies": { "@jscpd/core": "4.0.5", "reprism": "^0.0.11", "spark-md5": "^3.0.2" } }, "sha512-WzRujQtN5WedxZVDKuoanxmKAFrxcLrHpcA6kaM4z8AhGtWXZ325yseqgL5TZ8OK7Auwu7kQLlqhfk05fGYG7A=="], - "@jsonforms/core": ["@jsonforms/core@3.7.0", "", { "dependencies": { "@types/json-schema": "^7.0.3", "ajv": "^8.6.1", "ajv-formats": "^2.1.0", "lodash": "^4.17.21" } }, "sha512-CE9viWtwi9QWLqlWLeOul1/R1GRAyOA9y6OoUpsCc0FhyR+g5p29F3k0fUExHWxL0Sf4KHcXYkfhtqfRBPS8ww=="], - - "@jsonforms/react": ["@jsonforms/react@3.7.0", "", { "dependencies": { "lodash": "^4.17.21" }, "peerDependencies": { "@jsonforms/core": "3.7.0", "react": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-HkY7qAx8vW97wPEgZ7GxCB3iiXG1c95GuObxtcDHGPBJWMwnxWBnVYJmv5h7nthrInKsQKHZL5OusnC/sj/1GQ=="], - "@jsonjoy.com/base64": ["@jsonjoy.com/base64@1.1.2", "", { "peerDependencies": { "tslib": "2" } }, "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA=="], "@jsonjoy.com/buffers": ["@jsonjoy.com/buffers@17.67.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw=="], @@ -876,16 +755,6 @@ "@kubernetes/client-node": ["@kubernetes/client-node@0.21.0", "", { "dependencies": { "@types/js-yaml": "^4.0.1", "@types/node": "^20.1.1", "@types/request": "^2.47.1", "@types/ws": "^8.5.3", "byline": "^5.0.0", "isomorphic-ws": "^5.0.0", "js-yaml": "^4.1.0", "jsonpath-plus": "^8.0.0", "request": "^2.88.0", "rfc4648": "^1.3.0", "stream-buffers": "^3.0.2", "tar": "^7.0.0", "tslib": "^2.4.1", "ws": "^8.11.0" }, "optionalDependencies": { "openid-client": "^5.3.0" } }, "sha512-yYRbgMeyQbvZDHt/ZqsW3m4lRefzhbbJEuj8sVXM+bufKrgmzriA2oq7lWPH/k/LQIicAME9ixPUadTrxIF6dQ=="], - "@levischuck/tiny-cbor": ["@levischuck/tiny-cbor@0.2.11", "", {}, "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow=="], - - "@lezer/common": ["@lezer/common@1.5.2", "", {}, "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ=="], - - "@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="], - - "@lezer/json": ["@lezer/json@1.0.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ=="], - - "@lezer/lr": ["@lezer/lr@1.4.10", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A=="], - "@lobu/cli": ["@lobu/cli@workspace:packages/cli"], "@lobu/core": ["@lobu/core@workspace:packages/core"], @@ -910,8 +779,6 @@ "@lobu/worker": ["@lobu/worker@workspace:packages/worker"], - "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="], - "@mariozechner/clipboard": ["@mariozechner/clipboard@0.3.2", "", { "optionalDependencies": { "@mariozechner/clipboard-darwin-arm64": "0.3.2", "@mariozechner/clipboard-darwin-universal": "0.3.2", "@mariozechner/clipboard-darwin-x64": "0.3.2", "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", "@mariozechner/clipboard-linux-x64-musl": "0.3.2", "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" } }, "sha512-IHQpksNjo7EAtGuHFU+tbWDp5LarH3HU/8WiB9O70ZEoBPHOg0/6afwSLK0QyNMMmx4Bpi/zl6+DcBXe95nWYA=="], "@mariozechner/clipboard-darwin-arm64": ["@mariozechner/clipboard-darwin-arm64@0.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw=="], @@ -944,8 +811,6 @@ "@mariozechner/pi-tui": ["@mariozechner/pi-tui@0.51.6", "", { "dependencies": { "@types/mime-types": "^2.1.4", "chalk": "^5.5.0", "get-east-asian-width": "^1.3.0", "marked": "^15.0.12", "mime-types": "^3.0.1" } }, "sha512-mG/RH5qArwLXcbnR3BOb8MRDGj4MvUD+c/AYySmC6XTkF+LVDw6Vc14cUcusblIUaE1GNmp+dxsRORmnh+0whg=="], - "@marsidev/react-turnstile": ["@marsidev/react-turnstile@1.5.0", "", { "peerDependencies": { "react": "^17.0.2 || ^18.0.0 || ^19.0", "react-dom": "^17.0.2 || ^18.0.0 || ^19.0" } }, "sha512-Ph6mcj8u9WBDsBO7s9jKPsyRDz1sBPBJwrk+Ngx09vFInvKsQ6U6kW5amEcGq4dHOreB6DgFrOJk7/fy318YlQ=="], - "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], "@microsoft/teams.api": ["@microsoft/teams.api@2.0.8", "", { "dependencies": { "@microsoft/teams.cards": "2.0.8", "@microsoft/teams.common": "2.0.8", "jwt-decode": "^4.0.0", "qs": "^6.14.2" } }, "sha512-N13idaRZNnfL7aefzsn2rhPtujqke1QVM81bNWq1XeK+5yeXod3aLTmBY701DEKkZUXiSG0AogvzkNwVnBw4+g=="], @@ -1104,8 +969,6 @@ "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.13", "", { "os": "win32", "cpu": "x64" }, "sha512-6gy4hhQSjq/T/S9hC9m3NxY0RY+9Ww+XNlB+8koIMTsMSYEjk7Ho+hFHQz1Bn4W61Ub7Vykufg+jgDgPfa2GFA=="], - "@owletto/web": ["@owletto/web@workspace:packages/owletto-web"], - "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.19.1", "", { "os": "android", "cpu": "arm" }, "sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg=="], "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.19.1", "", { "os": "android", "cpu": "arm64" }, "sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA=="], @@ -1162,30 +1025,6 @@ "@pagefind/windows-x64": ["@pagefind/windows-x64@1.5.2", "", { "os": "win32", "cpu": "x64" }, "sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg=="], - "@peculiar/asn1-android": ["@peculiar/asn1-android@2.6.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-cBRCKtYPF7vJGN76/yG8VbxRcHLPF3HnkoHhKOZeHpoVtbMYfY9ROKtH3DtYUY9m8uI1Mh47PRhHf2hSK3xcSQ=="], - - "@peculiar/asn1-cms": ["@peculiar/asn1-cms@2.6.1", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "@peculiar/asn1-x509-attr": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw=="], - - "@peculiar/asn1-csr": ["@peculiar/asn1-csr@2.6.1", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w=="], - - "@peculiar/asn1-ecc": ["@peculiar/asn1-ecc@2.6.1", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g=="], - - "@peculiar/asn1-pfx": ["@peculiar/asn1-pfx@2.6.1", "", { "dependencies": { "@peculiar/asn1-cms": "^2.6.1", "@peculiar/asn1-pkcs8": "^2.6.1", "@peculiar/asn1-rsa": "^2.6.1", "@peculiar/asn1-schema": "^2.6.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw=="], - - "@peculiar/asn1-pkcs8": ["@peculiar/asn1-pkcs8@2.6.1", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw=="], - - "@peculiar/asn1-pkcs9": ["@peculiar/asn1-pkcs9@2.6.1", "", { "dependencies": { "@peculiar/asn1-cms": "^2.6.1", "@peculiar/asn1-pfx": "^2.6.1", "@peculiar/asn1-pkcs8": "^2.6.1", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "@peculiar/asn1-x509-attr": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw=="], - - "@peculiar/asn1-rsa": ["@peculiar/asn1-rsa@2.6.1", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA=="], - - "@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.6.0", "", { "dependencies": { "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg=="], - - "@peculiar/asn1-x509": ["@peculiar/asn1-x509@2.6.1", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA=="], - - "@peculiar/asn1-x509-attr": ["@peculiar/asn1-x509-attr@2.6.1", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ=="], - - "@peculiar/x509": ["@peculiar/x509@1.14.3", "", { "dependencies": { "@peculiar/asn1-cms": "^2.6.0", "@peculiar/asn1-csr": "^2.6.0", "@peculiar/asn1-ecc": "^2.6.0", "@peculiar/asn1-pkcs9": "^2.6.0", "@peculiar/asn1-rsa": "^2.6.0", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "pvtsutils": "^1.3.6", "reflect-metadata": "^0.2.2", "tslib": "^2.8.1", "tsyringe": "^4.10.0" } }, "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA=="], - "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], @@ -1228,88 +1067,6 @@ "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], - "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], - - "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], - - "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], - - "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.11", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q=="], - - "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="], - - "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="], - - "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], - - "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], - - "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], - - "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], - - "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], - - "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], - - "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="], - - "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], - - "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], - - "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], - - "@radix-ui/react-label": ["@radix-ui/react-label@2.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A=="], - - "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], - - "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="], - - "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], - - "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], - - "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], - - "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], - - "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], - - "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="], - - "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="], - - "@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw=="], - - "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], - - "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="], - - "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], - - "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], - - "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], - - "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], - - "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], - - "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.0", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA=="], - - "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], - - "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="], - - "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="], - - "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], - - "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], - - "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], - "@react-email/body": ["@react-email/body@0.3.0", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-uGo0BOOzjbMUo3lu+BIDWayvn5o6Xyfmnlla5VGf05n8gHMvO1ll7U4FtzWe3hxMLwt53pmc4iE0M+B5slG+Ug=="], "@react-email/button": ["@react-email/button@0.2.1", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-qXyj7RZLE7POy9BMKSoqQ00tOXThjOZSUnI2Yu9i29IHngPlmrNayIWBoVKtElES7OWwypUcpiajwi1mUWx6/A=="], @@ -1450,10 +1207,6 @@ "@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="], - "@simplewebauthn/browser": ["@simplewebauthn/browser@13.3.0", "", {}, "sha512-BE/UWv6FOToAdVk0EokzkqQQDOWtNydYlY6+OrmiZ5SCNmb41VehttboTetUM3T/fr6EAFYVXjz4My2wg230rQ=="], - - "@simplewebauthn/server": ["@simplewebauthn/server@13.3.0", "", { "dependencies": { "@hexagon/base64": "^1.1.27", "@levischuck/tiny-cbor": "^0.2.2", "@peculiar/asn1-android": "^2.6.0", "@peculiar/asn1-ecc": "^2.6.1", "@peculiar/asn1-rsa": "^2.6.1", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "@peculiar/x509": "^1.14.3" } }, "sha512-MLHYFrYG8/wK2i+86XMhiecK72nMaHKKt4bo+7Q1TbuG9iGjlSdfkPWKO5ZFE/BX+ygCJ7pr8H/AJeyAj1EaTQ=="], - "@sinclair/typebox": ["@sinclair/typebox@0.34.49", "", {}, "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A=="], "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], @@ -1560,10 +1313,6 @@ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], - - "@tabby_ai/hijri-converter": ["@tabby_ai/hijri-converter@1.0.5", "", {}, "sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ=="], - "@tailwindcss/node": ["@tailwindcss/node@4.2.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.4" } }, "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA=="], "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.4", "@tailwindcss/oxide-darwin-arm64": "4.2.4", "@tailwindcss/oxide-darwin-x64": "4.2.4", "@tailwindcss/oxide-freebsd-x64": "4.2.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", "@tailwindcss/oxide-linux-x64-musl": "4.2.4", "@tailwindcss/oxide-wasm32-wasi": "4.2.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" } }, "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q=="], @@ -1592,56 +1341,14 @@ "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw=="], - "@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.4", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.4", "@tailwindcss/oxide": "4.2.4", "postcss": "^8.5.6", "tailwindcss": "4.2.4" } }, "sha512-wgAVj6nUWAolAu8YFvzT2cTBIElWHkjZwFYovF+xsqKsW2ADxM/X2opxj5NsF/qVccAOjRNe8X2IdPzMsWyHTg=="], - "@tailwindcss/vite": ["@tailwindcss/vite@4.2.4", "", { "dependencies": { "@tailwindcss/node": "4.2.4", "@tailwindcss/oxide": "4.2.4", "tailwindcss": "4.2.4" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw=="], - "@tanstack/history": ["@tanstack/history@1.161.6", "", {}, "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg=="], - - "@tanstack/query-core": ["@tanstack/query-core@5.100.1", "", {}, "sha512-awvQhOO/2TrSCHE5LKKsXcvvj6WSBncwEcMFCB/ez0Qs0b17iyyivoGArNV3HFfXryZwCpnb/olsaBBKrIbtSw=="], - - "@tanstack/react-query": ["@tanstack/react-query@5.100.1", "", { "dependencies": { "@tanstack/query-core": "5.100.1" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-UgWRLhQKprC37SsO6y1zRabOqDmM2gsdTNPbqTT35yl7kOOhwXU4nyfOiGHXPwoEFJV1IpSk85hjIFjNFWVpzw=="], - - "@tanstack/react-router": ["@tanstack/react-router@1.168.23", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.168.15", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-+GblieDnutG6oipJJPNtRJjrWF8QTZEG/l0532+BngFkVK48oHNOcvIkSoAFYftK1egAwM7KBxXsb0Ou+X6/MQ=="], - - "@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.166.13", "", { "dependencies": { "@tanstack/router-devtools-core": "1.167.3" }, "peerDependencies": { "@tanstack/react-router": "^1.168.15", "@tanstack/router-core": "^1.168.11", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["@tanstack/router-core"] }, "sha512-6yKRFFJrEEOiGp5RAAuGCYsl81M4XAhJmLcu9PKj+HZle4A3dsP60lwHoqQYWHMK9nKKFkdXR+D8qxzxqtQbEA=="], - - "@tanstack/react-store": ["@tanstack/react-store@0.9.3", "", { "dependencies": { "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg=="], - - "@tanstack/react-table": ["@tanstack/react-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww=="], - - "@tanstack/router-core": ["@tanstack/router-core@1.168.15", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-Wr0424NDtD8fT/uALobMZ9DdcfsTyXtW5IPR++7zvW8/7RaIOeaqXpVDId8ywaGtqPWLWOfaUg2zUtYtukoXYA=="], - - "@tanstack/router-devtools": ["@tanstack/router-devtools@1.166.13", "", { "dependencies": { "@tanstack/react-router-devtools": "1.166.13", "clsx": "^2.1.1", "goober": "^2.1.16" }, "peerDependencies": { "@tanstack/react-router": "^1.168.15", "csstype": "^3.0.10", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["csstype"] }, "sha512-Qs8gkyI7m+eAxG3VcIOHuTSsUfA5ZxZcOa99ZyIIIJFxW6hy1k+m2s1J0ZYN1SNlip8P2ofd/MHiqmR1IWipMg=="], - - "@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.167.3", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16" }, "peerDependencies": { "@tanstack/router-core": "^1.168.11", "csstype": "^3.0.10" }, "optionalPeers": ["csstype"] }, "sha512-fJ1VMhyQgnoashTrP763c2HRc9kofgF61L7Jb3F6eTHAmCKtGVx8BRtiFt37sr3U0P0jmaaiiSPGP6nT5JtVNg=="], - - "@tanstack/router-generator": ["@tanstack/router-generator@1.166.33", "", { "dependencies": { "@babel/types": "^7.28.5", "@tanstack/router-core": "1.168.15", "@tanstack/router-utils": "1.161.7", "@tanstack/virtual-file-routes": "1.161.7", "magic-string": "^0.30.21", "prettier": "^3.5.0", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-MXP1WrEaZ13tlO5iJoXC+ZIFHNj5CtcvWuqlAZ3zXY70Musuq+mfUcKWMVdcIstnNqrZl5M2hfqLh5Zf5t4NVw=="], - - "@tanstack/router-plugin": ["@tanstack/router-plugin@1.167.23", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.168.15", "@tanstack/router-generator": "1.166.33", "@tanstack/router-utils": "1.161.7", "@tanstack/virtual-file-routes": "1.161.7", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2 || ^2.0.0", "@tanstack/react-router": "^1.168.23", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0", "vite-plugin-solid": "^2.11.10 || ^3.0.0-0", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"], "bin": { "intent": "bin/intent.js" } }, "sha512-dqfCd8gsZThbVQ8bcYMO62/hW5GCkUoPLnnjOd3fCWoEi+Ei5oWa/GnlgHCpG7bdeGr/K8isnYUmI9Ysq5vLrg=="], - - "@tanstack/router-utils": ["@tanstack/router-utils@1.161.7", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ansis": "^4.1.0", "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-VkY0u7ax/GD0qU6ZLLnfPC+UMxVzxRbvZp4yV4iUSXjgJZ/siAT5/QlLm9FEDJ9QDoC0VD9W7f00tKKreUI7Ng=="], - - "@tanstack/store": ["@tanstack/store@0.9.3", "", {}, "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw=="], - - "@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="], - - "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.161.7", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ=="], - "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="], "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], - "@triplit/client": ["@triplit/client@1.0.50", "", { "dependencies": { "@triplit/db": "^1.1.10", "@triplit/logger": "^0.0.3", "comlink": "^4.4.1", "superjson": "^2.2.1" } }, "sha512-3vjXTSdDQ3fzLDrewCK7elkAQc7CiDg0eZEOZInQbVMFRiakdieO5C2voSnNjSepIYHxDxFSBllgg32QsNpL9Q=="], - - "@triplit/db": ["@triplit/db@1.1.10", "", { "dependencies": { "@triplit/logger": "^0.0.3", "core-js": "^3.41.0", "elen": "^1.0.10", "nanoid": "^5.1.0", "sorted-btree": "^1.8.1", "web-worker": "^1.5.0" }, "peerDependencies": { "better-sqlite3": "*", "expo-sqlite": "*", "lmdb": "*", "uuidv7": "*" }, "optionalPeers": ["better-sqlite3", "expo-sqlite", "lmdb", "uuidv7"] }, "sha512-9BHDrlDvJOyA9Wl8AsmbUSLhilaReA8gpFoWYjS9VEcm5d6TtqKazPQrFm0/o2WNJdrs01+qR6CysAo449MAyA=="], - - "@triplit/logger": ["@triplit/logger@0.0.3", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-hHcoD6/BsNwBtXjEp8Wiy0/YA2SqIYXd1nMukEPBmFkjT7Cd30eqtsBRT0NRq2mIFX2mr5h9JaO27zDToKnwdw=="], - - "@triplit/react": ["@triplit/react@1.0.51", "", { "dependencies": { "@triplit/client": "^1.0.50" }, "peerDependencies": { "react": "*" } }, "sha512-yGmYWACWycrNHMEfnR1k6CQ398ESIBgFeM47fXFhrdhMJ84QgdRk6VF/C21P80D1Rk/AQePfB0TUIaY4U9BRJg=="], - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], @@ -1682,8 +1389,6 @@ "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="], - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.10", "", { "dependencies": { "@types/ms": "*", "@types/node": "*" } }, "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA=="], "@types/long": ["@types/long@4.0.2", "", {}, "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="], @@ -1758,8 +1463,6 @@ "@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.7", "", {}, "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="], - "@wojtekmaj/react-recaptcha-v3": ["@wojtekmaj/react-recaptcha-v3@0.1.4", "", { "dependencies": { "warning": "^4.0.0" }, "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-zszMOdgI+y1Dz3496pRFr3t68n9+OmX/puLQNnOBDC7WrjM+nOKGyjIMCTe+3J14KDvzcxETeiglyDMGl0Yh/Q=="], - "@workflow/serde": ["@workflow/serde@4.1.0-beta.2", "", {}, "sha512-8kkeoQKLDaKXefjV5dbhBj2aErfKp1Mc4pb6tj8144cF+Em5SPbyMbyLCHp+BVrFfFVCBluCtMx+jjvaFVZGww=="], "@xenova/transformers": ["@xenova/transformers@2.17.2", "", { "dependencies": { "@huggingface/jinja": "^0.2.2", "onnxruntime-web": "1.14.0", "sharp": "^0.32.0" }, "optionalDependencies": { "onnxruntime-node": "1.14.0" } }, "sha512-lZmHqzrVIkSvZdKZEx7IYY51TK0WDrC8eR0c5IMnBsO8di8are1zzw8BlLhyO2TklZKLN5UffNGs1IJwT6oOqQ=="], @@ -1784,8 +1487,6 @@ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="], - "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], @@ -1794,8 +1495,6 @@ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], - "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], "array-iterate": ["array-iterate@2.0.1", "", {}, "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg=="], @@ -1804,8 +1503,6 @@ "asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="], - "asn1js": ["asn1js@3.0.10", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.5", "tslib": "^2.8.1" } }, "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg=="], - "assert-never": ["assert-never@1.4.0", "", {}, "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA=="], "assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="], @@ -1826,8 +1523,6 @@ "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], - "autoprefixer": ["autoprefixer@10.5.0", "", { "dependencies": { "browserslist": "^4.28.2", "caniuse-lite": "^1.0.30001787", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong=="], - "aws-sign2": ["aws-sign2@0.7.0", "", {}, "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA=="], "aws4": ["aws4@1.13.2", "", {}, "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw=="], @@ -1838,8 +1533,6 @@ "b4a": ["b4a@1.8.0", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg=="], - "babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.12", "", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig=="], - "babel-plugin-transform-hook-names": ["babel-plugin-transform-hook-names@1.0.2", "", { "peerDependencies": { "@babel/core": "^7.12.10" } }, "sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw=="], "babel-walk": ["babel-walk@3.0.0-canary-5", "", { "dependencies": { "@babel/types": "^7.9.6" } }, "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw=="], @@ -1882,8 +1575,6 @@ "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], - "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], "blamer": ["blamer@1.0.7", "", { "dependencies": { "execa": "^4.0.0", "which": "^2.0.2" } }, "sha512-GbBStl/EVlSWkiJQBZps3H1iARBrC7vt++Jb/TTmCNu/jZ04VW7tSN1nScbFXBUy1AN+jzeL7Zep9sbQxLhXKA=="], @@ -1954,7 +1645,7 @@ "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], - "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], @@ -1964,8 +1655,6 @@ "cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="], - "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], - "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], "cli-highlight": ["cli-highlight@2.1.11", "", { "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", "mz": "^2.4.0", "parse5": "^5.1.1", "parse5-htmlparser2-tree-adapter": "^6.0.0", "yargs": "^16.0.0" }, "bin": { "highlight": "bin/highlight" } }, "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg=="], @@ -1984,8 +1673,6 @@ "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], - "cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="], - "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], @@ -2000,8 +1687,6 @@ "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - "comlink": ["comlink@4.4.2", "", {}, "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g=="], - "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], @@ -2022,22 +1707,16 @@ "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], - "cookie-es": ["cookie-es@3.1.1", "", {}, "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg=="], + "cookie-es": ["cookie-es@1.2.3", "", {}, "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw=="], "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], - "copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="], - - "core-js": ["core-js@3.49.0", "", {}, "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg=="], - "core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], "cpu-features": ["cpu-features@0.0.10", "", { "dependencies": { "buildcheck": "~0.0.6", "nan": "^2.19.0" } }, "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA=="], - "crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="], - "cron-parser": ["cron-parser@5.5.0", "", { "dependencies": { "luxon": "^3.7.1" } }, "sha512-oML4lKUXxizYswqmxuOCpgFS8BNUJpIu6k/2HVHyaL8Ynnf3wdf9tkns0yRdJLSIjkJ+b0DXHMZEHGpMwjnPww=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -2062,10 +1741,6 @@ "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], - "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], - - "date-fns-jalali": ["date-fns-jalali@4.1.0-0", "", {}, "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], @@ -2102,8 +1777,6 @@ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], - "devalue": ["devalue@5.7.1", "", {}, "sha512-MUbZ586EgQqdRnC4yDrlod3BEdyvE4TapGYHMW2CiaW+KkkFmWEFqBUaLltEZCGi0iFXCEjRF0OjF0DV2QHjOA=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], @@ -2146,14 +1819,10 @@ "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], - "echarts": ["echarts@5.6.0", "", { "dependencies": { "tslib": "2.3.0", "zrender": "5.6.1" } }, "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA=="], - "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], "electron-to-chromium": ["electron-to-chromium@1.5.344", "", {}, "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg=="], - "elen": ["elen@1.0.10", "", {}, "sha512-ZL799/V/kzxYJ6Wlfktreq6qQWfGc3VkGUQJW5lZQ8/MhsQiKTAwERPfhEwIsV2movRGe2DfV7H2MjRw76Z7Wg=="], - "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "emojilib": ["emojilib@2.4.0", "", {}, "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw=="], @@ -2306,10 +1975,6 @@ "forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="], - "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], - - "framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="], - "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], @@ -2332,8 +1997,6 @@ "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], @@ -2354,8 +2017,6 @@ "glob-to-regex.js": ["glob-to-regex.js@1.2.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ=="], - "goober": ["goober@2.1.18", "", { "peerDependencies": { "csstype": "^3.0.10" } }, "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw=="], - "google-auth-library": ["google-auth-library@10.6.2", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw=="], "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], @@ -2428,8 +2089,6 @@ "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], - "hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="], - "hono": ["hono@4.12.14", "", {}, "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w=="], "hono-pino": ["hono-pino@0.10.3", "", { "dependencies": { "defu": "^6.1.4" }, "peerDependencies": { "hono": ">=4.0.0", "pino": ">=7.1.0" } }, "sha512-n0RNPIFOoq25Fg8b4D5gus4sVqI0z+8I17ibl96+p43d07UnZ0EMM/It0qSgfc7UtaC+XP5FkFmRHwBp6owsNA=="], @@ -2438,8 +2097,6 @@ "html-to-text": ["html-to-text@9.0.5", "", { "dependencies": { "@selderee/plugin-htmlparser2": "^0.11.0", "deepmerge": "^4.3.1", "dom-serializer": "^2.0.0", "htmlparser2": "^8.0.2", "selderee": "^0.11.0" } }, "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg=="], - "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], - "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], "html-whitespace-sensitive-tag-names": ["html-whitespace-sensitive-tag-names@3.0.1", "", {}, "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA=="], @@ -2478,8 +2135,6 @@ "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], - "input-otp": ["input-otp@1.4.2", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA=="], - "inquirer": ["inquirer@9.3.8", "", { "dependencies": { "@inquirer/external-editor": "^1.0.2", "@inquirer/figures": "^1.0.3", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "1.0.0", "ora": "^5.4.1", "run-async": "^3.0.0", "rxjs": "^7.8.1", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" } }, "sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w=="], "ioredis": ["ioredis@5.10.1", "", { "dependencies": { "@ioredis/commands": "1.5.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA=="], @@ -2496,8 +2151,6 @@ "is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], - "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], - "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], @@ -2536,14 +2189,12 @@ "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], - "is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="], - "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], - "isbot": ["isbot@5.1.39", "", {}, "sha512-obH0yYahGXdzNxo+djmHhBYThUKDkz565cxkIlt2L9hXfv1NlaLKoDBHo6KxXsYrIXx2RK3x5vY36CfZcobxEw=="], - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "isolated-vm": ["isolated-vm@5.0.4", "", { "dependencies": { "prebuild-install": "^7.1.2" } }, "sha512-RYUf/JC4ldWz/oi2BVs8a1XIprQ71q6eQPBwySaF5Apu0KMyf2gIpElbCyPh2OEmRT+FYw1GOKSdkv7jw2KLxw=="], + "isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="], "isstream": ["isstream@0.1.2", "", {}, "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="], @@ -2694,16 +2345,12 @@ "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], - "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], "lru-memoizer": ["lru-memoizer@2.3.0", "", { "dependencies": { "lodash.clonedeep": "^4.5.0", "lru-cache": "6.0.0" } }, "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug=="], - "lucide-react": ["lucide-react@0.469.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw=="], - "luxon": ["luxon@3.7.2", "", {}, "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew=="], "magic-bytes.js": ["magic-bytes.js@1.13.0", "", {}, "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg=="], @@ -2872,10 +2519,6 @@ "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], - "motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="], - - "motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="], - "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -2884,8 +2527,6 @@ "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="], - "mutative": ["mutative@1.3.0", "", {}, "sha512-8MJj6URmOZAV70dpFe1YnSppRTKC4DsMkXQiBDFayLcDI4ljGokHxmpqaBQuDWa4iAxWaJJ1PS8vAmbntjjKmQ=="], - "mute-stream": ["mute-stream@1.0.0", "", {}, "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA=="], "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], @@ -3100,8 +2741,6 @@ "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], - "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], - "postgres": ["postgres@3.4.9", "", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="], "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], @@ -3126,8 +2765,6 @@ "promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="], - "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], - "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], @@ -3170,14 +2807,6 @@ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], - - "pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], - - "qr.js": ["qr.js@0.0.0", "", {}, "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ=="], - - "qrcode.react": ["qrcode.react@4.2.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA=="], - "qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="], "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], @@ -3202,33 +2831,13 @@ "react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="], - "react-async-script": ["react-async-script@1.2.0", "", { "dependencies": { "hoist-non-react-statics": "^3.3.0", "prop-types": "^15.5.0" }, "peerDependencies": { "react": ">=16.4.1" } }, "sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q=="], - - "react-day-picker": ["react-day-picker@9.14.0", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "@tabby_ai/hijri-converter": "1.0.5", "date-fns": "^4.1.0", "date-fns-jalali": "4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA=="], - "react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="], - "react-google-recaptcha": ["react-google-recaptcha@3.1.0", "", { "dependencies": { "prop-types": "^15.5.0", "react-async-script": "^1.2.0" }, "peerDependencies": { "react": ">=16.4.1" } }, "sha512-cYW2/DWas8nEKZGD7SCu9BSuVz8iOcOLHChHyi7upUuVhkpkhYG/6N3KDiTQ3XAiZ2UAZkfvYKMfAHOzBOcGEg=="], - - "react-hook-form": ["react-hook-form@7.73.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-VAfVYOPcx3piiEVQy95vyFmBwbVUsP/AUIN+mpFG8h11yshDd444nn0VyfaGWSRnhOLVgiDu7HIuBtAIzxn9dA=="], - - "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - - "react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="], - - "react-qr-code": ["react-qr-code@2.0.18", "", { "dependencies": { "prop-types": "^15.8.1", "qr.js": "0.0.0" }, "peerDependencies": { "react": "*" } }, "sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg=="], - "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], - "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], - - "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], - - "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], - "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], @@ -3350,10 +2959,6 @@ "send": ["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" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], - "seroval": ["seroval@1.5.2", "", {}, "sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q=="], - - "seroval-plugins": ["seroval-plugins@1.5.2", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg=="], - "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], "set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="], @@ -3388,8 +2993,6 @@ "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], - "simple-icons": ["simple-icons@16.17.0", "", {}, "sha512-bRrGtzM6NLgxeMWmRcfDdrRksECk101lRrCn6jjj6qzUB6lQ+E5smnr52rqS1kLPmbLpS/g6iF463j50M4BT7A=="], - "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], @@ -3408,10 +3011,6 @@ "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="], - "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], - - "sorted-btree": ["sorted-btree@1.8.1", "", {}, "sha512-395+XIP+wqNn3USkFSrNz7G3Ss/MXlZEqesxvzCRFwL14h6e8LukDHdLBePn5pwbm5OQ9vGu8mDyz2lLDIqamQ=="], - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -3472,16 +3071,12 @@ "strtok3": ["strtok3@10.3.5", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA=="], - "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], - "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], - "superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="], - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "supports-hyperlinks": ["supports-hyperlinks@3.2.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig=="], @@ -3494,8 +3089,6 @@ "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], - "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], - "tailwindcss": ["tailwindcss@4.2.4", "", {}, "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA=="], "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], @@ -3572,8 +3165,6 @@ "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], - "tsyringe": ["tsyringe@4.10.0", "", { "dependencies": { "tslib": "^1.9.3" } }, "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw=="], - "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], "turndown": ["turndown@7.2.4", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-I8yFsfRzmzK0WV1pNNOA4A7y4RDfFxPRxb3t+e3ui14qSGOxGtiSP6GjeX+Y6CHb7HYaFj7ECUD7VE5kQMZWGQ=="], @@ -3632,8 +3223,6 @@ "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], - "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], - "unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="], "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], @@ -3642,20 +3231,12 @@ "url-template": ["url-template@2.0.8", "", {}, "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="], - "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], - - "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], - - "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], - "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], - "vaul": ["vaul@1.1.2", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="], - "verror": ["verror@1.10.0", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw=="], "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], @@ -3676,22 +3257,14 @@ "void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="], - "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], - "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], - "warning": ["warning@4.0.3", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w=="], - "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], - "web-worker": ["web-worker@1.5.0", "", {}, "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw=="], - - "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="], @@ -3742,8 +3315,6 @@ "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], - "zrender": ["zrender@5.6.1", "", { "dependencies": { "tslib": "2.3.0" } }, "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag=="], - "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], "@astrojs/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], @@ -3764,8 +3335,6 @@ "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@daveyplate/better-auth-ui/better-call": ["better-call@2.0.2", "", { "dependencies": { "@better-auth/utils": "^0.3.1", "@better-fetch/fetch": "^1.1.21", "rou3": "^0.7.12", "set-cookie-parser": "^3.0.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-QqSKtfJD/ZzQdlm7BTUxT9RCA0AxcrZEMyU/yl7/uoFDoR7YCTdc555xQXjReo75M6/xkskPawPdhbn3fge4Cg=="], - "@discordjs/builders/discord-api-types": ["discord-api-types@0.38.47", "", {}, "sha512-XgXQodHQBAE6kfD7kMvVo30863iHX1LHSqNq6MGUTDwIFCCvHva13+rwxyxVXDqudyApMNAd32PGjgVETi5rjA=="], "@discordjs/formatters/discord-api-types": ["discord-api-types@0.38.47", "", {}, "sha512-XgXQodHQBAE6kfD7kMvVo30863iHX1LHSqNq6MGUTDwIFCCvHva13+rwxyxVXDqudyApMNAd32PGjgVETi5rjA=="], @@ -3796,8 +3365,6 @@ "@grpc/proto-loader/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], - "@instantdb/react/eventsource": ["eventsource@4.1.0", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ=="], - "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "@isaacs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], @@ -3812,8 +3379,6 @@ "@jitl/quickjs-wasmfile-release-sync/@jitl/quickjs-ffi-types": ["@jitl/quickjs-ffi-types@0.32.0", "", {}, "sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg=="], - "@jsonforms/core/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], - "@jsonjoy.com/fs-snapshot/@jsonjoy.com/json-pack": ["@jsonjoy.com/json-pack@17.67.0", "", { "dependencies": { "@jsonjoy.com/base64": "17.67.0", "@jsonjoy.com/buffers": "17.67.0", "@jsonjoy.com/codegen": "17.67.0", "@jsonjoy.com/json-pointer": "17.67.0", "@jsonjoy.com/util": "17.67.0", "hyperdyperid": "^1.2.0", "thingies": "^2.5.0", "tree-dump": "^1.1.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w=="], "@jsonjoy.com/fs-snapshot/@jsonjoy.com/util": ["@jsonjoy.com/util@17.67.0", "", { "dependencies": { "@jsonjoy.com/buffers": "17.67.0", "@jsonjoy.com/codegen": "17.67.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew=="], @@ -3884,62 +3449,10 @@ "@opentelemetry/sql-common/@opentelemetry/core": ["@opentelemetry/core@2.7.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-DT12SXVwV2eoJrGf4nnsvZojxxeQo+LlNAsoYGRRObPWTeN6APiqZ2+nqDCQDvQX40eLi1AePONS0onoASp3yQ=="], - "@owletto/web/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@prefresh/vite/@rollup/pluginutils": ["@rollup/pluginutils@4.2.1", "", { "dependencies": { "estree-walker": "^2.0.1", "picomatch": "^2.2.2" } }, "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ=="], "@prisma/instrumentation/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "import-in-the-middle": "^2.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA=="], - "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-avatar/@radix-ui/react-context": ["@radix-ui/react-context@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw=="], - - "@radix-ui/react-checkbox/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-collapsible/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-dropdown-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-focus-scope/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-popover/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-popper/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-roving-focus/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-select/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-slider/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-tabs/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-tooltip/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "@react-email/components/@react-email/render": ["@react-email/render@2.0.6", "", { "dependencies": { "html-to-text": "^9.0.5", "prettier": "^3.5.3" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-xOzaYkH3jLZKqN5MqrTXYnmqBYUnZSVbkxdb5PGGmDcK6sKDVMliaDiSwfXajRC9JtSHTcGc2tmGLHWuCgVpog=="], "@react-email/markdown/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], @@ -3970,14 +3483,6 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@tanstack/router-generator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@tanstack/router-plugin/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@tanstack/router-utils/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - - "@triplit/db/nanoid": ["nanoid@5.1.9", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw=="], - "@types/request/form-data": ["form-data@2.5.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="], "@vitest/coverage-v8/magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="], @@ -3996,8 +3501,6 @@ "bullmq/cron-parser": ["cron-parser@4.9.0", "", { "dependencies": { "luxon": "^3.2.1" } }, "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q=="], - "chokidar/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], - "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "cli-table3/@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], @@ -4014,8 +3517,6 @@ "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], - "echarts/tslib": ["tslib@2.3.0", "", {}, "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="], - "estree-util-build-jsx/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], "estree-util-to-js/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], @@ -4030,8 +3531,6 @@ "glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - "h3/cookie-es": ["cookie-es@1.2.3", "", {}, "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw=="], - "har-validator/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], "hast-util-from-html/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], @@ -4102,8 +3601,6 @@ "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], - "readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - "request/form-data": ["form-data@2.3.3", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ=="], "request/qs": ["qs@6.5.5", "", {}, "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ=="], @@ -4134,14 +3631,10 @@ "test-exclude/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - "tsup/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - "tsup/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], "tsup/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], - "tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], - "type-is/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "unstorage/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], @@ -4164,16 +3657,12 @@ "yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], - "zrender/tslib": ["tslib@2.3.0", "", {}, "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="], - "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "@daveyplate/better-auth-ui/better-call/@better-auth/utils": ["@better-auth/utils@0.3.1", "", {}, "sha512-+CGp4UmZSUrHHnpHhLPYu6cV+wSUSvVbZbNykxhUDocpVNTo9uFFxw/NqJlh1iC4wQ9HKKWGCKuZ5wUgS0v6Kg=="], - "@expressive-code/plugin-shiki/shiki/@shikijs/core": ["@shikijs/core@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA=="], "@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA=="], @@ -4282,30 +3771,6 @@ "@prisma/instrumentation/@opentelemetry/instrumentation/import-in-the-middle": ["import-in-the-middle@2.0.6", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw=="], - "@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-checkbox/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-collapsible/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-dropdown-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-focus-scope/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-popper/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-roving-focus/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-slider/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-tabs/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@sentry/node/@opentelemetry/sdk-trace-base/@opentelemetry/resources": ["@opentelemetry/resources@2.7.0", "", { "dependencies": { "@opentelemetry/core": "2.7.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-K+oi0hNMv94EpZbnW3eyu2X6SGVpD3O5DhG2NIp65Hc7lhAj9brRXTAVzh3wB82+q3ThakEf7Zd7RsFUqcTc7A=="], "@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], @@ -4354,8 +3819,6 @@ "test-exclude/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], - "tsup/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - "type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "unstorage/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile index 5baba7c93..f491a7f48 100644 --- a/docker/app/Dockerfile +++ b/docker/app/Dockerfile @@ -8,8 +8,10 @@ FROM node:22-slim AS builder WORKDIR /app # Bun for workspace install + tsc. git needed because some deps ship git URLs. +# python3 + build-essential needed for isolated-vm native build (node-gyp). RUN apt-get update && apt-get install -y --no-install-recommends \ git ca-certificates curl unzip \ + python3 build-essential \ && rm -rf /var/lib/apt/lists/* \ && curl -fsSL https://bun.sh/install | BUN_INSTALL=/usr/local bash \ && chmod +x /usr/local/bin/bun diff --git a/packages/owletto-backend/package.json b/packages/owletto-backend/package.json index 8849c8b29..9a1addefa 100644 --- a/packages/owletto-backend/package.json +++ b/packages/owletto-backend/package.json @@ -58,5 +58,8 @@ "typescript": "^5.7.2", "vite": "^6.0.0", "vitest": "^2.1.8" + }, + "optionalDependencies": { + "isolated-vm": "^5.0.4" } } diff --git a/packages/owletto-backend/src/__tests__/integration/sandbox/client-sdk-org.test.ts b/packages/owletto-backend/src/__tests__/integration/sandbox/client-sdk-org.test.ts new file mode 100644 index 000000000..bddae7a8b --- /dev/null +++ b/packages/owletto-backend/src/__tests__/integration/sandbox/client-sdk-org.test.ts @@ -0,0 +1,223 @@ +/** + * Integration tests for `ClientSDK.org()` — the cross-org accessor. + * + * Exercises membership resolution against the real `organization`/`member` + * tables, LRU caching behavior, and the access-denied path for non-members + * on private orgs. The handler delegation itself is covered by the existing + * per-tool integration tests; these tests focus on the context-swap and + * auth-reverification semantics introduced by PR-1. + */ + +import { beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { + AccessDeniedError, + buildClientSDK, + MembershipCache, + OrgNotFoundError, + resolveOrgMembership, +} from "../../../sandbox/client-sdk"; +import type { Env } from "../../../index"; +import type { ToolContext } from "../../../tools/registry"; +import { cleanupTestDatabase, getTestDb } from "../../setup/test-db"; +import { + addUserToOrganization, + createTestOrganization, + createTestUser, +} from "../../setup/test-fixtures"; + +const testEnv: Env = { + ENVIRONMENT: "test", + DATABASE_URL: process.env.DATABASE_URL, +}; + +describe("ClientSDK.org() accessor", () => { + let orgA: Awaited>; + let orgB: Awaited>; + let orgPublic: Awaited>; + let user1: Awaited>; + let user2: Awaited>; + + beforeAll(async () => { + await cleanupTestDatabase(); + orgA = await createTestOrganization({ name: "Org A", slug: "org-a-sdk" }); + orgB = await createTestOrganization({ name: "Org B", slug: "org-b-sdk" }); + orgPublic = await createTestOrganization({ + name: "Org Public", + slug: "org-public-sdk", + visibility: "public", + }); + user1 = await createTestUser({ email: "user1-sdk@test.example.com" }); + user2 = await createTestUser({ email: "user2-sdk@test.example.com" }); + await addUserToOrganization(user1.id, orgA.id, "owner"); + await addUserToOrganization(user2.id, orgB.id, "admin"); + }); + + function buildCtx(userId: string, orgId: string): ToolContext { + return { + organizationId: orgId, + userId, + memberRole: "owner", + isAuthenticated: true, + }; + } + + describe("resolveOrgMembership", () => { + let cache: MembershipCache; + beforeEach(() => { + cache = new MembershipCache(60_000); + }); + + it("resolves by slug for a member", async () => { + const ctx = buildCtx(user1.id, orgA.id); + const record = await resolveOrgMembership(orgA.slug, ctx, cache); + expect(record.orgId).toBe(orgA.id); + expect(record.role).toBe("owner"); + expect(record.visibility).toBe("private"); + }); + + it("resolves by id for a member", async () => { + const ctx = buildCtx(user1.id, orgA.id); + const record = await resolveOrgMembership(orgA.id, ctx, cache); + expect(record.slug).toBe(orgA.slug); + }); + + it("throws AccessDenied on private org the user is not a member of", async () => { + const ctx = buildCtx(user1.id, orgA.id); + await expect( + resolveOrgMembership(orgB.slug, ctx, cache) + ).rejects.toBeInstanceOf(AccessDeniedError); + }); + + it("returns record with role=null on public org for non-members", async () => { + const ctx = buildCtx(user1.id, orgA.id); + const record = await resolveOrgMembership(orgPublic.slug, ctx, cache); + expect(record.visibility).toBe("public"); + expect(record.role).toBeNull(); + }); + + it("throws OrgNotFound for an unknown slug", async () => { + const ctx = buildCtx(user1.id, orgA.id); + await expect( + resolveOrgMembership("does-not-exist-xyz", ctx, cache) + ).rejects.toBeInstanceOf(OrgNotFoundError); + }); + + it("caches subsequent lookups for both slug and id", async () => { + const ctx = buildCtx(user1.id, orgA.id); + await resolveOrgMembership(orgA.slug, ctx, cache); + // After first lookup, cache should have entries keyed by both id and slug. + expect(cache.get(user1.id, orgA.slug)).not.toBeNull(); + expect(cache.get(user1.id, orgA.id)).not.toBeNull(); + }); + }); + + describe("buildClientSDK", () => { + it("exposes every namespace", () => { + const ctx = buildCtx(user1.id, orgA.id); + const sdk = buildClientSDK(ctx, testEnv); + expect(sdk.entities).toBeDefined(); + expect(sdk.entitySchema).toBeDefined(); + expect(sdk.connections).toBeDefined(); + expect(sdk.feeds).toBeDefined(); + expect(sdk.authProfiles).toBeDefined(); + expect(sdk.operations).toBeDefined(); + expect(sdk.watchers).toBeDefined(); + expect(sdk.classifiers).toBeDefined(); + expect(sdk.viewTemplates).toBeDefined(); + expect(sdk.knowledge).toBeDefined(); + expect(sdk.organizations).toBeDefined(); + expect(sdk.query).toBeInstanceOf(Function); + expect(sdk.log).toBeInstanceOf(Function); + expect(sdk.org).toBeInstanceOf(Function); + }); + + it(".org() returns a fresh SDK for another org the caller belongs to", async () => { + await addUserToOrganization(user1.id, orgB.id, "member"); + const ctx = buildCtx(user1.id, orgA.id); + const sdk = buildClientSDK(ctx, testEnv); + const sdkB = await sdk.org(orgB.slug); + expect(sdkB).toBeDefined(); + expect(sdkB).not.toBe(sdk); + expect(sdkB.org).toBeInstanceOf(Function); + // Clean up so later tests see user1 back to orgA-only where applicable. + const sql = getTestDb(); + await sql`DELETE FROM "member" WHERE "userId" = ${user1.id} AND "organizationId" = ${orgB.id}`; + }); + + it(".org() throws AccessDenied on a non-member private org", async () => { + const ctx = buildCtx(user1.id, orgA.id); + const sdk = buildClientSDK(ctx, testEnv); + await expect(sdk.org(orgB.slug)).rejects.toBeInstanceOf( + AccessDeniedError + ); + }); + + it(".org() returns an SDK with memberRole=null for public orgs non-members", async () => { + const ctx = buildCtx(user1.id, orgA.id); + const cache = new MembershipCache(60_000); + const record = await resolveOrgMembership(orgPublic.slug, ctx, cache); + expect(record.role).toBeNull(); + // The SDK itself is built from ctx, so the org() call returns a valid + // ClientSDK — per-handler auth checks reject writes downstream. + const sdk = buildClientSDK(ctx, testEnv, { membershipCache: cache }); + const sdkPub = await sdk.org(orgPublic.slug); + expect(sdkPub).toBeDefined(); + }); + + it("chained .org() re-validates against the original user", async () => { + // Give user1 access to both orgA and orgB, then hop A → B → A. + await addUserToOrganization(user1.id, orgB.id, "member"); + const ctx = buildCtx(user1.id, orgA.id); + const sdk = buildClientSDK(ctx, testEnv); + const sdkB = await sdk.org(orgB.slug); + const sdkBackToA = await sdkB.org(orgA.slug); + expect(sdkBackToA).toBeDefined(); + const sql = getTestDb(); + await sql`DELETE FROM "member" WHERE "userId" = ${user1.id} AND "organizationId" = ${orgB.id}`; + }); + + it("membership cache shortcircuits a repeated .org() call", async () => { + await addUserToOrganization(user1.id, orgB.id, "member"); + const ctx = buildCtx(user1.id, orgA.id); + const cache = new MembershipCache(60_000); + const sdk = buildClientSDK(ctx, testEnv, { membershipCache: cache }); + await sdk.org(orgB.slug); + expect(cache.size()).toBeGreaterThan(0); + await sdk.org(orgB.slug); // second call hits cache; no new entries + const sizeAfter = cache.size(); + await sdk.org(orgB.id); // id lookup, already cached under id during first call + expect(cache.size()).toBe(sizeAfter); + + const sql = getTestDb(); + await sql`DELETE FROM "member" WHERE "userId" = ${user1.id} AND "organizationId" = ${orgB.id}`; + }); + + it("revocation is detected after cache TTL expires", async () => { + await addUserToOrganization(user1.id, orgB.id, "member"); + const ctx = buildCtx(user1.id, orgA.id); + const cache = new MembershipCache(5); // 5ms TTL + const sdk = buildClientSDK(ctx, testEnv, { membershipCache: cache }); + await sdk.org(orgB.slug); + + // Revoke membership. + const sql = getTestDb(); + await sql`DELETE FROM "member" WHERE "userId" = ${user1.id} AND "organizationId" = ${orgB.id}`; + + // Wait past TTL, then expect AccessDenied. + await new Promise((r) => setTimeout(r, 15)); + await expect(sdk.org(orgB.slug)).rejects.toBeInstanceOf( + AccessDeniedError + ); + }); + }); + + describe("non-member isolation", () => { + it("user2 cannot access orgA", async () => { + const ctx = buildCtx(user2.id, orgB.id); + const sdk = buildClientSDK(ctx, testEnv); + await expect(sdk.org(orgA.slug)).rejects.toBeInstanceOf( + AccessDeniedError + ); + }); + }); +}); diff --git a/packages/owletto-backend/src/__tests__/unit/sandbox/membership-cache.test.ts b/packages/owletto-backend/src/__tests__/unit/sandbox/membership-cache.test.ts new file mode 100644 index 000000000..c73000ab2 --- /dev/null +++ b/packages/owletto-backend/src/__tests__/unit/sandbox/membership-cache.test.ts @@ -0,0 +1,109 @@ +import { describe, expect, it } from "bun:test"; +import { MembershipCache } from "../../../sandbox/membership-cache"; + +describe("MembershipCache", () => { + it("stores and retrieves a record by any of its keys", () => { + const cache = new MembershipCache(60_000); + cache.set("user-1", ["org_abc", "buremba"], { + orgId: "org_abc", + slug: "buremba", + role: "admin", + visibility: "private", + }); + const byId = cache.get("user-1", "org_abc"); + const bySlug = cache.get("user-1", "buremba"); + expect(byId).not.toBeNull(); + expect(bySlug).not.toBeNull(); + expect(byId?.slug).toBe("buremba"); + expect(bySlug?.orgId).toBe("org_abc"); + }); + + it("is case-insensitive on the key", () => { + const cache = new MembershipCache(60_000); + cache.set("u", ["BuRemba"], { + orgId: "x", + slug: "buremba", + role: "member", + visibility: "private", + }); + expect(cache.get("u", "buremba")).not.toBeNull(); + expect(cache.get("u", "BUREMBA")).not.toBeNull(); + }); + + it("scopes entries by userId", () => { + const cache = new MembershipCache(60_000); + cache.set("alice", ["org1"], { + orgId: "org1", + slug: "org1", + role: "admin", + visibility: "private", + }); + expect(cache.get("alice", "org1")).not.toBeNull(); + expect(cache.get("bob", "org1")).toBeNull(); + }); + + it("expires entries after TTL", async () => { + const cache = new MembershipCache(5); // 5ms + cache.set("u", ["x"], { + orgId: "x", + slug: "x", + role: "member", + visibility: "private", + }); + expect(cache.get("u", "x")).not.toBeNull(); + await new Promise((r) => setTimeout(r, 10)); + expect(cache.get("u", "x")).toBeNull(); + }); + + it("evicts oldest entries past capacity", () => { + const cache = new MembershipCache(60_000); + for (let i = 0; i < 200; i++) { + cache.set(`u-${i}`, [`org-${i}`], { + orgId: `org-${i}`, + slug: `org-${i}`, + role: "member", + visibility: "private", + }); + } + // Cap is 128; first entries should be gone. + expect(cache.size()).toBeLessThanOrEqual(128); + expect(cache.get("u-0", "org-0")).toBeNull(); + expect(cache.get("u-199", "org-199")).not.toBeNull(); + }); + + it("refreshes LRU order on get", () => { + const cache = new MembershipCache(60_000); + for (let i = 0; i < 128; i++) { + cache.set(`u-${i}`, [`org-${i}`], { + orgId: `org-${i}`, + slug: `org-${i}`, + role: "member", + visibility: "private", + }); + } + // Touch the oldest. + expect(cache.get("u-0", "org-0")).not.toBeNull(); + // Add a new one — oldest (u-1) should be evicted, not u-0. + cache.set("u-fresh", ["org-fresh"], { + orgId: "org-fresh", + slug: "org-fresh", + role: "member", + visibility: "private", + }); + expect(cache.get("u-0", "org-0")).not.toBeNull(); + expect(cache.get("u-1", "org-1")).toBeNull(); + }); + + it("clears all entries", () => { + const cache = new MembershipCache(60_000); + cache.set("u", ["a"], { + orgId: "a", + slug: "a", + role: "member", + visibility: "private", + }); + cache.clear(); + expect(cache.size()).toBe(0); + expect(cache.get("u", "a")).toBeNull(); + }); +}); diff --git a/packages/owletto-backend/src/__tests__/unit/sandbox/method-metadata.test.ts b/packages/owletto-backend/src/__tests__/unit/sandbox/method-metadata.test.ts new file mode 100644 index 000000000..097a8b17c --- /dev/null +++ b/packages/owletto-backend/src/__tests__/unit/sandbox/method-metadata.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from "bun:test"; +import { + BANNED_PATHS, + METHOD_METADATA, + type MethodAccess, +} from "../../../sandbox/method-metadata"; + +describe("method-metadata", () => { + it("is non-empty", () => { + expect(Object.keys(METHOD_METADATA).length).toBeGreaterThan(20); + }); + + it("has valid access levels on every entry", () => { + const valid: MethodAccess[] = ["read", "write", "external"]; + for (const [path, meta] of Object.entries(METHOD_METADATA)) { + expect(valid).toContain(meta.access); + expect(meta.summary.length).toBeGreaterThan(0); + if (meta.example) { + expect(meta.example).toContain("client."); + } + void path; + } + }); + + it("uses dotted path keys", () => { + for (const path of Object.keys(METHOD_METADATA)) { + expect(path).toMatch(/^[a-zA-Z]+(\.[a-zA-Z]+)?$/); + } + }); + + it("never exposes banned paths", () => { + for (const banned of BANNED_PATHS) { + expect(METHOD_METADATA).not.toHaveProperty(banned); + } + }); + + it("classifies external side-effects correctly for known methods", () => { + expect(METHOD_METADATA["operations.execute"].access).toBe("external"); + expect(METHOD_METADATA["feeds.trigger"].access).toBe("external"); + expect(METHOD_METADATA["connections.test"].access).toBe("external"); + }); + + it("classifies reads correctly for known methods", () => { + expect(METHOD_METADATA["entities.list"].access).toBe("read"); + expect(METHOD_METADATA["watchers.list"].access).toBe("read"); + expect(METHOD_METADATA["organizations.list"].access).toBe("read"); + }); +}); diff --git a/packages/owletto-backend/src/__tests__/unit/sandbox/run-script.test.ts b/packages/owletto-backend/src/__tests__/unit/sandbox/run-script.test.ts new file mode 100644 index 000000000..7acfa82f8 --- /dev/null +++ b/packages/owletto-backend/src/__tests__/unit/sandbox/run-script.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "bun:test"; +import type { ClientSDK } from "../../../sandbox/client-sdk"; +import { getDefaultLimits, runScript } from "../../../sandbox/run-script"; + +describe("runScript", () => { + it("exposes default resource limits", () => { + const limits = getDefaultLimits(); + expect(limits.memoryMb).toBe(64); + expect(limits.timeoutMs).toBe(60_000); + expect(limits.sdkCallQuota).toBe(200); + expect(limits.outputBytes).toBe(262_144); + }); + + it("returns structured result shape", async () => { + const stubSdk = { log: () => undefined } as unknown as ClientSDK; + const result = await runScript({ + source: "export default async () => 42;", + sdk: stubSdk, + }); + expect(result).toHaveProperty("success"); + expect(result).toHaveProperty("logs"); + expect(result).toHaveProperty("durationMs"); + expect(result).toHaveProperty("sdkCalls"); + expect(result.durationMs).toBeGreaterThanOrEqual(0); + }); + + it("fails with RuntimeUnavailable or NotImplemented", async () => { + // PR-1 ships the scaffolding only; the runner either reports the optional + // native module missing, or a clear NotImplemented stub. PR-2 replaces the + // stub with the real isolated-vm bridge. + const stubSdk = { log: () => undefined } as unknown as ClientSDK; + const result = await runScript({ + source: "export default async () => 1;", + sdk: stubSdk, + }); + expect(result.success).toBe(false); + expect(result.error).toBeDefined(); + expect(["RuntimeUnavailable", "NotImplemented"]).toContain( + result.error?.name ?? "" + ); + }); +}); diff --git a/packages/owletto-backend/src/__tests__/unit/sandbox/typebox-to-signature.test.ts b/packages/owletto-backend/src/__tests__/unit/sandbox/typebox-to-signature.test.ts new file mode 100644 index 000000000..771f80cfa --- /dev/null +++ b/packages/owletto-backend/src/__tests__/unit/sandbox/typebox-to-signature.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, it } from "bun:test"; +import { Type } from "@sinclair/typebox"; +import { typeboxToSignature } from "../../../sandbox/typebox-to-signature"; + +describe("typeboxToSignature", () => { + it("renders primitives", () => { + expect(typeboxToSignature(Type.String())).toBe("string"); + expect(typeboxToSignature(Type.Number())).toBe("number"); + expect(typeboxToSignature(Type.Integer())).toBe("number"); + expect(typeboxToSignature(Type.Boolean())).toBe("boolean"); + expect(typeboxToSignature(Type.Null())).toBe("null"); + }); + + it("renders enum literal unions", () => { + const schema = Type.Union([Type.Literal("asc"), Type.Literal("desc")]); + expect(typeboxToSignature(schema)).toBe("'asc' | 'desc'"); + }); + + it("renders arrays with primitive element", () => { + expect(typeboxToSignature(Type.Array(Type.String()))).toBe("string[]"); + }); + + it("uses Array<> syntax when element has spaces", () => { + const schema = Type.Array( + Type.Union([Type.Literal("a"), Type.Literal("b")]) + ); + expect(typeboxToSignature(schema)).toBe("Array<'a' | 'b'>"); + }); + + it("renders objects with required + optional fields", () => { + const schema = Type.Object({ + id: Type.Number(), + name: Type.Optional(Type.String()), + }); + const sig = typeboxToSignature(schema); + expect(sig).toContain("id: number;"); + expect(sig).toContain("name?: string;"); + }); + + it("annotates with description as inline comment", () => { + const schema = Type.Object({ + status: Type.String({ description: "Entity status" }), + }); + const sig = typeboxToSignature(schema); + expect(sig).toContain("// Entity status"); + }); + + it("inlines nested objects", () => { + const schema = Type.Object({ + outer: Type.Object({ + inner: Type.String(), + }), + }); + const sig = typeboxToSignature(schema); + expect(sig).toContain("outer: {"); + expect(sig).toContain("inner: string;"); + }); + + it("caps recursion at maxDepth", () => { + // A self-reference would recurse forever; use a fake depth trap. + const schema: unknown = { type: "object", properties: {} }; + (schema as Record).properties = { self: schema }; + expect(typeboxToSignature(schema as never, { maxDepth: 2 })).toContain( + "unknown" + ); + }); + + it("handles const literal", () => { + const schema = Type.Literal("fixed"); + expect(typeboxToSignature(schema)).toBe("'fixed'"); + }); +}); diff --git a/packages/owletto-backend/src/sandbox/client-sdk.ts b/packages/owletto-backend/src/sandbox/client-sdk.ts new file mode 100644 index 000000000..fa7eab021 --- /dev/null +++ b/packages/owletto-backend/src/sandbox/client-sdk.ts @@ -0,0 +1,253 @@ +/** + * ClientSDK — one in-process SDK shared by the `execute` MCP tool (PR-2) and + * watcher reactions (PR-2 swap). Each namespace delegates to existing tool + * handlers, preserving per-call auth checks and audit/change events. + * + * Multi-org support is provided by `client.org(slugOrId)`: it re-validates + * membership against the `member` table, then returns a proxy SDK bound to a + * swapped `ToolContext`. Membership lookups are cached in a small LRU with a + * short TTL so a cross-org walk doesn't hammer Postgres, while still catching + * revocations within ~30 s. + */ + +import { getDb } from "../db/client"; +import type { Env } from "../index"; +import type { ToolContext } from "../tools/registry"; +import { getWorkspaceProvider } from "../workspace"; +import type { OrgInfo } from "../workspace/types"; +import { MembershipCache, type MembershipRecord } from "./membership-cache"; +import { + buildAuthProfilesNamespace, + buildClassifiersNamespace, + buildConnectionsNamespace, + buildEntitiesNamespace, + buildEntitySchemaNamespace, + buildFeedsNamespace, + buildKnowledgeNamespace, + buildOperationsNamespace, + buildOrganizationsNamespace, + buildViewTemplatesNamespace, + buildWatchersNamespace, +} from "./namespaces"; +import type { AuthProfilesNamespace } from "./namespaces/auth-profiles"; +import type { ClassifiersNamespace } from "./namespaces/classifiers"; +import type { ConnectionsNamespace } from "./namespaces/connections"; +import type { EntitiesNamespace } from "./namespaces/entities"; +import type { EntitySchemaNamespace } from "./namespaces/entity-schema"; +import type { FeedsNamespace } from "./namespaces/feeds"; +import type { KnowledgeNamespace } from "./namespaces/knowledge"; +import type { OperationsNamespace } from "./namespaces/operations"; +import type { OrganizationsNamespace } from "./namespaces/organizations"; +import type { ViewTemplatesNamespace } from "./namespaces/view-templates"; +import type { WatchersNamespace } from "./namespaces/watchers"; + +export interface ClientSDK { + entities: EntitiesNamespace; + entitySchema: EntitySchemaNamespace; + connections: ConnectionsNamespace; + feeds: FeedsNamespace; + authProfiles: AuthProfilesNamespace; + operations: OperationsNamespace; + watchers: WatchersNamespace; + classifiers: ClassifiersNamespace; + viewTemplates: ViewTemplatesNamespace; + knowledge: KnowledgeNamespace; + organizations: OrganizationsNamespace; + + /** + * Return a ClientSDK bound to a different organization the caller belongs + * to. Re-validates membership on each call; throws `AccessDenied` for + * private orgs the caller isn't a member of. + * + * Public-visibility orgs return an SDK with `memberRole: null` — reads + * succeed, writes fail at the handler-level access check. + * + * The returned SDK is fully independent: it has its own `.org()` that still + * resolves relative to the original user, so chains like + * `client.org('a').org('b')` are legal if the user is a member of both. + */ + org(slugOrId: string): Promise; + + /** Run a read-only SQL query scoped to the current organization. */ + query(sql: string, params?: unknown[]): Promise; + + /** Emit a structured log entry (captured by the invocation audit row in PR-3). */ + log(message: string, data?: Record): void; +} + +export interface BuildClientSDKOptions { + /** Pre-seeded membership cache shared across `.org()` calls in one isolate run. */ + membershipCache?: MembershipCache; +} + +// Re-export so callers can `import { MembershipCache } from '../sandbox/client-sdk'` +// if they prefer; new code should import from `./membership-cache` directly. +export { MembershipCache } from "./membership-cache"; +export type { MembershipRecord } from "./membership-cache"; + +// --------------------------------------------------------------------------- +// Membership resolution +// --------------------------------------------------------------------------- + +const UUID_RE = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; +// Better Auth emits org IDs as text (not UUIDs) — we accept both UUIDs and +// opaque ID strings as "id-shaped" when they don't match a slug pattern. +const ID_RE = /^[A-Za-z0-9_-]{20,}$/; + +export class AccessDeniedError extends Error { + code = "AccessDenied" as const; + constructor(message: string) { + super(message); + this.name = "AccessDenied"; + } +} + +export class OrgNotFoundError extends Error { + code = "OrgNotFound" as const; + constructor(message: string) { + super(message); + this.name = "OrgNotFound"; + } +} + +/** + * Resolve an org identifier (slug or id) into a membership record, checking + * the caller's access. Throws `OrgNotFound` if the org doesn't exist and + * `AccessDenied` if the caller has no read access (non-member on a private + * org). + */ +export async function resolveOrgMembership( + slugOrId: string, + ctx: ToolContext, + cache: MembershipCache +): Promise { + const cached = cache.get(ctx.userId, slugOrId); + if (cached) { + if (cached.visibility === "private" && cached.role === null) { + throw new AccessDeniedError( + `You are not a member of organization '${slugOrId}'.` + ); + } + return cached; + } + + const sql = getDb(); + const looksLikeId = UUID_RE.test(slugOrId) || ID_RE.test(slugOrId); + const lookup = looksLikeId + ? await sql`SELECT id, slug, visibility FROM "organization" WHERE id = ${slugOrId} LIMIT 1` + : await sql`SELECT id, slug, visibility FROM "organization" WHERE slug = ${slugOrId} LIMIT 1`; + + if (lookup.length === 0) { + throw new OrgNotFoundError(`Organization '${slugOrId}' not found.`); + } + const row = lookup[0] as { + id: string; + slug: string; + visibility: "public" | "private" | string; + }; + const visibility = row.visibility === "public" ? "public" : "private"; + + let role: string | null = null; + if (ctx.userId) { + const memberRows = await sql` + SELECT role FROM "member" + WHERE "organizationId" = ${row.id} AND "userId" = ${ctx.userId} + LIMIT 1 + `; + role = memberRows.length > 0 ? (memberRows[0].role as string) : null; + } + + const record: MembershipRecord = { + orgId: row.id, + slug: row.slug, + role, + visibility, + expiresAt: 0, // set by cache + }; + // Cache under both slug and id to avoid duplicate resolutions within the TTL. + cache.set(ctx.userId, [row.id, row.slug], record); + + if (visibility === "private" && role === null) { + throw new AccessDeniedError( + `You are not a member of organization '${slugOrId}'.` + ); + } + return record; +} + +// --------------------------------------------------------------------------- +// SDK construction +// --------------------------------------------------------------------------- + +/** + * Build a `ClientSDK` bound to the caller's current `ToolContext`. The SDK + * exposes `.org()` which constructs a fresh `ClientSDK` after re-validating + * membership against the shared `MembershipCache`. + */ +export function buildClientSDK( + ctx: ToolContext, + env: Env, + options: BuildClientSDKOptions = {} +): ClientSDK { + const cache = options.membershipCache ?? new MembershipCache(); + + const sdk: ClientSDK = { + entities: buildEntitiesNamespace(ctx, env), + entitySchema: buildEntitySchemaNamespace(ctx, env), + connections: buildConnectionsNamespace(ctx, env), + feeds: buildFeedsNamespace(ctx, env), + authProfiles: buildAuthProfilesNamespace(ctx, env), + operations: buildOperationsNamespace(ctx, env), + watchers: buildWatchersNamespace(ctx, env), + classifiers: buildClassifiersNamespace(ctx, env), + viewTemplates: buildViewTemplatesNamespace(ctx, env), + knowledge: buildKnowledgeNamespace(ctx, env), + organizations: buildOrganizationsNamespace(ctx, env), + + async org(slugOrId) { + const member = await resolveOrgMembership(slugOrId, ctx, cache); + const swapped: ToolContext = { + ...ctx, + organizationId: member.orgId, + memberRole: member.role, + }; + return buildClientSDK(swapped, env, { membershipCache: cache }); + }, + + async query(querySql, params) { + const { validateAndScopeQuery } = await import( + "../utils/execute-data-sources" + ); + const scoped = validateAndScopeQuery(querySql, ctx.organizationId); + const db = getDb(); + const rows = await db.begin(async (tx) => { + await tx.unsafe("SET TRANSACTION READ ONLY"); + await tx.unsafe("SET LOCAL statement_timeout = '5000'"); + const merged = (scoped.params as unknown[]).concat(params ?? []); + return tx.unsafe(scoped.sql, merged); + }); + return rows.map((r: Record) => ({ ...r })); + }, + + log(message, data) { + // Structured log; PR-3 routes these into the execute_invocation audit row. + // eslint-disable-next-line no-console + console.log(`[client-sdk] ${message}`, data ?? {}); + }, + }; + + return sdk; +} + +/** + * Convenience: look up `OrgInfo` for the caller's current org. Useful for the + * `search` tool's preamble and the web console's header chip. + */ +export async function getCurrentOrgInfo( + ctx: ToolContext +): Promise { + const provider = getWorkspaceProvider(); + const orgs = await provider.listOrganizations(undefined, ctx.userId); + return orgs.find((o) => o.id === ctx.organizationId) ?? null; +} diff --git a/packages/owletto-backend/src/sandbox/membership-cache.ts b/packages/owletto-backend/src/sandbox/membership-cache.ts new file mode 100644 index 000000000..e2165dd80 --- /dev/null +++ b/packages/owletto-backend/src/sandbox/membership-cache.ts @@ -0,0 +1,74 @@ +/** + * Small LRU cache for membership lookups performed by `client.org()`. + * + * Keeps cross-org walks cheap without hiding revocations longer than the TTL + * (default 30 s). Entries are keyed on a lowercased slug-or-id string plus the + * caller's user id, so two sessions share nothing. + */ + +export interface MembershipRecord { + orgId: string; + slug: string; + role: string | null; + visibility: "public" | "private"; + /** Unix ms expiry. Populated by the cache on set. */ + expiresAt: number; +} + +const DEFAULT_TTL_MS = 30_000; +const MAX_CACHE_ENTRIES = 128; + +export class MembershipCache { + private readonly entries = new Map(); + private readonly ttlMs: number; + + constructor(ttlMs: number = DEFAULT_TTL_MS) { + this.ttlMs = ttlMs; + } + + get(userId: string | null, key: string): MembershipRecord | null { + const cacheKey = this.key(userId, key); + const record = this.entries.get(cacheKey); + if (!record) return null; + if (record.expiresAt <= Date.now()) { + this.entries.delete(cacheKey); + return null; + } + // Refresh LRU order. + this.entries.delete(cacheKey); + this.entries.set(cacheKey, record); + return record; + } + + set( + userId: string | null, + keys: string[], + record: Omit + ): void { + const full: MembershipRecord = { + ...record, + expiresAt: Date.now() + this.ttlMs, + }; + for (const key of keys) { + const cacheKey = this.key(userId, key); + this.entries.set(cacheKey, full); + } + while (this.entries.size > MAX_CACHE_ENTRIES) { + const oldest = this.entries.keys().next().value; + if (oldest === undefined) break; + this.entries.delete(oldest); + } + } + + size(): number { + return this.entries.size; + } + + clear(): void { + this.entries.clear(); + } + + private key(userId: string | null, k: string): string { + return `${userId ?? "anon"}::${k.toLowerCase()}`; + } +} diff --git a/packages/owletto-backend/src/sandbox/method-metadata.ts b/packages/owletto-backend/src/sandbox/method-metadata.ts new file mode 100644 index 000000000..a9be49dd5 --- /dev/null +++ b/packages/owletto-backend/src/sandbox/method-metadata.ts @@ -0,0 +1,291 @@ +/** + * ClientSDK method metadata. + * + * Describes each SDK method for: + * - `search` MCP tool (summary, example, throws) + * - Dry-run classification (read | write | external) — PR-2 wires this into the wrapper + * - Static bans (e.g. `client.execute` must not be exposed recursively) + * + * Keyed by dotted SDK path (e.g. `watchers.list`, `entities.create`). + * Populated incrementally — PR-1 seeds the shape + a handful of examples. + * PR-2 validates coverage against the runtime dispatch table in CI. + */ + +export type MethodAccess = "read" | "write" | "external"; + +export interface MethodMetadata { + /** One-line human description. Shown in namespace listings. */ + summary: string; + /** Dry-run classification. Writes are intercepted; externals are never sent. */ + access: MethodAccess; + /** Declared error names the method may throw. Surfaces in drill-down. */ + throws?: readonly string[]; + /** Copy-pasteable TS snippet. Kept short (≤2 lines). */ + example?: string; + /** Cost hint: 'cheap' | 'normal' | 'expensive'. Normal if omitted. */ + cost?: "cheap" | "normal" | "expensive"; +} + +/** + * Metadata entries. Keys are dotted SDK paths. + * + * This table is intentionally sparse in PR-1 — PR-2 fills it to 100% coverage + * and adds a CI test that fails if a new SDK method ships without metadata. + */ +export const METHOD_METADATA: Record = { + // organizations + "organizations.list": { + summary: + "List organizations the authenticated user belongs to, plus public orgs they can read.", + access: "read", + example: "const orgs = await client.organizations.list();", + }, + "organizations.current": { + summary: "Return the session's current organization context.", + access: "read", + example: "const org = await client.organizations.current();", + }, + + // entities + "entities.list": { + summary: "List entities in the current organization with optional filters.", + access: "read", + example: + "const rows = await client.entities.list({ entity_type: 'company' });", + }, + "entities.get": { + summary: "Fetch a single entity by id.", + access: "read", + throws: ["EntityNotFound"], + example: "const entity = await client.entities.get(42);", + }, + "entities.create": { + summary: + "Create an entity with metadata validated against the entity type schema.", + access: "write", + throws: ["EntityTypeNotFound", "ValidationError"], + example: + "await client.entities.create({ type: 'company', name: 'Acme', metadata: {} });", + }, + "entities.update": { + summary: "Update an existing entity.", + access: "write", + }, + "entities.delete": { + summary: "Delete an entity.", + access: "write", + }, + "entities.link": { + summary: "Create a relationship between two entities.", + access: "write", + }, + "entities.unlink": { + summary: "Soft-delete an entity relationship.", + access: "write", + }, + "entities.search": { + summary: "Search entities by name / metadata.", + access: "read", + }, + + // knowledge + "knowledge.search": { + summary: "Semantic search over stored knowledge events.", + access: "read", + example: + "const hits = await client.knowledge.search({ query: 'revenue update', limit: 10 });", + }, + "knowledge.save": { + summary: "Persist a knowledge event, optionally associated with entities.", + access: "write", + }, + "knowledge.read": { + summary: "Read a knowledge event by id, or watcher-window context.", + access: "read", + }, + + // watchers + "watchers.list": { + summary: "List watchers, optionally filtered by entity.", + access: "read", + example: "const ws = await client.watchers.list({ entity_id: 42 });", + }, + "watchers.get": { + summary: "Fetch a watcher by id.", + access: "read", + throws: ["WatcherNotFound"], + }, + "watchers.create": { + summary: "Create a watcher with prompt, extraction schema, and sources.", + access: "write", + throws: ["EntityNotFound", "InvalidExtractionSchema"], + }, + "watchers.update": { + summary: "Update watcher config (model, schedule, sources).", + access: "write", + }, + "watchers.delete": { + summary: "Delete a watcher.", + access: "write", + }, + "watchers.setReactionScript": { + summary: "Attach a raw TS reaction script that fires on window completion.", + access: "write", + throws: ["CompileError"], + }, + + // connections (external side effects for execute) + "connections.list": { + summary: "List configured connections in the current organization.", + access: "read", + }, + "connections.get": { summary: "Get a connection by id.", access: "read" }, + "connections.create": { + summary: "Create a new connection.", + access: "write", + }, + "connections.connect": { + summary: + "Start an OAuth flow. Returns a connect_url to share with the user.", + access: "write", + }, + "connections.update": { + summary: "Update connection config.", + access: "write", + }, + "connections.delete": { summary: "Delete a connection.", access: "write" }, + "connections.test": { + summary: "Test connection credentials.", + access: "external", + }, + + // operations (run external actions) + "operations.listAvailable": { + summary: "List operations exposed by the active connections.", + access: "read", + }, + "operations.execute": { + summary: "Execute a connector action. Sends an external request.", + access: "external", + cost: "expensive", + }, + + // feeds + "feeds.list": { summary: "List data-sync feeds.", access: "read" }, + "feeds.get": { summary: "Get a feed by id.", access: "read" }, + "feeds.create": { summary: "Create a data-sync feed.", access: "write" }, + "feeds.update": { summary: "Update a feed.", access: "write" }, + "feeds.delete": { summary: "Delete a feed.", access: "write" }, + "feeds.trigger": { + summary: "Trigger an immediate sync for a feed.", + access: "external", + }, + + // authProfiles + "authProfiles.list": { + summary: "List reusable auth profiles.", + access: "read", + }, + "authProfiles.get": { summary: "Get an auth profile by id.", access: "read" }, + "authProfiles.create": { + summary: "Create an auth profile.", + access: "write", + }, + "authProfiles.update": { + summary: "Update an auth profile.", + access: "write", + }, + "authProfiles.delete": { + summary: "Delete an auth profile.", + access: "write", + }, + "authProfiles.test": { + summary: "Test auth profile credentials.", + access: "external", + }, + + // classifiers + "classifiers.list": { summary: "List classifier templates.", access: "read" }, + "classifiers.create": { + summary: "Create a classifier template.", + access: "write", + }, + "classifiers.delete": { + summary: "Delete a classifier template.", + access: "write", + }, + "classifiers.classify": { + summary: "Classify one or many content strings.", + access: "read", + }, + + // viewTemplates + "viewTemplates.get": { + summary: "Get the active view template for a resource.", + access: "read", + }, + "viewTemplates.set": { + summary: "Create or update a view template.", + access: "write", + }, + "viewTemplates.rollback": { + summary: "Roll back to a previous template version.", + access: "write", + }, + "viewTemplates.removeTab": { + summary: "Remove a named tab from a template.", + access: "write", + }, + + // entitySchema + "entitySchema.listTypes": { + summary: "List entity types in the organization.", + access: "read", + }, + "entitySchema.getType": { + summary: "Get an entity type by slug.", + access: "read", + }, + "entitySchema.createType": { + summary: "Create an entity type.", + access: "write", + }, + "entitySchema.updateType": { + summary: "Update an entity type.", + access: "write", + }, + "entitySchema.deleteType": { + summary: "Delete an entity type.", + access: "write", + }, + "entitySchema.listRelTypes": { + summary: "List relationship types.", + access: "read", + }, + "entitySchema.createRelType": { + summary: "Create a relationship type.", + access: "write", + }, + + // top-level + query: { + summary: + "Run a read-only SQL query against the organization-scoped virtual tables.", + access: "read", + example: + 'const rows = await client.query("SELECT * FROM entities WHERE entity_type = $1", ["company"]);', + }, + log: { + summary: + "Emit a structured log line (captured in the invocation audit row).", + access: "read", + cost: "cheap", + }, +}; + +/** Paths that must never appear as SDK methods. Enforced in PR-2 tests. */ +export const BANNED_PATHS = [ + "execute", + "client.execute", + "sdk.execute", +] as const; diff --git a/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts b/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts new file mode 100644 index 000000000..0fd3a390c --- /dev/null +++ b/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts @@ -0,0 +1,43 @@ +/** + * ClientSDK `authProfiles` namespace. Thin wrapper over `manageAuthProfiles`. + */ + +import type { Env } from "../../index"; +import { manageAuthProfiles } from "../../tools/admin/manage_auth_profiles"; +import type { ToolContext } from "../../tools/registry"; + +export interface AuthProfilesNamespace { + list(): Promise; + get(auth_profile_id: number): Promise; + test(auth_profile_id: number): Promise; + create(input: { + name: string; + connector_key: string; + credentials: Record; + }): Promise; + update(input: { + auth_profile_id: number; + [key: string]: unknown; + }): Promise; + delete(auth_profile_id: number): Promise; +} + +export function buildAuthProfilesNamespace( + ctx: ToolContext, + env: Env +): AuthProfilesNamespace { + const call = (payload: Record): Promise => + manageAuthProfiles(payload as never, env, ctx) as Promise; + + return { + list: () => call({ action: "list_auth_profiles" }), + get: (auth_profile_id) => + call({ action: "get_auth_profile", auth_profile_id }), + test: (auth_profile_id) => + call({ action: "test_auth_profile", auth_profile_id }), + create: (input) => call({ action: "create_auth_profile", ...input }), + update: (input) => call({ action: "update_auth_profile", ...input }), + delete: (auth_profile_id) => + call({ action: "delete_auth_profile", auth_profile_id }), + }; +} diff --git a/packages/owletto-backend/src/sandbox/namespaces/classifiers.ts b/packages/owletto-backend/src/sandbox/namespaces/classifiers.ts new file mode 100644 index 000000000..3b2fe8cb2 --- /dev/null +++ b/packages/owletto-backend/src/sandbox/namespaces/classifiers.ts @@ -0,0 +1,54 @@ +/** + * ClientSDK `classifiers` namespace. Thin wrapper over `manageClassifiers`. + */ + +import type { Env } from "../../index"; +import { manageClassifiers } from "../../tools/admin/manage_classifiers"; +import type { ToolContext } from "../../tools/registry"; + +export interface ClassifiersNamespace { + list(): Promise; + create(input: { + slug: string; + name: string; + [key: string]: unknown; + }): Promise; + createVersion(input: { + classifier_slug: string; + [key: string]: unknown; + }): Promise; + getVersions(classifier_slug: string): Promise; + setCurrentVersion(input: { + classifier_slug: string; + version_id: number; + }): Promise; + generateEmbeddings(classifier_slug: string): Promise; + delete(classifier_slug: string): Promise; + classify(input: { + classifier_slug: string; + value: string | string[]; + entity_id?: number; + }): Promise; +} + +export function buildClassifiersNamespace( + ctx: ToolContext, + env: Env +): ClassifiersNamespace { + const call = (payload: Record): Promise => + manageClassifiers(payload as never, env, ctx) as Promise; + + return { + list: () => call({ action: "list" }), + create: (input) => call({ action: "create", ...input }), + createVersion: (input) => call({ action: "create_version", ...input }), + getVersions: (classifier_slug) => + call({ action: "get_versions", classifier_slug }), + setCurrentVersion: (input) => + call({ action: "set_current_version", ...input }), + generateEmbeddings: (classifier_slug) => + call({ action: "generate_embeddings", classifier_slug }), + delete: (classifier_slug) => call({ action: "delete", classifier_slug }), + classify: (input) => call({ action: "classify", ...input }), + }; +} diff --git a/packages/owletto-backend/src/sandbox/namespaces/connections.ts b/packages/owletto-backend/src/sandbox/namespaces/connections.ts new file mode 100644 index 000000000..367fa917b --- /dev/null +++ b/packages/owletto-backend/src/sandbox/namespaces/connections.ts @@ -0,0 +1,69 @@ +/** + * ClientSDK `connections` namespace. Thin wrapper over `manageConnections`. + */ + +import type { Env } from "../../index"; +import { manageConnections } from "../../tools/admin/manage_connections"; +import type { ToolContext } from "../../tools/registry"; + +export interface ConnectionsNamespace { + list(input?: { connector_key?: string }): Promise; + listConnectorDefinitions(): Promise; + get(connection_id: number): Promise; + create(input: { + connector_key: string; + name?: string; + config?: Record; + }): Promise; + connect(input: { + connector_key: string; + auth_profile_id?: number; + }): Promise; + update(input: { + connection_id: number; + [key: string]: unknown; + }): Promise; + delete(connection_id: number): Promise; + test(connection_id: number): Promise; + installConnector(input: { + connector_key: string; + source_url?: string; + }): Promise; + uninstallConnector(connector_key: string): Promise; + toggleConnectorLogin(input: { + connector_key: string; + enabled: boolean; + }): Promise; + updateConnectorAuth(input: { + connector_key: string; + auth_config: Record; + }): Promise; +} + +export function buildConnectionsNamespace( + ctx: ToolContext, + env: Env +): ConnectionsNamespace { + const call = (payload: Record): Promise => + manageConnections(payload as never, env, ctx) as Promise; + + return { + list: (input) => call({ action: "list", ...input }), + listConnectorDefinitions: () => + call({ action: "list_connector_definitions" }), + get: (connection_id) => call({ action: "get", connection_id }), + create: (input) => call({ action: "create", ...input }), + connect: (input) => call({ action: "connect", ...input }), + update: (input) => call({ action: "update", ...input }), + delete: (connection_id) => call({ action: "delete", connection_id }), + test: (connection_id) => call({ action: "test", connection_id }), + installConnector: (input) => + call({ action: "install_connector", ...input }), + uninstallConnector: (connector_key) => + call({ action: "uninstall_connector", connector_key }), + toggleConnectorLogin: (input) => + call({ action: "toggle_connector_login", ...input }), + updateConnectorAuth: (input) => + call({ action: "update_connector_auth", ...input }), + }; +} diff --git a/packages/owletto-backend/src/sandbox/namespaces/entities.ts b/packages/owletto-backend/src/sandbox/namespaces/entities.ts new file mode 100644 index 000000000..18c719cd5 --- /dev/null +++ b/packages/owletto-backend/src/sandbox/namespaces/entities.ts @@ -0,0 +1,170 @@ +/** + * ClientSDK `entities` namespace. + * + * Delegates to `manageEntity` with action-discriminated payloads. Per-call auth + * checks fire inside the handler; this wrapper does not duplicate them. + */ + +import type { Env } from "../../index"; +import { manageEntity } from "../../tools/admin/manage_entity"; +import type { ToolContext } from "../../tools/registry"; + +export interface EntityListFilter { + entity_type?: string; + search?: string; + limit?: number; + offset?: number; + sort_by?: string; + sort_order?: "asc" | "desc"; + category?: string; + main_market?: string; + market?: string; +} + +export interface EntityCreateInput { + type: string; + name: string; + slug?: string; + content?: string; + parent_id?: number; + metadata?: Record; + enabled_classifiers?: string[]; +} + +export interface EntityUpdateInput { + entity_id: number; + name?: string; + slug?: string; + content?: string; + metadata?: Record; + enabled_classifiers?: string[]; +} + +export interface EntityLinkInput { + from_entity_id: number; + to_entity_id: number; + relationship_type_slug: string; + metadata?: Record; +} + +export interface EntitiesNamespace { + list(filter?: EntityListFilter): Promise; + get(entity_id: number): Promise; + create(input: EntityCreateInput): Promise; + update(input: EntityUpdateInput): Promise; + delete( + entity_id: number, + options?: { force_delete_tree?: boolean } + ): Promise; + link(input: EntityLinkInput): Promise; + unlink(input: { + from_entity_id: number; + to_entity_id: number; + relationship_type_slug: string; + }): Promise; + updateLink(input: { + from_entity_id: number; + to_entity_id: number; + relationship_type_slug: string; + metadata?: Record; + }): Promise; + listLinks(input: { + entity_id: number; + relationship_type_slug?: string; + limit?: number; + offset?: number; + }): Promise; + search(query: string, options?: { limit?: number }): Promise; +} + +export function buildEntitiesNamespace( + ctx: ToolContext, + env: Env +): EntitiesNamespace { + return { + list(filter) { + return manageEntity( + { action: "list", ...filter } as never, + env, + ctx + ) as Promise; + }, + get(entity_id) { + return manageEntity( + { action: "get", entity_id } as never, + env, + ctx + ) as Promise; + }, + create(input) { + return manageEntity( + { + action: "create", + entity_type: input.type, + name: input.name, + slug: input.slug, + content: input.content, + parent_id: input.parent_id, + metadata: input.metadata, + enabled_classifiers: input.enabled_classifiers, + } as never, + env, + ctx + ) as Promise; + }, + update(input) { + return manageEntity( + { action: "update", ...input } as never, + env, + ctx + ) as Promise; + }, + delete(entity_id, options) { + return manageEntity( + { + action: "delete", + entity_id, + force_delete_tree: options?.force_delete_tree, + } as never, + env, + ctx + ) as Promise; + }, + link(input) { + return manageEntity( + { action: "link", ...input } as never, + env, + ctx + ) as Promise; + }, + unlink(input) { + return manageEntity( + { action: "unlink", ...input } as never, + env, + ctx + ) as Promise; + }, + updateLink(input) { + return manageEntity( + { action: "update_link", ...input } as never, + env, + ctx + ) as Promise; + }, + listLinks(input) { + return manageEntity( + { action: "list_links", ...input } as never, + env, + ctx + ) as Promise; + }, + async search(query, options) { + const { search } = await import("../../tools/search"); + return search( + { query, limit: options?.limit } as never, + env, + ctx + ) as Promise; + }, + }; +} diff --git a/packages/owletto-backend/src/sandbox/namespaces/entity-schema.ts b/packages/owletto-backend/src/sandbox/namespaces/entity-schema.ts new file mode 100644 index 000000000..d590db787 --- /dev/null +++ b/packages/owletto-backend/src/sandbox/namespaces/entity-schema.ts @@ -0,0 +1,90 @@ +/** + * ClientSDK `entitySchema` namespace. Thin wrapper over `manageEntitySchema`. + * + * The underlying handler is discriminated by `schema_type` *and* `action`; the + * namespace splits those into method names so callers don't have to track both. + */ + +import type { Env } from "../../index"; +import { manageEntitySchema } from "../../tools/admin/manage_entity_schema"; +import type { ToolContext } from "../../tools/registry"; + +export interface EntitySchemaNamespace { + listTypes(): Promise; + getType(entity_type_slug: string): Promise; + createType(input: { + slug: string; + name: string; + [key: string]: unknown; + }): Promise; + updateType(input: { + entity_type_slug: string; + [key: string]: unknown; + }): Promise; + deleteType(entity_type_slug: string): Promise; + auditType(entity_type_slug: string): Promise; + + listRelTypes(): Promise; + getRelType(relationship_type_slug: string): Promise; + createRelType(input: { + slug: string; + name: string; + [key: string]: unknown; + }): Promise; + updateRelType(input: { + relationship_type_slug: string; + [key: string]: unknown; + }): Promise; + deleteRelType(relationship_type_slug: string): Promise; + addRule(input: { + relationship_type_slug: string; + [key: string]: unknown; + }): Promise; + removeRule(input: { + relationship_type_slug: string; + rule_id: number; + }): Promise; + listRules(relationship_type_slug: string): Promise; +} + +export function buildEntitySchemaNamespace( + ctx: ToolContext, + env: Env +): EntitySchemaNamespace { + const callEntity = (payload: Record): Promise => + manageEntitySchema( + { schema_type: "entity_type", ...payload } as never, + env, + ctx + ) as Promise; + const callRel = (payload: Record): Promise => + manageEntitySchema( + { schema_type: "relationship_type", ...payload } as never, + env, + ctx + ) as Promise; + + return { + listTypes: () => callEntity({ action: "list" }), + getType: (entity_type_slug) => + callEntity({ action: "get", entity_type_slug }), + createType: (input) => callEntity({ action: "create", ...input }), + updateType: (input) => callEntity({ action: "update", ...input }), + deleteType: (entity_type_slug) => + callEntity({ action: "delete", entity_type_slug }), + auditType: (entity_type_slug) => + callEntity({ action: "audit", entity_type_slug }), + + listRelTypes: () => callRel({ action: "list" }), + getRelType: (relationship_type_slug) => + callRel({ action: "get", relationship_type_slug }), + createRelType: (input) => callRel({ action: "create", ...input }), + updateRelType: (input) => callRel({ action: "update", ...input }), + deleteRelType: (relationship_type_slug) => + callRel({ action: "delete", relationship_type_slug }), + addRule: (input) => callRel({ action: "add_rule", ...input }), + removeRule: (input) => callRel({ action: "remove_rule", ...input }), + listRules: (relationship_type_slug) => + callRel({ action: "list_rules", relationship_type_slug }), + }; +} diff --git a/packages/owletto-backend/src/sandbox/namespaces/feeds.ts b/packages/owletto-backend/src/sandbox/namespaces/feeds.ts new file mode 100644 index 000000000..606374453 --- /dev/null +++ b/packages/owletto-backend/src/sandbox/namespaces/feeds.ts @@ -0,0 +1,38 @@ +/** + * ClientSDK `feeds` namespace. Thin wrapper over `manageFeeds`. + */ + +import type { Env } from "../../index"; +import { manageFeeds } from "../../tools/admin/manage_feeds"; +import type { ToolContext } from "../../tools/registry"; + +export interface FeedsNamespace { + list(input?: { connection_id?: number }): Promise; + get(feed_id: number): Promise; + create(input: { + connection_id: number; + name: string; + schedule?: string; + config?: Record; + }): Promise; + update(input: { feed_id: number; [key: string]: unknown }): Promise; + delete(feed_id: number): Promise; + trigger(feed_id: number): Promise; +} + +export function buildFeedsNamespace( + ctx: ToolContext, + env: Env +): FeedsNamespace { + const call = (payload: Record): Promise => + manageFeeds(payload as never, env, ctx) as Promise; + + return { + list: (input) => call({ action: "list_feeds", ...input }), + get: (feed_id) => call({ action: "get_feed", feed_id }), + create: (input) => call({ action: "create_feed", ...input }), + update: (input) => call({ action: "update_feed", ...input }), + delete: (feed_id) => call({ action: "delete_feed", feed_id }), + trigger: (feed_id) => call({ action: "trigger_feed", feed_id }), + }; +} diff --git a/packages/owletto-backend/src/sandbox/namespaces/index.ts b/packages/owletto-backend/src/sandbox/namespaces/index.ts new file mode 100644 index 000000000..d0ee1fb9c --- /dev/null +++ b/packages/owletto-backend/src/sandbox/namespaces/index.ts @@ -0,0 +1,15 @@ +/** + * Re-exports for the per-namespace SDK builders. + */ + +export { buildAuthProfilesNamespace } from "./auth-profiles"; +export { buildClassifiersNamespace } from "./classifiers"; +export { buildConnectionsNamespace } from "./connections"; +export { buildEntitiesNamespace } from "./entities"; +export { buildEntitySchemaNamespace } from "./entity-schema"; +export { buildFeedsNamespace } from "./feeds"; +export { buildKnowledgeNamespace } from "./knowledge"; +export { buildOperationsNamespace } from "./operations"; +export { buildOrganizationsNamespace } from "./organizations"; +export { buildViewTemplatesNamespace } from "./view-templates"; +export { buildWatchersNamespace } from "./watchers"; diff --git a/packages/owletto-backend/src/sandbox/namespaces/knowledge.ts b/packages/owletto-backend/src/sandbox/namespaces/knowledge.ts new file mode 100644 index 000000000..9b105b557 --- /dev/null +++ b/packages/owletto-backend/src/sandbox/namespaces/knowledge.ts @@ -0,0 +1,70 @@ +/** + * ClientSDK `knowledge` namespace. + * + * Wraps `search` (aka search_knowledge), `saveContent` (save_knowledge), and + * `getContent` (read_knowledge). These are the hot-path tools kept in PR-2 — + * the SDK surface mirrors the MCP tool surface for consistency. + */ + +import type { Env } from "../../index"; +import type { ToolContext } from "../../tools/registry"; + +export interface KnowledgeSearchInput { + query?: string; + entity_type?: string; + entity_id?: number; + parent_id?: number; + market?: string; + category?: string; + fuzzy?: boolean; + min_similarity?: number; + include_connections?: boolean; + include_content?: boolean; + limit?: number; +} + +export interface KnowledgeSaveInput { + entity_ids?: number[]; + content: string; + semantic_type: string; + metadata?: Record; + title?: string; + slug?: string; +} + +export interface KnowledgeReadInput { + /** Fetch a single content event by id. */ + content_id?: number; + /** Fetch knowledge for a watcher window (prompt rendering). */ + watcher_id?: number; + since?: string; + until?: string; + limit?: number; + entity_ids?: number[]; +} + +export interface KnowledgeNamespace { + search(input: KnowledgeSearchInput): Promise; + save(input: KnowledgeSaveInput): Promise; + read(input: KnowledgeReadInput): Promise; +} + +export function buildKnowledgeNamespace( + ctx: ToolContext, + env: Env +): KnowledgeNamespace { + return { + async search(input) { + const { search } = await import("../../tools/search"); + return search(input as never, env, ctx) as Promise; + }, + async save(input) { + const { saveContent } = await import("../../tools/save_content"); + return saveContent(input as never, env, ctx) as Promise; + }, + async read(input) { + const { getContent } = await import("../../tools/get_content"); + return getContent(input as never, env, ctx) as Promise; + }, + }; +} diff --git a/packages/owletto-backend/src/sandbox/namespaces/operations.ts b/packages/owletto-backend/src/sandbox/namespaces/operations.ts new file mode 100644 index 000000000..0ca4e4cf7 --- /dev/null +++ b/packages/owletto-backend/src/sandbox/namespaces/operations.ts @@ -0,0 +1,46 @@ +/** + * ClientSDK `operations` namespace. Thin wrapper over `manageOperations`. + * + * `execute` is the only method flagged `access: 'external'` — dry-run mode + * (PR-2) intercepts these calls instead of sending them. + */ + +import type { Env } from "../../index"; +import { manageOperations } from "../../tools/admin/manage_operations"; +import type { ToolContext } from "../../tools/registry"; + +export interface OperationsNamespace { + listAvailable(input?: { entity_id?: number }): Promise; + execute(input: { + connection_id: number; + operation_key: string; + input?: Record; + watcher_source?: { watcher_id: number; window_id: string }; + }): Promise; + listRuns(input?: { + connection_id?: number; + operation_key?: string; + limit?: number; + offset?: number; + }): Promise; + getRun(run_id: number): Promise; + approve(run_id: number): Promise; + reject(run_id: number, reason?: string): Promise; +} + +export function buildOperationsNamespace( + ctx: ToolContext, + env: Env +): OperationsNamespace { + const call = (payload: Record): Promise => + manageOperations(payload as never, env, ctx) as Promise; + + return { + listAvailable: (input) => call({ action: "list_available", ...input }), + execute: (input) => call({ action: "execute", ...input }), + listRuns: (input) => call({ action: "list_runs", ...input }), + getRun: (run_id) => call({ action: "get_run", run_id }), + approve: (run_id) => call({ action: "approve", run_id }), + reject: (run_id, reason) => call({ action: "reject", run_id, reason }), + }; +} diff --git a/packages/owletto-backend/src/sandbox/namespaces/organizations.ts b/packages/owletto-backend/src/sandbox/namespaces/organizations.ts new file mode 100644 index 000000000..f2292a325 --- /dev/null +++ b/packages/owletto-backend/src/sandbox/namespaces/organizations.ts @@ -0,0 +1,80 @@ +/** + * ClientSDK `organizations` namespace. + * + * Lets user scripts enumerate the orgs the caller belongs to and read the + * session's current org. The `.org()` accessor on the root SDK (see + * `client-sdk.ts`) handles the actual cross-org context swap. + */ + +import type { Env } from "../../index"; +import type { ToolContext } from "../../tools/registry"; +import { getWorkspaceProvider } from "../../workspace"; +import type { OrgInfo } from "../../workspace/types"; + +export interface OrgSummary { + id: string; + slug: string; + name: string; + is_member: boolean; + visibility: "public" | "private"; +} + +export interface OrganizationsNamespace { + /** + * List organizations accessible to the caller: member-ofs plus any public + * workspaces the session can read. + */ + list(options?: { search?: string }): Promise; + /** + * Return the session's current organization context. Reflects the URL pin + * (`/mcp/{slug}`) or the last successful `switch_organization`. + */ + current(): Promise; +} + +export function buildOrganizationsNamespace( + ctx: ToolContext, + _env: Env +): OrganizationsNamespace { + return { + async list(options) { + const provider = getWorkspaceProvider(); + const orgs = await provider.listOrganizations( + options?.search, + ctx.userId + ); + return orgs.map((o: OrgInfo) => ({ + id: o.id, + slug: o.slug, + name: o.name, + is_member: o.is_member, + visibility: o.visibility, + })); + }, + async current() { + const provider = getWorkspaceProvider(); + const orgs = await provider.listOrganizations(undefined, ctx.userId); + const current = orgs.find((o) => o.id === ctx.organizationId); + if (!current) { + // Public-workspace session where the user isn't a member: the org is + // readable but absent from listOrganizations. Fall back to a slug + // resolve so current() still returns something useful. + const slug = await provider.getOrgSlug(ctx.organizationId); + return { + id: ctx.organizationId, + slug: slug ?? ctx.organizationId, + name: slug ?? "unknown", + is_member: false, + visibility: "public", + }; + } + return { + id: current.id, + slug: current.slug, + name: current.name, + is_member: current.is_member, + visibility: current.visibility, + }; + }, + }; +} diff --git a/packages/owletto-backend/src/sandbox/namespaces/view-templates.ts b/packages/owletto-backend/src/sandbox/namespaces/view-templates.ts new file mode 100644 index 000000000..4859d0620 --- /dev/null +++ b/packages/owletto-backend/src/sandbox/namespaces/view-templates.ts @@ -0,0 +1,47 @@ +/** + * ClientSDK `viewTemplates` namespace. Thin wrapper over `manageViewTemplates`. + */ + +import type { Env } from "../../index"; +import { manageViewTemplates } from "../../tools/admin/manage_view_templates"; +import type { ToolContext } from "../../tools/registry"; + +type ResourceType = "entity_type" | "entity"; + +export interface ViewTemplatesNamespace { + get(input: { + resource_type: ResourceType; + resource_id: string; + }): Promise; + set(input: { + resource_type: ResourceType; + resource_id: string; + template: Record; + data_sources?: Record; + }): Promise; + rollback(input: { + resource_type: ResourceType; + resource_id: string; + version_id: number; + }): Promise; + removeTab(input: { + resource_type: ResourceType; + resource_id: string; + tab_name: string; + }): Promise; +} + +export function buildViewTemplatesNamespace( + ctx: ToolContext, + env: Env +): ViewTemplatesNamespace { + const call = (payload: Record): Promise => + manageViewTemplates(payload as never, env, ctx) as Promise; + + return { + get: (input) => call({ action: "get", ...input }), + set: (input) => call({ action: "set", ...input }), + rollback: (input) => call({ action: "rollback", ...input }), + removeTab: (input) => call({ action: "remove_tab", ...input }), + }; +} diff --git a/packages/owletto-backend/src/sandbox/namespaces/watchers.ts b/packages/owletto-backend/src/sandbox/namespaces/watchers.ts new file mode 100644 index 000000000..35f50b1b0 --- /dev/null +++ b/packages/owletto-backend/src/sandbox/namespaces/watchers.ts @@ -0,0 +1,107 @@ +/** + * ClientSDK `watchers` namespace. + * + * Thin wrapper over `manageWatchers`. Covers the hot-path operations + * (list, get, create, update, delete, reaction management). Result shape + * follows the existing handler return types — PR-2 tightens the typing. + */ + +import type { Env } from "../../index"; +import { + listWatchers, + manageWatchers, +} from "../../tools/admin/manage_watchers"; +import type { ToolContext } from "../../tools/registry"; + +export interface WatcherListFilter { + entity_id?: number; + status?: "active" | "paused" | "draft"; + limit?: number; + offset?: number; +} + +export interface WatcherCreateInput { + entity_id: number; + prompt: string; + extraction_schema: Record; + sources?: Array<{ name: string; query: string }>; + status?: "active" | "paused" | "draft"; + granularity_seconds?: number; + model?: string; +} + +export interface WatchersNamespace { + list(filter?: WatcherListFilter): Promise; + get(watcher_id: number): Promise; + create(input: WatcherCreateInput): Promise; + update(input: { + watcher_id: number; + [key: string]: unknown; + }): Promise; + delete(watcher_id: number): Promise; + setReactionScript(input: { + watcher_id: number; + source: string; + params?: Record; + }): Promise; + completeWindow(input: { + watcher_id: number; + window_id: string; + extracted_data: Record; + prompt_rendered?: string; + }): Promise; +} + +export function buildWatchersNamespace( + ctx: ToolContext, + env: Env +): WatchersNamespace { + return { + list(filter) { + return listWatchers( + (filter ?? {}) as never, + env, + ctx + ) as Promise; + }, + async get(watcher_id) { + const { getWatcher } = await import("../../tools/get_watchers"); + return getWatcher({ watcher_id } as never, env, ctx) as Promise; + }, + create(input) { + return manageWatchers( + { action: "create", ...input } as never, + env, + ctx + ) as Promise; + }, + update(input) { + return manageWatchers( + { action: "update", ...input } as never, + env, + ctx + ) as Promise; + }, + delete(watcher_id) { + return manageWatchers( + { action: "delete", watcher_id } as never, + env, + ctx + ) as Promise; + }, + setReactionScript(input) { + return manageWatchers( + { action: "set_reaction_script", ...input } as never, + env, + ctx + ) as Promise; + }, + completeWindow(input) { + return manageWatchers( + { action: "complete_window", ...input } as never, + env, + ctx + ) as Promise; + }, + }; +} diff --git a/packages/owletto-backend/src/sandbox/run-script.ts b/packages/owletto-backend/src/sandbox/run-script.ts new file mode 100644 index 000000000..474e325f7 --- /dev/null +++ b/packages/owletto-backend/src/sandbox/run-script.ts @@ -0,0 +1,141 @@ +/** + * Isolated-vm script runner. + * + * Compiles a TypeScript user-script via esbuild, runs it inside a V8 isolate + * with a bridge back to the host `ClientSDK`, and returns a structured result. + * + * Scope of PR-1: shape + skeleton. PR-2 wires this into `execute` and + * reactions. The native `isolated-vm` module is an **optional** dependency so + * the package installs cleanly on platforms without a matching prebuild; the + * module is loaded lazily at first call. + * + * Design invariants + * - Every call creates a fresh Isolate and disposes it. No pooling in v1. + * - Memory cap enforced by V8; CPU interrupts via `script.run({ timeout })`. + * - SDK calls cross the isolate boundary as JSON (ExternalCopy + Reference). + * - Console logs and the return value are captured and returned structurally. + */ + +import type { ClientSDK } from "./client-sdk"; + +/** Hard limits enforced by the runner. Callers can lower but not raise. */ +export interface RunLimits { + /** V8 isolate heap cap, MB. Default 64. */ + memoryMb?: number; + /** Wall-clock budget, ms. Default 60_000. */ + timeoutMs?: number; + /** SDK call quota. Scripts exceeding throw QuotaExceeded. Default 200. */ + sdkCallQuota?: number; + /** Captured output size cap (logs + return value), bytes. Default 262_144. */ + outputBytes?: number; +} + +export interface RunScriptOptions { + /** TypeScript source of the user script. esbuild compiles to CJS + esnext target. */ + source: string; + /** Injected into the guest as `ctx`. JSON-serializable. */ + context?: Record; + /** Host SDK the guest calls via the bridge. */ + sdk: ClientSDK; + limits?: RunLimits; + /** Entry-point function name exposed by user source. Default 'default'. */ + entryPoint?: "default" | "react"; +} + +export interface LogEntry { + level: "log" | "warn" | "error"; + message: string; + data?: Record; + ts: number; +} + +export interface RunScriptResult { + success: boolean; + returnValue?: unknown; + logs: LogEntry[]; + error?: { + name: string; + message: string; + stack?: string; + line?: number; + column?: number; + }; + durationMs: number; + sdkCalls: number; +} + +const DEFAULT_LIMITS: Required = { + memoryMb: 64, + timeoutMs: 60_000, + sdkCallQuota: 200, + outputBytes: 262_144, +}; + +/** + * Load `isolated-vm` lazily. Returns null when the optional native module is + * not installed (e.g. local dev on a Node version without a prebuild). Callers + * surface a clear error so users know how to remediate. + */ +async function loadIsolatedVm(): Promise { + try { + const mod = await import("isolated-vm"); + return mod; + } catch { + return null; + } +} + +/** + * Run a user script inside a V8 isolate. Skeleton implementation for PR-1 — + * the host-side SDK bridge and esbuild pipeline land in PR-2. Today returns a + * clear error if invoked so tests can verify the code path without requiring + * the native module. + */ +export async function runScript( + options: RunScriptOptions +): Promise { + const started = Date.now(); + const limits = { ...DEFAULT_LIMITS, ...(options.limits ?? {}) }; + + const ivm = await loadIsolatedVm(); + if (!ivm) { + return { + success: false, + logs: [], + error: { + name: "RuntimeUnavailable", + message: + "isolated-vm is not installed for this platform. Install with `bun install` on a supported Node version (18–24 with prebuilt binaries, or any version with python3 + build-essential available).", + }, + durationMs: Date.now() - started, + sdkCalls: 0, + }; + } + + // PR-1 ships the module wiring; the full esbuild + bridge implementation + // lands in PR-2. For now, this short-circuits with a descriptive error so + // tests can assert the loader path and limits shape without depending on + // the bridge. + void limits; + void options.sdk; + void options.source; + void options.context; + void options.entryPoint; + + return { + success: false, + logs: [], + error: { + name: "NotImplemented", + message: + "runScript implementation lands in PR-2 (execute + search tool wiring).", + }, + durationMs: Date.now() - started, + sdkCalls: 0, + }; +} + +/** Exposed for tests that need to assert default limits without invoking the runner. */ +export function getDefaultLimits(): Required { + return { ...DEFAULT_LIMITS }; +} diff --git a/packages/owletto-backend/src/sandbox/typebox-to-signature.ts b/packages/owletto-backend/src/sandbox/typebox-to-signature.ts new file mode 100644 index 000000000..9b42fad9a --- /dev/null +++ b/packages/owletto-backend/src/sandbox/typebox-to-signature.ts @@ -0,0 +1,107 @@ +/** + * TypeBox schema → inline TypeScript signature formatter. + * + * Used by the `search` MCP tool (PR-2) to render method signatures as + * copy-pasteable TS for LLMs. Walks a TypeBox schema and emits the + * inline-expanded type with JSDoc comments derived from `description`. + * + * Design notes + * - Enums become `'a' | 'b'` literal unions. + * - Optional/required flow from the schema. + * - Nested objects are inlined; array element types are inlined. + * - References ($ref) are not followed — emit the last path segment as an + * identifier. This keeps output bounded; search has explicit drill-down. + */ + +import type { TSchema } from "@sinclair/typebox"; + +export interface SignatureOptions { + /** Top-level indent (spaces) for nested fields. Default 2. */ + indent?: number; + /** Maximum recursion depth to prevent runaway on cyclic schemas. Default 6. */ + maxDepth?: number; +} + +export function typeboxToSignature( + schema: TSchema, + options: SignatureOptions = {} +): string { + const indent = options.indent ?? 2; + const maxDepth = options.maxDepth ?? 6; + return render(schema, 0, indent, maxDepth); +} + +function render( + schema: unknown, + depth: number, + indent: number, + maxDepth: number +): string { + if (depth > maxDepth) return "unknown"; + if (!schema || typeof schema !== "object") return "unknown"; + + const s = schema as Record; + + // Literal union via enum + if (Array.isArray(s.enum)) { + return (s.enum as unknown[]).map(formatLiteral).join(" | "); + } + + // Const literal + if (s.const !== undefined) { + return formatLiteral(s.const); + } + + // Union + if (Array.isArray(s.anyOf)) { + return (s.anyOf as unknown[]) + .map((v) => render(v, depth + 1, indent, maxDepth)) + .join(" | "); + } + if (Array.isArray(s.oneOf)) { + return (s.oneOf as unknown[]) + .map((v) => render(v, depth + 1, indent, maxDepth)) + .join(" | "); + } + + // Array + if (s.type === "array" && s.items) { + const inner = render(s.items, depth + 1, indent, maxDepth); + return inner.includes("|") || inner.includes(" ") + ? `Array<${inner}>` + : `${inner}[]`; + } + + // Object + if (s.type === "object" && s.properties && typeof s.properties === "object") { + const props = s.properties as Record; + const required = new Set((s.required as string[] | undefined) ?? []); + const keys = Object.keys(props); + if (keys.length === 0) return "Record"; + + const pad = " ".repeat(indent * (depth + 1)); + const closingPad = " ".repeat(indent * depth); + const lines = keys.map((key) => { + const prop = props[key] as Record | undefined; + const optional = required.has(key) ? "" : "?"; + const typeStr = render(prop, depth + 1, indent, maxDepth); + const description = prop?.description ? ` // ${prop.description}` : ""; + return `${pad}${key}${optional}: ${typeStr};${description}`; + }); + return `{\n${lines.join("\n")}\n${closingPad}}`; + } + + // Primitive + if (s.type === "string") return "string"; + if (s.type === "number" || s.type === "integer") return "number"; + if (s.type === "boolean") return "boolean"; + if (s.type === "null") return "null"; + + return "unknown"; +} + +function formatLiteral(value: unknown): string { + if (typeof value === "string") return `'${value.replace(/'/g, "\\'")}'`; + if (value === null) return "null"; + return String(value); +} From e896b5170eab928f18106b10ff655242a5da4e22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Emre=20Kabakc=C4=B1?= Date: Fri, 24 Apr 2026 18:30:50 +0100 Subject: [PATCH 02/19] fix(owletto-backend): audit ClientSDK against handler schemas + consolidate cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses PR-1 review from Pi and my own gap analysis. - Namespace payload alignment against real handler TypeBox schemas: * watchers.delete → watcher_ids[]; setReactionScript → reaction_script; completeWindow → window_token. watcher_id now string throughout. * entitySchema.* uses plain `slug`; addRule takes source/target_entity_type_slug + relationship_type_slug. * operations.execute.watcher_source.window_id is a number. * authProfiles keyed by slug, not numeric id; connections.connect takes auth_profile_slug; feeds.create requires feed_key. * viewTemplates.resource_id accepts string | number; classifiers uses classifier_id for CRUD, classifier_slug for classify. - Slug-vs-id resolution now tries slug first, falls back to id on miss. Drops the ID_RE regex that false-positived 20+ char slugs. - Consolidated on MultiTenantProvider's memberRoleCache (explicit invalidation on member-table writes). Separate sandbox LRU removed. Exposes getCachedMembershipRole / getCachedOrgBySlug / getOrgById. - client.query() drops the dead `params` arg — validateAndScopeQuery rejects user positional parameters and the old wrapper's merge was unsafe. Metadata example updated. - AccessDeniedError + OrgNotFoundError share a SdkError base. - method-metadata.ts covers every namespace method; the unit test asserts coverage so drift shows up in CI. - New integration test that dispatches real read-path methods through every namespace (catches the class of bug that static casts hid). - vitest.config.ts + migrations-dir walk-up so `OWLETTO_TEST_BACKEND=pglite` actually runs the integration suite. PGlite-incompatible handler queries (sql template-fragment composition, prepared-statement mismatches) run under real Postgres via a small pgOnlyIt helper. --- .../sandbox/client-sdk-org.test.ts | 122 ++++----- .../sandbox/namespace-dispatch.test.ts | 132 ++++++++++ .../src/__tests__/setup/test-db.ts | 27 +- .../unit/sandbox/membership-cache.test.ts | 109 --------- .../unit/sandbox/method-metadata.test.ts | 108 +++++++- .../owletto-backend/src/sandbox/client-sdk.ts | 192 +++++++-------- .../src/sandbox/membership-cache.ts | 74 ------ .../src/sandbox/method-metadata.ts | 231 +++++++++++++----- .../src/sandbox/namespaces/auth-profiles.ts | 40 +-- .../src/sandbox/namespaces/classifiers.ts | 87 +++++-- .../src/sandbox/namespaces/connections.ts | 33 ++- .../src/sandbox/namespaces/entity-schema.ts | 89 ++++--- .../src/sandbox/namespaces/feeds.ts | 26 +- .../src/sandbox/namespaces/operations.ts | 33 ++- .../src/sandbox/namespaces/view-templates.ts | 28 ++- .../src/sandbox/namespaces/watchers.ts | 115 ++++++--- .../src/workspace/multi-tenant.ts | 66 +++++ packages/owletto-backend/vitest.config.ts | 23 ++ 18 files changed, 951 insertions(+), 584 deletions(-) create mode 100644 packages/owletto-backend/src/__tests__/integration/sandbox/namespace-dispatch.test.ts delete mode 100644 packages/owletto-backend/src/__tests__/unit/sandbox/membership-cache.test.ts delete mode 100644 packages/owletto-backend/src/sandbox/membership-cache.ts create mode 100644 packages/owletto-backend/vitest.config.ts diff --git a/packages/owletto-backend/src/__tests__/integration/sandbox/client-sdk-org.test.ts b/packages/owletto-backend/src/__tests__/integration/sandbox/client-sdk-org.test.ts index bddae7a8b..d93c9dafa 100644 --- a/packages/owletto-backend/src/__tests__/integration/sandbox/client-sdk-org.test.ts +++ b/packages/owletto-backend/src/__tests__/integration/sandbox/client-sdk-org.test.ts @@ -1,23 +1,22 @@ /** * Integration tests for `ClientSDK.org()` — the cross-org accessor. * - * Exercises membership resolution against the real `organization`/`member` - * tables, LRU caching behavior, and the access-denied path for non-members - * on private orgs. The handler delegation itself is covered by the existing - * per-tool integration tests; these tests focus on the context-swap and - * auth-reverification semantics introduced by PR-1. + * Exercises the real `organization` / `member` tables and the shared + * auth-layer cache (`multi-tenant.ts#memberRoleCache`). Covers slug and id + * resolution, AccessDenied / OrgNotFound error shape, public-workspace + * fallback, and revocation flowing through the explicit cache invalidation. */ -import { beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, describe, expect, it } from "vitest"; +import type { Env } from "../../../index"; import { AccessDeniedError, buildClientSDK, - MembershipCache, OrgNotFoundError, resolveOrgMembership, } from "../../../sandbox/client-sdk"; -import type { Env } from "../../../index"; import type { ToolContext } from "../../../tools/registry"; +import { invalidateMembershipRoleCache } from "../../../workspace/multi-tenant"; import { cleanupTestDatabase, getTestDb } from "../../setup/test-db"; import { addUserToOrganization, @@ -62,52 +61,52 @@ describe("ClientSDK.org() accessor", () => { } describe("resolveOrgMembership", () => { - let cache: MembershipCache; - beforeEach(() => { - cache = new MembershipCache(60_000); - }); - it("resolves by slug for a member", async () => { const ctx = buildCtx(user1.id, orgA.id); - const record = await resolveOrgMembership(orgA.slug, ctx, cache); + const record = await resolveOrgMembership(orgA.slug, ctx); expect(record.orgId).toBe(orgA.id); expect(record.role).toBe("owner"); expect(record.visibility).toBe("private"); }); - it("resolves by id for a member", async () => { + it("resolves by id when slug lookup misses", async () => { const ctx = buildCtx(user1.id, orgA.id); - const record = await resolveOrgMembership(orgA.id, ctx, cache); + const record = await resolveOrgMembership(orgA.id, ctx); expect(record.slug).toBe(orgA.slug); }); it("throws AccessDenied on private org the user is not a member of", async () => { const ctx = buildCtx(user1.id, orgA.id); await expect( - resolveOrgMembership(orgB.slug, ctx, cache) + resolveOrgMembership(orgB.slug, ctx), ).rejects.toBeInstanceOf(AccessDeniedError); }); it("returns record with role=null on public org for non-members", async () => { const ctx = buildCtx(user1.id, orgA.id); - const record = await resolveOrgMembership(orgPublic.slug, ctx, cache); + const record = await resolveOrgMembership(orgPublic.slug, ctx); expect(record.visibility).toBe("public"); expect(record.role).toBeNull(); }); - it("throws OrgNotFound for an unknown slug", async () => { + it("throws OrgNotFound for an unknown slug-or-id", async () => { const ctx = buildCtx(user1.id, orgA.id); await expect( - resolveOrgMembership("does-not-exist-xyz", ctx, cache) + resolveOrgMembership("does-not-exist-xyz", ctx), ).rejects.toBeInstanceOf(OrgNotFoundError); }); - it("caches subsequent lookups for both slug and id", async () => { + it("resolves slugs that are long (no id-regex false-positive)", async () => { + const longSlug = "very-long-customer-success-platform-slug"; + const longOrg = await createTestOrganization({ + name: "Long Slug Org", + slug: longSlug, + }); + await addUserToOrganization(user1.id, longOrg.id, "member"); const ctx = buildCtx(user1.id, orgA.id); - await resolveOrgMembership(orgA.slug, ctx, cache); - // After first lookup, cache should have entries keyed by both id and slug. - expect(cache.get(user1.id, orgA.slug)).not.toBeNull(); - expect(cache.get(user1.id, orgA.id)).not.toBeNull(); + const record = await resolveOrgMembership(longSlug, ctx); + expect(record.slug).toBe(longSlug); + expect(record.role).toBe("member"); }); }); @@ -131,82 +130,57 @@ describe("ClientSDK.org() accessor", () => { expect(sdk.org).toBeInstanceOf(Function); }); - it(".org() returns a fresh SDK for another org the caller belongs to", async () => { - await addUserToOrganization(user1.id, orgB.id, "member"); - const ctx = buildCtx(user1.id, orgA.id); - const sdk = buildClientSDK(ctx, testEnv); - const sdkB = await sdk.org(orgB.slug); - expect(sdkB).toBeDefined(); - expect(sdkB).not.toBe(sdk); - expect(sdkB.org).toBeInstanceOf(Function); - // Clean up so later tests see user1 back to orgA-only where applicable. - const sql = getTestDb(); - await sql`DELETE FROM "member" WHERE "userId" = ${user1.id} AND "organizationId" = ${orgB.id}`; - }); - it(".org() throws AccessDenied on a non-member private org", async () => { const ctx = buildCtx(user1.id, orgA.id); const sdk = buildClientSDK(ctx, testEnv); await expect(sdk.org(orgB.slug)).rejects.toBeInstanceOf( - AccessDeniedError + AccessDeniedError, ); }); - it(".org() returns an SDK with memberRole=null for public orgs non-members", async () => { + it(".org() returns a public-org SDK with memberRole=null for non-members", async () => { const ctx = buildCtx(user1.id, orgA.id); - const cache = new MembershipCache(60_000); - const record = await resolveOrgMembership(orgPublic.slug, ctx, cache); - expect(record.role).toBeNull(); - // The SDK itself is built from ctx, so the org() call returns a valid - // ClientSDK — per-handler auth checks reject writes downstream. - const sdk = buildClientSDK(ctx, testEnv, { membershipCache: cache }); + const sdk = buildClientSDK(ctx, testEnv); const sdkPub = await sdk.org(orgPublic.slug); expect(sdkPub).toBeDefined(); }); + }); - it("chained .org() re-validates against the original user", async () => { - // Give user1 access to both orgA and orgB, then hop A → B → A. + describe("buildClientSDK with user1 also a member of orgB", () => { + beforeAll(async () => { await addUserToOrganization(user1.id, orgB.id, "member"); + // Clear any stale "not-a-member" negative cache from earlier tests. + invalidateMembershipRoleCache(orgB.id, user1.id); + }); + + it(".org() returns a fresh SDK for the other member org", async () => { const ctx = buildCtx(user1.id, orgA.id); const sdk = buildClientSDK(ctx, testEnv); const sdkB = await sdk.org(orgB.slug); - const sdkBackToA = await sdkB.org(orgA.slug); - expect(sdkBackToA).toBeDefined(); - const sql = getTestDb(); - await sql`DELETE FROM "member" WHERE "userId" = ${user1.id} AND "organizationId" = ${orgB.id}`; + expect(sdkB).toBeDefined(); + expect(sdkB).not.toBe(sdk); + expect(sdkB.org).toBeInstanceOf(Function); }); - it("membership cache shortcircuits a repeated .org() call", async () => { - await addUserToOrganization(user1.id, orgB.id, "member"); + it("chained .org() re-validates against the original user", async () => { const ctx = buildCtx(user1.id, orgA.id); - const cache = new MembershipCache(60_000); - const sdk = buildClientSDK(ctx, testEnv, { membershipCache: cache }); - await sdk.org(orgB.slug); - expect(cache.size()).toBeGreaterThan(0); - await sdk.org(orgB.slug); // second call hits cache; no new entries - const sizeAfter = cache.size(); - await sdk.org(orgB.id); // id lookup, already cached under id during first call - expect(cache.size()).toBe(sizeAfter); - - const sql = getTestDb(); - await sql`DELETE FROM "member" WHERE "userId" = ${user1.id} AND "organizationId" = ${orgB.id}`; + const sdk = buildClientSDK(ctx, testEnv); + const sdkB = await sdk.org(orgB.slug); + const sdkBackToA = await sdkB.org(orgA.slug); + expect(sdkBackToA).toBeDefined(); }); - it("revocation is detected after cache TTL expires", async () => { - await addUserToOrganization(user1.id, orgB.id, "member"); + it("revocation is detected after explicit cache invalidation", async () => { const ctx = buildCtx(user1.id, orgA.id); - const cache = new MembershipCache(5); // 5ms TTL - const sdk = buildClientSDK(ctx, testEnv, { membershipCache: cache }); + const sdk = buildClientSDK(ctx, testEnv); await sdk.org(orgB.slug); - // Revoke membership. const sql = getTestDb(); await sql`DELETE FROM "member" WHERE "userId" = ${user1.id} AND "organizationId" = ${orgB.id}`; + invalidateMembershipRoleCache(orgB.id, user1.id); - // Wait past TTL, then expect AccessDenied. - await new Promise((r) => setTimeout(r, 15)); await expect(sdk.org(orgB.slug)).rejects.toBeInstanceOf( - AccessDeniedError + AccessDeniedError, ); }); }); @@ -216,7 +190,7 @@ describe("ClientSDK.org() accessor", () => { const ctx = buildCtx(user2.id, orgB.id); const sdk = buildClientSDK(ctx, testEnv); await expect(sdk.org(orgA.slug)).rejects.toBeInstanceOf( - AccessDeniedError + AccessDeniedError, ); }); }); diff --git a/packages/owletto-backend/src/__tests__/integration/sandbox/namespace-dispatch.test.ts b/packages/owletto-backend/src/__tests__/integration/sandbox/namespace-dispatch.test.ts new file mode 100644 index 000000000..dabbecbc1 --- /dev/null +++ b/packages/owletto-backend/src/__tests__/integration/sandbox/namespace-dispatch.test.ts @@ -0,0 +1,132 @@ +/** + * Integration smoke test that actually dispatches through every namespace's + * read path. Catches field-name drift between the SDK wrapper and the handler + * TypeBox schema — the kind of bug static `as never` casts hide. + * + * Only read/list methods are exercised here; write smoke coverage lives in + * the per-tool integration tests that already exist in the repo. + */ + +import { beforeAll, describe, expect, it } from "vitest"; +import type { Env } from "../../../index"; +import { buildClientSDK, type ClientSDK } from "../../../sandbox/client-sdk"; +import type { ToolContext } from "../../../tools/registry"; +import { initWorkspaceProvider } from "../../../workspace"; +import { cleanupTestDatabase } from "../../setup/test-db"; +import { + addUserToOrganization, + createTestOrganization, + createTestUser, + seedSystemEntityTypes, +} from "../../setup/test-fixtures"; + +const testEnv: Env = { + ENVIRONMENT: "test", + DATABASE_URL: process.env.DATABASE_URL, +}; + +/** + * Some handlers compose postgres.js tagged-template fragments + * (`sql\`${query} ORDER BY ...\``). PGlite's socket shim treats the fragment + * as a parameter instead of inlining, which produces "Promise" as $1 and a + * syntax error. Those dispatches work on real Postgres. The test suite runs + * under PGlite by default (fast, zero-deps) so we skip those cases here. + */ +const IS_PGLITE = process.env.OWLETTO_TEST_BACKEND === "pglite"; +const pgOnlyIt = IS_PGLITE ? it.skip : it; + +describe("ClientSDK namespace dispatch (read paths)", () => { + let sdk: ClientSDK; + + beforeAll(async () => { + await cleanupTestDatabase(); + await seedSystemEntityTypes(); + await initWorkspaceProvider(); + const org = await createTestOrganization({ + name: "Dispatch Org", + slug: "dispatch-sdk", + }); + const user = await createTestUser({ + email: "dispatch-sdk@test.example.com", + }); + await addUserToOrganization(user.id, org.id, "owner"); + const ctx: ToolContext = { + organizationId: org.id, + userId: user.id, + memberRole: "owner", + isAuthenticated: true, + }; + sdk = buildClientSDK(ctx, testEnv); + }); + + pgOnlyIt("entities.list dispatches cleanly", async () => { + await expect(sdk.entities.list()).resolves.toBeDefined(); + }); + + pgOnlyIt("entitySchema.listTypes dispatches cleanly", async () => { + await expect(sdk.entitySchema.listTypes()).resolves.toBeDefined(); + }); + + it("entitySchema.listRelTypes dispatches cleanly", async () => { + await expect(sdk.entitySchema.listRelTypes()).resolves.toBeDefined(); + }); + + pgOnlyIt("connections.list dispatches cleanly", async () => { + await expect(sdk.connections.list()).resolves.toBeDefined(); + }); + + pgOnlyIt( + "connections.listConnectorDefinitions dispatches cleanly", + async () => { + await expect( + sdk.connections.listConnectorDefinitions(), + ).resolves.toBeDefined(); + }, + ); + + pgOnlyIt("feeds.list dispatches cleanly", async () => { + await expect(sdk.feeds.list()).resolves.toBeDefined(); + }); + + pgOnlyIt("authProfiles.list dispatches cleanly", async () => { + await expect(sdk.authProfiles.list()).resolves.toBeDefined(); + }); + + pgOnlyIt("operations.listAvailable dispatches cleanly", async () => { + await expect(sdk.operations.listAvailable()).resolves.toBeDefined(); + }); + + // NOTE: operations.listRuns trips a pre-existing handler bug on PGlite + // (un-awaited SQL fragment injected as $1). Covered separately once that + // handler is fixed — the wrapper dispatch itself is asserted by the + // listAvailable test above. + + pgOnlyIt("watchers.list dispatches cleanly", async () => { + await expect(sdk.watchers.list()).resolves.toBeDefined(); + }); + + pgOnlyIt("classifiers.list dispatches cleanly", async () => { + await expect(sdk.classifiers.list()).resolves.toBeDefined(); + }); + + pgOnlyIt("organizations.list dispatches cleanly", async () => { + const orgs = await sdk.organizations.list(); + expect(Array.isArray(orgs)).toBe(true); + }); + + pgOnlyIt("organizations.current returns the session org", async () => { + const current = await sdk.organizations.current(); + expect(current.slug).toBe("dispatch-sdk"); + }); + + pgOnlyIt("knowledge.search dispatches cleanly", async () => { + await expect( + sdk.knowledge.search({ query: "nothing-here-likely" }), + ).resolves.toBeDefined(); + }); + + it("query runs a scoped read-only statement", async () => { + const rows = await sdk.query("SELECT COUNT(*)::int AS n FROM entities"); + expect(Array.isArray(rows)).toBe(true); + }); +}); diff --git a/packages/owletto-backend/src/__tests__/setup/test-db.ts b/packages/owletto-backend/src/__tests__/setup/test-db.ts index 6a674b135..7533e0b8f 100644 --- a/packages/owletto-backend/src/__tests__/setup/test-db.ts +++ b/packages/owletto-backend/src/__tests__/setup/test-db.ts @@ -5,10 +5,28 @@ * Uses a separate test database to avoid affecting development data. */ -import { join } from 'node:path'; +import { existsSync } from 'node:fs'; +import { dirname, join } from 'node:path'; import postgres from 'postgres'; import { listMigrationFiles, loadMigrationUpSection } from '../../db/migration-loader'; +/** + * Walk up from startDir looking for `db/migrations`. Falls back to cwd so the + * historical behaviour (vitest invoked from repo root) still works even when + * no match is found upstream. + */ +function resolveMigrationsDir(startDir: string): string { + let dir = startDir; + for (let i = 0; i < 6; i++) { + const candidate = join(dir, 'db/migrations'); + if (existsSync(candidate)) return candidate; + const parent = dirname(dir); + if (parent === dir) break; + dir = parent; + } + return join(startDir, 'db/migrations'); +} + const TEST_SEED_USER_ID = 'test-seed-user'; const TEST_SEED_USER_EMAIL = 'test-seed-user@example.com'; const SKIP_ON_FRESH_SETUP = new Set(); @@ -75,9 +93,10 @@ export async function setupTestDatabase(): Promise { await db`CREATE EXTENSION IF NOT EXISTS "vector"`; await db`CREATE EXTENSION IF NOT EXISTS "pg_trgm"`; - // Run migrations in order - // Use process.cwd() since globalSetup runs from project root - const migrationsDir = join(process.cwd(), 'db/migrations'); + // Run migrations in order. Resolves `db/migrations` by walking up from the + // current working directory so vitest works whether invoked at the repo + // root or inside the package. + const migrationsDir = resolveMigrationsDir(process.cwd()); let migrationFiles: string[]; try { diff --git a/packages/owletto-backend/src/__tests__/unit/sandbox/membership-cache.test.ts b/packages/owletto-backend/src/__tests__/unit/sandbox/membership-cache.test.ts deleted file mode 100644 index c73000ab2..000000000 --- a/packages/owletto-backend/src/__tests__/unit/sandbox/membership-cache.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { describe, expect, it } from "bun:test"; -import { MembershipCache } from "../../../sandbox/membership-cache"; - -describe("MembershipCache", () => { - it("stores and retrieves a record by any of its keys", () => { - const cache = new MembershipCache(60_000); - cache.set("user-1", ["org_abc", "buremba"], { - orgId: "org_abc", - slug: "buremba", - role: "admin", - visibility: "private", - }); - const byId = cache.get("user-1", "org_abc"); - const bySlug = cache.get("user-1", "buremba"); - expect(byId).not.toBeNull(); - expect(bySlug).not.toBeNull(); - expect(byId?.slug).toBe("buremba"); - expect(bySlug?.orgId).toBe("org_abc"); - }); - - it("is case-insensitive on the key", () => { - const cache = new MembershipCache(60_000); - cache.set("u", ["BuRemba"], { - orgId: "x", - slug: "buremba", - role: "member", - visibility: "private", - }); - expect(cache.get("u", "buremba")).not.toBeNull(); - expect(cache.get("u", "BUREMBA")).not.toBeNull(); - }); - - it("scopes entries by userId", () => { - const cache = new MembershipCache(60_000); - cache.set("alice", ["org1"], { - orgId: "org1", - slug: "org1", - role: "admin", - visibility: "private", - }); - expect(cache.get("alice", "org1")).not.toBeNull(); - expect(cache.get("bob", "org1")).toBeNull(); - }); - - it("expires entries after TTL", async () => { - const cache = new MembershipCache(5); // 5ms - cache.set("u", ["x"], { - orgId: "x", - slug: "x", - role: "member", - visibility: "private", - }); - expect(cache.get("u", "x")).not.toBeNull(); - await new Promise((r) => setTimeout(r, 10)); - expect(cache.get("u", "x")).toBeNull(); - }); - - it("evicts oldest entries past capacity", () => { - const cache = new MembershipCache(60_000); - for (let i = 0; i < 200; i++) { - cache.set(`u-${i}`, [`org-${i}`], { - orgId: `org-${i}`, - slug: `org-${i}`, - role: "member", - visibility: "private", - }); - } - // Cap is 128; first entries should be gone. - expect(cache.size()).toBeLessThanOrEqual(128); - expect(cache.get("u-0", "org-0")).toBeNull(); - expect(cache.get("u-199", "org-199")).not.toBeNull(); - }); - - it("refreshes LRU order on get", () => { - const cache = new MembershipCache(60_000); - for (let i = 0; i < 128; i++) { - cache.set(`u-${i}`, [`org-${i}`], { - orgId: `org-${i}`, - slug: `org-${i}`, - role: "member", - visibility: "private", - }); - } - // Touch the oldest. - expect(cache.get("u-0", "org-0")).not.toBeNull(); - // Add a new one — oldest (u-1) should be evicted, not u-0. - cache.set("u-fresh", ["org-fresh"], { - orgId: "org-fresh", - slug: "org-fresh", - role: "member", - visibility: "private", - }); - expect(cache.get("u-0", "org-0")).not.toBeNull(); - expect(cache.get("u-1", "org-1")).toBeNull(); - }); - - it("clears all entries", () => { - const cache = new MembershipCache(60_000); - cache.set("u", ["a"], { - orgId: "a", - slug: "a", - role: "member", - visibility: "private", - }); - cache.clear(); - expect(cache.size()).toBe(0); - expect(cache.get("u", "a")).toBeNull(); - }); -}); diff --git a/packages/owletto-backend/src/__tests__/unit/sandbox/method-metadata.test.ts b/packages/owletto-backend/src/__tests__/unit/sandbox/method-metadata.test.ts index 097a8b17c..06eb4aaba 100644 --- a/packages/owletto-backend/src/__tests__/unit/sandbox/method-metadata.test.ts +++ b/packages/owletto-backend/src/__tests__/unit/sandbox/method-metadata.test.ts @@ -5,9 +5,107 @@ import { type MethodAccess, } from "../../../sandbox/method-metadata"; +/** + * Static surface description of every namespace method. Hand-maintained — when + * a namespace adds a method, this list must add it too, and a metadata entry + * must exist. The coverage test below enforces the latter. + */ +const NAMESPACE_METHODS: Record = { + entities: [ + "list", + "get", + "create", + "update", + "delete", + "link", + "unlink", + "updateLink", + "listLinks", + "search", + ], + entitySchema: [ + "listTypes", + "getType", + "createType", + "updateType", + "deleteType", + "auditType", + "listRelTypes", + "getRelType", + "createRelType", + "updateRelType", + "deleteRelType", + "addRule", + "removeRule", + "listRules", + ], + connections: [ + "list", + "listConnectorDefinitions", + "get", + "create", + "connect", + "update", + "delete", + "test", + "installConnector", + "uninstallConnector", + "toggleConnectorLogin", + "updateConnectorAuth", + ], + feeds: ["list", "get", "create", "update", "delete", "trigger"], + authProfiles: ["list", "get", "test", "create", "update", "delete"], + operations: [ + "listAvailable", + "execute", + "listRuns", + "getRun", + "approve", + "reject", + ], + watchers: [ + "list", + "get", + "create", + "update", + "delete", + "setReactionScript", + "completeWindow", + ], + classifiers: [ + "list", + "create", + "createVersion", + "getVersions", + "setCurrentVersion", + "generateEmbeddings", + "delete", + "classify", + ], + viewTemplates: ["get", "set", "rollback", "removeTab"], + knowledge: ["search", "save", "read"], + organizations: ["list", "current"], +}; + +/** Top-level SDK methods that aren't inside a namespace. */ +const TOP_LEVEL_METHODS = ["query", "log"] as const; + describe("method-metadata", () => { - it("is non-empty", () => { - expect(Object.keys(METHOD_METADATA).length).toBeGreaterThan(20); + it("has at least one entry per namespace method", () => { + const missing: string[] = []; + for (const [ns, methods] of Object.entries(NAMESPACE_METHODS)) { + for (const m of methods) { + const key = `${ns}.${m}`; + if (!(key in METHOD_METADATA)) missing.push(key); + } + } + expect(missing).toEqual([]); + }); + + it("has entries for top-level methods", () => { + for (const m of TOP_LEVEL_METHODS) { + expect(METHOD_METADATA).toHaveProperty(m); + } }); it("has valid access levels on every entry", () => { @@ -38,6 +136,7 @@ describe("method-metadata", () => { expect(METHOD_METADATA["operations.execute"].access).toBe("external"); expect(METHOD_METADATA["feeds.trigger"].access).toBe("external"); expect(METHOD_METADATA["connections.test"].access).toBe("external"); + expect(METHOD_METADATA["authProfiles.test"].access).toBe("external"); }); it("classifies reads correctly for known methods", () => { @@ -45,4 +144,9 @@ describe("method-metadata", () => { expect(METHOD_METADATA["watchers.list"].access).toBe("read"); expect(METHOD_METADATA["organizations.list"].access).toBe("read"); }); + + it("does not claim SQL positional parameters in the query example", () => { + const example = METHOD_METADATA.query.example ?? ""; + expect(example).not.toMatch(/\$\d+/); + }); }); diff --git a/packages/owletto-backend/src/sandbox/client-sdk.ts b/packages/owletto-backend/src/sandbox/client-sdk.ts index fa7eab021..0292b7e2f 100644 --- a/packages/owletto-backend/src/sandbox/client-sdk.ts +++ b/packages/owletto-backend/src/sandbox/client-sdk.ts @@ -3,19 +3,25 @@ * watcher reactions (PR-2 swap). Each namespace delegates to existing tool * handlers, preserving per-call auth checks and audit/change events. * - * Multi-org support is provided by `client.org(slugOrId)`: it re-validates - * membership against the `member` table, then returns a proxy SDK bound to a - * swapped `ToolContext`. Membership lookups are cached in a small LRU with a - * short TTL so a cross-org walk doesn't hammer Postgres, while still catching - * revocations within ~30 s. + * Multi-org support is provided by `client.org(slugOrId)`: it resolves the + * identifier (slug preferred, id fallback) via the auth layer's existing + * caches (see `workspace/multi-tenant.ts`), re-reads the caller's role on + * every call, and returns a proxy SDK bound to a swapped `ToolContext`. + * + * No separate LRU lives here — `MultiTenantProvider.memberRoleCache` is the + * single source of truth, which means explicit invalidations from member-write + * paths flow through to sandbox callers without extra plumbing. */ -import { getDb } from "../db/client"; import type { Env } from "../index"; import type { ToolContext } from "../tools/registry"; import { getWorkspaceProvider } from "../workspace"; +import { + getCachedMembershipRole, + getCachedOrgBySlug, + getOrgById, +} from "../workspace/multi-tenant"; import type { OrgInfo } from "../workspace/types"; -import { MembershipCache, type MembershipRecord } from "./membership-cache"; import { buildAuthProfilesNamespace, buildClassifiersNamespace, @@ -56,124 +62,104 @@ export interface ClientSDK { /** * Return a ClientSDK bound to a different organization the caller belongs - * to. Re-validates membership on each call; throws `AccessDenied` for - * private orgs the caller isn't a member of. + * to. Resolves `slugOrId` against the organization table (slug first, id + * fallback), then re-reads the caller's role from `member` via the shared + * `memberRoleCache`. Throws `AccessDenied` for private orgs the caller isn't + * a member of. * * Public-visibility orgs return an SDK with `memberRole: null` — reads * succeed, writes fail at the handler-level access check. * - * The returned SDK is fully independent: it has its own `.org()` that still - * resolves relative to the original user, so chains like - * `client.org('a').org('b')` are legal if the user is a member of both. + * Chained hops like `client.org('a').org('b')` are legal when the caller is + * a member of both; each hop re-validates against the original user. */ org(slugOrId: string): Promise; - /** Run a read-only SQL query scoped to the current organization. */ - query(sql: string, params?: unknown[]): Promise; + /** + * Run a read-only SQL query scoped to the current organization. User-side + * positional parameters are not supported (`validateAndScopeQuery` rejects + * `$N`); pass values via Handlebars `{{query.name}}` substitutions instead. + */ + query(sql: string): Promise; /** Emit a structured log entry (captured by the invocation audit row in PR-3). */ log(message: string, data?: Record): void; } -export interface BuildClientSDKOptions { - /** Pre-seeded membership cache shared across `.org()` calls in one isolate run. */ - membershipCache?: MembershipCache; -} - -// Re-export so callers can `import { MembershipCache } from '../sandbox/client-sdk'` -// if they prefer; new code should import from `./membership-cache` directly. -export { MembershipCache } from "./membership-cache"; -export type { MembershipRecord } from "./membership-cache"; - // --------------------------------------------------------------------------- -// Membership resolution +// Errors // --------------------------------------------------------------------------- -const UUID_RE = - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; -// Better Auth emits org IDs as text (not UUIDs) — we accept both UUIDs and -// opaque ID strings as "id-shaped" when they don't match a slug pattern. -const ID_RE = /^[A-Za-z0-9_-]{20,}$/; +export class SdkError extends Error { + readonly code: string; + constructor(code: string, message: string) { + super(message); + this.name = code; + this.code = code; + } +} -export class AccessDeniedError extends Error { - code = "AccessDenied" as const; +export class AccessDeniedError extends SdkError { constructor(message: string) { - super(message); - this.name = "AccessDenied"; + super("AccessDenied", message); } } -export class OrgNotFoundError extends Error { - code = "OrgNotFound" as const; +export class OrgNotFoundError extends SdkError { constructor(message: string) { - super(message); - this.name = "OrgNotFound"; + super("OrgNotFound", message); } } +// --------------------------------------------------------------------------- +// Membership resolution +// --------------------------------------------------------------------------- + +export interface ResolvedOrgMembership { + orgId: string; + slug: string; + role: string | null; + visibility: "public" | "private"; +} + /** - * Resolve an org identifier (slug or id) into a membership record, checking - * the caller's access. Throws `OrgNotFound` if the org doesn't exist and - * `AccessDenied` if the caller has no read access (non-member on a private - * org). + * Resolve an org identifier (slug or id) into a membership record. Slug is + * tried first (covers the common case and hits the auth-layer cache); on miss, + * the id path runs. Throws `OrgNotFound` if neither matches, `AccessDenied` if + * the caller has no read access (non-member on a private org). */ export async function resolveOrgMembership( slugOrId: string, ctx: ToolContext, - cache: MembershipCache -): Promise { - const cached = cache.get(ctx.userId, slugOrId); - if (cached) { - if (cached.visibility === "private" && cached.role === null) { - throw new AccessDeniedError( - `You are not a member of organization '${slugOrId}'.` - ); +): Promise { + let orgId: string; + let slug: string; + let visibility: "public" | "private"; + + const bySlug = await getCachedOrgBySlug(slugOrId); + if (bySlug) { + orgId = bySlug.id; + slug = slugOrId; + visibility = bySlug.visibility === "public" ? "public" : "private"; + } else { + const byId = await getOrgById(slugOrId); + if (!byId) { + throw new OrgNotFoundError(`Organization '${slugOrId}' not found.`); } - return cached; - } - - const sql = getDb(); - const looksLikeId = UUID_RE.test(slugOrId) || ID_RE.test(slugOrId); - const lookup = looksLikeId - ? await sql`SELECT id, slug, visibility FROM "organization" WHERE id = ${slugOrId} LIMIT 1` - : await sql`SELECT id, slug, visibility FROM "organization" WHERE slug = ${slugOrId} LIMIT 1`; - - if (lookup.length === 0) { - throw new OrgNotFoundError(`Organization '${slugOrId}' not found.`); - } - const row = lookup[0] as { - id: string; - slug: string; - visibility: "public" | "private" | string; - }; - const visibility = row.visibility === "public" ? "public" : "private"; - - let role: string | null = null; - if (ctx.userId) { - const memberRows = await sql` - SELECT role FROM "member" - WHERE "organizationId" = ${row.id} AND "userId" = ${ctx.userId} - LIMIT 1 - `; - role = memberRows.length > 0 ? (memberRows[0].role as string) : null; + orgId = slugOrId; + slug = byId.slug; + visibility = byId.visibility === "public" ? "public" : "private"; } - const record: MembershipRecord = { - orgId: row.id, - slug: row.slug, - role, - visibility, - expiresAt: 0, // set by cache - }; - // Cache under both slug and id to avoid duplicate resolutions within the TTL. - cache.set(ctx.userId, [row.id, row.slug], record); + const role = await getCachedMembershipRole(orgId, ctx.userId); if (visibility === "private" && role === null) { throw new AccessDeniedError( - `You are not a member of organization '${slugOrId}'.` + `You are not a member of organization '${slug}'.`, ); } - return record; + + return { orgId, slug, role, visibility }; } // --------------------------------------------------------------------------- @@ -183,15 +169,9 @@ export async function resolveOrgMembership( /** * Build a `ClientSDK` bound to the caller's current `ToolContext`. The SDK * exposes `.org()` which constructs a fresh `ClientSDK` after re-validating - * membership against the shared `MembershipCache`. + * membership against the shared auth-layer cache. */ -export function buildClientSDK( - ctx: ToolContext, - env: Env, - options: BuildClientSDKOptions = {} -): ClientSDK { - const cache = options.membershipCache ?? new MembershipCache(); - +export function buildClientSDK(ctx: ToolContext, env: Env): ClientSDK { const sdk: ClientSDK = { entities: buildEntitiesNamespace(ctx, env), entitySchema: buildEntitySchemaNamespace(ctx, env), @@ -206,33 +186,33 @@ export function buildClientSDK( organizations: buildOrganizationsNamespace(ctx, env), async org(slugOrId) { - const member = await resolveOrgMembership(slugOrId, ctx, cache); + const member = await resolveOrgMembership(slugOrId, ctx); const swapped: ToolContext = { ...ctx, organizationId: member.orgId, memberRole: member.role, }; - return buildClientSDK(swapped, env, { membershipCache: cache }); + return buildClientSDK(swapped, env); }, - async query(querySql, params) { - const { validateAndScopeQuery } = await import( - "../utils/execute-data-sources" - ); + async query(querySql) { + const [{ getDb }, { validateAndScopeQuery }] = await Promise.all([ + import("../db/client"), + import("../utils/execute-data-sources"), + ]); const scoped = validateAndScopeQuery(querySql, ctx.organizationId); const db = getDb(); const rows = await db.begin(async (tx) => { await tx.unsafe("SET TRANSACTION READ ONLY"); await tx.unsafe("SET LOCAL statement_timeout = '5000'"); - const merged = (scoped.params as unknown[]).concat(params ?? []); - return tx.unsafe(scoped.sql, merged); + return tx.unsafe(scoped.sql, scoped.params as unknown[]); }); return rows.map((r: Record) => ({ ...r })); }, log(message, data) { // Structured log; PR-3 routes these into the execute_invocation audit row. - // eslint-disable-next-line no-console + // biome-ignore lint/suspicious/noConsole: dev-level fallback; PR-3 swaps for a proper sink. console.log(`[client-sdk] ${message}`, data ?? {}); }, }; @@ -245,7 +225,7 @@ export function buildClientSDK( * `search` tool's preamble and the web console's header chip. */ export async function getCurrentOrgInfo( - ctx: ToolContext + ctx: ToolContext, ): Promise { const provider = getWorkspaceProvider(); const orgs = await provider.listOrganizations(undefined, ctx.userId); diff --git a/packages/owletto-backend/src/sandbox/membership-cache.ts b/packages/owletto-backend/src/sandbox/membership-cache.ts deleted file mode 100644 index e2165dd80..000000000 --- a/packages/owletto-backend/src/sandbox/membership-cache.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Small LRU cache for membership lookups performed by `client.org()`. - * - * Keeps cross-org walks cheap without hiding revocations longer than the TTL - * (default 30 s). Entries are keyed on a lowercased slug-or-id string plus the - * caller's user id, so two sessions share nothing. - */ - -export interface MembershipRecord { - orgId: string; - slug: string; - role: string | null; - visibility: "public" | "private"; - /** Unix ms expiry. Populated by the cache on set. */ - expiresAt: number; -} - -const DEFAULT_TTL_MS = 30_000; -const MAX_CACHE_ENTRIES = 128; - -export class MembershipCache { - private readonly entries = new Map(); - private readonly ttlMs: number; - - constructor(ttlMs: number = DEFAULT_TTL_MS) { - this.ttlMs = ttlMs; - } - - get(userId: string | null, key: string): MembershipRecord | null { - const cacheKey = this.key(userId, key); - const record = this.entries.get(cacheKey); - if (!record) return null; - if (record.expiresAt <= Date.now()) { - this.entries.delete(cacheKey); - return null; - } - // Refresh LRU order. - this.entries.delete(cacheKey); - this.entries.set(cacheKey, record); - return record; - } - - set( - userId: string | null, - keys: string[], - record: Omit - ): void { - const full: MembershipRecord = { - ...record, - expiresAt: Date.now() + this.ttlMs, - }; - for (const key of keys) { - const cacheKey = this.key(userId, key); - this.entries.set(cacheKey, full); - } - while (this.entries.size > MAX_CACHE_ENTRIES) { - const oldest = this.entries.keys().next().value; - if (oldest === undefined) break; - this.entries.delete(oldest); - } - } - - size(): number { - return this.entries.size; - } - - clear(): void { - this.entries.clear(); - } - - private key(userId: string | null, k: string): string { - return `${userId ?? "anon"}::${k.toLowerCase()}`; - } -} diff --git a/packages/owletto-backend/src/sandbox/method-metadata.ts b/packages/owletto-backend/src/sandbox/method-metadata.ts index a9be49dd5..caba55641 100644 --- a/packages/owletto-backend/src/sandbox/method-metadata.ts +++ b/packages/owletto-backend/src/sandbox/method-metadata.ts @@ -3,12 +3,13 @@ * * Describes each SDK method for: * - `search` MCP tool (summary, example, throws) - * - Dry-run classification (read | write | external) — PR-2 wires this into the wrapper + * - Dry-run classification (read | write | external) — PR-2 wires this into + * the wrapper so writes and external side-effects are intercepted. * - Static bans (e.g. `client.execute` must not be exposed recursively) * - * Keyed by dotted SDK path (e.g. `watchers.list`, `entities.create`). - * Populated incrementally — PR-1 seeds the shape + a handful of examples. - * PR-2 validates coverage against the runtime dispatch table in CI. + * Keyed by dotted SDK path (e.g. `watchers.list`, `entities.create`). The + * PR-1 coverage unit test asserts every method exported from a namespace has + * an entry here. */ export type MethodAccess = "read" | "write" | "external"; @@ -26,12 +27,6 @@ export interface MethodMetadata { cost?: "cheap" | "normal" | "expensive"; } -/** - * Metadata entries. Keys are dotted SDK paths. - * - * This table is intentionally sparse in PR-1 — PR-2 fills it to 100% coverage - * and adds a CI test that fails if a new SDK method ships without metadata. - */ export const METHOD_METADATA: Record = { // organizations "organizations.list": { @@ -72,7 +67,7 @@ export const METHOD_METADATA: Record = { access: "write", }, "entities.delete": { - summary: "Delete an entity.", + summary: "Delete an entity, optionally cascading to descendants.", access: "write", }, "entities.link": { @@ -83,14 +78,81 @@ export const METHOD_METADATA: Record = { summary: "Soft-delete an entity relationship.", access: "write", }, + "entities.updateLink": { + summary: "Update metadata / confidence on an existing relationship.", + access: "write", + }, + "entities.listLinks": { + summary: "List relationships for an entity.", + access: "read", + }, "entities.search": { - summary: "Search entities by name / metadata.", + summary: "Fuzzy search entities by name, optionally filtered by type.", + access: "read", + }, + + // entitySchema + "entitySchema.listTypes": { + summary: "List entity types in the organization.", + access: "read", + }, + "entitySchema.getType": { + summary: "Get an entity type by slug.", + access: "read", + }, + "entitySchema.createType": { + summary: "Create an entity type.", + access: "write", + }, + "entitySchema.updateType": { + summary: "Update an entity type.", + access: "write", + }, + "entitySchema.deleteType": { + summary: "Delete an entity type.", + access: "write", + }, + "entitySchema.auditType": { + summary: "List historical changes to an entity type.", + access: "read", + }, + "entitySchema.listRelTypes": { + summary: "List relationship types.", + access: "read", + }, + "entitySchema.getRelType": { + summary: "Get a relationship type by slug.", + access: "read", + }, + "entitySchema.createRelType": { + summary: "Create a relationship type.", + access: "write", + }, + "entitySchema.updateRelType": { + summary: "Update a relationship type.", + access: "write", + }, + "entitySchema.deleteRelType": { + summary: "Delete a relationship type.", + access: "write", + }, + "entitySchema.addRule": { + summary: + "Add an allowed source/target entity-type rule to a relationship type.", + access: "write", + }, + "entitySchema.removeRule": { + summary: "Remove a rule from a relationship type.", + access: "write", + }, + "entitySchema.listRules": { + summary: "List rules attached to a relationship type.", access: "read", }, // knowledge "knowledge.search": { - summary: "Semantic search over stored knowledge events.", + summary: "Semantic + structured search over stored knowledge events.", access: "read", example: "const hits = await client.knowledge.search({ query: 'revenue update', limit: 10 });", @@ -121,45 +183,72 @@ export const METHOD_METADATA: Record = { throws: ["EntityNotFound", "InvalidExtractionSchema"], }, "watchers.update": { - summary: "Update watcher config (model, schedule, sources).", + summary: "Update watcher config (schedule, model, sources).", access: "write", }, "watchers.delete": { - summary: "Delete a watcher.", + summary: "Delete one or more watchers.", access: "write", }, "watchers.setReactionScript": { - summary: "Attach a raw TS reaction script that fires on window completion.", + summary: + "Attach a raw TS reaction script (fires on window completion). Empty string removes it.", access: "write", throws: ["CompileError"], }, + "watchers.completeWindow": { + summary: + "Submit LLM-extracted data for a watcher window. Requires a signed window_token.", + access: "write", + }, - // connections (external side effects for execute) + // connections "connections.list": { summary: "List configured connections in the current organization.", access: "read", }, + "connections.listConnectorDefinitions": { + summary: "List connector definitions installed in this organization.", + access: "read", + }, "connections.get": { summary: "Get a connection by id.", access: "read" }, "connections.create": { - summary: "Create a new connection.", + summary: + "Create a connection manually (for connectors that do not require OAuth).", access: "write", }, "connections.connect": { summary: - "Start an OAuth flow. Returns a connect_url to share with the user.", + "Start an OAuth / auth-profile flow. Returns a connect_url to share with the user.", access: "write", }, "connections.update": { - summary: "Update connection config.", + summary: "Update connection config or auth profile.", access: "write", }, "connections.delete": { summary: "Delete a connection.", access: "write" }, "connections.test": { - summary: "Test connection credentials.", + summary: "Test connection credentials (sends an external probe).", access: "external", }, + "connections.installConnector": { + summary: "Install a connector definition into this organization.", + access: "write", + }, + "connections.uninstallConnector": { + summary: "Uninstall a connector definition.", + access: "write", + }, + "connections.toggleConnectorLogin": { + summary: "Enable/disable the login-with-connector flow.", + access: "write", + }, + "connections.updateConnectorAuth": { + summary: "Update org-wide auth config for a connector.", + access: "write", + }, - // operations (run external actions) + // operations "operations.listAvailable": { summary: "List operations exposed by the active connections.", access: "read", @@ -169,15 +258,31 @@ export const METHOD_METADATA: Record = { access: "external", cost: "expensive", }, + "operations.listRuns": { + summary: "List past operation runs.", + access: "read", + }, + "operations.getRun": { summary: "Get a single run by id.", access: "read" }, + "operations.approve": { + summary: "Approve a pending run that required human approval.", + access: "write", + }, + "operations.reject": { + summary: "Reject a pending run.", + access: "write", + }, // feeds "feeds.list": { summary: "List data-sync feeds.", access: "read" }, "feeds.get": { summary: "Get a feed by id.", access: "read" }, - "feeds.create": { summary: "Create a data-sync feed.", access: "write" }, + "feeds.create": { + summary: "Create a data-sync feed for a connection.", + access: "write", + }, "feeds.update": { summary: "Update a feed.", access: "write" }, "feeds.delete": { summary: "Delete a feed.", access: "write" }, "feeds.trigger": { - summary: "Trigger an immediate sync for a feed.", + summary: "Trigger an immediate sync for a feed (external side-effect).", access: "external", }, @@ -186,7 +291,14 @@ export const METHOD_METADATA: Record = { summary: "List reusable auth profiles.", access: "read", }, - "authProfiles.get": { summary: "Get an auth profile by id.", access: "read" }, + "authProfiles.get": { + summary: "Get an auth profile by slug.", + access: "read", + }, + "authProfiles.test": { + summary: "Test auth-profile credentials.", + access: "external", + }, "authProfiles.create": { summary: "Create an auth profile.", access: "write", @@ -199,24 +311,41 @@ export const METHOD_METADATA: Record = { summary: "Delete an auth profile.", access: "write", }, - "authProfiles.test": { - summary: "Test auth profile credentials.", - access: "external", - }, // classifiers - "classifiers.list": { summary: "List classifier templates.", access: "read" }, + "classifiers.list": { + summary: "List classifier templates.", + access: "read", + }, "classifiers.create": { summary: "Create a classifier template.", access: "write", }, + "classifiers.createVersion": { + summary: "Create a new version of an existing classifier.", + access: "write", + }, + "classifiers.getVersions": { + summary: "List versions of a classifier.", + access: "read", + }, + "classifiers.setCurrentVersion": { + summary: "Promote a version to current.", + access: "write", + }, + "classifiers.generateEmbeddings": { + summary: "Generate embeddings for attribute values (cost-heavy).", + access: "write", + cost: "expensive", + }, "classifiers.delete": { - summary: "Delete a classifier template.", + summary: "Delete a classifier.", access: "write", }, "classifiers.classify": { - summary: "Classify one or many content strings.", - access: "read", + summary: + "Apply a manual classification to one or many content records (single or batch).", + access: "write", }, // viewTemplates @@ -237,43 +366,13 @@ export const METHOD_METADATA: Record = { access: "write", }, - // entitySchema - "entitySchema.listTypes": { - summary: "List entity types in the organization.", - access: "read", - }, - "entitySchema.getType": { - summary: "Get an entity type by slug.", - access: "read", - }, - "entitySchema.createType": { - summary: "Create an entity type.", - access: "write", - }, - "entitySchema.updateType": { - summary: "Update an entity type.", - access: "write", - }, - "entitySchema.deleteType": { - summary: "Delete an entity type.", - access: "write", - }, - "entitySchema.listRelTypes": { - summary: "List relationship types.", - access: "read", - }, - "entitySchema.createRelType": { - summary: "Create a relationship type.", - access: "write", - }, - // top-level query: { summary: - "Run a read-only SQL query against the organization-scoped virtual tables.", + "Run a read-only SQL query against the organization-scoped virtual tables. No positional parameters — use Handlebars {{query.name}} substitutions inside the SQL when you need values.", access: "read", example: - 'const rows = await client.query("SELECT * FROM entities WHERE entity_type = $1", ["company"]);', + "const rows = await client.query(\"SELECT id, name FROM entities WHERE entity_type = 'company'\");", }, log: { summary: @@ -283,7 +382,7 @@ export const METHOD_METADATA: Record = { }, }; -/** Paths that must never appear as SDK methods. Enforced in PR-2 tests. */ +/** Paths that must never appear as SDK methods. Enforced by the coverage test. */ export const BANNED_PATHS = [ "execute", "client.execute", diff --git a/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts b/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts index 0fd3a390c..ac012565e 100644 --- a/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts +++ b/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts @@ -1,43 +1,55 @@ /** * ClientSDK `authProfiles` namespace. Thin wrapper over `manageAuthProfiles`. + * + * All identifiers are `auth_profile_slug: string` — the handler does not use + * numeric ids. */ import type { Env } from "../../index"; import { manageAuthProfiles } from "../../tools/admin/manage_auth_profiles"; import type { ToolContext } from "../../tools/registry"; +export type AuthProfileType = + | "env" + | "oauth_app" + | "oauth_account" + | "browser_session"; + export interface AuthProfilesNamespace { list(): Promise; - get(auth_profile_id: number): Promise; - test(auth_profile_id: number): Promise; + get(auth_profile_slug: string): Promise; + test(auth_profile_slug: string): Promise; create(input: { - name: string; + auth_profile_slug: string; + auth_type: AuthProfileType; connector_key: string; - credentials: Record; + display_name?: string; + config?: Record; }): Promise; update(input: { - auth_profile_id: number; - [key: string]: unknown; + auth_profile_slug: string; + display_name?: string; + config?: Record; }): Promise; - delete(auth_profile_id: number): Promise; + delete(auth_profile_slug: string): Promise; } export function buildAuthProfilesNamespace( ctx: ToolContext, - env: Env + env: Env, ): AuthProfilesNamespace { const call = (payload: Record): Promise => manageAuthProfiles(payload as never, env, ctx) as Promise; return { list: () => call({ action: "list_auth_profiles" }), - get: (auth_profile_id) => - call({ action: "get_auth_profile", auth_profile_id }), - test: (auth_profile_id) => - call({ action: "test_auth_profile", auth_profile_id }), + get: (auth_profile_slug) => + call({ action: "get_auth_profile", auth_profile_slug }), + test: (auth_profile_slug) => + call({ action: "test_auth_profile", auth_profile_slug }), create: (input) => call({ action: "create_auth_profile", ...input }), update: (input) => call({ action: "update_auth_profile", ...input }), - delete: (auth_profile_id) => - call({ action: "delete_auth_profile", auth_profile_id }), + delete: (auth_profile_slug) => + call({ action: "delete_auth_profile", auth_profile_slug }), }; } diff --git a/packages/owletto-backend/src/sandbox/namespaces/classifiers.ts b/packages/owletto-backend/src/sandbox/namespaces/classifiers.ts index 3b2fe8cb2..b274e32ec 100644 --- a/packages/owletto-backend/src/sandbox/namespaces/classifiers.ts +++ b/packages/owletto-backend/src/sandbox/namespaces/classifiers.ts @@ -1,54 +1,89 @@ /** * ClientSDK `classifiers` namespace. Thin wrapper over `manageClassifiers`. + * + * Most CRUD actions use `classifier_id: number`; `classify` uses + * `classifier_slug: string`. The namespace keeps those distinct. */ import type { Env } from "../../index"; import { manageClassifiers } from "../../tools/admin/manage_classifiers"; import type { ToolContext } from "../../tools/registry"; +export interface ClassifierCreateInput { + slug: string; + name: string; + description?: string; + attribute_key: string; + attribute_values?: Record; + entity_id?: number; + watcher_id: number; + min_similarity?: number; + fallback_value?: unknown; + created_by?: string; +} + +export interface ClassifierCreateVersionInput { + classifier_id: number; + name?: string; + description?: string; + attribute_values?: Record; + min_similarity?: number; + fallback_value?: unknown; + change_notes?: string; + set_as_current?: boolean; + created_by?: string; +} + +export interface ClassifierClassifyInput { + classifier_slug: string; + /** Single-mode update. */ + content_id?: number; + value?: string | null; + /** Batch mode. */ + classifications?: Array<{ + content_id: number; + value: string | null; + reasoning?: string; + }>; + source?: "llm" | "user"; + reasoning?: string; +} + export interface ClassifiersNamespace { - list(): Promise; - create(input: { - slug: string; - name: string; - [key: string]: unknown; - }): Promise; - createVersion(input: { - classifier_slug: string; - [key: string]: unknown; - }): Promise; - getVersions(classifier_slug: string): Promise; + list(input?: { entity_id?: number; status?: string }): Promise; + create(input: ClassifierCreateInput): Promise; + createVersion(input: ClassifierCreateVersionInput): Promise; + getVersions(classifier_id: number): Promise; setCurrentVersion(input: { - classifier_slug: string; - version_id: number; + classifier_id: number; + version: number; }): Promise; - generateEmbeddings(classifier_slug: string): Promise; - delete(classifier_slug: string): Promise; - classify(input: { - classifier_slug: string; - value: string | string[]; - entity_id?: number; + generateEmbeddings(input: { + classifier_id: number; + force_regenerate?: boolean; }): Promise; + delete(classifier_id: number): Promise; + classify(input: ClassifierClassifyInput): Promise; } export function buildClassifiersNamespace( ctx: ToolContext, - env: Env + env: Env, ): ClassifiersNamespace { const call = (payload: Record): Promise => manageClassifiers(payload as never, env, ctx) as Promise; return { - list: () => call({ action: "list" }), + list: (input) => call({ action: "list", ...input }), create: (input) => call({ action: "create", ...input }), createVersion: (input) => call({ action: "create_version", ...input }), - getVersions: (classifier_slug) => - call({ action: "get_versions", classifier_slug }), + getVersions: (classifier_id) => + call({ action: "get_versions", classifier_id }), setCurrentVersion: (input) => call({ action: "set_current_version", ...input }), - generateEmbeddings: (classifier_slug) => - call({ action: "generate_embeddings", classifier_slug }), - delete: (classifier_slug) => call({ action: "delete", classifier_slug }), + generateEmbeddings: (input) => + call({ action: "generate_embeddings", ...input }), + delete: (classifier_id) => call({ action: "delete", classifier_id }), classify: (input) => call({ action: "classify", ...input }), }; } diff --git a/packages/owletto-backend/src/sandbox/namespaces/connections.ts b/packages/owletto-backend/src/sandbox/namespaces/connections.ts index 367fa917b..4c14abc19 100644 --- a/packages/owletto-backend/src/sandbox/namespaces/connections.ts +++ b/packages/owletto-backend/src/sandbox/namespaces/connections.ts @@ -1,27 +1,38 @@ /** * ClientSDK `connections` namespace. Thin wrapper over `manageConnections`. + * + * `connect` is the entry point for setting up a new authenticated connection — + * the handler returns a `connect_url` that the caller must surface to the + * user's browser. Field names follow the handler schema. */ import type { Env } from "../../index"; import { manageConnections } from "../../tools/admin/manage_connections"; import type { ToolContext } from "../../tools/registry"; +export interface ConnectionsConnectInput { + connector_key: string; + display_name?: string; + auth_profile_slug?: string; +} + +export interface ConnectionsCreateInput { + connector_key: string; + display_name?: string; + config?: Record; +} + export interface ConnectionsNamespace { list(input?: { connector_key?: string }): Promise; listConnectorDefinitions(): Promise; get(connection_id: number): Promise; - create(input: { - connector_key: string; - name?: string; - config?: Record; - }): Promise; - connect(input: { - connector_key: string; - auth_profile_id?: number; - }): Promise; + create(input: ConnectionsCreateInput): Promise; + connect(input: ConnectionsConnectInput): Promise; update(input: { connection_id: number; - [key: string]: unknown; + display_name?: string; + auth_profile_slug?: string | null; + config?: Record; }): Promise; delete(connection_id: number): Promise; test(connection_id: number): Promise; @@ -42,7 +53,7 @@ export interface ConnectionsNamespace { export function buildConnectionsNamespace( ctx: ToolContext, - env: Env + env: Env, ): ConnectionsNamespace { const call = (payload: Record): Promise => manageConnections(payload as never, env, ctx) as Promise; diff --git a/packages/owletto-backend/src/sandbox/namespaces/entity-schema.ts b/packages/owletto-backend/src/sandbox/namespaces/entity-schema.ts index d590db787..b2bfae65c 100644 --- a/packages/owletto-backend/src/sandbox/namespaces/entity-schema.ts +++ b/packages/owletto-backend/src/sandbox/namespaces/entity-schema.ts @@ -1,90 +1,103 @@ /** - * ClientSDK `entitySchema` namespace. Thin wrapper over `manageEntitySchema`. + * ClientSDK `entitySchema` namespace. Delegates to `manageEntitySchema`, which + * is doubly discriminated by `schema_type` (entity_type vs relationship_type) + * and `action`. * - * The underlying handler is discriminated by `schema_type` *and* `action`; the - * namespace splits those into method names so callers don't have to track both. + * Field names mirror the handler: plain `slug` for the type identifier, + * `source_entity_type_slug` / `target_entity_type_slug` / `relationship_type_slug` + * for add_rule. */ import type { Env } from "../../index"; import { manageEntitySchema } from "../../tools/admin/manage_entity_schema"; import type { ToolContext } from "../../tools/registry"; +export interface EntitySchemaAddRuleInput { + slug: string; + source_entity_type_slug: string; + target_entity_type_slug: string; + relationship_type_slug?: string; + description?: string; +} + export interface EntitySchemaNamespace { listTypes(): Promise; - getType(entity_type_slug: string): Promise; + getType(slug: string): Promise; createType(input: { slug: string; name: string; - [key: string]: unknown; + description?: string; + icon?: string; + color?: string; + metadata_schema?: Record; + event_kinds?: Record; }): Promise; updateType(input: { - entity_type_slug: string; - [key: string]: unknown; + slug: string; + name?: string; + description?: string; + icon?: string; + color?: string; + metadata_schema?: Record; + event_kinds?: Record; }): Promise; - deleteType(entity_type_slug: string): Promise; - auditType(entity_type_slug: string): Promise; + deleteType(slug: string): Promise; + auditType(slug: string): Promise; listRelTypes(): Promise; - getRelType(relationship_type_slug: string): Promise; + getRelType(slug: string): Promise; createRelType(input: { slug: string; name: string; - [key: string]: unknown; + description?: string; + inverse_type_slug?: string | null; }): Promise; updateRelType(input: { - relationship_type_slug: string; - [key: string]: unknown; - }): Promise; - deleteRelType(relationship_type_slug: string): Promise; - addRule(input: { - relationship_type_slug: string; - [key: string]: unknown; - }): Promise; - removeRule(input: { - relationship_type_slug: string; - rule_id: number; + slug: string; + name?: string; + description?: string; + inverse_type_slug?: string | null; }): Promise; - listRules(relationship_type_slug: string): Promise; + deleteRelType(slug: string): Promise; + + addRule(input: EntitySchemaAddRuleInput): Promise; + removeRule(input: { slug: string; rule_id: number }): Promise; + listRules(slug: string): Promise; } export function buildEntitySchemaNamespace( ctx: ToolContext, - env: Env + env: Env, ): EntitySchemaNamespace { const callEntity = (payload: Record): Promise => manageEntitySchema( { schema_type: "entity_type", ...payload } as never, env, - ctx + ctx, ) as Promise; const callRel = (payload: Record): Promise => manageEntitySchema( { schema_type: "relationship_type", ...payload } as never, env, - ctx + ctx, ) as Promise; return { listTypes: () => callEntity({ action: "list" }), - getType: (entity_type_slug) => - callEntity({ action: "get", entity_type_slug }), + getType: (slug) => callEntity({ action: "get", slug }), createType: (input) => callEntity({ action: "create", ...input }), updateType: (input) => callEntity({ action: "update", ...input }), - deleteType: (entity_type_slug) => - callEntity({ action: "delete", entity_type_slug }), - auditType: (entity_type_slug) => - callEntity({ action: "audit", entity_type_slug }), + deleteType: (slug) => callEntity({ action: "delete", slug }), + auditType: (slug) => callEntity({ action: "audit", slug }), listRelTypes: () => callRel({ action: "list" }), - getRelType: (relationship_type_slug) => - callRel({ action: "get", relationship_type_slug }), + getRelType: (slug) => callRel({ action: "get", slug }), createRelType: (input) => callRel({ action: "create", ...input }), updateRelType: (input) => callRel({ action: "update", ...input }), - deleteRelType: (relationship_type_slug) => - callRel({ action: "delete", relationship_type_slug }), + deleteRelType: (slug) => callRel({ action: "delete", slug }), + addRule: (input) => callRel({ action: "add_rule", ...input }), removeRule: (input) => callRel({ action: "remove_rule", ...input }), - listRules: (relationship_type_slug) => - callRel({ action: "list_rules", relationship_type_slug }), + listRules: (slug) => callRel({ action: "list_rules", slug }), }; } diff --git a/packages/owletto-backend/src/sandbox/namespaces/feeds.ts b/packages/owletto-backend/src/sandbox/namespaces/feeds.ts index 606374453..8e610bd44 100644 --- a/packages/owletto-backend/src/sandbox/namespaces/feeds.ts +++ b/packages/owletto-backend/src/sandbox/namespaces/feeds.ts @@ -1,28 +1,42 @@ /** * ClientSDK `feeds` namespace. Thin wrapper over `manageFeeds`. + * + * `create_feed` requires `feed_key` — the connector-declared identifier for the + * data surface this feed will sync. */ import type { Env } from "../../index"; import { manageFeeds } from "../../tools/admin/manage_feeds"; import type { ToolContext } from "../../tools/registry"; +export interface FeedsCreateInput { + connection_id: number; + feed_key: string; + display_name?: string; + entity_ids?: number[]; + config?: Record; + schedule?: string; +} + export interface FeedsNamespace { list(input?: { connection_id?: number }): Promise; get(feed_id: number): Promise; - create(input: { - connection_id: number; - name: string; - schedule?: string; + create(input: FeedsCreateInput): Promise; + update(input: { + feed_id: number; + display_name?: string; + status?: string; + entity_ids?: number[]; config?: Record; + schedule?: string; }): Promise; - update(input: { feed_id: number; [key: string]: unknown }): Promise; delete(feed_id: number): Promise; trigger(feed_id: number): Promise; } export function buildFeedsNamespace( ctx: ToolContext, - env: Env + env: Env, ): FeedsNamespace { const call = (payload: Record): Promise => manageFeeds(payload as never, env, ctx) as Promise; diff --git a/packages/owletto-backend/src/sandbox/namespaces/operations.ts b/packages/owletto-backend/src/sandbox/namespaces/operations.ts index 0ca4e4cf7..5c05bcec8 100644 --- a/packages/owletto-backend/src/sandbox/namespaces/operations.ts +++ b/packages/owletto-backend/src/sandbox/namespaces/operations.ts @@ -9,28 +9,39 @@ import type { Env } from "../../index"; import { manageOperations } from "../../tools/admin/manage_operations"; import type { ToolContext } from "../../tools/registry"; +export interface OperationsExecuteInput { + connection_id: number; + operation_key: string; + input?: Record; + /** + * Watcher provenance when this operation fires from a reaction. Both ids are + * numeric. + */ + watcher_source?: { watcher_id: number; window_id: number }; +} + export interface OperationsNamespace { listAvailable(input?: { entity_id?: number }): Promise; - execute(input: { - connection_id: number; - operation_key: string; - input?: Record; - watcher_source?: { watcher_id: number; window_id: string }; - }): Promise; + execute(input: OperationsExecuteInput): Promise; listRuns(input?: { connection_id?: number; operation_key?: string; + status?: string; + approval_status?: string; limit?: number; offset?: number; }): Promise; getRun(run_id: number): Promise; - approve(run_id: number): Promise; - reject(run_id: number, reason?: string): Promise; + approve(input: { + run_id: number; + input?: Record; + }): Promise; + reject(input: { run_id: number; reason?: string }): Promise; } export function buildOperationsNamespace( ctx: ToolContext, - env: Env + env: Env, ): OperationsNamespace { const call = (payload: Record): Promise => manageOperations(payload as never, env, ctx) as Promise; @@ -40,7 +51,7 @@ export function buildOperationsNamespace( execute: (input) => call({ action: "execute", ...input }), listRuns: (input) => call({ action: "list_runs", ...input }), getRun: (run_id) => call({ action: "get_run", run_id }), - approve: (run_id) => call({ action: "approve", run_id }), - reject: (run_id, reason) => call({ action: "reject", run_id, reason }), + approve: (input) => call({ action: "approve", ...input }), + reject: (input) => call({ action: "reject", ...input }), }; } diff --git a/packages/owletto-backend/src/sandbox/namespaces/view-templates.ts b/packages/owletto-backend/src/sandbox/namespaces/view-templates.ts index 4859d0620..519108366 100644 --- a/packages/owletto-backend/src/sandbox/namespaces/view-templates.ts +++ b/packages/owletto-backend/src/sandbox/namespaces/view-templates.ts @@ -1,5 +1,8 @@ /** * ClientSDK `viewTemplates` namespace. Thin wrapper over `manageViewTemplates`. + * + * `resource_id` can be a string (entity_type slug) or a number (entity id) + * depending on `resource_type`. */ import type { Env } from "../../index"; @@ -7,33 +10,38 @@ import { manageViewTemplates } from "../../tools/admin/manage_view_templates"; import type { ToolContext } from "../../tools/registry"; type ResourceType = "entity_type" | "entity"; +type ResourceId = string | number; + +export interface ViewTemplateSetInput { + resource_type: ResourceType; + resource_id: ResourceId; + tab_name?: string; + tab_order?: number; + template: Record; + data_sources?: Record; +} export interface ViewTemplatesNamespace { get(input: { resource_type: ResourceType; - resource_id: string; - }): Promise; - set(input: { - resource_type: ResourceType; - resource_id: string; - template: Record; - data_sources?: Record; + resource_id: ResourceId; }): Promise; + set(input: ViewTemplateSetInput): Promise; rollback(input: { resource_type: ResourceType; - resource_id: string; + resource_id: ResourceId; version_id: number; }): Promise; removeTab(input: { resource_type: ResourceType; - resource_id: string; + resource_id: ResourceId; tab_name: string; }): Promise; } export function buildViewTemplatesNamespace( ctx: ToolContext, - env: Env + env: Env, ): ViewTemplatesNamespace { const call = (payload: Record): Promise => manageViewTemplates(payload as never, env, ctx) as Promise; diff --git a/packages/owletto-backend/src/sandbox/namespaces/watchers.ts b/packages/owletto-backend/src/sandbox/namespaces/watchers.ts index 35f50b1b0..d4cf83bec 100644 --- a/packages/owletto-backend/src/sandbox/namespaces/watchers.ts +++ b/packages/owletto-backend/src/sandbox/namespaces/watchers.ts @@ -1,9 +1,13 @@ /** - * ClientSDK `watchers` namespace. + * ClientSDK `watchers` namespace. Thin wrapper over `manageWatchers` + + * `listWatchers` + `getWatcher`. * - * Thin wrapper over `manageWatchers`. Covers the hot-path operations - * (list, get, create, update, delete, reaction management). Result shape - * follows the existing handler return types — PR-2 tightens the typing. + * Field names mirror the underlying handlers (see + * `packages/owletto-backend/src/tools/admin/manage_watchers.ts`): + * - `watcher_id` is a numeric string + * - `delete` takes `watcher_ids: string[]` + * - `complete_window` requires `window_token` (JWT) not a raw window id + * - `set_reaction_script` uses `reaction_script` as the source field */ import type { Env } from "../../index"; @@ -25,82 +29,127 @@ export interface WatcherCreateInput { prompt: string; extraction_schema: Record; sources?: Array<{ name: string; query: string }>; - status?: "active" | "paused" | "draft"; - granularity_seconds?: number; + schedule?: string; + slug?: string; + name?: string; + description?: string; + json_template?: Record; + keying_config?: Record; + classifiers?: Record; + condensation_prompt?: string; + reactions_guidance?: string; + agent_id?: string; + tags?: string[]; +} + +export interface WatcherUpdateInput { + watcher_id: string; + schedule?: string; + agent_id?: string; + model_config?: Record; + sources?: Array<{ name: string; query: string }>; +} + +export interface WatcherCompleteWindowInput { + watcher_id: string; + /** JWT obtained from read_knowledge(watcher_id, since, until). */ + window_token: string; + extracted_data: Record; + replace_existing?: boolean; model?: string; + run_metadata?: Record; } export interface WatchersNamespace { list(filter?: WatcherListFilter): Promise; - get(watcher_id: number): Promise; + get(watcher_id: string | number): Promise; create(input: WatcherCreateInput): Promise; - update(input: { - watcher_id: number; - [key: string]: unknown; - }): Promise; - delete(watcher_id: number): Promise; + update(input: WatcherUpdateInput): Promise; + /** + * Delete one or more watchers. Accepts a single id or an array; internally + * dispatched as `watcher_ids`. + */ + delete(watcher_id: string | string[]): Promise; setReactionScript(input: { - watcher_id: number; - source: string; - params?: Record; - }): Promise; - completeWindow(input: { - watcher_id: number; - window_id: string; - extracted_data: Record; - prompt_rendered?: string; + watcher_id: string; + /** TypeScript source. Empty string removes the script. */ + reaction_script: string; }): Promise; + completeWindow(input: WatcherCompleteWindowInput): Promise; +} + +function asWatcherIdString(v: string | number): string { + return typeof v === "number" ? String(v) : v; } export function buildWatchersNamespace( ctx: ToolContext, - env: Env + env: Env, ): WatchersNamespace { return { list(filter) { return listWatchers( (filter ?? {}) as never, env, - ctx + ctx, ) as Promise; }, async get(watcher_id) { const { getWatcher } = await import("../../tools/get_watchers"); - return getWatcher({ watcher_id } as never, env, ctx) as Promise; + return getWatcher( + { watcher_id: asWatcherIdString(watcher_id) } as never, + env, + ctx, + ) as Promise; }, create(input) { return manageWatchers( { action: "create", ...input } as never, env, - ctx + ctx, ) as Promise; }, update(input) { return manageWatchers( - { action: "update", ...input } as never, + { + action: "update", + ...input, + watcher_id: asWatcherIdString(input.watcher_id), + } as never, env, - ctx + ctx, ) as Promise; }, delete(watcher_id) { + const watcher_ids = Array.isArray(watcher_id) + ? watcher_id.map(asWatcherIdString) + : [asWatcherIdString(watcher_id)]; return manageWatchers( - { action: "delete", watcher_id } as never, + { action: "delete", watcher_ids } as never, env, - ctx + ctx, ) as Promise; }, setReactionScript(input) { return manageWatchers( - { action: "set_reaction_script", ...input } as never, + { + action: "set_reaction_script", + watcher_id: asWatcherIdString(input.watcher_id), + reaction_script: input.reaction_script, + } as never, env, - ctx + ctx, ) as Promise; }, completeWindow(input) { return manageWatchers( - { action: "complete_window", ...input } as never, + { + action: "complete_window", + ...input, + watcher_id: asWatcherIdString(input.watcher_id), + } as never, env, - ctx + ctx, ) as Promise; }, }; diff --git a/packages/owletto-backend/src/workspace/multi-tenant.ts b/packages/owletto-backend/src/workspace/multi-tenant.ts index aa60a5974..c8d59ed05 100644 --- a/packages/owletto-backend/src/workspace/multi-tenant.ts +++ b/packages/owletto-backend/src/workspace/multi-tenant.ts @@ -32,6 +32,72 @@ export function invalidateMembershipRoleCache( memberRoleCache.delete(`${organizationId}:${userId}`); } +/** + * Cache-backed membership-role lookup. Reuses the same 60s cache the auth + * middleware populates so writes on the `member` table that call + * `invalidateMembershipRoleCache` take effect for sandbox callers too. + */ +export async function getCachedMembershipRole( + organizationId: string, + userId: string | null +): Promise { + if (!userId) return null; + const key = `${organizationId}:${userId}`; + const cached = memberRoleCache.get(key); + if (cached !== undefined) return cached; + const rows = await simpleQuery( + getDb()` + SELECT role FROM "member" + WHERE "organizationId" = ${organizationId} AND "userId" = ${userId} + LIMIT 1 + ` + ); + const role = rows.length > 0 ? (rows[0].role as string) : null; + memberRoleCache.set(key, role); + return role; +} + +/** + * Cache-backed org lookup by slug. Returns `null` for unknown slugs. + */ +export async function getCachedOrgBySlug( + slug: string +): Promise<{ id: string; visibility: string } | null> { + const cached = orgSlugCache.get(slug); + if (cached) return cached; + const rows = await simpleQuery( + getDb()` + SELECT id, visibility FROM "organization" WHERE slug = ${slug} LIMIT 1 + ` + ); + if (rows.length === 0) return null; + const record = { + id: rows[0].id as string, + visibility: (rows[0].visibility as string) ?? "private", + }; + orgSlugCache.set(slug, record); + return record; +} + +/** + * Direct org lookup by id. Uncached — ids are a fallback path for the sandbox's + * `.org(slugOrId)` accessor, so the TTL cache hit rate would be near-zero. + */ +export async function getOrgById( + organizationId: string +): Promise<{ slug: string; visibility: string } | null> { + const rows = await simpleQuery( + getDb()` + SELECT slug, visibility FROM "organization" WHERE id = ${organizationId} LIMIT 1 + ` + ); + if (rows.length === 0) return null; + return { + slug: rows[0].slug as string, + visibility: (rows[0].visibility as string) ?? "private", + }; +} + /** * Test-only: clear all multi-tenant auth caches so a freshly-reset database * (new org/user/token IDs) is not shadowed by TTL'd entries from the previous run. diff --git a/packages/owletto-backend/vitest.config.ts b/packages/owletto-backend/vitest.config.ts new file mode 100644 index 000000000..f0f5fef59 --- /dev/null +++ b/packages/owletto-backend/vitest.config.ts @@ -0,0 +1,23 @@ +import { fileURLToPath } from "node:url"; +import { defineConfig } from "vitest/config"; + +const PACKAGE_ROOT = fileURLToPath(new URL(".", import.meta.url)); + +export default defineConfig({ + // Anchor vitest to this package so test-db.ts's `process.cwd()`-based + // migration resolution works the same way whether `vitest` is invoked from + // the repo root (e.g. via `bunx vitest --config packages/...`) or from + // inside the package. + root: PACKAGE_ROOT, + test: { + globalSetup: ["./src/__tests__/setup/global-setup.ts"], + // Integration tests need a DB ready before any test file starts. Unit tests + // don't touch the DB, so they run fast regardless. + include: ["src/**/*.test.ts"], + // bun:test-style unit tests live alongside vitest integration tests — skip + // those for vitest. They run via `bun test` (see the existing CI command). + exclude: ["src/__tests__/unit/**", "**/node_modules/**", "**/dist/**"], + testTimeout: 30_000, + hookTimeout: 60_000, + }, +}); From 0b43ac115303ad7f4ba7c169be48a7743e10d0a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Emre=20Kabakc=C4=B1?= Date: Fri, 24 Apr 2026 18:39:24 +0100 Subject: [PATCH 03/19] fix(owletto-backend): remaining namespace contract drift + serial integration runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second Pi review caught four more wrapper lies: - authProfiles.create/update now mirror the handler: `profile_kind` (not `auth_type`), `display_name` required on create, `credentials` + `auth_data` instead of a generic `config`. - viewTemplates.set exposes a single `json_template` (which may nest `data_sources`) instead of two separate fields; rollback takes `version` (a number) not `version_id`. - connections.updateConnectorAuth uses `auth_values` (the handler contract), not `auth_config`. - connections.installConnector drops the required `connector_key` — handler picks from `source_url` / `source_uri` / `source_code` / `mcp_url` and accepts optional `auth_values`. - Added app_auth_profile_slug + config to connections.{create, connect, update} for parity with the handler. vitest.config.ts: force singleFork pool so the two integration files can't stomp each other's fixtures via cleanupTestDatabase when run in parallel. --- .../src/sandbox/namespaces/auth-profiles.ts | 38 ++++++++++------- .../src/sandbox/namespaces/connections.ts | 42 ++++++++++++++----- .../src/sandbox/namespaces/view-templates.ts | 13 ++++-- packages/owletto-backend/vitest.config.ts | 5 +++ 4 files changed, 68 insertions(+), 30 deletions(-) diff --git a/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts b/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts index ac012565e..9bc2dda17 100644 --- a/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts +++ b/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts @@ -1,36 +1,44 @@ /** * ClientSDK `authProfiles` namespace. Thin wrapper over `manageAuthProfiles`. * - * All identifiers are `auth_profile_slug: string` — the handler does not use - * numeric ids. + * Field names follow the handler schema: + * - `auth_profile_slug: string` identifies the profile (no numeric ids) + * - `profile_kind` discriminates the auth mechanism + * - `credentials` (static key/value map) vs `auth_data` (OAuth/browser) */ import type { Env } from "../../index"; import { manageAuthProfiles } from "../../tools/admin/manage_auth_profiles"; import type { ToolContext } from "../../tools/registry"; -export type AuthProfileType = +export type AuthProfileKind = | "env" | "oauth_app" | "oauth_account" | "browser_session"; +export interface AuthProfileCreateInput { + auth_profile_slug: string; + profile_kind: AuthProfileKind; + connector_key: string; + display_name: string; + credentials?: Record; + auth_data?: Record; +} + +export interface AuthProfileUpdateInput { + auth_profile_slug: string; + display_name?: string; + credentials?: Record; + auth_data?: Record; +} + export interface AuthProfilesNamespace { list(): Promise; get(auth_profile_slug: string): Promise; test(auth_profile_slug: string): Promise; - create(input: { - auth_profile_slug: string; - auth_type: AuthProfileType; - connector_key: string; - display_name?: string; - config?: Record; - }): Promise; - update(input: { - auth_profile_slug: string; - display_name?: string; - config?: Record; - }): Promise; + create(input: AuthProfileCreateInput): Promise; + update(input: AuthProfileUpdateInput): Promise; delete(auth_profile_slug: string): Promise; } diff --git a/packages/owletto-backend/src/sandbox/namespaces/connections.ts b/packages/owletto-backend/src/sandbox/namespaces/connections.ts index 4c14abc19..712870344 100644 --- a/packages/owletto-backend/src/sandbox/namespaces/connections.ts +++ b/packages/owletto-backend/src/sandbox/namespaces/connections.ts @@ -14,12 +14,40 @@ export interface ConnectionsConnectInput { connector_key: string; display_name?: string; auth_profile_slug?: string; + app_auth_profile_slug?: string; + config?: Record; } export interface ConnectionsCreateInput { connector_key: string; display_name?: string; + auth_profile_slug?: string; + app_auth_profile_slug?: string; config?: Record; + created_by?: string; +} + +export interface ConnectionsUpdateInput { + connection_id: number; + display_name?: string; + status?: string; + auth_profile_slug?: string | null; + app_auth_profile_slug?: string | null; + config?: Record; +} + +/** + * `install_connector` accepts any of `source_url`, `source_uri`, `source_code`, + * or `mcp_url`. The handler picks the first non-null source. `auth_values` is + * an optional key-value map the handler stores as auth profiles. + */ +export interface ConnectionsInstallConnectorInput { + source_url?: string; + source_uri?: string; + source_code?: string; + compiled?: boolean; + mcp_url?: string; + auth_values?: Record; } export interface ConnectionsNamespace { @@ -28,18 +56,10 @@ export interface ConnectionsNamespace { get(connection_id: number): Promise; create(input: ConnectionsCreateInput): Promise; connect(input: ConnectionsConnectInput): Promise; - update(input: { - connection_id: number; - display_name?: string; - auth_profile_slug?: string | null; - config?: Record; - }): Promise; + update(input: ConnectionsUpdateInput): Promise; delete(connection_id: number): Promise; test(connection_id: number): Promise; - installConnector(input: { - connector_key: string; - source_url?: string; - }): Promise; + installConnector(input: ConnectionsInstallConnectorInput): Promise; uninstallConnector(connector_key: string): Promise; toggleConnectorLogin(input: { connector_key: string; @@ -47,7 +67,7 @@ export interface ConnectionsNamespace { }): Promise; updateConnectorAuth(input: { connector_key: string; - auth_config: Record; + auth_values: Record; }): Promise; } diff --git a/packages/owletto-backend/src/sandbox/namespaces/view-templates.ts b/packages/owletto-backend/src/sandbox/namespaces/view-templates.ts index 519108366..723b392e2 100644 --- a/packages/owletto-backend/src/sandbox/namespaces/view-templates.ts +++ b/packages/owletto-backend/src/sandbox/namespaces/view-templates.ts @@ -2,7 +2,9 @@ * ClientSDK `viewTemplates` namespace. Thin wrapper over `manageViewTemplates`. * * `resource_id` can be a string (entity_type slug) or a number (entity id) - * depending on `resource_type`. + * depending on `resource_type`. The handler stores the whole template as a + * single `json_template` object — callers may nest a `data_sources` key + * inside it when they want SQL-backed sources. */ import type { Env } from "../../index"; @@ -15,22 +17,25 @@ type ResourceId = string | number; export interface ViewTemplateSetInput { resource_type: ResourceType; resource_id: ResourceId; + json_template: Record; tab_name?: string; tab_order?: number; - template: Record; - data_sources?: Record; + change_notes?: string; } export interface ViewTemplatesNamespace { get(input: { resource_type: ResourceType; resource_id: ResourceId; + tab_name?: string; }): Promise; set(input: ViewTemplateSetInput): Promise; rollback(input: { resource_type: ResourceType; resource_id: ResourceId; - version_id: number; + /** Version number (not the row id) to roll back to. */ + version: number; + tab_name?: string; }): Promise; removeTab(input: { resource_type: ResourceType; diff --git a/packages/owletto-backend/vitest.config.ts b/packages/owletto-backend/vitest.config.ts index f0f5fef59..28d3e44ba 100644 --- a/packages/owletto-backend/vitest.config.ts +++ b/packages/owletto-backend/vitest.config.ts @@ -19,5 +19,10 @@ export default defineConfig({ exclude: ["src/__tests__/unit/**", "**/node_modules/**", "**/dist/**"], testTimeout: 30_000, hookTimeout: 60_000, + // Integration tests share one Postgres/PGlite. Running multiple files in + // parallel means one file's `cleanupTestDatabase()` can wipe another file's + // fixtures mid-run. Serialize files so fixtures stay stable. + pool: "forks", + poolOptions: { forks: { singleFork: true } }, }, }); From 5fd50c2349c5dd52ee4e405f68d8bbeabad809a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Emre=20Kabakc=C4=B1?= Date: Fri, 24 Apr 2026 18:42:11 +0100 Subject: [PATCH 04/19] fix(owletto-backend): authProfiles.create uses `slug` (not `auth_profile_slug`) Third-pass Pi review caught the asymmetry: the handler's create_auth_profile action reads `args.slug` to set the new profile's stable identifier, while get/test/update/delete read `auth_profile_slug` to identify an existing profile. Wrapper now mirrors the handler verbatim: - create: takes optional `slug` + `display_name` + `profile_kind` + `credentials`/`auth_data`/`requested_scopes`. - update: takes `auth_profile_slug` (required, identifies the row) + all the mutable fields, including optional new `slug` for renames. - get/test/delete: positional `auth_profile_slug`. - list: adds connector_key/provider/profile_kind filters for parity. Without this fix, `client.authProfiles.create({ slug: 'primary' })` would silently auto-derive the slug from display_name, and follow-up get('primary') would miss. Typed contract now matches runtime. --- .../src/sandbox/namespaces/auth-profiles.ts | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts b/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts index 9bc2dda17..e1b0cc301 100644 --- a/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts +++ b/packages/owletto-backend/src/sandbox/namespaces/auth-profiles.ts @@ -1,10 +1,14 @@ /** * ClientSDK `authProfiles` namespace. Thin wrapper over `manageAuthProfiles`. * - * Field names follow the handler schema: - * - `auth_profile_slug: string` identifies the profile (no numeric ids) - * - `profile_kind` discriminates the auth mechanism - * - `credentials` (static key/value map) vs `auth_data` (OAuth/browser) + * Field-name conventions mirror the handler schema exactly: + * - `create` takes an optional `slug` for the new profile (the handler + * auto-derives one from display_name if omitted). + * - `get`, `test`, `delete` look profiles up by `auth_profile_slug`. + * - `update` takes the existing `auth_profile_slug` plus an optional + * new `slug` if the caller wants to rename. + * - Credentials use `credentials` (key/value) or `auth_data` (OAuth/browser + * session state). */ import type { Env } from "../../index"; @@ -18,28 +22,43 @@ export type AuthProfileKind = | "browser_session"; export interface AuthProfileCreateInput { - auth_profile_slug: string; profile_kind: AuthProfileKind; connector_key: string; display_name: string; + /** Optional stable slug for the new profile. Auto-derived when omitted. */ + slug?: string; credentials?: Record; auth_data?: Record; + requested_scopes?: string[]; } export interface AuthProfileUpdateInput { + /** Identifies the profile to mutate. */ auth_profile_slug: string; display_name?: string; + /** Rename the profile. */ + slug?: string; credentials?: Record; auth_data?: Record; + requested_scopes?: string[]; + status?: string; + reconnect?: boolean; } export interface AuthProfilesNamespace { - list(): Promise; + list(input?: { + connector_key?: string; + provider?: string; + profile_kind?: AuthProfileKind; + }): Promise; get(auth_profile_slug: string): Promise; test(auth_profile_slug: string): Promise; create(input: AuthProfileCreateInput): Promise; update(input: AuthProfileUpdateInput): Promise; - delete(auth_profile_slug: string): Promise; + delete( + auth_profile_slug: string, + options?: { force?: boolean }, + ): Promise; } export function buildAuthProfilesNamespace( @@ -50,14 +69,18 @@ export function buildAuthProfilesNamespace( manageAuthProfiles(payload as never, env, ctx) as Promise; return { - list: () => call({ action: "list_auth_profiles" }), + list: (input) => call({ action: "list_auth_profiles", ...input }), get: (auth_profile_slug) => call({ action: "get_auth_profile", auth_profile_slug }), test: (auth_profile_slug) => call({ action: "test_auth_profile", auth_profile_slug }), create: (input) => call({ action: "create_auth_profile", ...input }), update: (input) => call({ action: "update_auth_profile", ...input }), - delete: (auth_profile_slug) => - call({ action: "delete_auth_profile", auth_profile_slug }), + delete: (auth_profile_slug, options) => + call({ + action: "delete_auth_profile", + auth_profile_slug, + ...options, + }), }; } From 2b8782998a25a400c40e68e4e08dde941ddc1a05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Emre=20Kabakc=C4=B1?= Date: Fri, 24 Apr 2026 18:46:54 +0100 Subject: [PATCH 05/19] chore(owletto-backend): drop unused env arg from organizations namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pi final-pass nit. Per the repo's convention (AGENTS.md §"When fixing unused-parameter errors, delete the parameter rather than prefixing with `_`"), drop `_env` from buildOrganizationsNamespace. Call site in client-sdk.ts adjusted. --- packages/owletto-backend/src/sandbox/client-sdk.ts | 2 +- .../owletto-backend/src/sandbox/namespaces/organizations.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/owletto-backend/src/sandbox/client-sdk.ts b/packages/owletto-backend/src/sandbox/client-sdk.ts index 0292b7e2f..168fe709a 100644 --- a/packages/owletto-backend/src/sandbox/client-sdk.ts +++ b/packages/owletto-backend/src/sandbox/client-sdk.ts @@ -183,7 +183,7 @@ export function buildClientSDK(ctx: ToolContext, env: Env): ClientSDK { classifiers: buildClassifiersNamespace(ctx, env), viewTemplates: buildViewTemplatesNamespace(ctx, env), knowledge: buildKnowledgeNamespace(ctx, env), - organizations: buildOrganizationsNamespace(ctx, env), + organizations: buildOrganizationsNamespace(ctx), async org(slugOrId) { const member = await resolveOrgMembership(slugOrId, ctx); diff --git a/packages/owletto-backend/src/sandbox/namespaces/organizations.ts b/packages/owletto-backend/src/sandbox/namespaces/organizations.ts index f2292a325..52f50b417 100644 --- a/packages/owletto-backend/src/sandbox/namespaces/organizations.ts +++ b/packages/owletto-backend/src/sandbox/namespaces/organizations.ts @@ -6,7 +6,6 @@ * `client-sdk.ts`) handles the actual cross-org context swap. */ -import type { Env } from "../../index"; import type { ToolContext } from "../../tools/registry"; import { getWorkspaceProvider } from "../../workspace"; import type { OrgInfo } from "../../workspace/types"; @@ -34,7 +33,6 @@ export interface OrganizationsNamespace { export function buildOrganizationsNamespace( ctx: ToolContext, - _env: Env ): OrganizationsNamespace { return { async list(options) { From aec6b26d309f01976d77fce00c06cbf2501c41fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Emre=20Kabakc=C4=B1?= Date: Fri, 24 Apr 2026 22:56:11 +0100 Subject: [PATCH 06/19] docs(plans): land multi-org execute + search plan alongside PR-1 scaffolding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plan content was originally on docs/mcp-multi-org-plan (PR #346) but that PR was overhead for a solo land — folding the doc into the same PR as its first implementation step. PRs 2–5 will reference this file. --- docs/plans/mcp-multi-org-and-execute.md | 332 ++++++++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 docs/plans/mcp-multi-org-and-execute.md diff --git a/docs/plans/mcp-multi-org-and-execute.md b/docs/plans/mcp-multi-org-and-execute.md new file mode 100644 index 000000000..272f8fc19 --- /dev/null +++ b/docs/plans/mcp-multi-org-and-execute.md @@ -0,0 +1,332 @@ +# MCP multi-org + `execute`/`search`: addendum to the search-execute design doc + +Extends `docs/mcp-search-execute-design-doc.md` (owletto proper, status "Planned, not yet implemented") with two scopes the original didn't fully land: (1) cross-org addressing inside `execute`, and (2) the full frontend + UX surface the new tools imply. Language decision: **TypeScript over a typed `ClientSDK` in `isolated-vm`** — reviewed by a second and third opinion (codex, pi), both concurred. Bash-as-primary was evaluated and rejected because reactions are the real workload and shell quoting degrades stored user code. + +Target repo for implementation: `packages/owletto-backend` + `packages/owletto-web` in the `lobu` monorepo. The owletto repo is deprecated. + +## Decisions locked in + +- `execute` runtime: `isolated-vm` V8 isolate (not bash, not node:vm, not subprocess). +- `execute` authoring language: TypeScript compiled via esbuild, same path as today's reaction scripts. +- Cross-org addressing: `client.org(slugOrId)` accessor returning a proxy SDK bound to a re-validated `ToolContext`. No per-tool `org_slug` parameter. +- Access level for `execute`: `write` (member-tier), not `admin`. Per-call `checkToolAccess` on every SDK method is the actual gate. +- `search` + `execute` exposed on **both** scoped (`/mcp/{slug}`) and unscoped (`/mcp`) endpoints. Scoped is the default for Claude/Cursor connectors today. +- `list_organizations` + `switch_organization` exposed on scoped endpoints too. Non-script users and read-tier members still need the serial-hop path. Rename `join_organization` → drop it (semantically it's a switch when the user is already a member, and a no-op entry when they're not). + +## Why TypeScript, short version + +Bash + a CLI was seriously considered. Rejected on four axes: + +1. **Reactions are stored, deferred user code.** Picking bash means stored reactions inherit shell quoting, pipe-failure semantics, `jq` shape drift, CLI version skew, and re-auth overhead — all as part of the durable product surface. Typed TS with TypeBox `Value.Errors` gives the model field-level repair signal. +2. **Multi-org efficiency.** One SDK client holds session context, caches membership, and reuses an auth handshake across N orgs. Bash turns a cross-org walk into N CLI invocations with N auth handshakes and N JSON parses. +3. **Runtime compounding.** Reactions today are TS source compiled by esbuild, stored in DB. `execute` and reactions sharing one SDK + one sandbox collapses two runtimes into one. Splitting them would cost a second sandbox forever. +4. **Agent fluency is an affordance problem.** LLMs write bash more natively than typed SDKs, but LLMs also repair typed errors far faster than shell errors. Solve fluency with tiny authored surface (one global `client`, top-level `await`, plain objects) and `search` returning copy-pasteable signatures — not by changing language. + +## Cross-org SDK: the `client.org()` accessor + +Today's `ClientSDK` is built once per request with a fixed `toolCtx.organizationId`. The cross-org accessor returns a proxy bound to a fresh `ToolContext`: + +```ts +export default async (ctx, client) => { + const orgs = await client.organizations.list(); // user's memberships + public orgs they can read + const buremba = orgs.find(o => o.slug === 'buremba'); + if (!buremba) throw new Error('buremba not found'); + const watchers = await client.org(buremba.id).watchers.list({ template: 'reddit' }); + return watchers.filter(w => w.status !== 'active' || w.pending > 0); +}; +``` + +Contract: + +- `client.org(slugOrId)` returns `ClientSDK`. Accepts slug or UUID. First call per `(userId, orgId)` tuple verifies membership against `member`; subsequent calls within the same isolate hit an in-process LRU cache keyed by `(userId, orgId)` with a 30s TTL. +- Membership lookup populates `memberRole` (`owner | admin | member`) into the swapped `ToolContext`. Public-visibility orgs the user isn't a member of return a `memberRole: null` context, and the existing `isPublicReadable(toolName, args)` path in `src/auth/tool-access.ts` gates writes. +- Non-member on a private org: `.org()` throws `AccessDenied` synchronously, before any SDK method dispatches. +- `client.organizations.{list, current}` — new SDK namespace. `list` wraps `listOrganizations`; `current` returns the session's default org. +- The `ctx` passed to `execute` scripts carries `organization_id` = the session's default org (pinned URL or last `switch_organization`). `client` with no `.org()` call uses that same default. + +Authz invariants preserved: + +- Every SDK method still fires `checkToolAccess(toolName, args, ctx)`. The org swap changes `ctx`, never bypasses the check. +- Membership is re-verified on each `.org()` call, not cached across calls for >30s. A script that calls `.org(X).entities.create()` after membership was revoked mid-script fails on the second call. +- Public-workspace scripts (`role: null` on session default) can read but never write, same as today's tool surface. + +## `execute` access level: write, not admin + +The original design doc says `getRequiredAccessLevel('execute') = 'admin'`. Flip to `write`. Rationale: + +- Per-method access checks already exist in the SDK dispatch. A member running `execute` can call any method they could call as a direct tool — composition does not create new authorization. +- An `admin`-only gate on `execute` would force members onto the aggregation-tool path we're explicitly killing. Members either deserve scripted composition or they don't. +- Read-tier sessions (no `mcp:write` scope or no member role) still can't call write methods inside a script — the first write attempt fails mid-execution with a typed `AccessDenied`. Partial side effects already committed remain committed; no two-phase rollback. +- Bare-minimum entry gate: `execute` requires authentication. Read-tier sessions can run read-only scripts. Write-tier runs anything their per-method access permits. + +Public-workspace callers (`role: null`) can run `execute` but every write method throws. `search` is read-only and available to everyone. + +## Scoped-endpoint UX fix: expose org tools everywhere + +Drop the "org-switching tools only on /mcp" rule in `src/tools/execute.ts` (`ORG_AGNOSTIC_TOOLS`). Expose `list_organizations` + `switch_organization` on `/mcp/{slug}` too. Reasons: + +- On a scoped URL the default org is the pinned one, but nothing is actually at risk by letting the user list memberships or switch mid-session. The pin is ergonomic, not a hard wall. +- Drop `join_organization` entirely. For already-authenticated users on a scoped URL, "join" is a misnomer — they're either already a member (no-op), or not (should fail with a "not a member" error, identical to `switch_organization`'s behavior). One tool, one semantic. + +Session-resume behavior (`src/mcp-handler.ts` line 356) still rejects cross-scope recovery (scoped ↔ unscoped mismatch). Unchanged — that's correct. + +## Authoring affordances + +These make "LLMs write bash more fluently than typed SDKs" a non-concern: + +- **Tiny surface in authored scripts.** `export default async (ctx, client) => { ... }`. One `client` global. No imports. Top-level `await` supported via esbuild's `format: 'esm'` wrapper. Plain objects/arrays. No classes, no decorators, no framework ceremony. +- **`search("ns.method")` returns signature + copy-pasteable example.** The design doc already specifies inline TypeBox-derived signatures. Extend each method's metadata with a minimal example literal: + + ```ts + // Example: + // const w = await client.watchers.list({ entity_id: 42, status: 'active' }); + ``` + + Stored in `src/sandbox/method-metadata.ts` next to the summary/throws annotations. +- **Structured errors keyed for repair.** TypeBox `Value.Errors` surface as: + + ```ts + { name: 'ValidationError', method: 'watchers.create', + fields: [{ path: 'extraction_schema', expected: 'object', got: 'string', + example: { type: 'object', properties: { ... } } }] } + ``` + + The `example` field on validation errors nudges the model to the right shape on retry. +- **Dry-run first-class.** `client.watchers.testReaction` already exists in the design doc. Add `execute` dry-run mode too: `{ script, dry_run: true }` runs under the same write-interception wrapper that reactions use, returning the `would_have` list without committing. Cost is one wrapper branch, same SDK. + +## Frontend plan + +Two new surfaces in `packages/owletto-web`, plus two upgrades. + +### New: `/[owner]/tools/execute` — script console + +A first-class execute + search page. Inspired by SQL console patterns; no equivalent exists today. + +- Monaco editor with TypeScript language mode. Seeded with the standard preamble: `export default async (ctx, client) => {`. +- Inline signature panel on the right — driven by the `search` tool. Search box + namespace tree. Selecting a method injects its example into the editor at cursor. +- Org selector dropdown (top of page) — defaults to session org, switches the default `ctx.organization_id` for the run. Independent of the `client.org()` in-script accessor (which overrides per-call). +- "Dry-run" button (writes intercepted, surfaced as `would_have` list) and "Run" button. Results pane below with structured JSON output, `logs` array, `error` with line/col mapping back to user source. +- Visible run history (last 20 per org) — click to reload a script. Stored per-user in localStorage initially; DB-backed later if needed. + +Files: +- `src/app/[owner]/tools/execute/page.tsx` (new route) +- `src/components/tools/execute-console/{editor,signature-panel,results-pane,run-history}.tsx` +- `src/hooks/use-execute.ts` → POSTs `{ script, dry_run, org_slug }` to `/api/mcp/execute` (internal proxy to the backend's MCP `execute` tool). + +### New: `/[owner]/settings/organizations` — org membership + invites + +Today org CRUD is a dropdown overlay. A dedicated page is needed for cross-org work: + +- Tab "Members" — list members of the current org with roles. +- Tab "Invites" — pending `invitation` rows sent to this user's email. Accept/decline. +- Tab "Your Organizations" — flat list of all orgs the user belongs to with direct-link switch. +- Tab "Delete" (owners only). + +Files: +- `src/app/[owner]/settings/organizations/page.tsx` +- `src/components/settings/organizations/{members,invites,my-orgs,delete}-tab.tsx` + +### Upgrade: watcher reaction editor + +Today: plain `