diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 78aada94..216cbe71 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -80,14 +80,6 @@ jobs: # Tauri updater signing TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - # Next.js env (desktop build) - NEXT_PUBLIC_FASTAPI_BASE_URL: ${{ secrets.NEXT_PUBLIC_FASTAPI_BASE_URL }} - NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }} - NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }} - NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }} - NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }} - NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }} - NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }} with: projectPath: apps/desktop # __VERSION__ is replaced by tauri-action with the version from diff --git a/CLAUDE.md b/CLAUDE.md index 02ebefd4..b6f5893f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,13 +1,16 @@ # MyDevTools — Claude Code instructions -All-in-one developer toolkit (~80 tools: formatters, security, generators, notes, bookmarks, tasks, API client…). Full product doc: `docs/MYDEVTOOLS.md`. Web-redesign handoff: `HANDOFF.md`. +All-in-one developer toolkit (~80 tools: formatters, security, generators, notes, bookmarks, tasks, API client…). Full product doc: `docs/MYDEVTOOLS.md`. ## Product model (current direction — do not regress) -- **The desktop Tauri app is the product.** Fully offline after a mandatory one-time browser activation (loopback OAuth + `mydevtools://` deep link; activation stored in SQLCipher `kv` key `activation`). No per-session login, no periodic recheck. -- **Free & open source (AGPL-3.0).** No plans, no pricing, no paywalls — never reintroduce them. "Free & Open Source" page; `/account/plan` redirects to `/dashboard` (shipped desktop builds hard-open it). -- **Web (`apps/web`) = marketing/auth only**: landing, SEO pages (download CTAs), login handoff, `/dashboard` (passkeys). Never reintroduce web tool pages or cloud sync. +- **The desktop Tauri app is the product.** Fully offline, no server. **No accounts, no sign-in, no activation** — the app opens straight to the dashboard. Never reintroduce auth, Firebase, or a backend. +- **Free & open source (AGPL-3.0).** No plans, no pricing, no paywalls — never reintroduce them. "Free & Open Source" page; `/account/plan` redirects to `/download` (shipped desktop builds hard-open the URL). +- **Web (`apps/web`) = marketing/SEO only**: landing, SEO pages (download CTAs). No login, no dashboard, no API routes except `/api/app-version` (updater). Never reintroduce web tool pages or cloud sync. +- Identity is local: fixed `uid` `"local"` from `utils/useAuth`, editable display name/avatar in local preferences (`useAppUser`). +- All app data goes through `lib/backend-api.ts` / `lib/desktop/api-fetch.ts` → Tauri `local_api` → SQLCipher. The `/api/v1/...` paths are the local Rust router's contract, never HTTP. - Vault/masterkey creation is offline-only (desktop Rust mirror). +- Usage analytics = `lib/telemetry.ts` only: opt-in, anonymous (rotating session id, app version, locale), two events (`app_started`, `tool_opened {tool}`). Never send tool input, paths, or any stable id. Vercel Analytics / Clarity are marketing-site-only — never re-add them to `apps/desktop-ui`. ## Monorepo (pnpm) @@ -15,14 +18,14 @@ All-in-one developer toolkit (~80 tools: formatters, security, generators, notes |---|---| | `apps/desktop-ui` | Next.js UI the Tauri app builds from — **all tool work happens here** (clone-then-prune of apps/web) | | `apps/desktop` | Tauri v2 shell — SQLCipher keyed by macOS Keychain, native Rust DB drivers | -| `apps/web` | Marketing/SEO/auth site (Next.js 16, React 19, Tailwind, shadcn/ui, next-intl) | -| `apps/backend` | FastAPI — health + auth only | +| `apps/web` | Marketing/SEO site (Next.js 16, React 19, Tailwind, shadcn/ui, next-intl) | ## Build / verify - `pnpm exec` is broken (implicit install fails on ignored build scripts). Use repo-local bins: `./node_modules/.bin/tsc --noEmit`, `./node_modules/.bin/jest`, `./node_modules/.bin/next dev`. Node at `/Users/max/.nvm/versions/node/v24.18.0/bin`. - Run `next typegen` after adding routes, else tsc errors on stale `.next/dev/types`. -- Known pre-existing failing suites (ignore): react-window, pending-invitations-badge, encrypted-tool-placeholder, workspace-store. +- Known pre-existing failing suite (ignore): react-window (`react-window` is not installed). +- Rust: `cargo check` / `cargo test` in `apps/desktop/src-tauri`. - Desktop-ui: `pnpm dev:tauri` (sets `NEXT_PUBLIC_TAURI=1`), `pnpm build:tauri`. ## Adding a tool diff --git a/README.md b/README.md index daeb3abc..51d261f0 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,12 @@

MyDevTools.tech

- Your all-in-one developer toolkit — fast, private, and beautifully crafted. + 80+ developer tools in one desktop app.
+ Completely offline. No account required. Free for everyone.

- 🌐 Live App • + ⬇️ Download✨ Tools🏗️ Stack🚀 Get Started • @@ -20,12 +21,18 @@ Next.js React TypeScript - FastAPI - Firebase - MongoDB + Tauri + Rust + SQLCipher License

+

+ 100% Offline + No account required + Free for everyone +

+

MyDevTools on Product Hunt @@ -36,13 +43,14 @@ ## 🔥 Why MyDevTools? -Most developer tools are scattered across dozens of tabs, riddled with ads, or send your data to unknown servers. **MyDevTools** changes that: +Most developer tools are scattered across dozens of tabs, riddled with ads, gated behind a sign-up, or quietly sending your data to someone else's server. **MyDevTools** changes that: -- ⚡ **Local-first** — Your data is processed on your device wherever possible. No unnecessary server round-trips. -- 🔒 **Privacy by design** — No ad tracking, no data harvesting. Your vault stays yours. -- 🎨 **Premium UI/UX** — Dark/light mode, silky animations, fully responsive across all devices. -- 🌍 **Internationalization** — Built-in i18n support via `next-intl`. -- 💯 **Free & open source** — Free for everyone, licensed under **GNU AGPLv3**. See [LICENSE](LICENSE). +- 🔌 **Completely offline** — A desktop app with no backend behind it. The app itself never needs a network: your data is processed and stored on your machine. Only tools you deliberately point at something remote (API client, database clients, DNS/WHOIS lookups) reach out, and only to the destination you choose. +- 🙅 **No account required** — No sign-up, no sign-in, no activation, no license key. Install it, open it, start working. +- 💯 **Free for everyone** — Every tool and every feature. No paid tier, no trial, no seat limits, no upsell. +- 🔒 **Privacy by design** — Nothing to harvest, because there is nowhere to send it. Credentials live in an AES-256 vault encrypted with a password only you know. +- 📖 **Open source** — Licensed under **GNU AGPLv3**. Read it, build it, fork it. See [LICENSE](LICENSE). +- 🎨 **Premium UI/UX** — Dark/light mode, silky animations, 27 languages. --- @@ -115,15 +123,6 @@ Most developer tools are scattered across dozens of tabs, riddled with ads, or s | **🔐 Password Manager** | Store credentials securely with client-side encryption. Your vault, your keys | | **✅ Task Manager** | Organize tasks with priorities, statuses, and drag-and-drop reordering | -### 👤 Public Developer Profile - -Share your developer identity with a public profile page at `mydevtools.tech/`: - -- Display name, avatar, and bio -- Social links (Twitter/X, LinkedIn, GitHub, Instagram, YouTube, Dev.to, Hashnode, personal website) -- GitHub contribution calendar and streak stats -- Shareable URL — no account required to view - --- ## 🏗️ Tech Stack @@ -146,31 +145,26 @@ Share your developer identity with a public profile page at `mydevtools.tech/ - + + --- @@ -365,7 +286,7 @@ Have an idea for a new tool or improvement? We'd love to hear it! If MyDevTools saves you time, consider giving it a **star** on GitHub — it helps others discover the project and motivates us to keep building! -[![Star on GitHub](https://img.shields.io/github/stars/itsmeakhil/mydevtools.tech?style=social)](https://github.com/itsmeakhil/mydevtools.tech) +[![Star on GitHub](https://img.shields.io/github/stars/mydevtools-tech/mydevtools?style=social)](https://github.com/mydevtools-tech/mydevtools) --- @@ -376,5 +297,5 @@ This project is licensed under the [GNU Affero General Public License v3.0](http ---

- Made with ❤️ by Akhil and contributors + Made with ❤️ by Akhil and contributors

diff --git a/apps/backend/.python-version b/apps/backend/.python-version deleted file mode 100644 index e4fba218..00000000 --- a/apps/backend/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.12 diff --git a/apps/backend/README.md b/apps/backend/README.md deleted file mode 100644 index 8198fe39..00000000 --- a/apps/backend/README.md +++ /dev/null @@ -1,166 +0,0 @@ -# FastAPI backend - -Backend service template for the monorepo. - -## Structure - -- `app/main.py` - FastAPI app entrypoint -- `app/api/router.py` - API router aggregation -- `app/api/routes/health.py` - health endpoint -- `app/api/routes/tasks/` - todo tasks + projects (MongoDB, mirrors Firestore shape) -- `app/api/routes/bookmarks/` - bookmarks + folders (MongoDB; same model as Zustand store / future Firestore) -- `app/api/routes/analytics/` - dashboard aggregates (counts per user across collections) -- `app/core/config.py` - environment settings -- `app/core/db.py` - MongoDB client -- `tests/` - backend tests - -## Quick start - -1. Create and activate a Python 3.10+ virtual environment. -2. Install dependencies: - - `pip install -e ".[dev]"` -3. Copy env file: - - `cp .env.example .env` -4. Run the API: - - `python -m uvicorn app.main:app --reload` - -## API endpoints - -- `GET /` - root message -- `GET /api/v1/health` - health check -- `POST /api/v1/auth/session` - verify Firebase ID token once; sets HttpOnly cookies (`mdt_at` access JWT ~15d, `mdt_rt` refresh ~60d) and upserts `users` in MongoDB (document `_id` = Firebase UID) -- `POST /api/v1/auth/refresh` - rotate tokens using the refresh cookie -- `POST /api/v1/auth/logout` - clear cookies and server-side refresh hash -- `GET /api/v1/auth/session/check` - 200 if the access JWT is valid -- `GET /api/v1/auth/me` - current user profile from the `users` collection -- `GET /docs` - Swagger UI -- `GET /api/v1/analytics/summary` - dashboard counts (password entries, bookmarks, tasks, projects, NoSQL connections, notes, API client data, JSON formatter docs) for the signed-in user - -### Todo app (parity with Firestore `tasks` / `projects`) - -Protected routes accept `Authorization: Bearer ` and/or the HttpOnly access cookie `mdt_at` (used by the Next.js BFF). Firebase ID tokens are only accepted on `POST /auth/session`. - -**Tasks** (`/api/v1/tasks`) - -| Method | Path | Maps to frontend | -|--------|------|------------------| -| GET | `/stats` | `fetchStats` / `allTaskStats` | -| GET | `/export` | `getFilteredTasksForExport` | -| POST | `/import` | `importTasks` | -| GET | `/` | paginated list (`status`, `projectId`, `page`, `pageSize`) | -| GET | `/{task_id}` | single task | -| POST | `/` | `addTask` | -| PATCH | `/{task_id}` | `updateTask` | -| PATCH | `/{task_id}/status` | `updateTaskStatus` | -| DELETE | `/{task_id}` | `deleteTask` | - -**Projects** (`/api/v1/projects`) - -| Method | Path | Maps to frontend | -|--------|------|------------------| -| GET | `/` | project list | -| POST | `/` | `addProject` | -| PATCH | `/{project_id}` | `updateProject` | -| DELETE | `/{project_id}` | `deleteProject` | - -Set `MONGO_DB_URL` and `MONGO_DB_NAME` in `.env`. Document layout and indexes: `app/api/routes/tasks/schema.py`. - -### Bookmarks (web store parity; Firestore-style `created_by` + string doc ids) - -The bookmarks UI today uses **localStorage** only (`bookmark-storage`). These APIs store the **same payload** in MongoDB using the same pattern as tasks: top-level collections `bookmarks` and `bookmarkFolders` with `created_by` = Firebase UID. String `_id` values match client-generated ids. - -Protected routes accept `Authorization: Bearer ` and/or the HttpOnly access cookie `mdt_at` (used by the Next.js BFF). Firebase ID tokens are only accepted on `POST /auth/session`. - -**Bookmarks** — `/api/v1/bookmarks` - -| Method | Path | Store action | -|--------|------|----------------| -| GET | `/snapshot` | full state (like `exportBookmarksToJSON`) | -| POST | `/import` | `importBookmarks` (upsert by id) | -| POST | `/clear-all` | `clearAll` | -| GET | `/` | list (`folderId` query: omit = all, `uncategorized` = no folder) | -| POST | `/` | `addBookmark` | -| GET | `/{id}` | single | -| PATCH | `/{id}` | `updateBookmark` | -| PATCH | `/{id}/move` | `moveBookmark` | -| DELETE | `/{id}` | `deleteBookmark` | - -**Folders** — `/api/v1/bookmark-folders` - -| Method | Path | Store action | -|--------|------|----------------| -| GET | `/` | all folders (sort `createdAt` asc, like a Firestore `orderBy`) | -| POST | `/` | `addFolder` | -| GET | `/{id}` | single | -| PATCH | `/{id}` | `updateFolder` | -| PATCH | `/{id}/expanded` | `toggleFolderExpanded` | -| DELETE | `/{id}` | `deleteFolder` (descendants removed; bookmarks → uncategorized) | - -Schema and indexes: `app/api/routes/bookmarks/schema.py`. - -## API JWT and cookies - -- Set `JWT_SECRET_KEY` in `.env` (required when `APP_ENV=production`). -- Optional: `AUTH_COOKIE_SECURE=true` when the API is only served over HTTPS. -- Access token lifetime defaults to **15 days**; refresh token **60 days** (`ACCESS_TOKEN_EXPIRE_DAYS` / `REFRESH_TOKEN_EXPIRE_DAYS` in `app/core/config.py`). - -Recommended MongoDB index for refresh lookup: - - db.users.create_index([("refresh_token_hash", 1)], sparse=True) - -## Firebase auth setup - -Set one of the following: - -- `FIREBASE_CREDENTIALS_JSON` in `.env` with full service-account JSON string (single-line escaped JSON), or -- `FIREBASE_CREDENTIALS_PATH` in `.env` with the local service-account JSON path, or -- `GOOGLE_APPLICATION_CREDENTIALS` / runtime ADC if deploying on GCP-managed infra. - -If both JSON and path are set, `FIREBASE_CREDENTIALS_JSON` is used first. - -## Redis cache - -### Environment - -| Var | Default | Purpose | -|-----|---------|---------| -| `REDIS_URL` | unset | If unset, cache is inert. `redis://...` or `rediss://...`. | -| `CACHE_ENABLED` | `true` | Global kill switch. | -| `CACHE_NAMESPACES` | `""` | Comma-separated namespaces to activate. Empty = no caching. | -| `CACHE_DEFAULT_TTL` | `120` | Fallback TTL seconds. | -| `CACHE_OP_TIMEOUT_MS` | `50` | Per-Redis-call timeout. | -| `CACHE_XFETCH_BETA` | `1.0` | XFetch tuning constant. Higher = refreshes earlier. | -| `CACHE_LOG_LEVEL` | `WARNING` | `app.cache` logger level. | - -### Rollout phases - -Ramp `CACHE_NAMESPACES` namespace-by-namespace; restart workers each phase. - -| Phase | Add to `CACHE_NAMESPACES` | -|-------|---------------------------| -| 1 | `auth_token,auth_user` | -| 2 | `user_preferences,dns_lookup` | -| 3 | `bookmarks,notes,code_snippets,api_client` | -| 4 | `tasks,passwords,url_shortener_owner` | -| 5 | `url_shortener_resolve,analytics_aggregate` | - -Kill switch: drop a namespace from env + restart workers. Global kill: `CACHE_ENABLED=false`. - -### Redis server config (prod) - -- `maxmemory` sized to expected working set (start with 512 MB for 1k users). -- `maxmemory-policy allkeys-lru`. -- `requirepass` set; TLS enabled if exposed outside VPC. -- Bind to private network only. - -## Load testing cache - -See `scripts/loadtest_cache.py`. Requires `locust` (install separately) and a running backend. - -1. Boot backend with `CACHE_ENABLED=false`; run a 5-min baseline. -2. Boot backend with `CACHE_ENABLED=true` + chosen `CACHE_NAMESPACES`; re-run. -3. Compare p50/p99 in `*_stats.csv`. Acceptance gates: p50 < 50ms, p99 < 200ms, error rate 0%. - -## From monorepo root - -- `pnpm dev:backend` - start backend server diff --git a/apps/backend/app/__init__.py b/apps/backend/app/__init__.py deleted file mode 100644 index cc5e7377..00000000 --- a/apps/backend/app/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Backend application package.""" diff --git a/apps/backend/app/api/__init__.py b/apps/backend/app/api/__init__.py deleted file mode 100644 index dff53e5a..00000000 --- a/apps/backend/app/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""API package.""" diff --git a/apps/backend/app/api/router.py b/apps/backend/app/api/router.py deleted file mode 100644 index b3e5d713..00000000 --- a/apps/backend/app/api/router.py +++ /dev/null @@ -1,10 +0,0 @@ -from fastapi import APIRouter - -from app.api.routes.auth.api import router as auth_router -from app.api.routes.auth.passkey.api import router as auth_passkey_router -from app.api.routes.health import router as health_router - -api_router = APIRouter() -api_router.include_router(health_router) -api_router.include_router(auth_router) -api_router.include_router(auth_passkey_router) diff --git a/apps/backend/app/api/routes/__init__.py b/apps/backend/app/api/routes/__init__.py deleted file mode 100644 index fb0a2f80..00000000 --- a/apps/backend/app/api/routes/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""API route modules.""" diff --git a/apps/backend/app/api/routes/auth/__init__.py b/apps/backend/app/api/routes/auth/__init__.py deleted file mode 100644 index fb0a2f80..00000000 --- a/apps/backend/app/api/routes/auth/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""API route modules.""" diff --git a/apps/backend/app/api/routes/auth/api.py b/apps/backend/app/api/routes/auth/api.py deleted file mode 100644 index 3d033079..00000000 --- a/apps/backend/app/api/routes/auth/api.py +++ /dev/null @@ -1,335 +0,0 @@ -import asyncio -import re -from typing import Annotated - -from fastapi import APIRouter, Cookie, Depends, Header, HTTPException, Request, Response, status - -from app.core.limiter import limiter -from app.core import audit -from app.core.config import get_settings - -from app.api.routes.auth.cookie_attach import attach_auth_cookies, clear_auth_cookies -from app.api.routes.auth.schema import ( - OkResponse, - SessionRequest, - UserProfileResponse, - UpdateProfileRequest, -) -from app.api.routes.auth.services import get_current_uid, get_current_user, verify_id_token, _token_cache_key -from app.core.cache import bump_version, cache_invalidate -from app.api.routes.auth.tokens import ( - create_access_token, - hash_refresh_token, - new_refresh_token, - try_decode_access_token_uid, -) -from app.api.routes.auth.users_repo import ( - clear_refresh_token_hash, - complete_onboarding, - find_uid_by_refresh_hash, - get_user_doc, - set_refresh_token_hash, - upsert_user_from_firebase_claims, - update_user_profile, - is_username_taken, -) -from app.core.auth_cookies import ACCESS_COOKIE_NAME, REFRESH_COOKIE_NAME - -router = APIRouter(prefix="/auth", tags=["auth"]) - - -@router.post("/session", response_model=UserProfileResponse, summary="Firebase login → JWT cookies") -@limiter.limit("10/minute") -async def create_session( - request: Request, - payload: SessionRequest, - response: Response, -) -> UserProfileResponse: - decoded = await asyncio.to_thread( - verify_id_token, - payload.id_token, - payload.check_revoked, - ) - uid = decoded.get("uid") - if not uid: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Token payload missing user id.", - ) - - await upsert_user_from_firebase_claims(decoded) - - access = create_access_token(uid) - raw_refresh = new_refresh_token() - settings = get_settings() - refresh_days = ( - settings.LONG_LIVED_REFRESH_TOKEN_EXPIRE_DAYS - if payload.long_lived - else settings.REFRESH_TOKEN_EXPIRE_DAYS - ) - await set_refresh_token_hash( - uid, hash_refresh_token(raw_refresh), long_lived=payload.long_lived - ) - attach_auth_cookies(response, access, raw_refresh, refresh_days=refresh_days) - - doc = await get_user_doc(uid) - if not doc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="User record missing after upsert.", - ) - audit.set_action("auth.login") - audit.set_entity("user", uid) - audit.set_summary("Signed in") - return UserProfileResponse( - uid=str(doc["_id"]), - email=doc.get("email"), - display_name=doc.get("display_name"), - photo_url=doc.get("photo_url"), - email_verified=bool(doc.get("email_verified")), - disabled=bool(doc.get("disabled")), - created_at=doc.get("created_at"), - onboarding_completed=bool(doc.get("onboarding_completed", False)), - workspace_setup_at=doc.get("workspace_setup_at"), - migrated_at=doc.get("migrated_at"), - migration_status=doc.get("migration_status"), - migration_progress=doc.get("migration_progress"), - ) - - -@router.post("/refresh", response_model=OkResponse, summary="Rotate tokens using refresh cookie") -@limiter.limit("10/minute") -async def refresh_session( - request: Request, - response: Response, - mdt_rt: Annotated[str | None, Cookie(alias=REFRESH_COOKIE_NAME)] = None, -) -> OkResponse: - if not mdt_rt or not mdt_rt.strip(): - clear_auth_cookies(response) - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Missing refresh token.", - ) - - token_hash = hash_refresh_token(mdt_rt.strip()) - uid = await find_uid_by_refresh_hash(token_hash) - if not uid: - clear_auth_cookies(response) - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid refresh token.", - ) - - # Preserve the session's long-lived flag across rotation so desktop - # sessions keep their 60-day cookie TTL on every refresh. - user_doc = await get_user_doc(uid) - long_lived = bool(user_doc.get("refresh_long_lived", False)) if user_doc else False - settings = get_settings() - refresh_days = ( - settings.LONG_LIVED_REFRESH_TOKEN_EXPIRE_DAYS - if long_lived - else settings.REFRESH_TOKEN_EXPIRE_DAYS - ) - new_raw = new_refresh_token() - await set_refresh_token_hash(uid, hash_refresh_token(new_raw), long_lived=long_lived) - access = create_access_token(uid) - attach_auth_cookies(response, access, new_raw, refresh_days=refresh_days) - audit.set_action("auth.token_refresh") - audit.set_entity("user", uid) - audit.set_summary("Refreshed session") - return OkResponse(ok=True) - - -@router.post( - "/logout", - response_model=OkResponse, - summary="Logout: clear cookies and server refresh hash", -) -async def logout( - response: Response, - authorization: Annotated[str | None, Header(alias="Authorization")] = None, - mdt_at: Annotated[str | None, Cookie(alias=ACCESS_COOKIE_NAME)] = None, - mdt_rt: Annotated[str | None, Cookie(alias=REFRESH_COOKIE_NAME)] = None, -) -> OkResponse: - uid: str | None = None - - token: str | None = None - if authorization: - scheme, _, value = authorization.partition(" ") - if scheme.lower() == "bearer" and value.strip(): - token = value.strip() - if not token and mdt_at and mdt_at.strip(): - token = mdt_at.strip() - if token: - uid = try_decode_access_token_uid(token) - - if uid is None and mdt_rt and mdt_rt.strip(): - uid = await find_uid_by_refresh_hash(hash_refresh_token(mdt_rt.strip())) - - clear_auth_cookies(response) - if uid: - await clear_refresh_token_hash(uid) - audit.set_action("auth.logout") - if uid: - audit.set_entity("user", uid) - audit.set_summary("Signed out") - try: - if token: - await cache_invalidate(ns="auth_token", key=_token_cache_key(token)) - if uid: - await bump_version(ns="auth_user", uid=uid) - except Exception: - pass # fail-open - return OkResponse(ok=True) - - -@router.get( - "/me", - response_model=UserProfileResponse, - summary="Current user profile from MongoDB", -) -async def me( - current_user: Annotated[UserProfileResponse, Depends(get_current_user)], -) -> UserProfileResponse: - return current_user - - -@router.patch( - "/profile", - response_model=UserProfileResponse, - summary="Update current user profile in MongoDB", -) -async def update_profile( - payload: UpdateProfileRequest, - current_user: Annotated[UserProfileResponse, Depends(get_current_user)], -) -> UserProfileResponse: - updates = {} - if payload.github_username is not None: - updates["github_username"] = payload.github_username - - if payload.username is not None: - if payload.username.strip(): - candidate = payload.username.strip().lower() - if not re.match(r"^[a-z0-9][a-z0-9_-]{1,28}[a-z0-9]$", candidate): - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="Username must be 3-30 characters, alphanumeric with hyphens/underscores, and start/end with a letter or digit.", - ) - reserved = {"admin", "api", "settings", "login", "logout", "profile", "dashboard", "app", "help", "null", "undefined"} - if candidate in reserved: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="This username is reserved.", - ) - if await is_username_taken(candidate, current_user.uid): - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail="Username is already taken.", - ) - updates["username"] = candidate - else: - updates["username"] = None - - if payload.social_links is not None: - updates["social_links"] = payload.social_links.model_dump() - - if payload.tech_stacks is not None: - updates["tech_stacks"] = payload.tech_stacks - - if payload.bio is not None: - updates["bio"] = payload.bio - - if payload.experiences is not None: - updates["experiences"] = [e.model_dump() for e in payload.experiences] - - if payload.projects is not None: - updates["projects"] = [p.model_dump() for p in payload.projects] - - if payload.education is not None: - updates["education"] = [e.model_dump() for e in payload.education] - - if payload.certifications is not None: - updates["certifications"] = [c.model_dump() for c in payload.certifications] - - if payload.portfolio_settings is not None: - updates["portfolio_settings"] = payload.portfolio_settings.model_dump() - - if payload.personal_info is not None: - updates["personal_info"] = payload.personal_info.model_dump() - - if payload.persona is not None: - updates["persona"] = payload.persona - - if updates: - await update_user_profile(current_user.uid, updates) - - if "github_username" in updates: - current_user.github_username = updates["github_username"] - if "username" in updates: - current_user.username = updates["username"] - if "social_links" in updates: - current_user.social_links = payload.social_links - if "tech_stacks" in updates: - current_user.tech_stacks = updates["tech_stacks"] - if "bio" in updates: - current_user.bio = updates["bio"] - if "experiences" in updates: - current_user.experiences = payload.experiences - if "projects" in updates: - current_user.projects = payload.projects - if "education" in updates: - current_user.education = payload.education - if "certifications" in updates: - current_user.certifications = payload.certifications - if "portfolio_settings" in updates: - current_user.portfolio_settings = payload.portfolio_settings - if "personal_info" in updates: - current_user.personal_info = payload.personal_info - - return current_user - - -@router.get( - "/session/check", - response_model=OkResponse, - summary="Validates access JWT (200 if ok)", -) -async def session_check(_uid: Annotated[str, Depends(get_current_uid)]) -> OkResponse: - return OkResponse(ok=True) - - -@router.post( - "/desktop-token", - summary="Mint a Firebase custom token for the desktop-app sign-in handoff", -) -async def desktop_token(uid: Annotated[str, Depends(get_current_uid)]) -> dict: - # The web session (browser) mints a short-lived Firebase custom token which - # is handed to the desktop app via the mydevtools:// deep link; the app - # signs in with it and runs the normal session exchange. - from app.core.firebase import get_firebase_app - - try: - from firebase_admin import auth as firebase_auth - except Exception: # pragma: no cover - firebase_admin always present in prod - raise HTTPException(status_code=503, detail="Firebase unavailable") - get_firebase_app() - token_bytes = firebase_auth.create_custom_token(uid) - token = token_bytes.decode("utf-8") if isinstance(token_bytes, bytes) else token_bytes - return {"token": token} - - -# ── Onboarding ──────────────────────────────────────────────────────────────── - - -@router.post( - "/onboarding/complete", - response_model=OkResponse, - summary="Mark onboarding as completed for the current user", -) -@limiter.limit("10/minute") -async def complete_onboarding_endpoint( - request: Request, - uid: Annotated[str, Depends(get_current_uid)], -) -> OkResponse: - await complete_onboarding(uid) - return OkResponse(ok=True) diff --git a/apps/backend/app/api/routes/auth/cookie_attach.py b/apps/backend/app/api/routes/auth/cookie_attach.py deleted file mode 100644 index 2fa4f6c7..00000000 --- a/apps/backend/app/api/routes/auth/cookie_attach.py +++ /dev/null @@ -1,36 +0,0 @@ -from fastapi import Response - -from app.core.auth_cookies import ACCESS_COOKIE_NAME, REFRESH_COOKIE_NAME -from app.core.config import get_settings - - -def attach_auth_cookies( - response: Response, - access: str, - refresh_plain: str, - refresh_days: int | None = None, -) -> None: - settings = get_settings() - access_max = settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60 - days = refresh_days if refresh_days is not None else settings.REFRESH_TOKEN_EXPIRE_DAYS - refresh_max = days * 24 * 3600 - common: dict = { - "httponly": True, - "secure": settings.AUTH_COOKIE_SECURE, - "samesite": "lax", - "path": "/", - } - response.set_cookie(ACCESS_COOKIE_NAME, access, max_age=access_max, **common) - response.set_cookie(REFRESH_COOKIE_NAME, refresh_plain, max_age=refresh_max, **common) - - -def clear_auth_cookies(response: Response) -> None: - settings = get_settings() - common: dict = { - "httponly": True, - "secure": settings.AUTH_COOKIE_SECURE, - "samesite": "lax", - "path": "/", - } - response.delete_cookie(ACCESS_COOKIE_NAME, **common) - response.delete_cookie(REFRESH_COOKIE_NAME, **common) diff --git a/apps/backend/app/api/routes/auth/passkey/__init__.py b/apps/backend/app/api/routes/auth/passkey/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/backend/app/api/routes/auth/passkey/api.py b/apps/backend/app/api/routes/auth/passkey/api.py deleted file mode 100644 index 7600eec1..00000000 --- a/apps/backend/app/api/routes/auth/passkey/api.py +++ /dev/null @@ -1,156 +0,0 @@ -from typing import Annotated - -from fastapi import APIRouter, Depends, HTTPException, Request, Response, status - -from app.api.routes.auth.cookie_attach import attach_auth_cookies -from app.api.routes.auth.passkey import services as passkey_services -from app.core.firebase import get_firebase_app - -try: - from firebase_admin import auth as firebase_auth -except ModuleNotFoundError: # pragma: no cover - firebase_auth = None # type: ignore[assignment] -from app.api.routes.auth.passkey.schema import ( - LoginFinishRequest, - PasskeyListResponse, - PasskeySummary, - RegisterFinishRequest, - RenamePasskeyRequest, -) -from app.api.routes.auth.services import get_current_uid -from app.api.routes.auth.tokens import ( - create_access_token, - hash_refresh_token, - new_refresh_token, -) -from app.api.routes.auth.users_repo import ( - list_passkeys, - remove_passkey, - rename_passkey, - set_refresh_token_hash, -) -from app.core import audit -from app.core.limiter import limiter - -router = APIRouter(prefix="/auth/passkey", tags=["auth-passkey"]) - - -# ── Registration (auth required) ────────────────────────────────────────────── - - -@router.post("/register/begin", summary="Begin passkey registration") -@limiter.limit("10/minute") -async def register_begin( - request: Request, - uid: Annotated[str, Depends(get_current_uid)], -) -> dict: - options = await passkey_services.begin_registration(uid) - audit.set_action("auth.passkey.register_begin") - audit.set_entity("user", uid) - return options - - -@router.post("/register/finish", summary="Finish passkey registration") -@limiter.limit("10/minute") -async def register_finish( - request: Request, - payload: RegisterFinishRequest, - uid: Annotated[str, Depends(get_current_uid)], -) -> dict: - result = await passkey_services.finish_registration(uid, payload.credential, payload.device_name) - audit.set_action("auth.passkey.register") - audit.set_entity("user", uid) - audit.set_summary(f"Registered passkey: {result['device_name']}") - return result - - -# ── Login (anonymous) ───────────────────────────────────────────────────────── - - -@router.post("/login/begin", summary="Begin passkey login (resident-key)") -@limiter.limit("20/minute") -async def login_begin(request: Request) -> dict: - return await passkey_services.begin_login() - - -@router.post("/login/finish", summary="Finish passkey login → set session cookies") -@limiter.limit("20/minute") -async def login_finish( - request: Request, - payload: LoginFinishRequest, - response: Response, -) -> dict: - uid = await passkey_services.finish_login(payload.credential) - access = create_access_token(uid) - raw_refresh = new_refresh_token() - await set_refresh_token_hash(uid, hash_refresh_token(raw_refresh)) - attach_auth_cookies(response, access, raw_refresh) - audit.set_action("auth.passkey.login") - audit.set_entity("user", uid) - audit.set_summary("Signed in with passkey") - - # Firebase custom token so the frontend can hydrate its Firebase session. - # RequireAuth gates on Firebase user; passkey-only backend cookies aren't enough. - firebase_token: str | None = None - if firebase_auth is not None: - try: - get_firebase_app() - token_bytes = firebase_auth.create_custom_token(uid) - firebase_token = token_bytes.decode("utf-8") if isinstance(token_bytes, bytes) else token_bytes - except Exception: - firebase_token = None # fail soft — backend cookies still work - - return {"uid": uid, "firebase_custom_token": firebase_token} - - -# ── Manage (auth required) ──────────────────────────────────────────────────── - - -@router.get("", response_model=PasskeyListResponse, summary="List user's passkeys") -async def list_endpoint(uid: Annotated[str, Depends(get_current_uid)]) -> PasskeyListResponse: - passkeys = await list_passkeys(uid) - return PasskeyListResponse( - passkeys=[ - PasskeySummary( - credential_id=p["credential_id"], - device_name=p.get("device_name"), - backed_up=bool(p.get("backed_up", False)), - transports=list(p.get("transports") or []), - created_at=p.get("created_at"), - last_used_at=p.get("last_used_at"), - ) - for p in passkeys - ] - ) - - -@router.patch("/{credential_id}", summary="Rename a passkey") -@limiter.limit("20/minute") -async def rename_endpoint( - request: Request, - credential_id: str, - payload: RenamePasskeyRequest, - uid: Annotated[str, Depends(get_current_uid)], -) -> dict: - modified = await rename_passkey(uid, credential_id, payload.device_name.strip()) - if not modified: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Passkey not found.") - audit.set_action("auth.passkey.rename") - audit.set_entity("user", uid) - return {"ok": True} - - -@router.delete("/{credential_id}", summary="Delete a passkey") -@limiter.limit("10/minute") -async def delete_endpoint( - request: Request, - credential_id: str, - uid: Annotated[str, Depends(get_current_uid)], -) -> dict: - modified = await remove_passkey(uid, credential_id) - if not modified: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Passkey not found.") - audit.set_action("auth.passkey.delete") - audit.set_entity("user", uid) - audit.set_summary("Removed a passkey") - return {"ok": True} diff --git a/apps/backend/app/api/routes/auth/passkey/challenges.py b/apps/backend/app/api/routes/auth/passkey/challenges.py deleted file mode 100644 index 1be39c9c..00000000 --- a/apps/backend/app/api/routes/auth/passkey/challenges.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import annotations - -import datetime as dt -from typing import Literal - -from app.database import db_manager -from app.utils.collection_name import WEBAUTHN_CHALLENGES - -ChallengeKind = Literal["register", "login"] - - -def _key(kind: ChallengeKind, subject: str) -> str: - return f"{kind}:{subject}" - - -async def put(kind: ChallengeKind, subject: str, challenge_b64: str) -> None: - """Store a challenge keyed by (kind, subject). TTL index expires the doc.""" - now = dt.datetime.now(dt.timezone.utc) - await db_manager.update_one( - WEBAUTHN_CHALLENGES, - {"_id": _key(kind, subject)}, - {"$set": {"challenge": challenge_b64, "kind": kind, "created_at": now}}, - upsert=True, - ) - - -async def pop(kind: ChallengeKind, subject: str) -> str | None: - """Atomically fetch + delete the challenge. Returns None if missing/expired.""" - doc = await db_manager.find_one_and_update( - WEBAUTHN_CHALLENGES, - {"_id": _key(kind, subject)}, - {"$set": {"_consumed": True}}, - return_document=False, - ) - if not doc: - return None - await db_manager.delete_one(WEBAUTHN_CHALLENGES, {"_id": _key(kind, subject)}) - return doc.get("challenge") diff --git a/apps/backend/app/api/routes/auth/passkey/schema.py b/apps/backend/app/api/routes/auth/passkey/schema.py deleted file mode 100644 index cb1e15b5..00000000 --- a/apps/backend/app/api/routes/auth/passkey/schema.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from typing import Any - -from pydantic import BaseModel, Field - - -class PublicKeyCredentialOptions(BaseModel): - """Pass-through container — server returns the WebAuthn JSON straight from the lib.""" - - options: dict[str, Any] - - -class RegisterFinishRequest(BaseModel): - credential: dict[str, Any] - device_name: str | None = Field(default=None, max_length=80) - - -class LoginFinishRequest(BaseModel): - credential: dict[str, Any] - - -class PasskeySummary(BaseModel): - credential_id: str - device_name: str | None = None - backed_up: bool = False - transports: list[str] = Field(default_factory=list) - created_at: int | None = None - last_used_at: int | None = None - - -class PasskeyListResponse(BaseModel): - passkeys: list[PasskeySummary] - - -class RenamePasskeyRequest(BaseModel): - device_name: str = Field(min_length=1, max_length=80) diff --git a/apps/backend/app/api/routes/auth/passkey/services.py b/apps/backend/app/api/routes/auth/passkey/services.py deleted file mode 100644 index cc625c3e..00000000 --- a/apps/backend/app/api/routes/auth/passkey/services.py +++ /dev/null @@ -1,232 +0,0 @@ -from __future__ import annotations - -import base64 -import secrets -from typing import Any - -from fastapi import HTTPException, status -from webauthn import ( - generate_authentication_options, - generate_registration_options, - options_to_json, - verify_authentication_response, - verify_registration_response, -) -from webauthn.helpers.cose import COSEAlgorithmIdentifier -from webauthn.helpers.structs import ( - AuthenticatorSelectionCriteria, - AuthenticatorTransport, - PublicKeyCredentialDescriptor, - ResidentKeyRequirement, - UserVerificationRequirement, -) - -from app.api.routes.auth.passkey import challenges -from app.api.routes.auth.users_repo import ( - add_passkey, - find_user_by_credential_id, - find_user_by_user_handle, - get_or_create_webauthn_user_handle, - get_user_doc, - list_passkeys, - update_passkey_usage, -) -from app.core.config import get_settings -from app.utils.utils import create_timestamp - - -def _b64url_encode(raw: bytes) -> str: - return base64.urlsafe_b64encode(raw).rstrip(b"=").decode("ascii") - - -def _b64url_decode(value: str) -> bytes: - pad = "=" * (-len(value) % 4) - return base64.urlsafe_b64decode(value + pad) - - -def _origins() -> list[str]: - raw = get_settings().WEBAUTHN_ORIGINS - return [o.strip() for o in raw.split(",") if o.strip()] - - -# ── Registration ────────────────────────────────────────────────────────────── - - -async def begin_registration(uid: str) -> dict[str, Any]: - user = await get_user_doc(uid) - if not user: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found.") - - new_handle = _b64url_encode(secrets.token_bytes(32)) - handle_b64 = await get_or_create_webauthn_user_handle(uid, new_handle) - - existing = await list_passkeys(uid) - exclude = [ - PublicKeyCredentialDescriptor(id=_b64url_decode(p["credential_id"])) - for p in existing - if p.get("credential_id") - ] - - settings = get_settings() - opts = generate_registration_options( - rp_id=settings.WEBAUTHN_RP_ID, - rp_name=settings.WEBAUTHN_RP_NAME, - user_id=_b64url_decode(handle_b64), - user_name=user.get("email") or uid, - user_display_name=user.get("display_name") or user.get("email") or uid, - supported_pub_key_algs=[ - COSEAlgorithmIdentifier.ECDSA_SHA_256, - COSEAlgorithmIdentifier.RSASSA_PKCS1_v1_5_SHA_256, - ], - authenticator_selection=AuthenticatorSelectionCriteria( - resident_key=ResidentKeyRequirement.REQUIRED, - user_verification=UserVerificationRequirement.PREFERRED, - ), - exclude_credentials=exclude, - ) - - await challenges.put("register", uid, _b64url_encode(opts.challenge)) - # options_to_json returns a JSON string with browser-ready (base64url) values - import json as _json - - return _json.loads(options_to_json(opts)) - - -async def finish_registration( - uid: str, credential: dict[str, Any], device_name: str | None -) -> dict[str, Any]: - challenge_b64 = await challenges.pop("register", uid) - if not challenge_b64: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail="Challenge expired or missing." - ) - - settings = get_settings() - try: - verification = verify_registration_response( - credential=credential, - expected_challenge=_b64url_decode(challenge_b64), - expected_rp_id=settings.WEBAUTHN_RP_ID, - expected_origin=_origins(), - require_user_verification=False, - ) - except Exception as exc: # webauthn lib raises a variety of subclasses - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail=f"Passkey registration failed: {exc}" - ) from exc - - credential_id_b64 = _b64url_encode(verification.credential_id) - # Dedup: a single credential can only belong to one user - if await find_user_by_credential_id(credential_id_b64): - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Passkey is already registered." - ) - - transports_raw = credential.get("response", {}).get("transports") or [] - aaguid = ( - verification.aaguid - if isinstance(verification.aaguid, str) - else (_b64url_encode(verification.aaguid) if verification.aaguid else None) - ) - - passkey_doc = { - "credential_id": credential_id_b64, - "public_key": _b64url_encode(verification.credential_public_key), - "sign_count": int(verification.sign_count or 0), - "transports": [str(t) for t in transports_raw], - "aaguid": aaguid, - "device_name": device_name or "Passkey", - "backed_up": bool(getattr(verification, "credential_backed_up", False)), - "created_at": create_timestamp(), - "last_used_at": None, - } - await add_passkey(uid, passkey_doc) - return {"credential_id": credential_id_b64, "device_name": passkey_doc["device_name"]} - - -# ── Authentication ──────────────────────────────────────────────────────────── - - -async def begin_login() -> dict[str, Any]: - settings = get_settings() - opts = generate_authentication_options( - rp_id=settings.WEBAUTHN_RP_ID, - user_verification=UserVerificationRequirement.PREFERRED, - allow_credentials=[], # resident-key discovery - ) - # Anonymous flow → key challenge by its own bytes so finish can locate it - challenge_b64 = _b64url_encode(opts.challenge) - await challenges.put("login", challenge_b64, challenge_b64) - - import json as _json - - return _json.loads(options_to_json(opts)) - - -async def finish_login(credential: dict[str, Any]) -> str: - """Verify assertion; return uid on success.""" - # Recover challenge from clientDataJSON to look it up - try: - client_data_b64 = credential["response"]["clientDataJSON"] - client_data = _b64url_decode(client_data_b64) - import json as _json - - challenge_b64 = _json.loads(client_data.decode("utf-8")).get("challenge") - except Exception as exc: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail="Malformed credential payload." - ) from exc - if not challenge_b64: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail="Missing challenge in clientDataJSON." - ) - - stored = await challenges.pop("login", challenge_b64) - if not stored or stored != challenge_b64: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail="Challenge expired or unknown." - ) - - user_handle_b64 = credential.get("response", {}).get("userHandle") - user = None - if user_handle_b64: - user = await find_user_by_user_handle(user_handle_b64) - if not user: - user = await find_user_by_credential_id(credential.get("id", "")) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, detail="Unknown passkey." - ) - - credential_id = credential.get("id", "") - passkey = next( - (p for p in (user.get("passkeys") or []) if p.get("credential_id") == credential_id), - None, - ) - if not passkey: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, detail="Credential not registered for user." - ) - - settings = get_settings() - try: - verification = verify_authentication_response( - credential=credential, - expected_challenge=_b64url_decode(challenge_b64), - expected_rp_id=settings.WEBAUTHN_RP_ID, - expected_origin=_origins(), - credential_public_key=_b64url_decode(passkey["public_key"]), - credential_current_sign_count=int(passkey.get("sign_count") or 0), - require_user_verification=False, - ) - except Exception as exc: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, detail=f"Passkey verification failed: {exc}" - ) from exc - - await update_passkey_usage( - uid=str(user["_id"]), - credential_id=credential_id, - sign_count=int(verification.new_sign_count or 0), - ) - return str(user["_id"]) diff --git a/apps/backend/app/api/routes/auth/schema.py b/apps/backend/app/api/routes/auth/schema.py deleted file mode 100644 index aa4697fc..00000000 --- a/apps/backend/app/api/routes/auth/schema.py +++ /dev/null @@ -1,190 +0,0 @@ - -from pydantic import BaseModel, ConfigDict, Field, field_validator - - -class SessionRequest(BaseModel): - """Exchange a Firebase ID token once; API returns HttpOnly cookies (no JWT in JSON).""" - - id_token: str = Field(min_length=1) - check_revoked: bool = True - # Desktop app requests a long-lived (60-day) refresh cookie. - long_lived: bool = False - - -class SocialLinks(BaseModel): - website: str | None = None - twitter: str | None = None - instagram: str | None = None - linkedin: str | None = None - youtube: str | None = None - devto: str | None = None - hashnode: str | None = None - github: str | None = None - - -class Experience(BaseModel): - id: str - company: str - role: str - startDate: str - endDate: str | None = None - description: str | None = None - technologies: list[str] = Field(default_factory=list) - employmentType: str | None = None - location: str | None = None - - -class Project(BaseModel): - id: str - title: str - description: str - imageUrl: str | None = None - githubUrl: str | None = None - liveUrl: str | None = None - technologies: list[str] = Field(default_factory=list) - - -class Education(BaseModel): - id: str - institution: str - degree: str - startDate: str - endDate: str | None = None - description: str | None = None - - -class PortfolioSettings(BaseModel): - theme: str | None = "bento" - font: str | None = "sans" - accentColor: str | None = "#3b82f6" - rssFeedUrl: str | None = None - showGithubStats: bool = True - resumePdfUrl: str | None = None - - - -class Language(BaseModel): - name: str - level: str = "Fluent" - - -class Certification(BaseModel): - id: str - name: str - issuer: str - issueDate: str | None = None - expiryDate: str | None = None - credentialUrl: str | None = None - - -class PersonalInfo(BaseModel): - phone: str | None = None - location: str | None = None - date_of_birth: str | None = None - nationality: str | None = None - headline: str | None = None - languages: list[Language] = Field(default_factory=list) - hobbies: list[str] = Field(default_factory=list) - - @field_validator("languages", mode="before") - @classmethod - def coerce_languages(cls, v: list) -> list: - return [{"name": item, "level": "Fluent"} if isinstance(item, str) else item for item in v] - - -class UserProfileResponse(BaseModel): - uid: str - email: str | None = None - display_name: str | None = None - photo_url: str | None = None - email_verified: bool - disabled: bool - github_username: str | None = None - username: str | None = None - bio: str | None = None - social_links: SocialLinks | None = None - tech_stacks: list[str] = Field(default_factory=list) - experiences: list[Experience] = Field(default_factory=list) - projects: list[Project] = Field(default_factory=list) - education: list[Education] = Field(default_factory=list) - certifications: list[Certification] = Field(default_factory=list) - portfolio_settings: PortfolioSettings | None = None - personal_info: PersonalInfo | None = None - # Role picked during onboarding (frontend/backend/fullstack/devops/qa/designer/exploring) - persona: str | None = None - created_at: int | None = None - onboarding_completed: bool = False - # Workspace migration fields (T25) - workspace_setup_at: int | None = None - migrated_at: int | None = None - migration_status: str | None = None - migration_progress: dict | None = None - - -class UpdateProfileRequest(BaseModel): - github_username: str | None = None - username: str | None = None - bio: str | None = None - social_links: SocialLinks | None = None - tech_stacks: list[str] | None = None - experiences: list[Experience] | None = None - projects: list[Project] | None = None - education: list[Education] | None = None - certifications: list[Certification] | None = None - portfolio_settings: PortfolioSettings | None = None - personal_info: PersonalInfo | None = None - persona: str | None = None - - -class OkResponse(BaseModel): - ok: bool - - -# ── Master-password vault ───────────────────────────────────────────────────── - -class KeyVerifier(BaseModel): - """AES-GCM ciphertext used to verify the derived key without storing the password.""" - - model_config = ConfigDict(extra="ignore") - - encrypted: str = Field(min_length=1) - iv: str = Field(min_length=1) - - -class MasterVaultSetupRequest(BaseModel): - """Client sends PBKDF2 salt + key-verifier blob; server never sees the raw password.""" - - salt: str = Field(min_length=1) - verifier: KeyVerifier - - -class MasterVaultOut(BaseModel): - model_config = ConfigDict(extra="ignore") - - salt: str - verifier: KeyVerifier - createdAt: int - - -# ── Backup codes ────────────────────────────────────────────────────────────── - -class BackupCodeEntry(BaseModel): - codeId: str = Field(min_length=1) - codeSalt: str = Field(min_length=1) - encrypted: str = Field(min_length=1) - iv: str = Field(min_length=1) - used: bool = False - - -class StoreBackupCodesRequest(BaseModel): - codes: list[BackupCodeEntry] = Field(min_length=1, max_length=8) - - -class BackupCodeLookupRequest(BaseModel): - codeId: str = Field(min_length=1) - - -class BackupCodeDataOut(BaseModel): - codeSalt: str - encrypted: str - iv: str diff --git a/apps/backend/app/api/routes/auth/services.py b/apps/backend/app/api/routes/auth/services.py deleted file mode 100644 index 9ac1f4f6..00000000 --- a/apps/backend/app/api/routes/auth/services.py +++ /dev/null @@ -1,138 +0,0 @@ -import hashlib -from typing import Annotated - -from fastapi import Cookie, Depends, Header, HTTPException, Request, status - -from app.api.routes.auth.schema import UserProfileResponse, PersonalInfo, Certification -from app.api.routes.auth.tokens import decode_access_token -from app.api.routes.auth.users_repo import get_user_doc -from app.core.auth_cookies import ACCESS_COOKIE_NAME -from app.core.cache import cached as _cached, cache_invalidate, get_or_set -from app.core.cache.keys import build_key -from app.core.firebase import get_firebase_app - -try: - from firebase_admin import auth as firebase_auth -except ModuleNotFoundError: # pragma: no cover - firebase_auth = None # type: ignore[assignment] - - -def _token_cache_key(token: str) -> str: - h = hashlib.sha256(token.encode("utf-8")).hexdigest()[:16] - return build_key(ns="auth_token", scope="global", uid=None, ver=None, op="verify", args_hash=h) - - -def verify_id_token(id_token: str, check_revoked: bool = False) -> dict: - if firebase_auth is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="firebase-admin is not installed.", - ) - - get_firebase_app() - try: - return firebase_auth.verify_id_token(id_token, check_revoked=check_revoked) - except firebase_auth.RevokedIdTokenError as exc: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Token has been revoked.", - ) from exc - except (firebase_auth.InvalidIdTokenError, firebase_auth.UserDisabledError, ValueError) as exc: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid Firebase ID token.", - ) from exc - except Exception as exc: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Unable to verify Firebase token.", - ) from exc - - -async def verify_id_token_cached(id_token: str, check_revoked: bool = False) -> dict: - key = _token_cache_key(id_token) - - async def _loader(): - return verify_id_token(id_token, check_revoked=check_revoked) - - return await get_or_set(ns="auth_token", key=key, loader=_loader) - - -@_cached(ns="auth_user", ttl=60, scope="user") -async def _fetch_user_doc_cached(*, uid: str) -> dict | None: - return await get_user_doc(uid) - - -def get_current_uid( - authorization: Annotated[str | None, Header(alias="Authorization")] = None, - mdt_at: Annotated[str | None, Cookie(alias=ACCESS_COOKIE_NAME)] = None, -) -> str: - token: str | None = None - if authorization: - scheme, _, value = authorization.partition(" ") - if scheme.lower() == "bearer" and value.strip(): - token = value.strip() - if not token and mdt_at and mdt_at.strip(): - token = mdt_at.strip() - if not token: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Not authenticated.", - ) - return decode_access_token(token) - - -async def get_current_user( - request: Request, - uid: str = Depends(get_current_uid), -) -> UserProfileResponse: - """Resolve the current user profile, memoized per request on - ``request.state.current_user_doc``. - - Multiple ``Depends(get_current_user)`` in the same request hit Mongo - once. If a handler mutates the user document mid-request and needs - fresh data afterwards, reset the slot first: - ``request.state.current_user_doc = None``. - """ - cached_doc = getattr(request.state, "current_user_doc", None) - if cached_doc is not None and cached_doc.get("_id") == uid: - doc = cached_doc - else: - doc = await _fetch_user_doc_cached(uid=uid) - if not doc: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="User not found.", - ) - if doc.get("disabled"): - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="Account disabled.", - ) - request.state.current_user_doc = doc - - return UserProfileResponse( - uid=str(doc["_id"]), - email=doc.get("email"), - display_name=doc.get("display_name"), - photo_url=doc.get("photo_url"), - email_verified=bool(doc.get("email_verified")), - disabled=bool(doc.get("disabled")), - github_username=doc.get("github_username"), - username=doc.get("username"), - bio=doc.get("bio"), - social_links=doc.get("social_links"), - tech_stacks=doc.get("tech_stacks") or [], - experiences=doc.get("experiences") or [], - projects=doc.get("projects") or [], - education=doc.get("education") or [], - certifications=[Certification(**c) for c in doc.get("certifications") or []], - portfolio_settings=doc.get("portfolio_settings"), - personal_info=PersonalInfo(**doc["personal_info"]) if doc.get("personal_info") else None, - created_at=doc.get("created_at"), - onboarding_completed=bool(doc.get("onboarding_completed", False)), - workspace_setup_at=doc.get("workspace_setup_at"), - migrated_at=doc.get("migrated_at"), - migration_status=doc.get("migration_status"), - migration_progress=doc.get("migration_progress"), - ) diff --git a/apps/backend/app/api/routes/auth/tokens.py b/apps/backend/app/api/routes/auth/tokens.py deleted file mode 100644 index eb5c1a58..00000000 --- a/apps/backend/app/api/routes/auth/tokens.py +++ /dev/null @@ -1,83 +0,0 @@ -from __future__ import annotations - -import hashlib -import secrets -from datetime import datetime, timedelta, timezone -from typing import Any - -from jose import JWTError, jwt - -from app.core.config import get_settings - - -def _utcnow() -> datetime: - return datetime.now(timezone.utc) - - -def hash_refresh_token(raw: str) -> str: - return hashlib.sha256(raw.encode("utf-8")).hexdigest() - - -def new_refresh_token() -> str: - return secrets.token_urlsafe(48) - - -def create_access_token(uid: str) -> str: - settings = get_settings() - expire = _utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) - payload: dict[str, Any] = { - "sub": uid, - "typ": "access", - "exp": expire, - "iat": _utcnow(), - } - return jwt.encode(payload, settings.JWT_SECRET_KEY, algorithm=settings.JWT_ALGORITHM) - - -def _payload_uid(payload: dict[str, Any]) -> str | None: - if payload.get("typ") != "access": - return None - uid = payload.get("sub") - return uid if isinstance(uid, str) and uid else None - - -def try_decode_access_token_uid(token: str) -> str | None: - settings = get_settings() - try: - payload = jwt.decode( - token, - settings.JWT_SECRET_KEY, - algorithms=[settings.JWT_ALGORITHM], - options={"verify_aud": False}, - ) - except JWTError: - return None - return _payload_uid(payload) - - -def decode_access_token(token: str) -> str: - settings = get_settings() - try: - payload = jwt.decode( - token, - settings.JWT_SECRET_KEY, - algorithms=[settings.JWT_ALGORITHM], - options={"verify_aud": False}, - ) - except JWTError as exc: - from fastapi import HTTPException, status - - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid or expired access token.", - ) from exc - - uid = _payload_uid(payload) - if not uid: - from fastapi import HTTPException, status - - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid access token.", - ) - return uid diff --git a/apps/backend/app/api/routes/auth/users_repo.py b/apps/backend/app/api/routes/auth/users_repo.py deleted file mode 100644 index e561cb4c..00000000 --- a/apps/backend/app/api/routes/auth/users_repo.py +++ /dev/null @@ -1,236 +0,0 @@ -from __future__ import annotations - -from typing import Any - -from app.utils.collection_name import USERS -from app.utils.utils import create_timestamp -from app.core.cache import bump_version -from app.database import db_manager - - -async def upsert_user_from_firebase_claims(decoded: dict[str, Any]) -> None: - uid = decoded.get("uid") - if not uid: - return - now = create_timestamp() - doc = { - "_id": uid, - "uid": uid, - "email": decoded.get("email"), - "display_name": decoded.get("name"), - "photo_url": decoded.get("picture"), - "email_verified": bool(decoded.get("email_verified")), - "disabled": False, - "updated_at": now, - } - await db_manager.update_one( - USERS, - {"_id": uid}, - {"$set": doc, "$setOnInsert": {"created_at": now, "onboarding_completed": False}}, - upsert=True, - ) - - -async def get_user_doc(uid: str) -> dict[str, Any] | None: - return await db_manager.find_one(USERS, {"_id": uid}) - - -async def is_username_taken(username: str, exclude_uid: str) -> bool: - count = await db_manager.count_documents(USERS, {"username": username, "_id": {"$ne": exclude_uid}}) - return count > 0 - - -async def get_user_doc_by_username(username: str) -> dict[str, Any] | None: - return await db_manager.find_one(USERS, {"username": username}) - - -async def update_user_profile(uid: str, updates: dict[str, Any]) -> None: - if not updates: - return - now = create_timestamp() - updates["updated_at"] = now - await db_manager.update_one(USERS, {"_id": uid}, {"$set": updates}) - await bump_version(ns="auth_user", uid=uid) - - -async def set_refresh_token_hash( - uid: str, token_hash: str, long_lived: bool = False -) -> None: - now = create_timestamp() - await db_manager.update_one( - USERS, - {"_id": uid}, - { - "$set": { - "refresh_token_hash": token_hash, - "refresh_long_lived": long_lived, - "updated_at": now, - } - }, - ) - await bump_version(ns="auth_user", uid=uid) - - -async def clear_refresh_token_hash(uid: str) -> None: - now = create_timestamp() - await db_manager.update_one( - USERS, {"_id": uid}, {"$unset": {"refresh_token_hash": ""}, "$set": {"updated_at": now}} - ) - await bump_version(ns="auth_user", uid=uid) - - -async def find_uid_by_refresh_hash(token_hash: str) -> str | None: - doc = await db_manager.find_one(USERS, {"refresh_token_hash": token_hash}, projection={"_id": 1}) - if not doc: - return None - uid = doc.get("_id") - return str(uid) if uid is not None else None - - -async def complete_onboarding(uid: str) -> None: - now = create_timestamp() - await db_manager.update_one( - USERS, {"_id": uid}, {"$set": {"onboarding_completed": True, "updated_at": now}} - ) - await bump_version(ns="auth_user", uid=uid) - - -# ── Passkeys / WebAuthn ─────────────────────────────────────────────────────── - - -async def get_or_create_webauthn_user_handle(uid: str, handle_b64: str) -> str: - """Set handle on first call (conditional, race-safe); return stored value.""" - now = create_timestamp() - await db_manager.update_one( - USERS, - {"_id": uid, "webauthn_user_handle": {"$exists": False}}, - {"$set": {"webauthn_user_handle": handle_b64, "updated_at": now}}, - ) - doc = await db_manager.find_one(USERS, {"_id": uid}, projection={"webauthn_user_handle": 1}) - return (doc or {}).get("webauthn_user_handle") or handle_b64 - - -async def list_passkeys(uid: str) -> list[dict[str, Any]]: - doc = await db_manager.find_one(USERS, {"_id": uid}, projection={"passkeys": 1}) - return list((doc or {}).get("passkeys") or []) - - -async def add_passkey(uid: str, passkey: dict[str, Any]) -> None: - now = create_timestamp() - await db_manager.update_one( - USERS, - {"_id": uid}, - {"$push": {"passkeys": passkey}, "$set": {"updated_at": now}}, - ) - await bump_version(ns="auth_user", uid=uid) - - -async def remove_passkey(uid: str, credential_id: str) -> int: - now = create_timestamp() - res = await db_manager.update_one( - USERS, - {"_id": uid, "passkeys.credential_id": credential_id}, - {"$pull": {"passkeys": {"credential_id": credential_id}}, "$set": {"updated_at": now}}, - ) - await bump_version(ns="auth_user", uid=uid) - return getattr(res, "modified_count", 0) - - -async def update_passkey_usage(uid: str, credential_id: str, sign_count: int) -> None: - now = create_timestamp() - await db_manager.update_one( - USERS, - {"_id": uid, "passkeys.credential_id": credential_id}, - { - "$set": { - "passkeys.$.sign_count": sign_count, - "passkeys.$.last_used_at": now, - "updated_at": now, - } - }, - ) - - -async def rename_passkey(uid: str, credential_id: str, device_name: str) -> int: - now = create_timestamp() - res = await db_manager.update_one( - USERS, - {"_id": uid, "passkeys.credential_id": credential_id}, - {"$set": {"passkeys.$.device_name": device_name, "updated_at": now}}, - ) - return getattr(res, "modified_count", 0) - - -async def find_user_by_user_handle(user_handle_b64: str) -> dict[str, Any] | None: - return await db_manager.find_one(USERS, {"webauthn_user_handle": user_handle_b64}) - - -async def find_user_by_credential_id(credential_id: str) -> dict[str, Any] | None: - return await db_manager.find_one(USERS, {"passkeys.credential_id": credential_id}) - - -# ── Workspace setup ─────────────────────────────────────────────────────────── - - -async def mark_workspace_setup(uid: str, personal_workspace_id: str) -> None: - await db_manager.update_one( - USERS, - {"_id": uid}, - {"$set": { - "workspace_setup_at": create_timestamp(), - "personal_workspace_id": personal_workspace_id, - }}, - ) - await bump_version(ns="auth_user", uid=uid) - - -async def get_workspace_setup_at(uid: str) -> int | None: - doc = await db_manager.find_one(USERS, {"_id": uid}) - if not doc: - return None - return doc.get("workspace_setup_at") - - -async def get_personal_workspace_id(uid: str) -> str | None: - doc = await db_manager.find_one(USERS, {"_id": uid}) - if not doc: - return None - return doc.get("personal_workspace_id") - - -async def mark_migration_pending(uid: str) -> None: - await db_manager.update_one( - USERS, {"_id": uid}, {"$set": {"migration_status": "pending"}} - ) - await bump_version(ns="auth_user", uid=uid) - - -async def get_migrated_at(uid: str) -> int | None: - doc = await db_manager.find_one(USERS, {"_id": uid}) - if not doc: - return None - return doc.get("migrated_at") - - -async def get_migration_progress(uid: str) -> dict | None: - doc = await db_manager.find_one(USERS, {"_id": uid}) - return (doc or {}).get("migration_progress") - - -async def set_migration_progress(uid: str, progress: dict) -> None: - await db_manager.update_one( - USERS, {"_id": uid}, {"$set": {"migration_progress": progress}} - ) - await bump_version(ns="auth_user", uid=uid) - - -async def mark_migrated(uid: str) -> None: - await db_manager.update_one( - USERS, - {"_id": uid}, - {"$set": { - "migrated_at": create_timestamp(), - "migration_status": "done", - }}, - ) - await bump_version(ns="auth_user", uid=uid) diff --git a/apps/backend/app/api/routes/health.py b/apps/backend/app/api/routes/health.py deleted file mode 100644 index 4fce22bc..00000000 --- a/apps/backend/app/api/routes/health.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import APIRouter - -router = APIRouter(prefix="/health", tags=["health"]) - - -@router.get("", summary="Health check") -def health_check() -> dict[str, str]: - return {"status": "ok"} diff --git a/apps/backend/app/core/__init__.py b/apps/backend/app/core/__init__.py deleted file mode 100644 index d654410a..00000000 --- a/apps/backend/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Core backend modules (config, utils, etc.).""" diff --git a/apps/backend/app/core/audit.py b/apps/backend/app/core/audit.py deleted file mode 100644 index f891d6fb..00000000 --- a/apps/backend/app/core/audit.py +++ /dev/null @@ -1,126 +0,0 @@ -from __future__ import annotations - -import re -from contextvars import ContextVar -from dataclasses import dataclass - -# Field names whose values are safe to record verbatim in the audit diff. -# Anything NOT in this set is recorded as "[redacted]" (default-deny). -SAFE_FIELDS: set[str] = { - "title", "name", "tags", "folderId", "parentId", "status", "statusOrder", - "color", "icon", "description", "url", "isExpanded", "projectId", - "priority", "dueDate", "completed", "language", "createdAt", "updatedAt", -} - -REDACTED = "[redacted]" - - -@dataclass -class AuditContext: - action: str | None = None - module: str | None = None - entity_type: str | None = None - entity_id: str | None = None - changes: list[dict] | None = None - summary: str | None = None - - -_audit_ctx: ContextVar[AuditContext | None] = ContextVar("audit_ctx", default=None) - - -def current_context() -> AuditContext | None: - return _audit_ctx.get() - - -def set_action(action: str) -> None: - ctx = _audit_ctx.get() - if ctx is not None: - ctx.action = action - - -def set_entity(entity_type: str, entity_id: str | None) -> None: - ctx = _audit_ctx.get() - if ctx is not None: - ctx.entity_type = entity_type - ctx.entity_id = entity_id - - -def set_summary(text: str) -> None: - ctx = _audit_ctx.get() - if ctx is not None: - ctx.summary = text - - -def set_changes(changes: list[dict]) -> None: - ctx = _audit_ctx.get() - if ctx is not None: - ctx.changes = changes - - -def add_change(field_name: str, before, after) -> None: - ctx = _audit_ctx.get() - if ctx is None: - return - if ctx.changes is None: - ctx.changes = [] - ctx.changes.append({"field": field_name, "before": before, "after": after}) - - -def _redact(field_name: str, value, allow_fields: set[str]): - return value if field_name in allow_fields else REDACTED - - -def diff(before: dict | None, after: dict | None, allow_fields: set[str] | None = None) -> list[dict]: - allow = SAFE_FIELDS if allow_fields is None else allow_fields - before = before or {} - after = after or {} - changes: list[dict] = [] - for key in sorted(set(before) | set(after)): - if key in ("_id", "created_by"): - continue - b = before.get(key) - a = after.get(key) - if b == a: - continue - changes.append({ - "field": key, - "before": _redact(key, b, allow), - "after": _redact(key, a, allow), - }) - return changes - - -_BROWSERS = [ - ("Edg", "Edge"), ("OPR", "Opera"), ("Chrome", "Chrome"), - ("Firefox", "Firefox"), ("Safari", "Safari"), -] - - -def parse_user_agent(ua: str | None) -> dict: - fallback = {"browser": "Unknown", "os": "Unknown", "device_type": "desktop"} - if not ua: - return fallback - browser = "Unknown" - for token, name in _BROWSERS: - if token in ua: - browser = name - break - if "Windows" in ua: - os_name = "Windows" - elif "Mac OS X" in ua or "Macintosh" in ua: - os_name = "macOS" - elif "Android" in ua: - os_name = "Android" - elif "iPhone" in ua or "iPad" in ua or "iOS" in ua: - os_name = "iOS" - elif "Linux" in ua: - os_name = "Linux" - else: - os_name = "Unknown" - if re.search(r"Mobi|iPhone|Android.*Mobile", ua): - device_type = "mobile" - elif "iPad" in ua or ("Android" in ua and "Mobile" not in ua): - device_type = "tablet" - else: - device_type = "desktop" - return {"browser": browser, "os": os_name, "device_type": device_type} diff --git a/apps/backend/app/core/audit_middleware.py b/apps/backend/app/core/audit_middleware.py deleted file mode 100644 index 6ef1341c..00000000 --- a/apps/backend/app/core/audit_middleware.py +++ /dev/null @@ -1,111 +0,0 @@ -from __future__ import annotations - -import asyncio -import datetime -import logging - -from starlette.middleware.base import BaseHTTPMiddleware -from starlette.requests import Request - -from app.core import audit -from app.core.auth_cookies import ACCESS_COOKIE_NAME -from app.database import db_manager -from app.api.routes.auth.tokens import try_decode_access_token_uid -from app.utils.collection_name import AUDIT_LOG -from app.utils.utils import create_timestamp, new_id - -logger = logging.getLogger(__name__) - -_SKIP_METHODS = {"GET", "HEAD", "OPTIONS"} -_TTL_DAYS = 90 - - -def _extract_uid(request: Request) -> str | None: - token = None - auth = request.headers.get("authorization") - if auth: - scheme, _, value = auth.partition(" ") - if scheme.lower() == "bearer" and value.strip(): - token = value.strip() - if not token: - cookie = request.cookies.get(ACCESS_COOKIE_NAME) - if cookie and cookie.strip(): - token = cookie.strip() - if not token: - return None - try: - return try_decode_access_token_uid(token) - except Exception: # never let auth decode break auditing - return None - - -def _client_ip(request: Request) -> str | None: - fwd = request.headers.get("x-forwarded-for") - if fwd: - return fwd.split(",")[0].strip() - return request.client.host if request.client else None - - -def _module_from_path(path: str) -> str | None: - # /api/v1//... - parts = [p for p in path.split("/") if p] - if len(parts) >= 3 and parts[0] == "api" and parts[1] == "v1": - return parts[2] - return None - - -async def write_audit_event(doc: dict) -> None: - try: - await db_manager.insert_one(AUDIT_LOG, doc) - except Exception as exc: # swallow — auditing must never break requests - logger.warning("audit write failed: %s", exc) - - -class AuditMiddleware(BaseHTTPMiddleware): - async def dispatch(self, request: Request, call_next): - path = request.url.path - if request.method in _SKIP_METHODS or not path.startswith("/api/v1"): - return await call_next(request) - if path.startswith("/api/v1/health"): - return await call_next(request) - - ctx = audit.AuditContext() - token = audit._audit_ctx.set(ctx) - started = create_timestamp() - uid = _extract_uid(request) - status_code = 500 - try: - response = await call_next(request) - status_code = response.status_code - return response - finally: - try: - latency = create_timestamp() - started - module = ctx.module or _module_from_path(path) - action = ctx.action or (f"{module}.{request.method.lower()}" if module else request.method.lower()) - ts = create_timestamp() - doc = { - "_id": new_id(), - "uid": uid, - "action": action, - "module": module, - "entity_type": ctx.entity_type, - "entity_id": ctx.entity_id, - "method": request.method, - "path": path, - "status": status_code, - "outcome": "success" if status_code < 400 else "failure", - "changes": ctx.changes, - "summary": ctx.summary, - "ip": _client_ip(request), - "ua_raw": request.headers.get("user-agent"), - "device": audit.parse_user_agent(request.headers.get("user-agent")), - "latency_ms": latency, - "ts": ts, - "expireAt": datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=_TTL_DAYS), - } - asyncio.create_task(write_audit_event(doc)) - except Exception as exc: # never propagate - logger.warning("audit envelope build failed: %s", exc) - finally: - audit._audit_ctx.reset(token) diff --git a/apps/backend/app/core/auth_cookies.py b/apps/backend/app/core/auth_cookies.py deleted file mode 100644 index 043d0fa9..00000000 --- a/apps/backend/app/core/auth_cookies.py +++ /dev/null @@ -1,4 +0,0 @@ -"""HttpOnly cookie names for API access and refresh tokens (same-origin with Next.js proxy).""" - -ACCESS_COOKIE_NAME = "mdt_at" -REFRESH_COOKIE_NAME = "mdt_rt" diff --git a/apps/backend/app/core/cache/__init__.py b/apps/backend/app/core/cache/__init__.py deleted file mode 100644 index f87b1a7b..00000000 --- a/apps/backend/app/core/cache/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Public cache API.""" -from app.core.cache.decorator import ( - bump_version, - cache_invalidate, - cached, - get_or_set, -) - -__all__ = ["cached", "bump_version", "cache_invalidate", "get_or_set"] diff --git a/apps/backend/app/core/cache/decorator.py b/apps/backend/app/core/cache/decorator.py deleted file mode 100644 index 542cf5d7..00000000 --- a/apps/backend/app/core/cache/decorator.py +++ /dev/null @@ -1,233 +0,0 @@ -"""Cache decorator + helpers — read path, fail-open, version invalidation.""" -from __future__ import annotations - -import asyncio -import functools -import inspect -import logging -import random -import time -from typing import Any, Awaitable, Callable, Literal, Optional - -from redis.exceptions import RedisError - -from app.core.cache.keys import ( - NAMESPACES, - NamespaceSpec, - args_hash as _args_hash, - build_key, - version_key, -) -from app.core.cache.serializer import dumps, loads -from app.core.cache.xfetch import should_refresh, unwrap_payload, wrap_payload - -log = logging.getLogger("app.cache") - - -# --------------------------------------------------------------------------- -# Thin shims — patched by tests via monkeypatch.setattr("...decorator.") -# --------------------------------------------------------------------------- - -def is_namespace_enabled(ns: str) -> bool: # pragma: no cover - from app.core.cache.flags import is_namespace_enabled as _real - return _real(ns) - - -def get_redis(): # pragma: no cover - from app.core.redis_client import get_redis as _real - return _real() - - -def _secret() -> bytes: - try: - from app.core.config import get_settings - s = get_settings() - return (s.JWT_SECRET_KEY or "default-cache-secret").encode("utf-8") - except Exception: # noqa: BLE001 - return b"default-cache-secret" - - -# --------------------------------------------------------------------------- -# Internal helpers -# --------------------------------------------------------------------------- - -_DEFAULT_TIMEOUT_S: float = 0.05 # 50 ms fallback when settings unavailable - - -async def _safe(coro: Awaitable, *, op: str, ns: str) -> Any: - try: - from app.core.config import get_settings - timeout = get_settings().CACHE_OP_TIMEOUT_MS / 1000 - except Exception: # noqa: BLE001 - timeout = _DEFAULT_TIMEOUT_S - try: - return await asyncio.wait_for(coro, timeout=timeout) - except (RedisError, asyncio.TimeoutError, ConnectionError, OSError) as exc: - log.warning("cache.error op=%s ns=%s err=%s msg=%s", op, ns, type(exc).__name__, exc) - return None - - -async def _get_version(r, ns: str, uid: str) -> int: - raw = await _safe(r.get(version_key(ns, uid)), op="ver_get", ns=ns) - if raw is None: - return 0 - try: - return int(raw) - except (TypeError, ValueError): - return 0 - - -# --------------------------------------------------------------------------- -# Public API -# --------------------------------------------------------------------------- - -def cached( - *, - ns: str, - ttl: Optional[int] = None, - scope: Optional[Literal["user", "global"]] = None, - strategy: Optional[Literal["simple", "xfetch"]] = None, - key: Optional[Callable[..., str]] = None, -): - spec: NamespaceSpec | None = NAMESPACES.get(ns) - if spec is None: - raise ValueError(f"Namespace not registered: {ns!r}") - eff_scope = scope or spec["scope"] - eff_ttl = ttl or spec["default_ttl"] - eff_strategy = strategy or spec["default_strategy"] - if eff_strategy == "xfetch" and eff_scope != "global": - raise ValueError("xfetch requires scope='global'") - - def decorator(fn: Callable[..., Awaitable[Any]]): - if not inspect.iscoroutinefunction(fn): - raise TypeError(f"@cached requires async fn; got {fn!r}") - op_name = fn.__name__ - - @functools.wraps(fn) - async def wrapper(*args, **kwargs): - # always allow disabled-namespace short-circuit - if not is_namespace_enabled(ns): - return await fn(*args, **kwargs) - - r = get_redis() - if r is None: - return await fn(*args, **kwargs) - - uid = kwargs.get("uid") - if eff_scope == "user" and not uid: - raise ValueError(f"{op_name}: scope='user' requires uid kwarg") - - # build args_hash from kwargs except 'uid' - hash_args = {k: v for k, v in kwargs.items() if k != "uid"} - ah = key(**kwargs) if key else _args_hash(hash_args, secret=_secret()) - - ver = await _get_version(r, ns, uid) if eff_scope == "user" else None - k = build_key(ns=ns, scope=eff_scope, uid=uid, ver=ver, op=op_name, args_hash=ah) - k_bytes = k.encode() - - raw = await _safe(r.get(k_bytes), op="get", ns=ns) - if raw is not None: - try: - payload = loads(raw) - except Exception as exc: # noqa: BLE001 - log.warning("cache.deserialize.failed ns=%s err=%s", ns, exc) - payload = None - - if eff_strategy == "xfetch" and isinstance(payload, dict) and "v" in payload: - value, computed_at, delta = unwrap_payload(payload) - try: - from app.core.config import get_settings as _gs - beta = _gs().CACHE_XFETCH_BETA - except Exception: # noqa: BLE001 - beta = 1.0 - if should_refresh( - computed_at=computed_at, - ttl=eff_ttl, - delta=delta, - beta=beta, - now=time.time(), - rand=random.random(), - ): - asyncio.create_task(_refresh(fn, args, kwargs, r, k_bytes, eff_ttl, ns, eff_strategy)) - log.debug("cache.hit ns=%s op=%s", ns, op_name) - return value - - log.debug("cache.hit ns=%s op=%s", ns, op_name) - return payload - - log.debug("cache.miss ns=%s op=%s", ns, op_name) - t0 = time.time() - result = await fn(*args, **kwargs) - delta = max(time.time() - t0, 0.001) - - if eff_strategy == "xfetch": - stored = dumps(wrap_payload(result, computed_at=time.time(), delta=delta)) - else: - stored = dumps(result) - - await _safe(r.setex(k_bytes, eff_ttl, stored), op="setex", ns=ns) - return result - - return wrapper - - return decorator - - -async def _refresh(fn, args, kwargs, r, k_bytes, ttl, ns, strategy): - try: - t0 = time.time() - result = await fn(*args, **kwargs) - delta = max(time.time() - t0, 0.001) - if strategy == "xfetch": - stored = dumps(wrap_payload(result, computed_at=time.time(), delta=delta)) - else: - stored = dumps(result) - await _safe(r.setex(k_bytes, ttl, stored), op="setex_refresh", ns=ns) - log.info("cache.xfetch.refresh ns=%s", ns) - except Exception as exc: # noqa: BLE001 - log.warning("cache.xfetch.refresh.failed ns=%s err=%s", ns, exc) - - -async def bump_version(*, ns: str, uid: str) -> None: - r = get_redis() - if r is None: - return - await _safe(r.incr(version_key(ns, uid)), op="ver_incr", ns=ns) - log.info("cache.bump_version ns=%s uid=%s", ns, uid) - - -async def cache_invalidate(*, ns: str, key: str) -> None: - r = get_redis() - if r is None: - return - await _safe(r.delete(key.encode() if isinstance(key, str) else key), op="del", ns=ns) - - -async def get_or_set( - *, - ns: str, - key: str, - loader: Callable[[], Awaitable[Any]], - ttl: Optional[int] = None, - strategy: Optional[Literal["simple", "xfetch"]] = None, -) -> Any: - spec = NAMESPACES.get(ns) - if spec is None: - return await loader() - eff_ttl = ttl or spec["default_ttl"] - eff_strategy = strategy or spec["default_strategy"] - if not is_namespace_enabled(ns): - return await loader() - r = get_redis() - if r is None: - return await loader() - raw = await _safe(r.get(key.encode()), op="get", ns=ns) - if raw is not None: - return loads(raw) - result = await loader() - if eff_strategy == "xfetch": - stored = dumps(wrap_payload(result, computed_at=time.time(), delta=0.001)) - else: - stored = dumps(result) - await _safe(r.setex(key.encode(), eff_ttl, stored), op="setex", ns=ns) - return result diff --git a/apps/backend/app/core/cache/flags.py b/apps/backend/app/core/cache/flags.py deleted file mode 100644 index 70354702..00000000 --- a/apps/backend/app/core/cache/flags.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Per-namespace cache enable flags.""" -from __future__ import annotations - -from functools import lru_cache - -from app.core import config - - -@lru_cache(maxsize=1) -def _parsed_namespaces() -> frozenset[str]: - raw = config.get_settings().CACHE_NAMESPACES or "" - return frozenset(p.strip() for p in raw.split(",") if p.strip()) - - -def is_namespace_enabled(ns: str) -> bool: - s = config.get_settings() - if not s.CACHE_ENABLED: - return False - return ns in _parsed_namespaces() diff --git a/apps/backend/app/core/cache/keys.py b/apps/backend/app/core/cache/keys.py deleted file mode 100644 index 7bc1427c..00000000 --- a/apps/backend/app/core/cache/keys.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Cache key builders + namespace registry.""" -from __future__ import annotations - -import hashlib -from typing import Literal, TypedDict - -import orjson - - -class NamespaceSpec(TypedDict): - scope: Literal["user", "global"] - default_ttl: int - default_strategy: Literal["simple", "xfetch"] - - -NAMESPACES: dict[str, NamespaceSpec] = {} - - -def register_namespace( - name: str, - *, - scope: Literal["user", "global"], - default_ttl: int, - default_strategy: Literal["simple", "xfetch"] = "simple", -) -> None: - if scope == "user" and default_strategy == "xfetch": - raise ValueError("xfetch requires scope='global'") - NAMESPACES[name] = {"scope": scope, "default_ttl": default_ttl, "default_strategy": default_strategy} - - -def build_key( - *, - ns: str, - scope: str, - uid: str | None, - ver: int | None, - op: str, - args_hash: str, -) -> str: - if scope == "user": - if uid is None or ver is None: - raise ValueError("user-scoped key requires uid and ver") - return f"cache:{ns}:u:{uid}:v{ver}:{op}:{args_hash}" - return f"cache:{ns}:g:{op}:{args_hash}" - - -def version_key(ns: str, uid: str) -> str: - return f"cache:ver:{ns}:u:{uid}" - - -def args_hash(kwargs: dict, *, secret: bytes) -> str: - payload = orjson.dumps(kwargs, option=orjson.OPT_SORT_KEYS) - return hashlib.blake2b(payload, digest_size=8, key=secret[:64]).hexdigest() - - -# --- spec-locked namespaces --- -register_namespace("auth_token", scope="global", default_ttl=300) -register_namespace("auth_user", scope="user", default_ttl=60) diff --git a/apps/backend/app/core/cache/serializer.py b/apps/backend/app/core/cache/serializer.py deleted file mode 100644 index c7462acd..00000000 --- a/apps/backend/app/core/cache/serializer.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Cache serializer — orjson with Pydantic + datetime + bytes support.""" -from __future__ import annotations - -import base64 -from typing import Any - -import orjson -from pydantic import BaseModel - - -def _default(obj: Any) -> Any: - if isinstance(obj, BaseModel): - return obj.model_dump(mode="json") - if isinstance(obj, bytes): - return base64.b64encode(obj).decode("ascii") - raise TypeError(f"Type {type(obj).__name__} not serializable") - - -def dumps(value: Any) -> bytes: - return orjson.dumps( - value, - default=_default, - option=orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_NUMPY, - ) - - -def loads(payload: bytes) -> Any: - return orjson.loads(payload) diff --git a/apps/backend/app/core/cache/xfetch.py b/apps/backend/app/core/cache/xfetch.py deleted file mode 100644 index 8f7f8eff..00000000 --- a/apps/backend/app/core/cache/xfetch.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Probabilistic early-expiration (XFetch). - -Reference: "Optimal Probabilistic Cache Stampede Prevention" (Vattani et al., 2015). -""" -from __future__ import annotations - -import math -from typing import Any - - -def should_refresh( - *, - computed_at: float, - ttl: float, - delta: float, - beta: float, - now: float, - rand: float, -) -> bool: - """Return True if the caller should refresh the cached value now.""" - if rand <= 0.0: - rand = 1e-12 - if rand > 1.0: - rand = 1.0 - threshold = computed_at + ttl + beta * delta * math.log(rand) - return now >= threshold - - -def wrap_payload(value: Any, *, computed_at: float, delta: float) -> dict: - return {"v": value, "ca": computed_at, "dt": delta} - - -def unwrap_payload(payload: dict) -> tuple[Any, float, float]: - return payload["v"], float(payload["ca"]), float(payload["dt"]) diff --git a/apps/backend/app/core/config.py b/apps/backend/app/core/config.py deleted file mode 100644 index cabeba47..00000000 --- a/apps/backend/app/core/config.py +++ /dev/null @@ -1,76 +0,0 @@ -from functools import lru_cache - -from pydantic import Field -from pydantic_settings import BaseSettings, SettingsConfigDict - - -class Settings(BaseSettings): - APP_NAME: str = "MyDevTools API" - APP_ENV: str = "development" - APP_DEBUG: bool = True - APP_HOST: str = "0.0.0.0" - APP_PORT: int = 8000 - - FIREBASE_CREDENTIALS_JSON: str | None = None - - MONGO_DB_URL: str = "mongodb://localhost:27017" - MONGO_DB_NAME: str = "mydevtools" - - # HS256 secret for API JWTs (set a strong value in production). - JWT_SECRET_KEY: str = Field( - default="DEFAULT_DEV_JWT_SECRET_NOT_FOR_PRODUCTION", - min_length=16, - ) - JWT_ALGORITHM: str = "HS256" - ACCESS_TOKEN_EXPIRE_MINUTES: int - REFRESH_TOKEN_EXPIRE_DAYS: int - # Long-lived sessions (desktop app): refresh cookie TTL when the client - # requests `long_lived`. Decoupled from the web REFRESH_TOKEN_EXPIRE_DAYS. - LONG_LIVED_REFRESH_TOKEN_EXPIRE_DAYS: int = 60 - # HttpOnly cookies: use Secure=true over HTTPS (recommended in production). - AUTH_COOKIE_SECURE: bool = False - - ALLOWED_ORIGINS: str - - # WebAuthn / passkeys - WEBAUTHN_RP_ID: str - WEBAUTHN_RP_NAME: str - WEBAUTHN_ORIGINS: str - WEBAUTHN_CHALLENGE_TTL_SECONDS: int - - # Email (Resend) - RESEND_API_KEY: str | None = None - INVITATION_FROM_EMAIL: str = "MyDevTools " - APP_PUBLIC_URL: str = "http://localhost:3000" - - # Redis + cache - REDIS_URL: str | None = None - CACHE_ENABLED: bool = True - CACHE_NAMESPACES: str = "" # comma-separated; empty = no-op - CACHE_DEFAULT_TTL: int = 120 # seconds - CACHE_OP_TIMEOUT_MS: int = 50 # per Redis call - CACHE_XFETCH_BETA: float = 1.0 # XFetch tuning constant - CACHE_LOG_LEVEL: str = "WARNING" - - model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8") - - def model_post_init(self, __context: object) -> None: # type: ignore[override] - dev_placeholder = "DEFAULT_DEV_JWT_SECRET_NOT_FOR_PRODUCTION" - if self.APP_ENV == "production" and self.JWT_SECRET_KEY == dev_placeholder: - raise ValueError( - "JWT_SECRET_KEY must be set in the environment when APP_ENV=production", - ) - if self.APP_ENV == "production" and not self.AUTH_COOKIE_SECURE: - raise ValueError( - "AUTH_COOKIE_SECURE must be true when APP_ENV=production", - ) - # L-2 fix: prevent debug mode in production - if self.APP_ENV == "production" and self.APP_DEBUG: - raise ValueError( - "APP_DEBUG must be false when APP_ENV=production", - ) - - -@lru_cache -def get_settings() -> Settings: - return Settings() diff --git a/apps/backend/app/core/firebase.py b/apps/backend/app/core/firebase.py deleted file mode 100644 index a70b0ba1..00000000 --- a/apps/backend/app/core/firebase.py +++ /dev/null @@ -1,41 +0,0 @@ -import json -from pathlib import Path - -try: - import firebase_admin - from firebase_admin import credentials -except ModuleNotFoundError: # pragma: no cover - firebase_admin = None # type: ignore[assignment] - credentials = None # type: ignore[assignment] - -from app.core.config import get_settings - - -def get_firebase_app(): - """ - Return the initialized Firebase app instance. - If already initialized, reuse the default app. - """ - if firebase_admin is None or credentials is None: - raise RuntimeError( - "firebase-admin is not installed. Install backend deps to use Firebase auth." - ) - try: - return firebase_admin.get_app() - except ValueError: - pass - - settings = get_settings() - cred_json = settings.FIREBASE_CREDENTIALS_JSON - - if cred_json: - try: - data = json.loads(cred_json) - except json.JSONDecodeError as exc: - raise ValueError("Invalid FIREBASE_CREDENTIALS_JSON value. Must be valid JSON.") from exc - cert = credentials.Certificate(data) - return firebase_admin.initialize_app(cert) - - - - return firebase_admin.initialize_app() diff --git a/apps/backend/app/core/indexes.py b/apps/backend/app/core/indexes.py deleted file mode 100644 index d595a1e0..00000000 --- a/apps/backend/app/core/indexes.py +++ /dev/null @@ -1,23 +0,0 @@ -from app.database import db_manager -from app.utils.collection_name import ( - AUDIT_LOG, - USERS, - WEBAUTHN_CHALLENGES, -) -from app.core.config import get_settings - - -async def ensure_indexes() -> None: - await db_manager.create_index(USERS, "refresh_token_hash", sparse=True) - await db_manager.create_index(USERS, "username", unique=True, sparse=True) - # Passkeys / WebAuthn - await db_manager.create_index(USERS, "webauthn_user_handle", unique=True, sparse=True) - await db_manager.create_index(USERS, "passkeys.credential_id", sparse=True) - await db_manager.create_index( - WEBAUTHN_CHALLENGES, - "created_at", - expire_after_seconds=get_settings().WEBAUTHN_CHALLENGE_TTL_SECONDS, - ) - await db_manager.create_index(AUDIT_LOG, [("uid", 1), ("ts", -1)]) - await db_manager.create_index(AUDIT_LOG, [("uid", 1), ("module", 1), ("ts", -1)]) - await db_manager.create_index(AUDIT_LOG, "expireAt", expire_after_seconds=0) diff --git a/apps/backend/app/core/limiter.py b/apps/backend/app/core/limiter.py deleted file mode 100644 index 9916f871..00000000 --- a/apps/backend/app/core/limiter.py +++ /dev/null @@ -1,32 +0,0 @@ -import logging - -from fastapi import Request -from slowapi import Limiter -from slowapi.util import get_remote_address - -from app.core.config import get_settings - -log = logging.getLogger("app.limiter") - - -def _get_client_ip(request: Request) -> str: - forwarded = request.headers.get("x-forwarded-for") - if forwarded: - return forwarded.split(",")[0].strip() - return get_remote_address(request) - - -def _build_limiter() -> Limiter: - settings = get_settings() - if settings.REDIS_URL: - try: - return Limiter( - key_func=_get_client_ip, - storage_uri=settings.REDIS_URL, - ) - except Exception as exc: # noqa: BLE001 - log.warning("limiter.redis.fallback err=%s", exc) - return Limiter(key_func=_get_client_ip) - - -limiter = _build_limiter() diff --git a/apps/backend/app/core/redis_client.py b/apps/backend/app/core/redis_client.py deleted file mode 100644 index a10e63ce..00000000 --- a/apps/backend/app/core/redis_client.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Lazy async Redis singleton + lifespan helpers.""" -from __future__ import annotations - -import asyncio -import logging -from typing import Optional - -from redis.asyncio import Redis -from redis.exceptions import RedisError - -from app.core.config import get_settings - -log = logging.getLogger("app.cache") - -_client: Optional[Redis] = None - - -def get_redis() -> Optional[Redis]: - """Return the singleton Redis client or None if unavailable.""" - settings = get_settings() - if settings.REDIS_URL is None: - return None - return _client - - -async def open_redis() -> None: - """Open the pool and ping; called from FastAPI lifespan.""" - global _client - settings = get_settings() - if settings.REDIS_URL is None: - log.info("redis.disabled url_unset") - return - try: - client = Redis.from_url( - settings.REDIS_URL, - encoding="utf-8", - decode_responses=False, - socket_timeout=settings.CACHE_OP_TIMEOUT_MS / 1000, - socket_connect_timeout=2.0, - health_check_interval=30, - ) - await asyncio.wait_for(client.ping(), timeout=2.0) - _client = client - log.info("redis.connected") - except (RedisError, asyncio.TimeoutError, OSError) as exc: - log.error("redis.connect.failed err=%s msg=%s", type(exc).__name__, exc) - _client = None # fail-open: cache layer treats all ops as miss - - -async def close_redis() -> None: - """Close the pool.""" - global _client - if _client is not None: - try: - await _client.aclose() - except Exception as exc: # noqa: BLE001 - log.warning("redis.close.failed err=%s", exc) - _client = None - - -async def is_redis_available() -> bool: - if _client is None: - return False - try: - await asyncio.wait_for(_client.ping(), timeout=0.5) - return True - except (RedisError, asyncio.TimeoutError): - return False diff --git a/apps/backend/app/database/__init__.py b/apps/backend/app/database/__init__.py deleted file mode 100644 index d654410a..00000000 --- a/apps/backend/app/database/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Core backend modules (config, utils, etc.).""" diff --git a/apps/backend/app/database/db.py b/apps/backend/app/database/db.py deleted file mode 100644 index eae23c62..00000000 --- a/apps/backend/app/database/db.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Any - -from app.core.config import get_settings -from motor.motor_asyncio import AsyncIOMotorClient -_client: Any = None - - -def get_mongo_client(): - global _client - if _client is None: - settings = get_settings() - _client = AsyncIOMotorClient(settings.MONGO_DB_URL, maxIdleTimeMS=300000) - return _client - - -def get_db(): - settings = get_settings() - return get_mongo_client()[settings.MONGO_DB_NAME] diff --git a/apps/backend/app/database/db_manager.py b/apps/backend/app/database/db_manager.py deleted file mode 100644 index bfc841e0..00000000 --- a/apps/backend/app/database/db_manager.py +++ /dev/null @@ -1,102 +0,0 @@ -from app.database.db import get_db - -db = get_db() - - -async def insert_one(collection_name, data): - return await db[collection_name].insert_one(data) - - -async def insert_many(collection_name, data_list): - return await db[collection_name].insert_many(data_list) - - -async def update_one(collection_name, query, payload, upsert=False, array_filters=None): - return await db[collection_name].update_one( - query, payload, upsert=upsert, array_filters=array_filters - ) - - -async def update_many(collection_name, query, payload): - return await db[collection_name].update_many(query, payload) - - -async def find_one_and_update(collection_name, query, update_query, return_document=False, upsert=False): - return await db[collection_name].find_one_and_update( - query, update_query, return_document=return_document, upsert=upsert - ) - - -async def replace_one(collection_name, query, replacement, upsert=False): - return await db[collection_name].replace_one(query, replacement, upsert=upsert) - - -async def delete_one(collection_name, query): - return await db[collection_name].delete_one(query) - - -async def delete_many(collection_name, query): - return await db[collection_name].delete_many(query) - - -async def bulk_write(collection_name, data, ordered=True): - return await db[collection_name].bulk_write(data, ordered=ordered) - - -async def find_one(collection_name, query, projection=None): - return await db[collection_name].find_one(query, projection) - - -async def find( - collection_name, - query, - projection=None, - sort=None, - skip=0, - limit=0, - collation=None, -): - cursor = db[collection_name].find(query, projection) - if collation: - cursor = cursor.collation(collation) - if skip: - cursor = cursor.skip(skip) - if sort: - if isinstance(sort, tuple): - sort = [sort] - cursor = cursor.sort(sort) - if limit: - return await cursor.to_list(length=limit) - return await cursor.to_list(length=None) - - -async def count_documents(collection_name, query): - return await db[collection_name].count_documents(query) - - -async def distinct(collection_name, field, query): - if query is None: - query = {} - return await db[collection_name].distinct(field, query) - - -async def aggregate(collection_name, query): - cursor = db[collection_name].aggregate(query) - return await cursor.to_list(length=None) - - -async def create_index( - collection_name, field, unique=False, sparse=False, expire_after_seconds=None -): - kwargs = {"unique": unique, "sparse": sparse} - if expire_after_seconds is not None: - kwargs["expireAfterSeconds"] = expire_after_seconds - await db[collection_name].create_index(field, **kwargs) - - -async def drop_index(collection_name, name): - await db[collection_name].drop_index(name) - - -async def list_indexes(collection_name): - return await db[collection_name].list_indexes().to_list(length=None) diff --git a/apps/backend/app/main.py b/apps/backend/app/main.py deleted file mode 100644 index 1a2c6343..00000000 --- a/apps/backend/app/main.py +++ /dev/null @@ -1,287 +0,0 @@ -import logging -from contextlib import asynccontextmanager - -from fastapi import FastAPI, Request, Response -from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import HTMLResponse -from slowapi import _rate_limit_exceeded_handler -from slowapi.errors import RateLimitExceeded -from slowapi.middleware import SlowAPIMiddleware - -from app.api.router import api_router -from app.core.audit_middleware import AuditMiddleware -from app.core.config import get_settings -from app.core.limiter import limiter - -settings = get_settings() - - -@asynccontextmanager -async def lifespan(_app: FastAPI): - try: - from app.core.indexes import ensure_indexes - await ensure_indexes() - except Exception as exc: - logging.getLogger(__name__).warning("Index creation failed: %s", exc) - - from app.core.redis_client import open_redis, close_redis - await open_redis() - - try: - yield - finally: - await close_redis() - - -app = FastAPI( - title=settings.APP_NAME, - debug=settings.APP_DEBUG, - docs_url="/docs" if settings.APP_DEBUG else None, - redoc_url="/redoc" if settings.APP_DEBUG else None, - openapi_url="/openapi.json" if settings.APP_DEBUG else None, - lifespan=lifespan, -) - -app.state.limiter = limiter -app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) -app.add_middleware(SlowAPIMiddleware) - -app.add_middleware( - CORSMiddleware, - allow_origins=[o.strip() for o in settings.ALLOWED_ORIGINS.split(",") if o.strip()], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -app.add_middleware(AuditMiddleware) - - -@app.middleware("http") -async def security_headers(request: Request, call_next) -> Response: - response = await call_next(request) - response.headers["X-Frame-Options"] = "DENY" - response.headers["X-Content-Type-Options"] = "nosniff" - response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" - response.headers["Permissions-Policy"] = "geolocation=(), microphone=(), camera=()" - # M-5 fix: add Content-Security-Policy - response.headers["Content-Security-Policy"] = ( - "default-src 'self'; " - "script-src 'self'; " - "style-src 'self' 'unsafe-inline'; " - "img-src 'self' data: https:; " - "font-src 'self' https://fonts.gstatic.com; " - "connect-src 'self'" - ) - if settings.APP_ENV == "production": - response.headers["Strict-Transport-Security"] = "max-age=63072000; includeSubDomains" - return response - - -app.include_router(api_router, prefix="/api/v1") - - -@app.get("/", tags=["root"], summary="Root endpoint") -def root() -> HTMLResponse: - # Minimal "shadcn-like" black/white landing page (no frontend build required). - # If you later want a full shadcn UI, we can host a Next.js page separately. - app_name = settings.APP_NAME - return HTMLResponse( - f""" - - - - - {app_name} - - - -
-
-
-
-
{app_name}
-
FastAPI backend • Firebase auth + MongoDB template
-
-
- API Docs -
-
-
- -
-

Auth-only backend.

-

- This service exposes health and authentication helpers. All tool data lives - on the user's device — the desktop app stores it locally, so there are no CRUD endpoints here. -

- - - -
-
-

Auth

-

Verify Firebase ID tokens and fetch the current user profile.

-
-
-

Passkeys

-

WebAuthn register/login and credential management.

-
-
-

Health

-

Liveness probe at /api/v1/health.

-
-
-
- -
-
© {app_name} • Deployed on Fastapi Cloud .
-
-
- -""", - status_code=200, - ) diff --git a/apps/backend/app/utils/collection_name.py b/apps/backend/app/utils/collection_name.py deleted file mode 100644 index 15e8328d..00000000 --- a/apps/backend/app/utils/collection_name.py +++ /dev/null @@ -1,3 +0,0 @@ -USERS = "users" -AUDIT_LOG = "audit_log" -WEBAUTHN_CHALLENGES = "webauthn_challenges" diff --git a/apps/backend/app/utils/utils.py b/apps/backend/app/utils/utils.py deleted file mode 100644 index 0a0720bc..00000000 --- a/apps/backend/app/utils/utils.py +++ /dev/null @@ -1,18 +0,0 @@ -import secrets -import time - -from pymongo.errors import PyMongoError - - -def create_timestamp() -> int: - return int(time.time() * 1000) - - -def new_id() -> str: - return secrets.token_urlsafe(18) - - -def is_duplicate_key_error(exc: PyMongoError) -> bool: - msg = str(exc) - return "E11000" in msg or "duplicate" in msg.lower() - diff --git a/apps/backend/package.json b/apps/backend/package.json deleted file mode 100644 index c4e69bfe..00000000 --- a/apps/backend/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@mydevtools/backend", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000", - "start": "python -m uvicorn app.main:app --host 0.0.0.0 --port 8000", - "lint": "python -m ruff check .", - "format": "python -m ruff format .", - "test": "python -m pytest", - "build": "echo \"Python backend does not require a build step.\"" - } -} diff --git a/apps/backend/pyproject.toml b/apps/backend/pyproject.toml deleted file mode 100644 index 6faec595..00000000 --- a/apps/backend/pyproject.toml +++ /dev/null @@ -1,49 +0,0 @@ -[project] -name = "mydevtools-backend" -version = "0.1.0" -description = "FastAPI backend for mydevtools monorepo" -readme = "README.md" -requires-python = ">=3.10" -dependencies = [ - "fastapi[standard]", - "uvicorn[standard]>=0.35.0", - "pydantic-settings>=2.10.1", - "firebase-admin>=7.1.0", - "motor>=3.7.0", - "python-jose[cryptography]>=3.5.0", - "boto3>=1.38.0", - "slowapi>=0.1.9", - "redis[asyncio]>=5.0", - "orjson>=3.10", - "webauthn>=2.5.0", - "resend>=0.7,<1.0", -] - -[project.optional-dependencies] -dev = [ - "pytest>=8.4.1", - "httpx>=0.28.1", - "ruff>=0.13.0", - "testcontainers[redis]>=4.0", - "soft-webauthn>=0.1.4", -] - -[tool.pytest.ini_options] -pythonpath = ["."] -addopts = "-q" -testpaths = ["tests"] -asyncio_mode = "auto" -asyncio_default_fixture_loop_scope = "session" -asyncio_default_test_loop_scope = "session" - -[tool.ruff] -line-length = 100 -target-version = "py310" - -[tool.ruff.lint] -select = ["E", "F", "I", "UP", "B"] - -[dependency-groups] -dev = [ - "pytest-asyncio>=1.4.0", -] diff --git a/apps/backend/scripts/loadtest_cache.py b/apps/backend/scripts/loadtest_cache.py deleted file mode 100644 index 8a99fec1..00000000 --- a/apps/backend/scripts/loadtest_cache.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Load test: 1000 concurrent users, 90/10 read/write. - -Usage: - pip install locust - CACHE_ENABLED=false locust -f scripts/loadtest_cache.py --headless -u 1000 -r 100 -t 5m \ - --host http://localhost:8000 --csv=baseline - CACHE_ENABLED=true locust -f scripts/loadtest_cache.py --headless -u 1000 -r 100 -t 5m \ - --host http://localhost:8000 --csv=with_cache - -Compare baseline_stats.csv vs with_cache_stats.csv. -""" -import random -import string - -from locust import HttpUser, between, task - - -def _rand_str(n: int = 8) -> str: - return "".join(random.choices(string.ascii_lowercase, k=n)) - - -class DevToolsUser(HttpUser): - wait_time = between(0.5, 2.0) - headers: dict[str, str] - - def on_start(self): - # Replace with a real test token issuance flow for the env. - # Sketch: hit /auth/anon-login or seed a user. - self.headers = {"Authorization": f"Bearer {self._token()}"} - - def _token(self) -> str: - # Pull from env or local fixture file. Out of scope here. - import os - return os.environ.get("LOADTEST_TOKEN", "") - - @task(45) - def list_bookmarks(self): - self.client.get("/bookmarks", headers=self.headers, name="GET /bookmarks") - - @task(20) - def list_notes(self): - self.client.get("/notes", headers=self.headers, name="GET /notes") - - @task(15) - def list_snippets(self): - self.client.get("/code-snippets", headers=self.headers, name="GET /code-snippets") - - @task(10) - def analytics_top(self): - self.client.get("/analytics/top-tools?days=7", headers=self.headers, name="GET /analytics/top-tools") - - @task(10) - def write_bookmark(self): - self.client.post( - "/bookmarks", - json={"title": _rand_str(), "url": f"https://example.com/{_rand_str()}", "tags": []}, - headers=self.headers, - name="POST /bookmarks", - ) diff --git a/apps/backend/tests/conftest.py b/apps/backend/tests/conftest.py deleted file mode 100644 index 60af7bb9..00000000 --- a/apps/backend/tests/conftest.py +++ /dev/null @@ -1,99 +0,0 @@ -import os -import pytest -from httpx import AsyncClient, ASGITransport -from starlette.requests import Request - -# Set required environment variables for tests BEFORE any imports -os.environ.setdefault("APP_ENV", "test") -os.environ.setdefault("APP_DEBUG", "true") -os.environ.setdefault("MONGO_DB_URL", "mongodb://localhost:27017") -os.environ.setdefault("MONGO_DB_NAME", "mydevtools_test") -os.environ.setdefault("JWT_SECRET_KEY", "test_secret_key_at_least_16_chars") -os.environ.setdefault("ACCESS_TOKEN_EXPIRE_MINUTES", "30") -os.environ.setdefault("REFRESH_TOKEN_EXPIRE_DAYS", "7") -os.environ.setdefault("ALLOWED_ORIGINS", "http://localhost:3000") -os.environ.setdefault("WEBAUTHN_RP_ID", "localhost") -os.environ.setdefault("WEBAUTHN_RP_NAME", "MyDevTools") -os.environ.setdefault("WEBAUTHN_ORIGINS", "http://localhost:3000") -os.environ.setdefault("WEBAUTHN_CHALLENGE_TTL_SECONDS", "300") - -from app.database import db_manager -from app.utils.collection_name import USERS - -pytest_plugins = ("pytest_asyncio",) - - -@pytest.fixture -async def clean_db(): - """Drop USERS before and after test.""" - collections = [USERS] - - # Clean before test - for coll in collections: - await db_manager.delete_many(coll, {}) - - yield - - # Clean after test - for coll in collections: - await db_manager.delete_many(coll, {}) - - -@pytest.fixture -def make_request(): - """Create a Starlette Request with optional cookies.""" - def _make_request(cookies=None): - scope = { - "type": "http", - "method": "GET", - "path": "/", - "query_string": b"", - "headers": [], - } - request = Request(scope) - if cookies: - request._cookies = cookies - return request - return _make_request - - -@pytest.fixture -def count_inserts(): - """Count db_manager.insert_one calls.""" - insert_count = {"count": 0} - original_insert_one = db_manager.insert_one - - async def tracked_insert_one(*args, **kwargs): - insert_count["count"] += 1 - return await original_insert_one(*args, **kwargs) - - db_manager.insert_one = tracked_insert_one - - def get_count(): - return insert_count["count"] - - yield get_count - - db_manager.insert_one = original_insert_one - - -@pytest.fixture -async def authed_client(clean_db) -> AsyncClient: - """Create an authenticated HTTP client with get_current_uid stubbed to return test-uid.""" - from app.api.routes.auth.services import get_current_uid - from app.main import app - - test_uid = "test-uid" - - def override_get_current_uid(): - return test_uid - - app.dependency_overrides[get_current_uid] = override_get_current_uid - - async with AsyncClient( - transport=ASGITransport(app=app), - base_url="http://test", - ) as client: - yield client - - app.dependency_overrides.clear() diff --git a/apps/backend/tests/test_audit_helper.py b/apps/backend/tests/test_audit_helper.py deleted file mode 100644 index 3d7d8b5f..00000000 --- a/apps/backend/tests/test_audit_helper.py +++ /dev/null @@ -1,70 +0,0 @@ -from app.utils.collection_name import AUDIT_LOG -from app.core import audit - - -def test_audit_log_collection_name(): - assert AUDIT_LOG == "audit_log" - - -def test_diff_passes_safe_fields_through(): - changes = audit.diff({"title": "Old"}, {"title": "New"}) - assert changes == [{"field": "title", "before": "Old", "after": "New"}] - - -def test_diff_redacts_sensitive_fields(): - changes = audit.diff( - {"password": "old-secret"}, {"password": "new-secret"} - ) - assert changes == [ - {"field": "password", "before": "[redacted]", "after": "[redacted]"} - ] - - -def test_diff_redacts_unknown_fields_by_default(): - changes = audit.diff({"mystery": 1}, {"mystery": 2}) - assert changes == [ - {"field": "mystery", "before": "[redacted]", "after": "[redacted]"} - ] - - -def test_diff_ignores_unchanged_fields(): - assert audit.diff({"title": "Same"}, {"title": "Same"}) == [] - - -def test_diff_handles_create_and_delete(): - assert audit.diff(None, {"title": "New"}) == [ - {"field": "title", "before": None, "after": "New"} - ] - assert audit.diff({"title": "Gone"}, None) == [ - {"field": "title", "before": "Gone", "after": None} - ] - - -def test_parse_user_agent_chrome_macos(): - d = audit.parse_user_agent( - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " - "(KHTML, like Gecko) Chrome/120.0 Safari/537.36" - ) - assert d["browser"] == "Chrome" - assert d["os"] == "macOS" - assert d["device_type"] == "desktop" - - -def test_parse_user_agent_fallback(): - d = audit.parse_user_agent(None) - assert d == {"browser": "Unknown", "os": "Unknown", "device_type": "desktop"} - - -def test_context_mutation_round_trip(): - token = audit._audit_ctx.set(audit.AuditContext()) - try: - audit.set_entity("bookmark", "abc") - audit.set_action("bookmark.create") - audit.add_change("title", None, "Hi") - ctx = audit.current_context() - assert ctx.entity_type == "bookmark" - assert ctx.entity_id == "abc" - assert ctx.action == "bookmark.create" - assert ctx.changes == [{"field": "title", "before": None, "after": "Hi"}] - finally: - audit._audit_ctx.reset(token) diff --git a/apps/backend/tests/test_audit_middleware.py b/apps/backend/tests/test_audit_middleware.py deleted file mode 100644 index 8c7ec953..00000000 --- a/apps/backend/tests/test_audit_middleware.py +++ /dev/null @@ -1,88 +0,0 @@ -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient - -from app.core import audit -from app.core.audit_middleware import AuditMiddleware - - -@pytest.fixture -def captured(monkeypatch): - docs = [] - - async def fake_insert_one(collection_name, data): - docs.append((collection_name, data)) - - # Run the fire-and-forget task synchronously so assertions are deterministic. - monkeypatch.setattr("app.core.audit_middleware.db_manager.insert_one", fake_insert_one) - return docs - - -def build_app(): - app = FastAPI() - app.add_middleware(AuditMiddleware) - - @app.post("/api/v1/bookmarks") - async def create(): - audit.set_action("bookmark.create") - audit.set_entity("bookmark", "bk1") - audit.set_summary("Created bookmark 'GitHub'") - audit.add_change("title", None, "GitHub") - return {"id": "bk1"} - - @app.get("/api/v1/bookmarks") - async def listing(): - return [] - - @app.delete("/api/v1/bookmarks/{bid}") - async def fail(bid: str): - from fastapi import HTTPException - raise HTTPException(status_code=404, detail="nope") - - return app - - -def test_write_is_logged_with_envelope_and_detail(captured): - client = TestClient(build_app()) - res = client.post( - "/api/v1/bookmarks", - headers={"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/120.0 Safari/537.36"}, - ) - assert res.status_code == 200 - assert len(captured) == 1 - _, doc = captured[0] - assert doc["action"] == "bookmark.create" - assert doc["module"] == "bookmarks" - assert doc["entity_id"] == "bk1" - assert doc["method"] == "POST" - assert doc["outcome"] == "success" - assert doc["device"]["browser"] == "Chrome" - assert doc["changes"] == [{"field": "title", "before": None, "after": "GitHub"}] - assert "expireAt" in doc and "ts" in doc - - -def test_get_is_not_logged(captured): - client = TestClient(build_app()) - client.get("/api/v1/bookmarks") - assert captured == [] - - -def test_failed_write_logged_as_failure(captured): - client = TestClient(build_app()) - res = client.delete("/api/v1/bookmarks/bk1") - assert res.status_code == 404 - assert len(captured) == 1 - _, doc = captured[0] - assert doc["outcome"] == "failure" - assert doc["status"] == 404 - assert doc["module"] == "bookmarks" - - -def test_audit_write_failure_does_not_break_request(monkeypatch): - async def boom(collection_name, data): - raise RuntimeError("db down") - - monkeypatch.setattr("app.core.audit_middleware.db_manager.insert_one", boom) - client = TestClient(build_app()) - res = client.post("/api/v1/bookmarks") - assert res.status_code == 200 # user response unaffected diff --git a/apps/backend/tests/test_auth_memo.py b/apps/backend/tests/test_auth_memo.py deleted file mode 100644 index 45d00828..00000000 --- a/apps/backend/tests/test_auth_memo.py +++ /dev/null @@ -1,138 +0,0 @@ -import pytest -from fastapi import Depends, FastAPI -from fastapi.testclient import TestClient - -from app.api.routes.auth.services import get_current_user, get_current_uid -from app.api.routes.auth.schema import UserProfileResponse - - -@pytest.fixture -def app_with_double_dep(monkeypatch): - """FastAPI app whose endpoint asks for get_current_user twice. - - Patches the Mongo lookup with a counter so we can assert it ran once. - """ - calls = {"n": 0} - - async def fake_get_user_doc(uid: str): - calls["n"] += 1 - return { - "_id": uid, - "email": "u@example.com", - "display_name": "U", - "photo_url": None, - "email_verified": True, - "disabled": False, - } - - def fake_get_current_uid() -> str: - return "uid-abc" - - monkeypatch.setattr( - "app.api.routes.auth.services.get_user_doc", - fake_get_user_doc, - ) - - app = FastAPI() - - # Override via FastAPI dependency_overrides rather than monkeypatching the module name, - # so the dep graph through Depends() is preserved. - app.dependency_overrides[get_current_uid] = fake_get_current_uid - - @app.get("/twice") - async def twice( - a: UserProfileResponse = Depends(get_current_user), - b: UserProfileResponse = Depends(get_current_user), - ) -> dict: - return {"a_uid": a.uid, "b_uid": b.uid, "calls": calls["n"]} - - return app, calls - - -def test_get_current_user_memoized_per_request(app_with_double_dep) -> None: - app, calls = app_with_double_dep - client = TestClient(app) - res = client.get("/twice") - assert res.status_code == 200 - body = res.json() - assert body["a_uid"] == body["b_uid"] == "uid-abc" - assert calls["n"] == 1, "Mongo lookup must run once per request" - - -def test_get_current_user_fresh_each_new_request(app_with_double_dep) -> None: - app, calls = app_with_double_dep - client = TestClient(app) - client.get("/twice") - client.get("/twice") - # Two requests × one lookup each - assert calls["n"] == 2 - - -@pytest.fixture -def app_with_stale_cache(monkeypatch): - """FastAPI app that pre-seeds a stale cached doc with mismatched uid. - - Tests that the cache guard correctly rejects a doc with a different _id - and fetches fresh from Mongo. - """ - calls = {"n": 0} - - async def fake_get_user_doc(uid: str): - calls["n"] += 1 - return { - "_id": uid, - "email": "u@example.com", - "display_name": "U", - "photo_url": None, - "email_verified": True, - "disabled": False, - } - - def fake_get_current_uid() -> str: - return "uid-abc" - - monkeypatch.setattr( - "app.api.routes.auth.services.get_user_doc", - fake_get_user_doc, - ) - - app = FastAPI() - - # Override via FastAPI dependency_overrides rather than monkeypatching the module name, - # so the dep graph through Depends() is preserved. - app.dependency_overrides[get_current_uid] = fake_get_current_uid - - @app.middleware("http") - async def inject_stale_cache(request, call_next): - """Pre-seed request.state with a cached doc from a different uid.""" - request.state.current_user_doc = { - "_id": "other-uid", - "email": "other@example.com", - "display_name": "Other", - "photo_url": None, - "email_verified": True, - "disabled": False, - } - response = await call_next(request) - return response - - @app.get("/twice") - async def twice( - a: UserProfileResponse = Depends(get_current_user), - b: UserProfileResponse = Depends(get_current_user), - ) -> dict: - return {"a_uid": a.uid, "b_uid": b.uid, "calls": calls["n"]} - - return app, calls - - -def test_get_current_user_cache_bypassed_on_uid_mismatch(app_with_stale_cache) -> None: - app, calls = app_with_stale_cache - client = TestClient(app) - res = client.get("/twice") - assert res.status_code == 200 - body = res.json() - # Should fetch fresh and return the correct uid, not "other-uid" - assert body["a_uid"] == body["b_uid"] == "uid-abc" - # Should have fetched fresh once (not used stale cache) - assert calls["n"] == 1, "Cache must be bypassed when _id doesn't match uid" diff --git a/apps/backend/tests/test_cache_config.py b/apps/backend/tests/test_cache_config.py deleted file mode 100644 index 20334093..00000000 --- a/apps/backend/tests/test_cache_config.py +++ /dev/null @@ -1,15 +0,0 @@ -import os -from app.core.config import Settings - - -def test_cache_defaults(): - os.environ.pop("CACHE_ENABLED", None) - os.environ.pop("CACHE_NAMESPACES", None) - s = Settings(ALLOWED_ORIGINS="http://localhost", ACCESS_TOKEN_EXPIRE_MINUTES=30, REFRESH_TOKEN_EXPIRE_DAYS=7) - assert s.REDIS_URL is None - assert s.CACHE_ENABLED is True - assert s.CACHE_NAMESPACES == "" - assert s.CACHE_DEFAULT_TTL == 120 - assert s.CACHE_OP_TIMEOUT_MS == 50 - assert s.CACHE_XFETCH_BETA == 1.0 - assert s.CACHE_LOG_LEVEL == "WARNING" diff --git a/apps/backend/tests/test_cache_decorator.py b/apps/backend/tests/test_cache_decorator.py deleted file mode 100644 index df3895ea..00000000 --- a/apps/backend/tests/test_cache_decorator.py +++ /dev/null @@ -1,136 +0,0 @@ -import asyncio -import pytest - -from app.core.cache.decorator import cached, bump_version -from app.core.cache import keys as keys_mod - - -class _FakeRedis: - def __init__(self): - self.store: dict[bytes, bytes] = {} - self.versions: dict[bytes, int] = {} - self.fail: bool = False - self.calls: list[tuple[str, str]] = [] - - async def get(self, k): - self.calls.append(("get", k.decode() if isinstance(k, bytes) else k)) - if self.fail: - raise ConnectionError("down") - return self.store.get(k if isinstance(k, bytes) else k.encode()) - - async def setex(self, k, ttl, v): - self.calls.append(("setex", k.decode() if isinstance(k, bytes) else k)) - if self.fail: - raise ConnectionError("down") - self.store[k if isinstance(k, bytes) else k.encode()] = v - - async def incr(self, k): - if self.fail: - raise ConnectionError("down") - key = k if isinstance(k, bytes) else k.encode() - self.versions[key] = self.versions.get(key, 0) + 1 - return self.versions[key] - - async def delete(self, k): - if self.fail: - raise ConnectionError("down") - self.store.pop(k if isinstance(k, bytes) else k.encode(), None) - - -@pytest.fixture -def fake_redis(monkeypatch): - r = _FakeRedis() - monkeypatch.setattr("app.core.cache.decorator.get_redis", lambda: r) - # also make ver-key lookups return string bytes - async def _get(k): - v = r.versions.get(k if isinstance(k, bytes) else k.encode()) - return str(v).encode() if v is not None else None - # override to support both reads and version reads - orig = r.get - async def patched_get(k): - # version key path - if (k if isinstance(k, str) else k.decode()).startswith("cache:ver:"): - return await _get(k) - return await orig(k) - monkeypatch.setattr(r, "get", patched_get) - return r - - -@pytest.fixture -def enable_ns(monkeypatch): - monkeypatch.setattr("app.core.cache.decorator.is_namespace_enabled", lambda ns: True) - monkeypatch.setattr("app.core.cache.decorator._secret", lambda: b"test-secret") - - -@pytest.mark.asyncio -async def test_decorator_miss_then_hit(fake_redis, enable_ns): - calls = {"n": 0} - - @cached(ns="auth_user", ttl=60, scope="user") - async def list_bookmarks(*, uid: str): - calls["n"] += 1 - return [{"id": "a"}] - - r1 = await list_bookmarks(uid="u1") - r2 = await list_bookmarks(uid="u1") - assert r1 == r2 == [{"id": "a"}] - assert calls["n"] == 1 # second call served from cache - - -@pytest.mark.asyncio -async def test_bump_version_invalidates(fake_redis, enable_ns): - calls = {"n": 0} - - @cached(ns="auth_user", ttl=60, scope="user") - async def list_bookmarks(*, uid: str): - calls["n"] += 1 - return [{"id": "a"}] - - await list_bookmarks(uid="u1") - await bump_version(ns="auth_user", uid="u1") - await list_bookmarks(uid="u1") - assert calls["n"] == 2 - - -@pytest.mark.asyncio -async def test_fail_open(fake_redis, enable_ns): - fake_redis.fail = True - - @cached(ns="auth_user", ttl=60, scope="user") - async def list_bookmarks(*, uid: str): - return [{"id": "from-mongo"}] - - out = await list_bookmarks(uid="u1") - assert out == [{"id": "from-mongo"}] - - -@pytest.mark.asyncio -async def test_disabled_namespace_skips_redis(fake_redis, monkeypatch): - monkeypatch.setattr("app.core.cache.decorator.is_namespace_enabled", lambda ns: False) - monkeypatch.setattr("app.core.cache.decorator._secret", lambda: b"x") - - @cached(ns="auth_user", ttl=60, scope="user") - async def list_bookmarks(*, uid: str): - return [{"id": "a"}] - - fake_redis.calls.clear() - await list_bookmarks(uid="u1") - assert fake_redis.calls == [] - - -@pytest.mark.asyncio -async def test_user_scope_requires_uid(fake_redis, enable_ns): - @cached(ns="auth_user", ttl=60, scope="user") - async def list_bookmarks(**kw): - return [] - - with pytest.raises(ValueError, match="uid"): - await list_bookmarks(folder_id="x") - - -def test_public_api(): - from app.core.cache import cached, bump_version, cache_invalidate, get_or_set - assert callable(cached) - assert callable(bump_version) - assert callable(cache_invalidate) - assert callable(get_or_set) diff --git a/apps/backend/tests/test_cache_flags.py b/apps/backend/tests/test_cache_flags.py deleted file mode 100644 index 0ab7cf33..00000000 --- a/apps/backend/tests/test_cache_flags.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from app.core import config -from app.core.cache.flags import is_namespace_enabled - - -def _set(monkeypatch, enabled=True, namespaces=""): - monkeypatch.setattr(config, "get_settings", lambda: type("S", (), { - "CACHE_ENABLED": enabled, - "CACHE_NAMESPACES": namespaces, - })()) - # invalidate any LRU cache on flags - from app.core.cache import flags as f - f._parsed_namespaces.cache_clear() - - -def test_disabled_globally(monkeypatch): - _set(monkeypatch, enabled=False, namespaces="bookmarks") - assert is_namespace_enabled("bookmarks") is False - - -def test_empty_namespaces(monkeypatch): - _set(monkeypatch, enabled=True, namespaces="") - assert is_namespace_enabled("bookmarks") is False - - -def test_matching_namespace(monkeypatch): - _set(monkeypatch, enabled=True, namespaces="bookmarks,notes") - assert is_namespace_enabled("bookmarks") is True - assert is_namespace_enabled("notes") is True - assert is_namespace_enabled("tasks") is False - - -def test_whitespace_tolerant(monkeypatch): - _set(monkeypatch, enabled=True, namespaces="bookmarks , notes") - assert is_namespace_enabled("bookmarks") is True - assert is_namespace_enabled("notes") is True diff --git a/apps/backend/tests/test_cache_integration.py b/apps/backend/tests/test_cache_integration.py deleted file mode 100644 index ce7f213d..00000000 --- a/apps/backend/tests/test_cache_integration.py +++ /dev/null @@ -1,106 +0,0 @@ -"""Integration tests for cache decorator against real Redis container.""" -import asyncio - -import pytest -from testcontainers.redis import RedisContainer - -from app.core.cache import cached, bump_version - - -def _docker_available() -> bool: - """Check if Docker is available by trying to connect to it.""" - try: - import docker - client = docker.from_env() - client.ping() - return True - except Exception: # noqa: BLE001 - return False - - -# Skip entire module if Docker is not available -pytestmark = pytest.mark.skipif( - not _docker_available(), - reason="docker not available" -) - - -@pytest.fixture(scope="module") -def redis_container(): - """Spin up a real Redis container for the test module.""" - with RedisContainer("redis:7-alpine") as c: - yield c - - -@pytest.fixture -async def real_redis(redis_container, monkeypatch): - """Set up real Redis connection via test container.""" - url = f"redis://{redis_container.get_container_host_ip()}:{redis_container.get_exposed_port(6379)}" - monkeypatch.setenv("REDIS_URL", url) - monkeypatch.setenv("CACHE_NAMESPACES", "bookmarks,notes,analytics_aggregate") - - # Reset Settings cache so it reads new env vars - from app.core import config - config.get_settings.cache_clear() - - # Reset flags cache - from app.core.cache import flags - flags._parsed_namespaces.cache_clear() - - # Open the real Redis connection - from app.core.redis_client import open_redis, close_redis - await open_redis() - - yield - - # Clean up - await close_redis() - - -@pytest.mark.asyncio -async def test_real_decorator_hit(real_redis): - """Test that decorator correctly caches and hits on second call.""" - calls = {"n": 0} - - @cached(ns="bookmarks", ttl=60, scope="user") - async def list_bookmarks(*, uid: str): - calls["n"] += 1 - return [{"id": "x"}] - - await list_bookmarks(uid="u1") - await list_bookmarks(uid="u1") - assert calls["n"] == 1, "Second call should be served from cache" - - -@pytest.mark.asyncio -async def test_real_bump_invalidates(real_redis): - """Test that bump_version correctly invalidates cached entries.""" - calls = {"n": 0} - - @cached(ns="notes", ttl=60, scope="user") - async def list_notes(*, uid: str): - calls["n"] += 1 - return [{"id": "y"}] - - await list_notes(uid="u1") - await bump_version(ns="notes", uid="u1") - await list_notes(uid="u1") - assert calls["n"] == 2, "After bump_version, cache should be invalidated" - - -@pytest.mark.asyncio -async def test_cross_user_isolation(real_redis): - """Test that cache is properly isolated per user.""" - calls = {"n": 0} - - @cached(ns="bookmarks", ttl=60, scope="user") - async def list_bookmarks(*, uid: str): - calls["n"] += 1 - return [{"uid": uid}] - - await list_bookmarks(uid="u1") - await bump_version(ns="bookmarks", uid="u1") # only u1 invalidated - await list_bookmarks(uid="u1") # re-fetch u1 after bump (u1-after-bump) - await list_bookmarks(uid="u2") - await list_bookmarks(uid="u2") # second u2 call must hit cache - assert calls["n"] == 3, "u1, u1-after-bump, u2-first (u2-second = cache hit)" diff --git a/apps/backend/tests/test_cache_keys.py b/apps/backend/tests/test_cache_keys.py deleted file mode 100644 index fafcb778..00000000 --- a/apps/backend/tests/test_cache_keys.py +++ /dev/null @@ -1,47 +0,0 @@ -import pytest - -from app.core.cache.keys import ( - NAMESPACES, - args_hash, - build_key, - register_namespace, - version_key, -) - - -def test_register_and_lookup(): - register_namespace("bookmarks_test", scope="user", default_ttl=120, default_strategy="simple") - assert NAMESPACES["bookmarks_test"]["scope"] == "user" - - -def test_build_user_key(): - key = build_key(ns="bookmarks", scope="user", uid="u1", ver=7, op="list", args_hash="abcd1234") - assert key == "cache:bookmarks:u:u1:v7:list:abcd1234" - - -def test_build_global_key_no_version(): - key = build_key(ns="urlshort", scope="global", uid=None, ver=None, op="resolve", args_hash="ff00ee11") - assert key == "cache:urlshort:g:resolve:ff00ee11" - - -def test_version_key(): - assert version_key("bookmarks", "u1") == "cache:ver:bookmarks:u:u1" - - -def test_args_hash_deterministic(): - h1 = args_hash({"a": 1, "b": 2}, secret=b"k") - h2 = args_hash({"b": 2, "a": 1}, secret=b"k") - assert h1 == h2 - assert len(h1) == 16 - - -def test_args_hash_changes_with_input(): - h1 = args_hash({"a": 1}, secret=b"k") - h2 = args_hash({"a": 2}, secret=b"k") - assert h1 != h2 - - -def test_args_hash_changes_with_secret(): - h1 = args_hash({"a": 1}, secret=b"k1") - h2 = args_hash({"a": 1}, secret=b"k2") - assert h1 != h2 diff --git a/apps/backend/tests/test_cache_serializer.py b/apps/backend/tests/test_cache_serializer.py deleted file mode 100644 index 54124005..00000000 --- a/apps/backend/tests/test_cache_serializer.py +++ /dev/null @@ -1,44 +0,0 @@ -from datetime import datetime, timezone - -import pytest -from pydantic import BaseModel - -from app.core.cache.serializer import dumps, loads - - -class Sample(BaseModel): - id: str - when: datetime - tags: list[str] - - -def test_round_trip_dict(): - payload = {"a": 1, "b": "two", "c": [1, 2, 3]} - assert loads(dumps(payload)) == payload - - -def test_pydantic_round_trip(): - s = Sample(id="x", when=datetime(2026, 1, 1, tzinfo=timezone.utc), tags=["a", "b"]) - raw = dumps(s) - parsed = loads(raw) - assert parsed["id"] == "x" - assert parsed["tags"] == ["a", "b"] - assert parsed["when"].startswith("2026-01-01") - - -def test_list_of_pydantic(): - items = [Sample(id=str(i), when=datetime(2026, 1, 1, tzinfo=timezone.utc), tags=[]) for i in range(3)] - parsed = loads(dumps(items)) - assert isinstance(parsed, list) - assert parsed[0]["id"] == "0" - - -def test_none_round_trip(): - assert loads(dumps(None)) is None - - -def test_bytes_round_trip(): - raw = dumps({"k": b"\x00\xff"}) - # bytes auto-serialized as base64 string by orjson default - parsed = loads(raw) - assert "k" in parsed diff --git a/apps/backend/tests/test_cache_xfetch.py b/apps/backend/tests/test_cache_xfetch.py deleted file mode 100644 index b8020a89..00000000 --- a/apps/backend/tests/test_cache_xfetch.py +++ /dev/null @@ -1,30 +0,0 @@ -import math - -import pytest - -from app.core.cache.xfetch import should_refresh, unwrap_payload, wrap_payload - - -def test_beta_zero_never_refreshes_early(): - # beta=0 reduces to: now > computed_at + ttl → only after TTL - assert should_refresh(computed_at=0, ttl=100, delta=5, beta=0, now=50, rand=0.001) is False - assert should_refresh(computed_at=0, ttl=100, delta=5, beta=0, now=101, rand=0.5) is True - - -def test_high_beta_refreshes_earlier(): - # With rand → 0, ln(rand) → -inf, refresh fires very early when beta > 0 - fires_at_t50 = should_refresh(computed_at=0, ttl=100, delta=5, beta=10, now=50, rand=1e-9) - assert fires_at_t50 is True - - -def test_rand_near_one_no_early_refresh(): - # rand close to 1 → ln(rand) close to 0 → only past TTL - assert should_refresh(computed_at=0, ttl=100, delta=5, beta=1, now=80, rand=0.999) is False - - -def test_wrap_unwrap_round_trip(): - p = wrap_payload({"x": 1}, computed_at=12345.0, delta=2.5) - val, ca, dt = unwrap_payload(p) - assert val == {"x": 1} - assert ca == 12345.0 - assert dt == 2.5 diff --git a/apps/backend/tests/test_health.py b/apps/backend/tests/test_health.py deleted file mode 100644 index fe8d6b24..00000000 --- a/apps/backend/tests/test_health.py +++ /dev/null @@ -1,18 +0,0 @@ -from fastapi.testclient import TestClient - -from app.main import app - -client = TestClient(app) - - -def test_root() -> None: - response = client.get("/") - assert response.status_code == 200 - assert response.headers["content-type"].startswith("text/html") - assert "Minimal, production-ready" in response.text - - -def test_health() -> None: - response = client.get("/api/v1/health") - assert response.status_code == 200 - assert response.json() == {"status": "ok"} diff --git a/apps/backend/tests/test_lifespan_redis.py b/apps/backend/tests/test_lifespan_redis.py deleted file mode 100644 index 99a21d45..00000000 --- a/apps/backend/tests/test_lifespan_redis.py +++ /dev/null @@ -1,11 +0,0 @@ -import pytest -from httpx import ASGITransport, AsyncClient - - -@pytest.mark.asyncio -async def test_app_boots_without_redis(monkeypatch): - monkeypatch.delenv("REDIS_URL", raising=False) - from app.main import app - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - r = await ac.get("/api/v1/health") - assert r.status_code == 200 diff --git a/apps/backend/tests/test_passkey_challenges.py b/apps/backend/tests/test_passkey_challenges.py deleted file mode 100644 index 0afa78b4..00000000 --- a/apps/backend/tests/test_passkey_challenges.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Unit tests for the WebAuthn challenge store. - -ponytail: only the challenge plumbing has hand-written logic worth testing — -the WebAuthn crypto belongs to py-webauthn, the repo helpers belong to -db_manager. Endpoint-level register/login tests need a real authenticator -simulator (e.g. soft-webauthn) which is overkill for now; add when shipping -to prod requires it. -""" - -from __future__ import annotations - -import datetime as dt - -import pytest - -from app.api.routes.auth.passkey import challenges - - -class _FakeDb: - """Minimal in-memory stand-in for `app.database.db_manager`.""" - - def __init__(self) -> None: - self.docs: dict[str, dict] = {} - - async def update_one(self, _collection, query, payload, upsert=False, array_filters=None): - _id = query.get("_id") - if _id is None: - return None - if _id not in self.docs: - if not upsert: - return None - self.docs[_id] = {"_id": _id} - target = self.docs[_id] - for k, v in (payload.get("$set") or {}).items(): - target[k] = v - return None - - async def find_one_and_update(self, _collection, query, update_query, return_document=False): - _id = query.get("_id") - if _id is None or _id not in self.docs: - return None - snapshot = dict(self.docs[_id]) - for k, v in (update_query.get("$set") or {}).items(): - self.docs[_id][k] = v - return snapshot - - async def delete_one(self, _collection, query): - _id = query.get("_id") - if _id in self.docs: - del self.docs[_id] - - -@pytest.fixture -def fake_db(monkeypatch): - db = _FakeDb() - monkeypatch.setattr("app.api.routes.auth.passkey.challenges.db_manager", db) - return db - - -@pytest.mark.asyncio -async def test_put_then_pop_returns_stored_challenge(fake_db) -> None: - await challenges.put("register", "uid-1", "challenge-bytes-b64") - got = await challenges.pop("register", "uid-1") - assert got == "challenge-bytes-b64" - - -@pytest.mark.asyncio -async def test_pop_is_one_shot(fake_db) -> None: - await challenges.put("login", "challenge-key", "c") - assert await challenges.pop("login", "challenge-key") == "c" - # Second pop must miss — single-use enforced - assert await challenges.pop("login", "challenge-key") is None - - -@pytest.mark.asyncio -async def test_pop_on_missing_key_returns_none(fake_db) -> None: - assert await challenges.pop("register", "never-issued") is None - - -@pytest.mark.asyncio -async def test_put_overwrites_previous_challenge(fake_db) -> None: - await challenges.put("register", "uid-2", "first") - await challenges.put("register", "uid-2", "second") - got = await challenges.pop("register", "uid-2") - assert got == "second" - - -@pytest.mark.asyncio -async def test_register_and_login_keys_are_disjoint(fake_db) -> None: - await challenges.put("register", "uid-3", "reg") - await challenges.put("login", "uid-3", "log") - assert await challenges.pop("register", "uid-3") == "reg" - assert await challenges.pop("login", "uid-3") == "log" - - -@pytest.mark.asyncio -async def test_created_at_is_set_for_ttl_index(fake_db) -> None: - await challenges.put("register", "uid-4", "x") - stored = fake_db.docs["register:uid-4"] - assert isinstance(stored.get("created_at"), dt.datetime) - # MongoDB TTL needs a timezone-aware UTC datetime - assert stored["created_at"].tzinfo is not None diff --git a/apps/backend/tests/test_passkey_integration.py b/apps/backend/tests/test_passkey_integration.py deleted file mode 100644 index 3ca0c707..00000000 --- a/apps/backend/tests/test_passkey_integration.py +++ /dev/null @@ -1,380 +0,0 @@ -"""End-to-end passkey tests driven by the soft-webauthn authenticator. - -Exercises register/begin → register/finish → login/begin → login/finish via -FastAPI's TestClient. Repo + token + cookie helpers are stubbed in memory so -the test does not require MongoDB or Firebase. -""" - -from __future__ import annotations - -import base64 -import json -from typing import Any - -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient -from slowapi import _rate_limit_exceeded_handler -from slowapi.errors import RateLimitExceeded -from soft_webauthn import SoftWebauthnDevice - -from app.api.routes.auth.passkey.api import router as passkey_router -from app.api.routes.auth.services import get_current_uid -from app.core.limiter import limiter - - -def _b64url(b: bytes) -> str: - return base64.urlsafe_b64encode(b).rstrip(b"=").decode("ascii") - - -def _decode_b64url(s: str) -> bytes: - pad = "=" * (-len(s) % 4) - return base64.urlsafe_b64decode(s + pad) - - -# ── In-memory stand-ins for repo + challenges + tokens ──────────────────────── - - -class _Store: - """Per-test in-memory state shared by the patched repo functions.""" - - def __init__(self) -> None: - self.users: dict[str, dict[str, Any]] = {} - self.challenges: dict[str, str] = {} - - -def _patch_services(monkeypatch, store: _Store) -> None: - s = "app.api.routes.auth.passkey.services" - - async def fake_get_user_doc(uid: str): - return store.users.get(uid) - - async def fake_get_or_create_handle(uid: str, handle_b64: str) -> str: - user = store.users.setdefault(uid, {"_id": uid, "email": f"{uid}@x.test"}) - if "webauthn_user_handle" not in user: - user["webauthn_user_handle"] = handle_b64 - return user["webauthn_user_handle"] - - async def fake_list_passkeys(uid: str): - return list((store.users.get(uid) or {}).get("passkeys") or []) - - async def fake_add_passkey(uid: str, passkey: dict[str, Any]): - store.users.setdefault(uid, {"_id": uid}).setdefault("passkeys", []).append(passkey) - - async def fake_find_by_credential_id(cred_id: str): - for u in store.users.values(): - for p in u.get("passkeys", []): - if p.get("credential_id") == cred_id: - return u - return None - - async def fake_find_by_user_handle(handle: str): - for u in store.users.values(): - if u.get("webauthn_user_handle") == handle: - return u - return None - - async def fake_update_usage(uid: str, credential_id: str, sign_count: int): - u = store.users.get(uid) - if not u: - return - for p in u.get("passkeys", []): - if p.get("credential_id") == credential_id: - p["sign_count"] = sign_count - p["last_used_at"] = 1 - return - - monkeypatch.setattr(f"{s}.get_user_doc", fake_get_user_doc) - monkeypatch.setattr(f"{s}.get_or_create_webauthn_user_handle", fake_get_or_create_handle) - monkeypatch.setattr(f"{s}.list_passkeys", fake_list_passkeys) - monkeypatch.setattr(f"{s}.add_passkey", fake_add_passkey) - monkeypatch.setattr(f"{s}.find_user_by_credential_id", fake_find_by_credential_id) - monkeypatch.setattr(f"{s}.find_user_by_user_handle", fake_find_by_user_handle) - monkeypatch.setattr(f"{s}.update_passkey_usage", fake_update_usage) - - # Challenge store: keep it in memory - async def fake_put(kind: str, subject: str, ch: str): - store.challenges[f"{kind}:{subject}"] = ch - - async def fake_pop(kind: str, subject: str): - return store.challenges.pop(f"{kind}:{subject}", None) - - monkeypatch.setattr(f"{s}.challenges.put", fake_put) - monkeypatch.setattr(f"{s}.challenges.pop", fake_pop) - - # Stub the WebAuthn RP config — soft-webauthn defaults to localhost - from app.core.config import get_settings - - settings = get_settings() - monkeypatch.setattr(settings, "WEBAUTHN_RP_ID", "localhost", raising=False) - monkeypatch.setattr(settings, "WEBAUTHN_RP_NAME", "Test", raising=False) - monkeypatch.setattr(settings, "WEBAUTHN_ORIGINS", "http://localhost", raising=False) - - -def _patch_api(monkeypatch) -> None: - a = "app.api.routes.auth.passkey.api" - - async def fake_set_refresh(uid: str, h: str): - return None - - monkeypatch.setattr(f"{a}.set_refresh_token_hash", fake_set_refresh) - monkeypatch.setattr(f"{a}.create_access_token", lambda uid: f"access-{uid}") - monkeypatch.setattr(f"{a}.new_refresh_token", lambda: "refresh-raw") - monkeypatch.setattr(f"{a}.hash_refresh_token", lambda s: f"hash-{s}") - - -@pytest.fixture -def app_and_store(monkeypatch): - store = _Store() - _patch_services(monkeypatch, store) - _patch_api(monkeypatch) - - # In-memory slowapi store is module-level; reset between tests so - # rate-limit budgets don't leak across the suite. - try: - limiter.reset() - except Exception: - pass - - app = FastAPI() - app.state.limiter = limiter - app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) - app.include_router(passkey_router, prefix="/api/v1") - - app.dependency_overrides[get_current_uid] = lambda: "uid-test" - return app, store - - -# ── Helpers to drive the soft-webauthn authenticator ────────────────────────── - - -def _options_for_soft_webauthn_register(opts_json: dict) -> dict: - """Translate the public-key JSON returned by the API into the shape - SoftWebauthnDevice.create() expects (challenge as bytes, user.id as bytes). - """ - public_key = { - "rp": opts_json["rp"], - "user": { - "id": _decode_b64url(opts_json["user"]["id"]), - "name": opts_json["user"]["name"], - "displayName": opts_json["user"]["displayName"], - }, - "challenge": _decode_b64url(opts_json["challenge"]), - "pubKeyCredParams": opts_json["pubKeyCredParams"], - } - return {"publicKey": public_key} - - -def _options_for_soft_webauthn_login(opts_json: dict) -> dict: - public_key = { - "rpId": opts_json["rpId"], - "challenge": _decode_b64url(opts_json["challenge"]), - "allowCredentials": [], - } - return {"publicKey": public_key} - - -def _credential_for_api_register(cred: dict) -> dict: - return { - "id": _b64url(cred["rawId"]), - "rawId": _b64url(cred["rawId"]), - "response": { - "clientDataJSON": _b64url(cred["response"]["clientDataJSON"]), - "attestationObject": _b64url(cred["response"]["attestationObject"]), - "transports": ["internal"], - }, - "type": cred["type"], - } - - -def _credential_for_api_login(assertion: dict) -> dict: - user_handle = assertion["response"].get("userHandle") - return { - "id": _b64url(assertion["rawId"]), - "rawId": _b64url(assertion["rawId"]), - "response": { - "clientDataJSON": _b64url(assertion["response"]["clientDataJSON"]), - "authenticatorData": _b64url(assertion["response"]["authenticatorData"]), - "signature": _b64url(assertion["response"]["signature"]), - "userHandle": _b64url(user_handle) if user_handle else None, - }, - "type": assertion["type"], - } - - -# ── Tests ───────────────────────────────────────────────────────────────────── - - -def test_register_then_login_roundtrip(app_and_store) -> None: - app, store = app_and_store - # Seed the user record so begin_registration finds them - store.users["uid-test"] = {"_id": "uid-test", "email": "u@x.test"} - client = TestClient(app) - device = SoftWebauthnDevice() - - # 1. Register: begin - res = client.post("/api/v1/auth/passkey/register/begin") - assert res.status_code == 200, res.text - reg_opts = res.json() - assert reg_opts["challenge"] - assert reg_opts["user"]["id"] - - # 2. Soft authenticator creates the credential - sw_cred = device.create(_options_for_soft_webauthn_register(reg_opts), "http://localhost") - api_cred = _credential_for_api_register(sw_cred) - - # 3. Register: finish - res = client.post( - "/api/v1/auth/passkey/register/finish", - json={"credential": api_cred, "device_name": "Test Device"}, - ) - assert res.status_code == 200, res.text - assert res.json()["device_name"] == "Test Device" - assert len(store.users["uid-test"]["passkeys"]) == 1 - - # 4. Login: begin (no auth needed) - res = client.post("/api/v1/auth/passkey/login/begin") - assert res.status_code == 200, res.text - login_opts = res.json() - - # 5. Soft authenticator signs the assertion - sw_assertion = device.get(_options_for_soft_webauthn_login(login_opts), "http://localhost") - api_assertion = _credential_for_api_login(sw_assertion) - - # 6. Login: finish — cookies should be set, uid returned - res = client.post( - "/api/v1/auth/passkey/login/finish", json={"credential": api_assertion} - ) - assert res.status_code == 200, res.text - assert res.json()["uid"] == "uid-test" - set_cookie = res.headers.get("set-cookie") or "" - assert "mdt_at=" in set_cookie or "mdt_rt=" in set_cookie - - # 7. sign_count must advance - stored = store.users["uid-test"]["passkeys"][0] - assert stored["sign_count"] >= 1 - - -def test_register_finish_rejects_when_challenge_missing(app_and_store) -> None: - app, store = app_and_store - store.users["uid-test"] = {"_id": "uid-test", "email": "u@x.test"} - client = TestClient(app) - - res = client.post( - "/api/v1/auth/passkey/register/finish", - json={"credential": {"id": "x", "rawId": "x", "type": "public-key", "response": {}}}, - ) - assert res.status_code == 400 - assert "challenge" in res.json()["detail"].lower() - - -def test_duplicate_credential_id_rejected(app_and_store) -> None: - app, store = app_and_store - store.users["uid-test"] = {"_id": "uid-test", "email": "u@x.test"} - client = TestClient(app) - device = SoftWebauthnDevice() - - # First registration succeeds - res = client.post("/api/v1/auth/passkey/register/begin") - sw_cred = device.create(_options_for_soft_webauthn_register(res.json()), "http://localhost") - assert ( - client.post( - "/api/v1/auth/passkey/register/finish", - json={"credential": _credential_for_api_register(sw_cred), "device_name": "A"}, - ).status_code - == 200 - ) - - # Replay the same credential under a fresh challenge — services should - # spot the existing credential_id and 409 - res = client.post("/api/v1/auth/passkey/register/begin") - # Reuse the same device (same credential_id) but sign a new challenge — - # the begin endpoint adds it to excludeCredentials, but a misbehaving - # client could still send it. Force the duplicate path: - res2 = client.post( - "/api/v1/auth/passkey/register/finish", - json={"credential": _credential_for_api_register(sw_cred), "device_name": "B"}, - ) - # Either 400 (challenge mismatch from py-webauthn) or 409 (duplicate guard). - # Both signal the right thing — we just want to confirm it doesn't 200. - assert res2.status_code in (400, 409) - - -def test_login_finish_rejects_unknown_credential(app_and_store) -> None: - app, store = app_and_store - # Seed the user record so registration can succeed, but throw the - # passkey away locally before login — server should not find it. - store.users["uid-test"] = {"_id": "uid-test", "email": "u@x.test"} - client = TestClient(app) - device = SoftWebauthnDevice() - - # Provision the device against the right rpID via a real registration, - # then wipe the server-side passkey so it appears unknown at login time. - reg_opts = client.post("/api/v1/auth/passkey/register/begin").json() - sw_cred = device.create(_options_for_soft_webauthn_register(reg_opts), "http://localhost") - client.post( - "/api/v1/auth/passkey/register/finish", - json={"credential": _credential_for_api_register(sw_cred), "device_name": "X"}, - ) - store.users["uid-test"]["passkeys"] = [] # forget the registration - - login_opts = client.post("/api/v1/auth/passkey/login/begin").json() - sw_assertion = device.get(_options_for_soft_webauthn_login(login_opts), "http://localhost") - res = client.post( - "/api/v1/auth/passkey/login/finish", - json={"credential": _credential_for_api_login(sw_assertion)}, - ) - assert res.status_code == 401 - - -def test_list_and_delete_passkey(app_and_store) -> None: - app, store = app_and_store - store.users["uid-test"] = { - "_id": "uid-test", - "passkeys": [ - { - "credential_id": "cred-abc", - "public_key": "pk", - "sign_count": 0, - "transports": [], - "device_name": "Demo", - "backed_up": True, - "created_at": 1, - "last_used_at": None, - } - ], - } - - # remove_passkey needs its own monkeypatch — list_endpoint uses list_passkeys - # imported into api.py, so the api-side imports are the ones we patch here. - import importlib - - api_mod = importlib.import_module("app.api.routes.auth.passkey.api") - - async def fake_list(uid: str): - return list((store.users.get(uid) or {}).get("passkeys") or []) - - async def fake_remove(uid: str, cred_id: str) -> int: - user = store.users.get(uid) or {} - before = len(user.get("passkeys") or []) - user["passkeys"] = [p for p in user.get("passkeys", []) if p.get("credential_id") != cred_id] - return before - len(user["passkeys"]) - - api_mod.list_passkeys = fake_list # type: ignore[attr-defined] - api_mod.remove_passkey = fake_remove # type: ignore[attr-defined] - - client = TestClient(app) - res = client.get("/api/v1/auth/passkey") - assert res.status_code == 200 - body = res.json() - assert len(body["passkeys"]) == 1 - assert body["passkeys"][0]["credential_id"] == "cred-abc" - - res = client.delete("/api/v1/auth/passkey/cred-abc") - assert res.status_code == 200 - assert res.json()["ok"] is True - assert store.users["uid-test"]["passkeys"] == [] - - # Re-delete should 404 - res = client.delete("/api/v1/auth/passkey/cred-abc") - assert res.status_code == 404 diff --git a/apps/backend/tests/test_passkey_mongo_integration.py b/apps/backend/tests/test_passkey_mongo_integration.py deleted file mode 100644 index 882f23d6..00000000 --- a/apps/backend/tests/test_passkey_mongo_integration.py +++ /dev/null @@ -1,181 +0,0 @@ -"""Real-MongoDB tests for passkey helpers. - -Skipped unless Docker is available. Validates the things the in-memory fake -can't: - - `webauthn_user_handle` unique index actually rejects collisions - - `get_or_create_webauthn_user_handle` is race-safe under concurrent writes - - positional `$` operator updates the correct array element - - `$pull` semantics match what production sees -""" - -from __future__ import annotations - -import asyncio - -import pytest - - -def _docker_available() -> bool: - try: - import docker - - client = docker.from_env() - client.ping() - return True - except Exception: # noqa: BLE001 - return False - - -pytestmark = pytest.mark.skipif(not _docker_available(), reason="docker not available") - - -@pytest.fixture(scope="module") -def mongo_container(): - from testcontainers.mongodb import MongoDbContainer - - with MongoDbContainer("mongo:7") as c: - yield c - - -@pytest.fixture -async def repo(mongo_container, monkeypatch): - """Point users_repo.db_manager at a fresh Motor client + DB per test.""" - from motor.motor_asyncio import AsyncIOMotorClient - - url = mongo_container.get_connection_url() - client = AsyncIOMotorClient(url) - db = client["test_mdt"] - - # Wipe USERS before each test for isolation - from app.utils.collection_name import USERS - - await db[USERS].drop() - - # Apply the same indexes production would - await db[USERS].create_index("webauthn_user_handle", unique=True, sparse=True) - await db[USERS].create_index("passkeys.credential_id", sparse=True) - - # Patch db_manager to use this DB - class _Adapter: - async def update_one(self, collection, query, payload, upsert=False, array_filters=None): - return await db[collection].update_one( - query, payload, upsert=upsert, array_filters=array_filters - ) - - async def find_one(self, collection, query, projection=None): - return await db[collection].find_one(query, projection) - - async def insert_one(self, collection, document): - return await db[collection].insert_one(document) - - adapter = _Adapter() - monkeypatch.setattr("app.api.routes.auth.users_repo.db_manager", adapter) - - async def noop_bump(*args, **kwargs): - return None - - monkeypatch.setattr("app.api.routes.auth.users_repo.bump_version", noop_bump) - - yield adapter, db - - client.close() - - -async def _seed(db, uid: str = "uid-1") -> None: - from app.utils.collection_name import USERS - - await db[USERS].insert_one({"_id": uid, "email": f"{uid}@x.test"}) - - -@pytest.mark.asyncio -async def test_handle_unique_index_rejects_collisions(repo) -> None: - from app.utils.collection_name import USERS - from pymongo.errors import DuplicateKeyError - - _adapter, db = repo - await _seed(db, "uid-A") - await _seed(db, "uid-B") - - await db[USERS].update_one({"_id": "uid-A"}, {"$set": {"webauthn_user_handle": "shared"}}) - - with pytest.raises(DuplicateKeyError): - await db[USERS].update_one({"_id": "uid-B"}, {"$set": {"webauthn_user_handle": "shared"}}) - - -@pytest.mark.asyncio -async def test_get_or_create_handle_is_race_safe(repo) -> None: - """Hammer the helper concurrently for one user; only the first writer wins.""" - from app.api.routes.auth import users_repo - - _adapter, db = repo - await _seed(db, "uid-race") - - proposals = [f"handle-{i}" for i in range(50)] - results = await asyncio.gather( - *(users_repo.get_or_create_webauthn_user_handle("uid-race", h) for h in proposals) - ) - - # All callers must receive the same final value - assert len(set(results)) == 1 - final = results[0] - assert final in proposals - - # And the stored doc reflects the same value - stored = await users_repo.find_user_by_user_handle(final) - assert stored is not None and stored["_id"] == "uid-race" - - -@pytest.mark.asyncio -async def test_positional_update_only_touches_target_element(repo) -> None: - from app.api.routes.auth import users_repo - - _adapter, db = repo - await _seed(db) - await users_repo.add_passkey("uid-1", {"credential_id": "c1", "device_name": "A", "sign_count": 0}) - await users_repo.add_passkey("uid-1", {"credential_id": "c2", "device_name": "B", "sign_count": 0}) - - await users_repo.update_passkey_usage("uid-1", "c2", 9) - await users_repo.rename_passkey("uid-1", "c2", "Renamed B") - - passkeys = await users_repo.list_passkeys("uid-1") - by_id = {p["credential_id"]: p for p in passkeys} - assert by_id["c1"]["device_name"] == "A" - assert by_id["c1"]["sign_count"] == 0 - assert by_id["c2"]["device_name"] == "Renamed B" - assert by_id["c2"]["sign_count"] == 9 - - -@pytest.mark.asyncio -async def test_remove_passkey_pulls_only_matching_credential(repo) -> None: - from app.api.routes.auth import users_repo - - _adapter, db = repo - await _seed(db) - await users_repo.add_passkey("uid-1", {"credential_id": "c1"}) - await users_repo.add_passkey("uid-1", {"credential_id": "c2"}) - await users_repo.add_passkey("uid-1", {"credential_id": "c3"}) - - removed = await users_repo.remove_passkey("uid-1", "c2") - assert removed == 1 - remaining = [p["credential_id"] for p in await users_repo.list_passkeys("uid-1")] - assert remaining == ["c1", "c3"] - - -@pytest.mark.asyncio -async def test_find_by_credential_id_scans_array(repo) -> None: - from app.api.routes.auth import users_repo - - _adapter, db = repo - await _seed(db, "uid-A") - await _seed(db, "uid-B") - await users_repo.add_passkey("uid-A", {"credential_id": "cred-A1"}) - await users_repo.add_passkey("uid-A", {"credential_id": "cred-A2"}) - await users_repo.add_passkey("uid-B", {"credential_id": "cred-B1"}) - - found = await users_repo.find_user_by_credential_id("cred-A2") - assert found is not None and found["_id"] == "uid-A" - - found = await users_repo.find_user_by_credential_id("cred-B1") - assert found is not None and found["_id"] == "uid-B" - - assert await users_repo.find_user_by_credential_id("missing") is None diff --git a/apps/backend/tests/test_passkey_users_repo.py b/apps/backend/tests/test_passkey_users_repo.py deleted file mode 100644 index 6e1e5d4f..00000000 --- a/apps/backend/tests/test_passkey_users_repo.py +++ /dev/null @@ -1,286 +0,0 @@ -"""Tests for the passkey helpers in users_repo against an in-memory fake of -db_manager that implements the Mongo update operators we actually use. - -Why in-memory rather than mongomock: motor + mongomock-motor is brittle across -pymongo/motor versions, and the helpers exercise a small, well-defined slice -of Mongo's query language. The fake below implements just the operators the -production code touches ($set, $push, $pull, $exists, positional $) so we can -verify the helpers' intent without booting a real database. -""" - -from __future__ import annotations - -import copy -from collections.abc import Iterable -from typing import Any - -import pytest - - -# ── A tiny Mongo-shaped fake ────────────────────────────────────────────────── - - -class _UpdateResult: - def __init__(self, modified_count: int = 0) -> None: - self.modified_count = modified_count - - -def _matches(doc: dict, query: dict) -> bool: - for key, expected in query.items(): - # _id, plain fields - if "." not in key: - if isinstance(expected, dict) and "$exists" in expected: - present = key in doc - if bool(expected["$exists"]) != present: - return False - continue - if isinstance(expected, dict) and "$ne" in expected: - if doc.get(key) == expected["$ne"]: - return False - continue - if doc.get(key) != expected: - return False - continue - # Dotted, e.g. "passkeys.credential_id" — match if any element in the - # array equals expected at the same sub-path. - root, _, sub = key.partition(".") - arr = doc.get(root) or [] - if not any(isinstance(item, dict) and item.get(sub) == expected for item in arr): - return False - return True - - -def _apply_update(doc: dict, query: dict, payload: dict) -> int: - modified = 0 - # $set with positional operator support - for op, ops in payload.items(): - if op == "$set": - for k, v in ops.items(): - if ".$" in k: - root, _, rest = k.partition(".$.") - # Determine which element in `root` the original query targeted - target_field, target_value = None, None - for qk, qv in query.items(): - if qk.startswith(f"{root}."): - target_field = qk.split(".", 1)[1] - target_value = qv - break - arr = doc.get(root) or [] - for item in arr: - if isinstance(item, dict) and ( - target_field is None or item.get(target_field) == target_value - ): - item[rest] = v - modified = 1 - break - else: - doc[k] = v - modified = max(modified, 1) - elif op == "$push": - for k, v in ops.items(): - doc.setdefault(k, []).append(v) - modified = max(modified, 1) - elif op == "$pull": - for k, criterion in ops.items(): - before = list(doc.get(k) or []) - if isinstance(criterion, dict): - doc[k] = [ - item - for item in before - if not all(item.get(ck) == cv for ck, cv in criterion.items()) - ] - else: - doc[k] = [item for item in before if item != criterion] - if len(doc[k]) != len(before): - modified = max(modified, 1) - elif op == "$unset": - for k in ops: - if k in doc: - del doc[k] - modified = max(modified, 1) - return modified - - -class _FakeDb: - def __init__(self) -> None: - self.docs: dict[str, dict[str, Any]] = {} - - async def update_one(self, _collection, query, payload, upsert=False, array_filters=None): - for doc in self.docs.values(): - if _matches(doc, query): - modified = _apply_update(doc, query, payload) - return _UpdateResult(modified_count=modified) - if upsert and "_id" in query: - new_doc: dict[str, Any] = {"_id": query["_id"]} - # $setOnInsert - for k, v in (payload.get("$setOnInsert") or {}).items(): - new_doc[k] = copy.deepcopy(v) - _apply_update(new_doc, query, payload) - self.docs[query["_id"]] = new_doc - return _UpdateResult(modified_count=0) - return _UpdateResult(modified_count=0) - - async def find_one(self, _collection, query, projection=None): - for doc in self.docs.values(): - if _matches(doc, query): - if projection: - return { - k: v for k, v in doc.items() if k in projection or k == "_id" - } - return copy.deepcopy(doc) - return None - - async def count_documents(self, _collection, query): - return sum(1 for d in self.docs.values() if _matches(d, query)) - - -@pytest.fixture -def fake_db(monkeypatch): - db = _FakeDb() - monkeypatch.setattr("app.api.routes.auth.users_repo.db_manager", db) - - async def noop_bump(*args, **kwargs): - return None - - monkeypatch.setattr("app.api.routes.auth.users_repo.bump_version", noop_bump) - return db - - -# ── Tests ───────────────────────────────────────────────────────────────────── - - -async def _seed_user(db: _FakeDb, uid: str = "uid-1") -> None: - db.docs[uid] = {"_id": uid, "email": "u@x.test"} - - -@pytest.mark.asyncio -async def test_get_or_create_handle_sets_on_first_call(fake_db) -> None: - from app.api.routes.auth import users_repo - - await _seed_user(fake_db) - got = await users_repo.get_or_create_webauthn_user_handle("uid-1", "handle-1") - assert got == "handle-1" - assert fake_db.docs["uid-1"]["webauthn_user_handle"] == "handle-1" - - -@pytest.mark.asyncio -async def test_get_or_create_handle_idempotent_second_call_keeps_first(fake_db) -> None: - from app.api.routes.auth import users_repo - - await _seed_user(fake_db) - first = await users_repo.get_or_create_webauthn_user_handle("uid-1", "handle-A") - second = await users_repo.get_or_create_webauthn_user_handle("uid-1", "handle-B") - assert first == "handle-A" - assert second == "handle-A" # the second proposed handle is rejected - assert fake_db.docs["uid-1"]["webauthn_user_handle"] == "handle-A" - - -@pytest.mark.asyncio -async def test_add_and_list_passkeys(fake_db) -> None: - from app.api.routes.auth import users_repo - - await _seed_user(fake_db) - assert await users_repo.list_passkeys("uid-1") == [] - - await users_repo.add_passkey("uid-1", {"credential_id": "c1", "device_name": "A"}) - await users_repo.add_passkey("uid-1", {"credential_id": "c2", "device_name": "B"}) - - passkeys = await users_repo.list_passkeys("uid-1") - assert [p["credential_id"] for p in passkeys] == ["c1", "c2"] - - -@pytest.mark.asyncio -async def test_remove_passkey_returns_count_and_pulls_matching(fake_db) -> None: - from app.api.routes.auth import users_repo - - await _seed_user(fake_db) - await users_repo.add_passkey("uid-1", {"credential_id": "c1", "device_name": "A"}) - await users_repo.add_passkey("uid-1", {"credential_id": "c2", "device_name": "B"}) - - removed = await users_repo.remove_passkey("uid-1", "c1") - assert removed == 1 - remaining = await users_repo.list_passkeys("uid-1") - assert [p["credential_id"] for p in remaining] == ["c2"] - - -@pytest.mark.asyncio -async def test_remove_passkey_missing_returns_zero(fake_db) -> None: - from app.api.routes.auth import users_repo - - await _seed_user(fake_db) - await users_repo.add_passkey("uid-1", {"credential_id": "c1"}) - removed = await users_repo.remove_passkey("uid-1", "does-not-exist") - assert removed == 0 - # Did not collateral-pull the existing one - assert len(await users_repo.list_passkeys("uid-1")) == 1 - - -@pytest.mark.asyncio -async def test_rename_passkey_updates_only_matching_element(fake_db) -> None: - from app.api.routes.auth import users_repo - - await _seed_user(fake_db) - await users_repo.add_passkey("uid-1", {"credential_id": "c1", "device_name": "Old A"}) - await users_repo.add_passkey("uid-1", {"credential_id": "c2", "device_name": "Old B"}) - - modified = await users_repo.rename_passkey("uid-1", "c2", "New B") - assert modified == 1 - names = {p["credential_id"]: p["device_name"] for p in await users_repo.list_passkeys("uid-1")} - assert names == {"c1": "Old A", "c2": "New B"} - - -@pytest.mark.asyncio -async def test_rename_passkey_missing_returns_zero(fake_db) -> None: - from app.api.routes.auth import users_repo - - await _seed_user(fake_db) - await users_repo.add_passkey("uid-1", {"credential_id": "c1", "device_name": "Old"}) - modified = await users_repo.rename_passkey("uid-1", "ghost", "Whatever") - assert modified == 0 - - -@pytest.mark.asyncio -async def test_update_passkey_usage_bumps_sign_count_and_last_used(fake_db) -> None: - from app.api.routes.auth import users_repo - - await _seed_user(fake_db) - await users_repo.add_passkey( - "uid-1", {"credential_id": "c1", "sign_count": 0, "last_used_at": None} - ) - - await users_repo.update_passkey_usage("uid-1", "c1", 7) - - passkeys = await users_repo.list_passkeys("uid-1") - assert passkeys[0]["sign_count"] == 7 - assert isinstance(passkeys[0]["last_used_at"], int) - assert passkeys[0]["last_used_at"] > 0 - - -@pytest.mark.asyncio -async def test_find_user_by_user_handle(fake_db) -> None: - from app.api.routes.auth import users_repo - - await _seed_user(fake_db, "uid-A") - await _seed_user(fake_db, "uid-B") - await users_repo.get_or_create_webauthn_user_handle("uid-A", "handle-A") - await users_repo.get_or_create_webauthn_user_handle("uid-B", "handle-B") - - found = await users_repo.find_user_by_user_handle("handle-B") - assert found is not None - assert found["_id"] == "uid-B" - assert await users_repo.find_user_by_user_handle("nope") is None - - -@pytest.mark.asyncio -async def test_find_user_by_credential_id(fake_db) -> None: - from app.api.routes.auth import users_repo - - await _seed_user(fake_db, "uid-A") - await _seed_user(fake_db, "uid-B") - await users_repo.add_passkey("uid-A", {"credential_id": "cred-A1"}) - await users_repo.add_passkey("uid-B", {"credential_id": "cred-B1"}) - - found = await users_repo.find_user_by_credential_id("cred-B1") - assert found is not None - assert found["_id"] == "uid-B" - assert await users_repo.find_user_by_credential_id("missing") is None diff --git a/apps/backend/tests/test_redis_client.py b/apps/backend/tests/test_redis_client.py deleted file mode 100644 index f71c75a4..00000000 --- a/apps/backend/tests/test_redis_client.py +++ /dev/null @@ -1,16 +0,0 @@ -import pytest -from app.core import redis_client - - -@pytest.mark.asyncio -async def test_get_redis_returns_none_without_url(monkeypatch): - monkeypatch.setattr("app.core.redis_client._client", None) - monkeypatch.setattr("app.core.redis_client.get_settings", lambda: type("S", (), {"REDIS_URL": None})()) - assert redis_client.get_redis() is None - - -@pytest.mark.asyncio -async def test_open_redis_noop_without_url(monkeypatch): - monkeypatch.setattr("app.core.redis_client.get_settings", lambda: type("S", (), {"REDIS_URL": None, "CACHE_OP_TIMEOUT_MS": 50})()) - await redis_client.open_redis() - assert redis_client.get_redis() is None diff --git a/apps/backend/uv.lock b/apps/backend/uv.lock deleted file mode 100644 index 13f05438..00000000 --- a/apps/backend/uv.lock +++ /dev/null @@ -1,2681 +0,0 @@ -version = 1 -revision = 2 -requires-python = ">=3.10" -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version == '3.13.*'", - "python_full_version >= '3.11' and python_full_version < '3.13'", - "python_full_version < '3.11'", -] - -[[package]] -name = "annotated-doc" -version = "0.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, -] - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, -] - -[[package]] -name = "anyio" -version = "4.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "idna" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, -] - -[[package]] -name = "async-timeout" -version = "5.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, -] - -[[package]] -name = "backports-asyncio-runner" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, -] - -[[package]] -name = "boto3" -version = "1.43.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "botocore" }, - { name = "jmespath" }, - { name = "s3transfer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/95/cd/bba36079f5d4bd63db7385e6b9dc1845db32407c3f18f56aaddafb75097f/boto3-1.43.2.tar.gz", hash = "sha256:be951cc22769fbcda73fac523b031ee38db45c3ae2b0d828c76b8f6e8e683073", size = 113108, upload-time = "2026-05-01T19:43:13.632Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/e5/c9cee72ef678dabcc27acaf8228a2d4157ad26b00e1cc5d48886f8a94c2c/boto3-1.43.2-py3-none-any.whl", hash = "sha256:796e859cfb5e93c55276ce746f8020f691eda6b68a0ec4ce4f6fd07a1cca6859", size = 140501, upload-time = "2026-05-01T19:43:10.5Z" }, -] - -[[package]] -name = "botocore" -version = "1.43.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jmespath" }, - { name = "python-dateutil" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2c/b0/65d4c85f16367fb6147d391652d0c386f24b029536f7026e7b98740166cd/botocore-1.43.2.tar.gz", hash = "sha256:7b2ec87b6d0720bff920451ce930e71c2a99cdea48d0eaa66ccf0b21ea747e03", size = 15301186, upload-time = "2026-05-01T19:42:59.748Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/52/f57ded73f1527a18e0712281eb49c4ae240038bb4dc7083fd288b4adc811/botocore-1.43.2-py3-none-any.whl", hash = "sha256:b823454d751a1c24bb403b5b07ab65007689654abb21787df923684e0743976c", size = 14982693, upload-time = "2026-05-01T19:42:54.602Z" }, -] - -[[package]] -name = "cachecontrol" -version = "0.14.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "msgpack" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2d/f6/c972b32d80760fb79d6b9eeb0b3010a46b89c0b23cf6329417ff7886cd22/cachecontrol-0.14.4.tar.gz", hash = "sha256:e6220afafa4c22a47dd0badb319f84475d79108100d04e26e8542ef7d3ab05a1", size = 16150, upload-time = "2025-11-14T04:32:13.138Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/79/c45f2d53efe6ada1110cf6f9fca095e4ff47a0454444aefdde6ac4789179/cachecontrol-0.14.4-py3-none-any.whl", hash = "sha256:b7ac014ff72ee199b5f8af1de29d60239954f223e948196fa3d84adaffc71d2b", size = 22247, upload-time = "2025-11-14T04:32:11.733Z" }, -] - -[[package]] -name = "cbor2" -version = "5.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/cb/09939728be094d155b5d4ac262e39877875f5f7e36eea66beb359f647bd0/cbor2-5.9.0.tar.gz", hash = "sha256:85c7a46279ac8f226e1059275221e6b3d0e370d2bb6bd0500f9780781615bcea", size = 111231, upload-time = "2026-03-22T15:56:50.638Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/bf/12b337e5354e47f6378da18989480c0c1e2cc5fe9b865e6fab45d6332aa6/cbor2-5.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:55bea0dd9a7d354e35f4e5fe58ceab393e76962713749dc3a0a64a0e5d19545e", size = 70577, upload-time = "2026-03-22T15:55:54.174Z" }, - { url = "https://files.pythonhosted.org/packages/45/7b/74c524ce81c1ddc6c44b4865028ffb7d3a8e7ae653b1061650375a28cbb1/cbor2-5.9.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095dc49e75572841a9534cbfdabc2a17487ea4ee33341436abc4a7ac7245a3a", size = 261074, upload-time = "2026-03-22T15:55:55.654Z" }, - { url = "https://files.pythonhosted.org/packages/4b/97/c496c71422b2ca18ff2acc5f49e8c45cd7294b7df1359eccec78021b428e/cbor2-5.9.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25bec7beb2089465382b1be72e78667fe9090598800826559c3e3008cf0db743", size = 255498, upload-time = "2026-03-22T15:55:57.256Z" }, - { url = "https://files.pythonhosted.org/packages/c2/40/9bd7e66dba7aea674a440e004faea406de42c49aeac23453954b67768532/cbor2-5.9.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cc5efec69055c3c470997935d95762be7e4bfd1248d88fb1a33bb7e0f45712e9", size = 255683, upload-time = "2026-03-22T15:55:58.721Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d1/fa3e158dbe4c08091495b720c604624b285bc272afdbcfac2150725d955b/cbor2-5.9.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:420d2490c7836c81151b4bd591c35cffc55391e33e7e333c50fda391bcea7d31", size = 250798, upload-time = "2026-03-22T15:55:59.953Z" }, - { url = "https://files.pythonhosted.org/packages/7c/16/3186084b441c4b0caebfaaa2c66a57bde82ceaacd469570c77c8030b6b95/cbor2-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:d1a21c006760f95acd9509cc5a7d15d6fc82e58f721f94fa9039b4e77189a6e5", size = 69436, upload-time = "2026-03-22T15:56:01.466Z" }, - { url = "https://files.pythonhosted.org/packages/36/1f/57d00cd13e0f9391bcd616795aa78409210a7464570fe21e3b9cd42eb5a7/cbor2-5.9.0-cp310-cp310-win_arm64.whl", hash = "sha256:08388ea54195738602b4c4999966bcaef6f0b17d293c9658658409d9fff96f57", size = 65312, upload-time = "2026-03-22T15:56:02.804Z" }, - { url = "https://files.pythonhosted.org/packages/43/aa/317c7118b8dda4c9563125c1a12c70c5b41e36677964a49c72b1aac061ec/cbor2-5.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0485d3372fc832c5e16d4eb45fa1a20fc53e806e6c29a1d2b0d3e176cedd52b9", size = 70578, upload-time = "2026-03-22T15:56:03.835Z" }, - { url = "https://files.pythonhosted.org/packages/31/43/fe29b1f897770011a5e7497f4523c2712282ee4a6cbf775ea6383fb7afb9/cbor2-5.9.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9d6e4e0f988b0e766509a8071975a8ee99f930e14a524620bf38083106158d2", size = 268738, upload-time = "2026-03-22T15:56:05.222Z" }, - { url = "https://files.pythonhosted.org/packages/0a/1a/e494568f3d8aafbcdfe361df44c3bcf5cdab5183e25ea08e3d3f9fcf4075/cbor2-5.9.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5326336f633cc89dfe543c78829c16c3a6449c2c03277d1ddba99086c3323363", size = 262571, upload-time = "2026-03-22T15:56:06.411Z" }, - { url = "https://files.pythonhosted.org/packages/42/2e/92acd6f87382fd44a34d9d7e85cc45372e6ba664040b72d1d9df648b25d0/cbor2-5.9.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5e702b02d42a5ace45425b595ffe70fe35aebaf9a3cdfdc2c758b6189c744422", size = 262356, upload-time = "2026-03-22T15:56:08.236Z" }, - { url = "https://files.pythonhosted.org/packages/3f/68/52c039a28688baeeb78b0be7483855e6c66ea05884a937444deede0c87b8/cbor2-5.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2372d357d403e7912f104ff085950ffc82a5854d6d717f1ca1ce16a40a0ef5a7", size = 257604, upload-time = "2026-03-22T15:56:09.835Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e4/10d96a7f73ed9227090ce6e3df5d73329eb6a267dab7d5b989e6fbf6c504/cbor2-5.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:1d02b65f070fd726bdc310d927228975bb655d155bf059b6eb7cacefb3dca86f", size = 69388, upload-time = "2026-03-22T15:56:11.28Z" }, - { url = "https://files.pythonhosted.org/packages/d4/c6/eea5829aa5a649db540f47ea35f4bf2313383d28246f0cbc50432cfad6b3/cbor2-5.9.0-cp311-cp311-win_arm64.whl", hash = "sha256:837754ece9052b3f607047e1741e5f852a538aa2b0ee3db11c82a8fa11804aa4", size = 65315, upload-time = "2026-03-22T15:56:12.326Z" }, - { url = "https://files.pythonhosted.org/packages/ee/39/72d8a5a4b06565561ec28f4fcb41aff7bb77f51705c01f00b8254a2aca4f/cbor2-5.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1f223dffb1bcdd2764665f04c1152943d9daa4bc124a576cd8dee1cad4264313", size = 71223, upload-time = "2026-03-22T15:56:13.68Z" }, - { url = "https://files.pythonhosted.org/packages/09/fd/7ddf3d3153b54c69c3be77172b8d9aa3a9d74f62a7fbde614d53eaeed9a4/cbor2-5.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae6c706ac1d85a0b3cb3395308fd0c4d55e3202b4760773675957e93cdff45fc", size = 287865, upload-time = "2026-03-22T15:56:14.813Z" }, - { url = "https://files.pythonhosted.org/packages/db/9d/7ede2cc42f9bb4260492e7d29d2aab781eacbbcfb09d983de1e695077199/cbor2-5.9.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4cd43d8fc374b31643b2830910f28177a606a7bc84975a62675dd3f2e320fc7b", size = 288246, upload-time = "2026-03-22T15:56:16.113Z" }, - { url = "https://files.pythonhosted.org/packages/ce/9d/588ebc7c5bc5843f609b05fe07be8575c7dec987735b0bbc908ac9c1264a/cbor2-5.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4aa07b392cc3d76fb31c08a46a226b58c320d1c172ff3073e864409ced7bc50f", size = 280214, upload-time = "2026-03-22T15:56:17.519Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a1/6fc8f4b15c6a27e7fbb7966c30c2b4b18c274a3221fa2f5e6235502d34bc/cbor2-5.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:971d425b3a23b75953d8853d5f9911bdeefa09d759ee3b5e6b07b5ff3cbd9073", size = 282162, upload-time = "2026-03-22T15:56:18.975Z" }, - { url = "https://files.pythonhosted.org/packages/cf/20/9a22cfe08be16ddfeef2542cf4eeed1b29f3f57ddbba0b42f7e0bb8331fd/cbor2-5.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:34a6cb15e6ab6a8eae94ad2041731cd3ef786af43a8df99f847969af5b902ee7", size = 70049, upload-time = "2026-03-22T15:56:20.502Z" }, - { url = "https://files.pythonhosted.org/packages/c6/9e/695f92d09006614034e25a9f5b10620f3b219f79c1bec3c37b7c6f27a7a9/cbor2-5.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:7d1ddc4541e7367ac58c2470cc0df847f7137167fe4f5729e2d3cc0b993d7da4", size = 65382, upload-time = "2026-03-22T15:56:21.526Z" }, - { url = "https://files.pythonhosted.org/packages/81/c5/4901e21a8afe9448fd947b11e8f383903207cd6dd0800e5f5a386838de5b/cbor2-5.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fbb06f34aa645b4deca66643bba3d400d20c15312d1fe88d429be60c1ab50f27", size = 71284, upload-time = "2026-03-22T15:56:22.836Z" }, - { url = "https://files.pythonhosted.org/packages/1b/10/df643a381aebc3f05486de4813662bc58accb640fc3275cb276a75e89694/cbor2-5.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac684fe195c39821fca70d18afbf748f728aefbfbf88456018d299e559b8cae0", size = 287682, upload-time = "2026-03-22T15:56:24.024Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0c/8aa6b766059ae4a0ca1ec3ff96fe3823a69a7be880dba2e249f7fbe2700b/cbor2-5.9.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a54fbb32cb828c214f7f333a707e4aec61182e7efdc06ea5d9596d3ecee624a", size = 288009, upload-time = "2026-03-22T15:56:25.305Z" }, - { url = "https://files.pythonhosted.org/packages/74/07/6236bc25c183a9cf7e8062e5dddf9eae9b0b14ebf14a58a69fe5a1e872c6/cbor2-5.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4753a6d1bc71054d9179557bc65740860f185095ccb401d46637fff028a5b3ec", size = 280437, upload-time = "2026-03-22T15:56:26.479Z" }, - { url = "https://files.pythonhosted.org/packages/4e/0a/84328d23c3c68874ac6497edb9b1900579a1028efa54734df3f1762bbc15/cbor2-5.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:380e534482b843e43442b87d8777a7bf9bed20cb7526f89b780c3400f617304b", size = 282247, upload-time = "2026-03-22T15:56:28.644Z" }, - { url = "https://files.pythonhosted.org/packages/9b/f6/89b4627e09d028c8e5fcaf7cb55f225c33ce6e037ec1844e65d02bcfa945/cbor2-5.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:dcf0f695873e5c94bd072d6af8698e72b8fb7f7a18f37e0bced1041b7111a6cf", size = 70089, upload-time = "2026-03-22T15:56:29.801Z" }, - { url = "https://files.pythonhosted.org/packages/e2/7c/efadcd5f0102db692490e4e206988a2f98d39a09912090db497a2b800885/cbor2-5.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:f7c9751a9611601ab326d8f5837f01379195bbf06175fb4effeb552140e7c9e8", size = 65466, upload-time = "2026-03-22T15:56:30.823Z" }, - { url = "https://files.pythonhosted.org/packages/08/7d/9ccc36d10ef96e6038e48046ebe1ce35a1e7814da0e1e204d09e6ef09b8d/cbor2-5.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23606d31ba1368bd1b6602e3020ee88fe9523ca80e8630faf6b2fc904fd84560", size = 71500, upload-time = "2026-03-22T15:56:31.876Z" }, - { url = "https://files.pythonhosted.org/packages/70/e1/a6cca2cc72e13f00030c6a649f57ae703eb2c620806ab70c40db8eab33fa/cbor2-5.9.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0322296b9d52f55880e300ba8ba09ecf644303b99b51138bbb1c0fb644fa7c3e", size = 286953, upload-time = "2026-03-22T15:56:33.292Z" }, - { url = "https://files.pythonhosted.org/packages/08/3c/24cd5ef488a957d90e016f200a3aad820e4c2f85edd61c9fe4523007a1ee/cbor2-5.9.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:422817286c1d0ce947fb2f7eca9212b39bddd7231e8b452e2d2cc52f15332dba", size = 285454, upload-time = "2026-03-22T15:56:34.703Z" }, - { url = "https://files.pythonhosted.org/packages/a4/35/dca96818494c0ba47cdd73e8d809b27fa91f8fa0ce32a068a09237687454/cbor2-5.9.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9a4907e0c3035bb8836116854ed8e56d8aef23909d601fa59706320897ec2551", size = 279441, upload-time = "2026-03-22T15:56:35.888Z" }, - { url = "https://files.pythonhosted.org/packages/a4/44/d3362378b16e53cf7e535a3f5aed8476e2109068154e24e31981ef5bde9e/cbor2-5.9.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fb7afe77f8d269e42d7c4b515c6fd14f1ccc0625379fb6829b269f493d16eddd", size = 279673, upload-time = "2026-03-22T15:56:37.08Z" }, - { url = "https://files.pythonhosted.org/packages/43/d1/3533a697e5842fff7c2f64912eb251f8dcab3a8b5d88e228d6eebc3b5021/cbor2-5.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:86baf870d4c0bfc6f79de3801f3860a84ab76d9c8b0abb7f081f2c14c38d79d3", size = 71940, upload-time = "2026-03-22T15:56:38.366Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e2/c6ba75f3fb25dfa15ab6999cc8709c821987e9ed8e375d7f58539261bcb9/cbor2-5.9.0-cp314-cp314-win_arm64.whl", hash = "sha256:7221483fad0c63afa4244624d552abf89d7dfdbc5f5edfc56fc1ff2b4b818975", size = 67639, upload-time = "2026-03-22T15:56:39.39Z" }, - { url = "https://files.pythonhosted.org/packages/42/ff/b83492b096fbef26e9cb62c1a4bf2d3cef579ea7b33138c6c37c4ae66f67/cbor2-5.9.0-py3-none-any.whl", hash = "sha256:27695cbd70c90b8de5c4a284642c2836449b14e2c2e07e3ffe0744cb7669a01b", size = 24627, upload-time = "2026-03-22T15:56:48.847Z" }, -] - -[[package]] -name = "certifi" -version = "2026.4.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, -] - -[[package]] -name = "cffi" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, - { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, - { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, - { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, - { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, - { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, - { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, - { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" }, - { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" }, - { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" }, - { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" }, - { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" }, - { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" }, - { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" }, - { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" }, - { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" }, - { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" }, - { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" }, - { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" }, - { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" }, - { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, - { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, - { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, - { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, - { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, - { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, - { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, - { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, - { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, - { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, - { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, - { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, - { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, - { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, - { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, - { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, - { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, - { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, - { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, - { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, - { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, - { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, - { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, - { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, - { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, - { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, - { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, - { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, - { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, - { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, - { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, - { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, - { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, - { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, - { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, - { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, - { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, - { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, - { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, - { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, - { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, - { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, - { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, - { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, - { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, - { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, - { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, - { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, - { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, - { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, - { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, - { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, -] - -[[package]] -name = "click" -version = "8.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "cryptography" -version = "44.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/53/d6/1411ab4d6108ab167d06254c5be517681f1e331f90edf1379895bcb87020/cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053", size = 711096, upload-time = "2025-05-02T19:36:04.667Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/53/c776d80e9d26441bb3868457909b4e74dd9ccabd182e10b2b0ae7a07e265/cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88", size = 6670281, upload-time = "2025-05-02T19:34:50.665Z" }, - { url = "https://files.pythonhosted.org/packages/6a/06/af2cf8d56ef87c77319e9086601bef621bedf40f6f59069e1b6d1ec498c5/cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137", size = 3959305, upload-time = "2025-05-02T19:34:53.042Z" }, - { url = "https://files.pythonhosted.org/packages/ae/01/80de3bec64627207d030f47bf3536889efee8913cd363e78ca9a09b13c8e/cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c", size = 4171040, upload-time = "2025-05-02T19:34:54.675Z" }, - { url = "https://files.pythonhosted.org/packages/bd/48/bb16b7541d207a19d9ae8b541c70037a05e473ddc72ccb1386524d4f023c/cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76", size = 3963411, upload-time = "2025-05-02T19:34:56.61Z" }, - { url = "https://files.pythonhosted.org/packages/42/b2/7d31f2af5591d217d71d37d044ef5412945a8a8e98d5a2a8ae4fd9cd4489/cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359", size = 3689263, upload-time = "2025-05-02T19:34:58.591Z" }, - { url = "https://files.pythonhosted.org/packages/25/50/c0dfb9d87ae88ccc01aad8eb93e23cfbcea6a6a106a9b63a7b14c1f93c75/cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43", size = 4196198, upload-time = "2025-05-02T19:35:00.988Z" }, - { url = "https://files.pythonhosted.org/packages/66/c9/55c6b8794a74da652690c898cb43906310a3e4e4f6ee0b5f8b3b3e70c441/cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01", size = 3966502, upload-time = "2025-05-02T19:35:03.091Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f7/7cb5488c682ca59a02a32ec5f975074084db4c983f849d47b7b67cc8697a/cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d", size = 4196173, upload-time = "2025-05-02T19:35:05.018Z" }, - { url = "https://files.pythonhosted.org/packages/d2/0b/2f789a8403ae089b0b121f8f54f4a3e5228df756e2146efdf4a09a3d5083/cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904", size = 4087713, upload-time = "2025-05-02T19:35:07.187Z" }, - { url = "https://files.pythonhosted.org/packages/1d/aa/330c13655f1af398fc154089295cf259252f0ba5df93b4bc9d9c7d7f843e/cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44", size = 4299064, upload-time = "2025-05-02T19:35:08.879Z" }, - { url = "https://files.pythonhosted.org/packages/10/a8/8c540a421b44fd267a7d58a1fd5f072a552d72204a3f08194f98889de76d/cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d", size = 2773887, upload-time = "2025-05-02T19:35:10.41Z" }, - { url = "https://files.pythonhosted.org/packages/b9/0d/c4b1657c39ead18d76bbd122da86bd95bdc4095413460d09544000a17d56/cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d", size = 3209737, upload-time = "2025-05-02T19:35:12.12Z" }, - { url = "https://files.pythonhosted.org/packages/34/a3/ad08e0bcc34ad436013458d7528e83ac29910943cea42ad7dd4141a27bbb/cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f", size = 6673501, upload-time = "2025-05-02T19:35:13.775Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f0/7491d44bba8d28b464a5bc8cc709f25a51e3eac54c0a4444cf2473a57c37/cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759", size = 3960307, upload-time = "2025-05-02T19:35:15.917Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c8/e5c5d0e1364d3346a5747cdcd7ecbb23ca87e6dea4f942a44e88be349f06/cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645", size = 4170876, upload-time = "2025-05-02T19:35:18.138Z" }, - { url = "https://files.pythonhosted.org/packages/73/96/025cb26fc351d8c7d3a1c44e20cf9a01e9f7cf740353c9c7a17072e4b264/cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2", size = 3964127, upload-time = "2025-05-02T19:35:19.864Z" }, - { url = "https://files.pythonhosted.org/packages/01/44/eb6522db7d9f84e8833ba3bf63313f8e257729cf3a8917379473fcfd6601/cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54", size = 3689164, upload-time = "2025-05-02T19:35:21.449Z" }, - { url = "https://files.pythonhosted.org/packages/68/fb/d61a4defd0d6cee20b1b8a1ea8f5e25007e26aeb413ca53835f0cae2bcd1/cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93", size = 4198081, upload-time = "2025-05-02T19:35:23.187Z" }, - { url = "https://files.pythonhosted.org/packages/1b/50/457f6911d36432a8811c3ab8bd5a6090e8d18ce655c22820994913dd06ea/cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c", size = 3967716, upload-time = "2025-05-02T19:35:25.426Z" }, - { url = "https://files.pythonhosted.org/packages/35/6e/dca39d553075980ccb631955c47b93d87d27f3596da8d48b1ae81463d915/cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f", size = 4197398, upload-time = "2025-05-02T19:35:27.678Z" }, - { url = "https://files.pythonhosted.org/packages/9b/9d/d1f2fe681eabc682067c66a74addd46c887ebacf39038ba01f8860338d3d/cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5", size = 4087900, upload-time = "2025-05-02T19:35:29.312Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f5/3599e48c5464580b73b236aafb20973b953cd2e7b44c7c2533de1d888446/cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b", size = 4301067, upload-time = "2025-05-02T19:35:31.547Z" }, - { url = "https://files.pythonhosted.org/packages/a7/6c/d2c48c8137eb39d0c193274db5c04a75dab20d2f7c3f81a7dcc3a8897701/cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028", size = 2775467, upload-time = "2025-05-02T19:35:33.805Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ad/51f212198681ea7b0deaaf8846ee10af99fba4e894f67b353524eab2bbe5/cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334", size = 3210375, upload-time = "2025-05-02T19:35:35.369Z" }, - { url = "https://files.pythonhosted.org/packages/7f/10/abcf7418536df1eaba70e2cfc5c8a0ab07aa7aa02a5cbc6a78b9d8b4f121/cryptography-44.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cad399780053fb383dc067475135e41c9fe7d901a97dd5d9c5dfb5611afc0d7d", size = 3393192, upload-time = "2025-05-02T19:35:37.468Z" }, - { url = "https://files.pythonhosted.org/packages/06/59/ecb3ef380f5891978f92a7f9120e2852b1df6f0a849c277b8ea45b865db2/cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:21a83f6f35b9cc656d71b5de8d519f566df01e660ac2578805ab245ffd8523f8", size = 3898419, upload-time = "2025-05-02T19:35:39.065Z" }, - { url = "https://files.pythonhosted.org/packages/bb/d0/35e2313dbb38cf793aa242182ad5bc5ef5c8fd4e5dbdc380b936c7d51169/cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fc3c9babc1e1faefd62704bb46a69f359a9819eb0292e40df3fb6e3574715cd4", size = 4117892, upload-time = "2025-05-02T19:35:40.839Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c8/31fb6e33b56c2c2100d76de3fd820afaa9d4d0b6aea1ccaf9aaf35dc7ce3/cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:e909df4053064a97f1e6565153ff8bb389af12c5c8d29c343308760890560aff", size = 3900855, upload-time = "2025-05-02T19:35:42.599Z" }, - { url = "https://files.pythonhosted.org/packages/43/2a/08cc2ec19e77f2a3cfa2337b429676406d4bb78ddd130a05c458e7b91d73/cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:dad80b45c22e05b259e33ddd458e9e2ba099c86ccf4e88db7bbab4b747b18d06", size = 4117619, upload-time = "2025-05-02T19:35:44.774Z" }, - { url = "https://files.pythonhosted.org/packages/02/68/fc3d3f84022a75f2ac4b1a1c0e5d6a0c2ea259e14cd4aae3e0e68e56483c/cryptography-44.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:479d92908277bed6e1a1c69b277734a7771c2b78633c224445b5c60a9f4bc1d9", size = 3136570, upload-time = "2025-05-02T19:35:46.94Z" }, - { url = "https://files.pythonhosted.org/packages/8d/4b/c11ad0b6c061902de5223892d680e89c06c7c4d606305eb8de56c5427ae6/cryptography-44.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:896530bc9107b226f265effa7ef3f21270f18a2026bc09fed1ebd7b66ddf6375", size = 3390230, upload-time = "2025-05-02T19:35:49.062Z" }, - { url = "https://files.pythonhosted.org/packages/58/11/0a6bf45d53b9b2290ea3cec30e78b78e6ca29dc101e2e296872a0ffe1335/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9b4d4a5dbee05a2c390bf212e78b99434efec37b17a4bff42f50285c5c8c9647", size = 3895216, upload-time = "2025-05-02T19:35:51.351Z" }, - { url = "https://files.pythonhosted.org/packages/0a/27/b28cdeb7270e957f0077a2c2bfad1b38f72f1f6d699679f97b816ca33642/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02f55fb4f8b79c1221b0961488eaae21015b69b210e18c386b69de182ebb1259", size = 4115044, upload-time = "2025-05-02T19:35:53.044Z" }, - { url = "https://files.pythonhosted.org/packages/35/b0/ec4082d3793f03cb248881fecefc26015813199b88f33e3e990a43f79835/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dd3db61b8fe5be220eee484a17233287d0be6932d056cf5738225b9c05ef4fff", size = 3898034, upload-time = "2025-05-02T19:35:54.72Z" }, - { url = "https://files.pythonhosted.org/packages/0b/7f/adf62e0b8e8d04d50c9a91282a57628c00c54d4ae75e2b02a223bd1f2613/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:978631ec51a6bbc0b7e58f23b68a8ce9e5f09721940933e9c217068388789fe5", size = 4114449, upload-time = "2025-05-02T19:35:57.139Z" }, - { url = "https://files.pythonhosted.org/packages/87/62/d69eb4a8ee231f4bf733a92caf9da13f1c81a44e874b1d4080c25ecbb723/cryptography-44.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5d20cc348cca3a8aa7312f42ab953a56e15323800ca3ab0706b8cd452a3a056c", size = 3134369, upload-time = "2025-05-02T19:35:58.907Z" }, -] - -[[package]] -name = "deprecated" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, -] - -[[package]] -name = "dnspython" -version = "2.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, -] - -[[package]] -name = "docker" -version = "7.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "requests" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, -] - -[[package]] -name = "ecdsa" -version = "0.19.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/25/ca/8de7744cb3bc966c85430ca2d0fcaeea872507c6a4cf6e007f7fe269ed9d/ecdsa-0.19.2.tar.gz", hash = "sha256:62635b0ac1ca2e027f82122b5b81cb706edc38cd91c63dda28e4f3455a2bf930", size = 202432, upload-time = "2026-03-26T09:58:17.675Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/79/119091c98e2bf49e24ed9f3ae69f816d715d2904aefa6a2baa039a2ba0b0/ecdsa-0.19.2-py2.py3-none-any.whl", hash = "sha256:840f5dc5e375c68f36c1a7a5b9caad28f95daa65185c9253c0c08dd952bb7399", size = 150818, upload-time = "2026-03-26T09:58:15.808Z" }, -] - -[[package]] -name = "email-validator" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dnspython" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - -[[package]] -name = "fastapi" -version = "0.136.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-doc" }, - { name = "pydantic" }, - { name = "starlette" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5d/45/c130091c2dfa061bbfe3150f2a5091ef1adf149f2a8d2ae769ecaf6e99a2/fastapi-0.136.1.tar.gz", hash = "sha256:7af665ad7acfa0a3baf8983d393b6b471b9da10ede59c60045f49fbc89a0fa7f", size = 397448, upload-time = "2026-04-23T16:49:44.046Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/ff/2e4eca3ade2c22fe1dea7043b8ee9dabe47753349eb1b56a202de8af6349/fastapi-0.136.1-py3-none-any.whl", hash = "sha256:a6e9d7eeada96c93a4d69cb03836b44fa34e2854accb7244a1ece36cd4781c3f", size = 117683, upload-time = "2026-04-23T16:49:42.437Z" }, -] - -[package.optional-dependencies] -standard = [ - { name = "email-validator" }, - { name = "fastapi-cli", extra = ["standard"] }, - { name = "fastar" }, - { name = "httpx" }, - { name = "jinja2" }, - { name = "pydantic-extra-types" }, - { name = "pydantic-settings" }, - { name = "python-multipart" }, - { name = "uvicorn", extra = ["standard"] }, -] - -[[package]] -name = "fastapi-cli" -version = "0.0.24" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "rich-toolkit" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typer" }, - { name = "uvicorn", extra = ["standard"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6e/58/74797ae9e4610cfa0c6b34c8309096d3b20bb29be3b8b5fbf1004d10fa5f/fastapi_cli-0.0.24.tar.gz", hash = "sha256:1afc9c9e21d7ebc8a3ca5e31790cd8d837742be7e4f8b9236e99cb3451f0de00", size = 19043, upload-time = "2026-02-24T10:45:10.476Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/4b/68f9fe268e535d79c76910519530026a4f994ce07189ac0dded45c6af825/fastapi_cli-0.0.24-py3-none-any.whl", hash = "sha256:4a1f78ed798f106b4fee85ca93b85d8fe33c0a3570f775964d37edb80b8f0edc", size = 12304, upload-time = "2026-02-24T10:45:09.552Z" }, -] - -[package.optional-dependencies] -standard = [ - { name = "fastapi-cloud-cli" }, - { name = "uvicorn", extra = ["standard"] }, -] - -[[package]] -name = "fastapi-cloud-cli" -version = "0.17.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "fastar" }, - { name = "httpx" }, - { name = "pydantic", extra = ["email"] }, - { name = "rich-toolkit" }, - { name = "rignore" }, - { name = "sentry-sdk" }, - { name = "typer" }, - { name = "uvicorn", extra = ["standard"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/96/57/cee8e91b83f39e75ae5562a2237261442a8179dcb3b631c7398113157398/fastapi_cloud_cli-0.17.1.tar.gz", hash = "sha256:0baece208fa88063bec46dccb5fb512f3199162092165e57654b44e64adbc44d", size = 47409, upload-time = "2026-04-27T13:38:07.094Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/a0/e252b68cf155409afabea037ab2971f41509481838847f6503fe890884ea/fastapi_cloud_cli-0.17.1-py3-none-any.whl", hash = "sha256:325e0199bdac7cb86f5df4f4a1d2070054095588088ef7b923a60cec458dcd63", size = 34046, upload-time = "2026-04-27T13:38:08.319Z" }, -] - -[[package]] -name = "fastar" -version = "0.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/0f/0aeb3fc50046617702acc0078b277b58367fd62eb727b9ec733ae0e8bbcc/fastar-0.11.0.tar.gz", hash = "sha256:aa7f100f7313c03fdb20f1385927ba95671071ba308ad0c1763fef295e1895ce", size = 70238, upload-time = "2026-04-13T17:11:17.143Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/4a/0d79fe52243a4130aa41d0a3a9eea22e00427db761e1a6782ee817c50222/fastar-0.11.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e7c906ad371ca365591ebcb7630009923f3eceb20956814494d15591a78e9e46", size = 709786, upload-time = "2026-04-13T17:09:53.974Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e4/77c94eaafc035e39f5ce5176e32743da4e3fe890f28790e708e53d8f75cd/fastar-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6919497b35fa5bd978d2c26ee117cf1771b90ee5073f7518e44b9bc364b57715", size = 632127, upload-time = "2026-04-13T17:09:39.023Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f6/97658dd992f4e45747d35adb24c0b100f6b6d451490685ae3fe8a3a2ee1b/fastar-0.11.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:56b50206aeedd99e22b83289e6fb3ff8f7d7da4407d2419902e4716b4f90585a", size = 869608, upload-time = "2026-04-13T17:09:08.268Z" }, - { url = "https://files.pythonhosted.org/packages/e9/fc/81c1ec4d8146a437399e7b95631b51be312f323a9ce64569f932db6c3914/fastar-0.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a1811a69ae81d469720df0c8af3f84f834a93b5e4f8be0e0e8bde6a52fa11f2", size = 762925, upload-time = "2026-04-13T17:07:52.788Z" }, - { url = "https://files.pythonhosted.org/packages/b9/35/49baf480ecb197aea7ce2515c503a2f25061958dd3b4c98e98a3a11cdcc7/fastar-0.11.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:10486238c55589a3947c38f9cfb88a67d8a608eb8dddc722038237d0278a41d7", size = 759913, upload-time = "2026-04-13T17:08:07.324Z" }, - { url = "https://files.pythonhosted.org/packages/94/eb/946f1980267f2824efb7d7c518d47a49b89c0e9cd7c449301f5a7531558a/fastar-0.11.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1555ef9992d368a6ec39092276990cef8d329c39a1d86ebd847eaa3b10efd472", size = 926054, upload-time = "2026-04-13T17:08:22.196Z" }, - { url = "https://files.pythonhosted.org/packages/0c/19/d5eb611085ce054382570d8d4e24a5e2ff23cd6d2404528a6643841d6059/fastar-0.11.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1f4aca0a9620b76988bbf6225cdea6678a392902444ca18bb8a51495b165a89", size = 818594, upload-time = "2026-04-13T17:08:52.366Z" }, - { url = "https://files.pythonhosted.org/packages/4a/52/18e8d55c0d3d917713f381cb2d0cb793da00c209c802e011d8dc72018cd5/fastar-0.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75beeecac7d11a666a6c4a0b7f7e80842ae5cf523f2f890b99c78fc82b403545", size = 823005, upload-time = "2026-04-13T17:09:23.051Z" }, - { url = "https://files.pythonhosted.org/packages/2c/b4/0fecdcf33e5aaffe777b96a1c10a3204fe0b05bf18e971033a0bfedafc1c/fastar-0.11.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a08cdf5d16daa401c65c9c7493a18db7dc515c52155a17071ec7098bb07da9d3", size = 887115, upload-time = "2026-04-13T17:08:37.385Z" }, - { url = "https://files.pythonhosted.org/packages/08/f8/2a6ad1c2523eb72a4595a9331162fc67ce0f0aee3348728598026c516986/fastar-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6e210375e5a7ba53586cbd6017aa417d2d2ceacbe8671682470281bd0a15e8ef", size = 973595, upload-time = "2026-04-13T17:10:09.258Z" }, - { url = "https://files.pythonhosted.org/packages/5c/a6/2aa48843228673feacc2b80876b8924e63ea9c5f5f607bd7a72416b86bae/fastar-0.11.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a2988eb2604b8e15670f355425e8c800e4dcd4edfbcbfe194397f8f17b7eb19e", size = 1036988, upload-time = "2026-04-13T17:10:26.133Z" }, - { url = "https://files.pythonhosted.org/packages/92/ac/3dd14b21c323e8484f47c910110d1d93139ba44621ac2c4c597dbe9fcdb7/fastar-0.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:34abc857b46068fdf91d157bd0203bfd6791dc7a432d1ed180f5af6c2f5bcce9", size = 1078267, upload-time = "2026-04-13T17:10:43.645Z" }, - { url = "https://files.pythonhosted.org/packages/de/a1/3f89e58d6fa99160c9e7e17220c8ab5040b5cc017c4fac2356c6ed18453d/fastar-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0d884be84e37a01053776395441fc960031974e0265801ce574efc3d05e0cdaf", size = 1032551, upload-time = "2026-04-13T17:11:00.667Z" }, - { url = "https://files.pythonhosted.org/packages/f6/ea/24dd3cfc2096933d7d2a80c926e79602cff1fa481124ed2165b60c1dd9ef/fastar-0.11.0-cp310-cp310-win32.whl", hash = "sha256:c721c1ad758e3e4c2c1fd9e96911a0fa58c0a6be5668f1bcfd0b741e72c7cb63", size = 456022, upload-time = "2026-04-13T17:11:41.859Z" }, - { url = "https://files.pythonhosted.org/packages/82/ef/6eb39ee9cdd59822d1c7337c4d28fdc948885bdf455af9e70efa9879e06f/fastar-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:ba4180b7c3080f55f9035fdd7d8c39fe0e1485087a68ff615bb4784a10b8106b", size = 488392, upload-time = "2026-04-13T17:11:27.486Z" }, - { url = "https://files.pythonhosted.org/packages/11/7a/fb367bdaf4efa2c7952a45aeab2e87a564293ecffe150af673ec8edfda46/fastar-0.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b82fd6f996e65a86f67a6bd64dd22ef3e8ae2dcaed0ae3b550e71f7e1bbb1df5", size = 709869, upload-time = "2026-04-13T17:09:55.62Z" }, - { url = "https://files.pythonhosted.org/packages/80/ff/b87efb0dcfd081c62c7c7601d7681dabe63103cd51fc16f8d57a1ab45961/fastar-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27eed386fd0558e6daa29211111bbd7b740f7c7e881197f8a00ac7c0f3cdb1d7", size = 631668, upload-time = "2026-04-13T17:09:40.537Z" }, - { url = "https://files.pythonhosted.org/packages/24/7c/0ed6dd38b9adc04b3a8ec3b7045908e7c2170ba0ff6e6d2c51bc9fc770f3/fastar-0.11.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a6931bebc1d8e95ddeef55732c195449e6b44ef33aa31b325505097ed3b4d6aa", size = 869663, upload-time = "2026-04-13T17:09:09.78Z" }, - { url = "https://files.pythonhosted.org/packages/58/ce/8b7fb3f23855accebaaf2d2637eac7f261a7a5d936f861a172079f1ef511/fastar-0.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:891f72ce42a5e28a74fbd4d5fbf1a3ac1a1163d13cbc200cbd005fb0fabc54bd", size = 762938, upload-time = "2026-04-13T17:07:54.51Z" }, - { url = "https://files.pythonhosted.org/packages/07/cc/5491e2b677bb841f768e3aba052d0344338a5c78aa5d4c18b443831a8e8d/fastar-0.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5b83c1f61f7017d6e1498568038f8745440cfc16ca2f697ec81bac83050108f6", size = 759232, upload-time = "2026-04-13T17:08:08.864Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b7/643630bdbd179e41e9fae31c03b4cf6061dbf4d6fbbae8425d16eb12545d/fastar-0.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db73a9b765a516e73983b25341e7b5e0189733878279e278b2295131b0e3a21e", size = 926271, upload-time = "2026-04-13T17:08:23.68Z" }, - { url = "https://files.pythonhosted.org/packages/09/5d/37ade50003b4540e0a53ef100f6692d7ab2ac1122d5acf39920cc09a3e8b/fastar-0.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:625827d52eb4e8fec942e0233f125ff8010fcf6a67c0a974a8e5f4666b771e3c", size = 818634, upload-time = "2026-04-13T17:08:54.268Z" }, - { url = "https://files.pythonhosted.org/packages/c3/ff/135d177de32cc1e837c99019e4643e6e79352bde49544d4ece5b5eebf56b/fastar-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7f5fd8fa21ec0a88296a38dc5d7fc35efd3b26d46a17b8b7c73c5563925ca15", size = 822755, upload-time = "2026-04-13T17:09:25.01Z" }, - { url = "https://files.pythonhosted.org/packages/27/cb/b835dbe76ceac7fa6105851468c259ffd06830eb9c029402e499d0ec153b/fastar-0.11.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8c15af91b8cd87ddf23ea55355ae513c1de3ab67178f26dad017c9e9c0af6096", size = 887101, upload-time = "2026-04-13T17:08:39.248Z" }, - { url = "https://files.pythonhosted.org/packages/9e/54/aa8289eb57fc550535470397cb051f5a58a7c89ca4de31d5502b916dd894/fastar-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03a112395a8b0bff251423bd1564c012f0cc058ad8b6bd8fba96f3d7fc117e44", size = 973606, upload-time = "2026-04-13T17:10:10.98Z" }, - { url = "https://files.pythonhosted.org/packages/1f/fd/776d50a0897c01dc6bfd0926772ee913436fdae91b9affaf0a0cbd09f0a1/fastar-0.11.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f2994bb8f5f8c11eb12beae1e6e77a907173c9819236b8a4c8f0573652ceccce", size = 1036696, upload-time = "2026-04-13T17:10:28.502Z" }, - { url = "https://files.pythonhosted.org/packages/c8/f1/cf0f9b499fb37ac065c8a01ec642f96a3c5eb849c38ae983b59f3b3245e0/fastar-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dcf99e4b5973d842c7f19c776c3a83cdc0977d505edce6206438505c0456b517", size = 1078182, upload-time = "2026-04-13T17:10:45.318Z" }, - { url = "https://files.pythonhosted.org/packages/f8/9e/21e4701aec4a1123d4dc4d31578dc18875582b5710e4725f7ceb752a248b/fastar-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29c9c386dc0d5dda78845a8e6b1480d26ab861c1e0b68f42ae5735cb70ca07f1", size = 1032336, upload-time = "2026-04-13T17:11:02.364Z" }, - { url = "https://files.pythonhosted.org/packages/ce/e2/5872b28c72c27ec1a00760eace6ff35f714f41ebbd5208cf016b12e29250/fastar-0.11.0-cp311-cp311-win32.whl", hash = "sha256:030b2580fc394f2c9b7890b6735810404e9b9ed5e0344db150b945965b5482b7", size = 457368, upload-time = "2026-04-13T17:11:43.528Z" }, - { url = "https://files.pythonhosted.org/packages/fd/6e/ce6832a16193eb4466f4108be8809c249b51cb1f89dd7894545700d079d5/fastar-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:83ab57ae067969cd0b483ac3b6dccc4b595fc77f5c820760998648d4c42822b5", size = 488605, upload-time = "2026-04-13T17:11:29.161Z" }, - { url = "https://files.pythonhosted.org/packages/15/5a/9cfb80661cf38fd7b0889224beb7d2746784d4ade2a931ed9775a18d8602/fastar-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:27b1a4cee2298b704de8151d310462ee7335ed036011ca9aa6e784b30b6c73a9", size = 464580, upload-time = "2026-04-13T17:11:18.583Z" }, - { url = "https://files.pythonhosted.org/packages/0f/06/a5773706afc8bd496769786590bbc56d2d0ee419a299cc12ea3f5717fcf3/fastar-0.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3c51f1c2cdddbd1420d2897ace7738e36c65e17f6ae84e0bfe763f8d1068bb97", size = 708394, upload-time = "2026-04-13T17:09:57.269Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a6/d5e2a4e48495616440a21eed07558219ca90243ad00b0502586f95bd4833/fastar-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0d9d6b052baf5380baea866675dab6ccd04ec2460d12b1c46f10ce3f4ee6a820", size = 628417, upload-time = "2026-04-13T17:09:42.145Z" }, - { url = "https://files.pythonhosted.org/packages/ab/69/9816d69ac8265c9e50456637a487ccfb7a9c566efd9dbcd673df9c2558c2/fastar-0.11.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bd2f05666d4df7e14885b5c38fefd92a785917387513d33d837ff42ec143a22f", size = 863950, upload-time = "2026-04-13T17:09:11.506Z" }, - { url = "https://files.pythonhosted.org/packages/5b/0d/f88daad53aff2e754b6b5ff2a7113f72447a34f6ef17cc23ca99988117b7/fastar-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e6e74aba1ae77ca4aedcaf1697cd413319f4c88a5ccbe5b42c709517c5097e", size = 760737, upload-time = "2026-04-13T17:07:55.958Z" }, - { url = "https://files.pythonhosted.org/packages/2f/a6/82ef4ecd969d50d92ed3ed9dbd8fe77faa24be5e5736f716edc9f4ce8d62/fastar-0.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38ef77fe940bbc9b37a98bd838727f844b11731cd39358a2640ff864fb385086", size = 757603, upload-time = "2026-04-13T17:08:10.623Z" }, - { url = "https://files.pythonhosted.org/packages/03/35/50249f0d827251f8ac511495e2eacccebda80a00a0ad73e9615b8113b84f/fastar-0.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8955e61b32d6aff82c983217abf80933fd823b0e727586fc72f08043d996fd59", size = 923952, upload-time = "2026-04-13T17:08:25.526Z" }, - { url = "https://files.pythonhosted.org/packages/7b/d8/faee41659e9c379d906d24eaee6d6833ac8cfef0a5df480e5c2a8d3efb33/fastar-0.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:483532442cdb08fbff0169510224eae0836f2f672cea6aacb52847d90fefdc46", size = 816574, upload-time = "2026-04-13T17:08:56.076Z" }, - { url = "https://files.pythonhosted.org/packages/22/47/0448ea7992b997dad2bf004bfd98eca74b5858630eae080b50c7b17d9ddc/fastar-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef5a6071121e05d8287fc75bccb054bcbac8bb0501200a0c0a8feeace5303ea4", size = 819382, upload-time = "2026-04-13T17:09:26.66Z" }, - { url = "https://files.pythonhosted.org/packages/33/ef/0d63eb43586831b7a6f8b22c4d77125a7c594423af1f4f090fa9541b9b40/fastar-0.11.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:e45e598af5afe8412197d4786efd6cf29be02e7d3d4f6a3461149eae5d7e94f1", size = 885254, upload-time = "2026-04-13T17:08:40.9Z" }, - { url = "https://files.pythonhosted.org/packages/01/25/edd584675d69e49a165052c3ee886df1c5d574f3e7d813c990306387c623/fastar-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e160919b1c47ddb8538e7e8eb4cd527281b40f0bf75110a75993838ef61f286", size = 971239, upload-time = "2026-04-13T17:10:12.997Z" }, - { url = "https://files.pythonhosted.org/packages/a5/37/e8bb24f506ba2b08fbaf36c5800e843bd4d542954e9331f00418e2d23349/fastar-0.11.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4bb4dc0fc8f7a6807febcebce8a2f3626ba4955a9263d81ecc630aad83be84c0", size = 1035185, upload-time = "2026-04-13T17:10:30.207Z" }, - { url = "https://files.pythonhosted.org/packages/9a/bf/be753736296338149ee4cb3e92e2b5423d6ba17c7b951d15218fd7e99bbf/fastar-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4ec95af56aa173f6e320e1183001bf108ba59beaf13edd1fc8200648db203588", size = 1072191, upload-time = "2026-04-13T17:10:47.072Z" }, - { url = "https://files.pythonhosted.org/packages/d2/cd/a81c1aaafb5a22ce57c98ae22f39c89413ed53e4ee6e1b1444b0bd666a6c/fastar-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:136cf342735464091c39dc3708168f9fdeb9ebea40b1ead937c61afaf46143d9", size = 1028054, upload-time = "2026-04-13T17:11:04.293Z" }, - { url = "https://files.pythonhosted.org/packages/ec/88/1ce4eed3d70627c95f49ca017f6bbbf2ddcc4b0c601d293259de7689bc20/fastar-0.11.0-cp312-cp312-win32.whl", hash = "sha256:35f23c11b556cc4d3704587faacbc0037f7bdf6c4525cd1d09c70bda4b1c6809", size = 454198, upload-time = "2026-04-13T17:11:45.168Z" }, - { url = "https://files.pythonhosted.org/packages/8f/1d/26ce92f4331cd61a69840db9ca6115829805eec24f285481a854f578e917/fastar-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:920bc56c3c0b8a8ca492904941d1883c1c947c858cd93343356c29122a38f44c", size = 486697, upload-time = "2026-04-13T17:11:31.084Z" }, - { url = "https://files.pythonhosted.org/packages/ed/96/e6eda4480559c69b05d466e7b5ea9170e81fef3795a73e059959a3258319/fastar-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:395248faf89e8a6bd5dc1fd544c8465113b627cb6d7c8b296796b60ebea33593", size = 462591, upload-time = "2026-04-13T17:11:20.577Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d6/3be260037e86fb694e88d47f583bac3a0188c99cee1a6b257ac26cb6b53c/fastar-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:33f544b08b4541b678e53749b4552a44720d96761fb79c172b005b1089c443ed", size = 707975, upload-time = "2026-04-13T17:09:58.866Z" }, - { url = "https://files.pythonhosted.org/packages/e1/cd/7867aefb1784662554a335f2952c75a50f0c70585ed0d2210d6cc15e5627/fastar-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:91c1c792447e4a642745f347ff9847c52af39633071c57ee67ed53c157fc3506", size = 628460, upload-time = "2026-04-13T17:09:43.776Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2b/d11d84bdd5e0e377771b955755771e3460b290da5809cb78c1b735ee2228/fastar-0.11.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:881247e6b6eaea59fc6569f9b61447aa6b9fc2ee864e048b4643d69c52745805", size = 863054, upload-time = "2026-04-13T17:09:13.048Z" }, - { url = "https://files.pythonhosted.org/packages/25/39/d3f428b318fa940b1b6e785b8d54fc895dfb5d5b945ef8d5442ffa904fb2/fastar-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:863b7929845c9fec92ef6c8d59579cf46af5136655e5342f8df5cebe46cab06c", size = 760247, upload-time = "2026-04-13T17:07:57.396Z" }, - { url = "https://files.pythonhosted.org/packages/9e/04/03949aee82aabb8ede06ac5a4a5579ffaf98a8fe59ce958494508ff15513/fastar-0.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:96b4a57df12bf3211662627a3ea29d62ecb314a2434a0d0843f9fc23e47536e5", size = 756512, upload-time = "2026-04-13T17:08:12.415Z" }, - { url = "https://files.pythonhosted.org/packages/3f/0c/2ca1ae0a3828ca51047962d932b80daca2522db73e8cb9d040cb6ebe28d5/fastar-0.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceef1c2c4df7b7b8ebd3f5d718bbf457b9bbdf25ce0bd07870211ec4fbd9aff4", size = 922183, upload-time = "2026-04-13T17:08:27.187Z" }, - { url = "https://files.pythonhosted.org/packages/65/68/7fe808b1f73a68e686f25434f538c6dc10ef4dfb3db0ace22cd861744bf8/fastar-0.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8e545918441910a779659d4759ad0eef349e935fbdb4668a666d3681567eb05", size = 816394, upload-time = "2026-04-13T17:08:57.657Z" }, - { url = "https://files.pythonhosted.org/packages/1f/17/07d086080f8a83b8d7966955e29bcdbd6a060f5bd949dc9d5abd3658cead/fastar-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28095bb8f821e85fc2764e1a55f03e5e2876dee2abe7cd0ee9420d929905d643", size = 818983, upload-time = "2026-04-13T17:09:28.46Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e2/2c4edf0910af2e814ff6d65b77a91196d472ca8a9fb2033bd983f6856caa/fastar-0.11.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0fafb95ecbe70f666a5e9b35dd63974ccdc9bb3d99ccdbd4014a823ec3e659b5", size = 884689, upload-time = "2026-04-13T17:08:42.763Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ba/04fdcbd6558e60de4ced3b55230fac47675d181252582b2fcec3c74608e5/fastar-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:af48fed039b94016629dcdad1c95c90c486326dd068de2b0a4df419ee09b6821", size = 970677, upload-time = "2026-04-13T17:10:15.124Z" }, - { url = "https://files.pythonhosted.org/packages/df/b3/2b860a9658550167dbd5824c85e88d0b4b912bf493e42a6322544d6e483d/fastar-0.11.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:74cd96163f39b8638ab4e8d49708ca887959672a22871d8170d01f067319533b", size = 1034026, upload-time = "2026-04-13T17:10:32.318Z" }, - { url = "https://files.pythonhosted.org/packages/b7/9b/fa42ea1188b144bac4b1b60753dfd449974a4d5eda132029ee7711569f94/fastar-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4e8b993cb5613bab495ed482810bedc0986633fcb9a3b55c37ec88e0d6714f6a", size = 1071147, upload-time = "2026-04-13T17:10:48.833Z" }, - { url = "https://files.pythonhosted.org/packages/95/c8/d2e501556dca9f1fbc9246111a31792fb49ad908fa4927f34938a97a3604/fastar-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfe39d91fc28e37e06162d94afe01050220edb7df554acb5b702b5503e564816", size = 1028377, upload-time = "2026-04-13T17:11:06.374Z" }, - { url = "https://files.pythonhosted.org/packages/db/33/5f11f23eca0a569cd052507bc45dda2e5468697f8665728d25be44120f7d/fastar-0.11.0-cp313-cp313-win32.whl", hash = "sha256:c5f63d4d99ff4bfb37c659982ec413358bdee747005348756cc50a04d412d989", size = 454089, upload-time = "2026-04-13T17:11:46.821Z" }, - { url = "https://files.pythonhosted.org/packages/da/2f/35ff03c939cba7a255a9132367873fec6c355fd06a7f84fedcbaf4c8129f/fastar-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8690ed1928d31ded3ada308e1086525fb3871f5fa81e1b69601a3f7774004583", size = 486312, upload-time = "2026-04-13T17:11:32.86Z" }, - { url = "https://files.pythonhosted.org/packages/ef/71/ee9246cbfcbfd4144558f35e7e9a306ffe0a7564730a5188c45f21d2dab8/fastar-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:d977ded9d98a0719a305e0a4d5ee811f1d3e856d853a50acb8ae833c3cd6d5d2", size = 461975, upload-time = "2026-04-13T17:11:22.589Z" }, - { url = "https://files.pythonhosted.org/packages/7a/cd/3644c48ecac456f928c12d47ec3bed36c36555b17c3859856f1ff860265d/fastar-0.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:71375bd6f03c2a43eb47bd949ea38ff45434917f9cdac79675c5b9f60de4fa73", size = 707860, upload-time = "2026-04-13T17:10:00.371Z" }, - { url = "https://files.pythonhosted.org/packages/69/ca/dee04476ae3626b2b040a60ad84628f77e1ffd8444232f2426b0ca1e0d7e/fastar-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:eddfd9cab16e19ae247fe44bf992cb403ccfe27d3931d6de29a4695d95ad386c", size = 628216, upload-time = "2026-04-13T17:09:45.355Z" }, - { url = "https://files.pythonhosted.org/packages/dc/5e/9395c7353d079cb4f5be0f7982ce0dc9f2e7dec5fd175eef466729d6023a/fastar-0.11.0-cp314-cp314-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c371f1d4386c699018bb64eb2fa785feacf32785559049d2bb72fe4af023f53", size = 864378, upload-time = "2026-04-13T17:09:14.611Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ba/1e4f67148223ff219612b6281a6000357abbcc2417964fa5c83f11d68fce/fastar-0.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cad7fa41e3e66554387481c1a09365e4638becd322904932674159d5f4046728", size = 760921, upload-time = "2026-04-13T17:07:59.138Z" }, - { url = "https://files.pythonhosted.org/packages/0f/82/09d11fb6d12f17993ffaf32ffd30c3c121a11e2966e84f19fb6f66430118/fastar-0.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf36652fa71b83761717c9899b98732498f8a2cb6327ff16bbf07f6be85c3437", size = 757012, upload-time = "2026-04-13T17:08:14.186Z" }, - { url = "https://files.pythonhosted.org/packages/52/1f/5aeeacc4cb65615e2c9292cd9c5b0cd6fb6d2e6ee472ca6adc6c1b1b22ef/fastar-0.11.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f68ff8c17833053da4841720e95edde80ce45bb994b6b7d51418dddaac70ee47", size = 924510, upload-time = "2026-04-13T17:08:28.741Z" }, - { url = "https://files.pythonhosted.org/packages/bb/1a/1e5bdabbeaf2e856928956292609f2ff6a650f94480fb8afaca30229e483/fastar-0.11.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4563ed37a12ea1cdc398af8571258d24b988bf342b7b3bf5451bd5891243280c", size = 816602, upload-time = "2026-04-13T17:08:59.461Z" }, - { url = "https://files.pythonhosted.org/packages/87/24/f960147910da3bed41a3adfcb026e17d5f50f4cf467a3324237a7088f61a/fastar-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cee63c9875cba3b70dc44338c560facc5d6e763047dcc4a30501f9a68cf5f890", size = 819452, upload-time = "2026-04-13T17:09:29.926Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f4/3e77d7901d5707fd7f8a352e153c8ae09ea974e6fabad0b7c4eb9944b8d4/fastar-0.11.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:bd76bfffae6d0a91f4ac4a612f721e7aec108db97dccdd120ae063cd66959f27", size = 885254, upload-time = "2026-04-13T17:08:44.285Z" }, - { url = "https://files.pythonhosted.org/packages/47/01/1585edd5ec47782ae93cd94edf05828e0ab02ef00aec00aea4194a600464/fastar-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f5b707501ec01c1bc0518f741f01d322e50c9adc19a451aa24f67a2316e9397", size = 971496, upload-time = "2026-04-13T17:10:17.024Z" }, - { url = "https://files.pythonhosted.org/packages/f1/e9/6874c9d1236ded565a0bed54b320ac9f165f287b1d89490fb70f9f323c81/fastar-0.11.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:37c0b5a88a657839aad98b0a6c9e4ac4c2c15d6b49c44ee3935c6b08e9d3e479", size = 1034685, upload-time = "2026-04-13T17:10:34.063Z" }, - { url = "https://files.pythonhosted.org/packages/14/d8/4ab20613ce2983427aee958e39be878dba874aa227c530a845e32429c4f6/fastar-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6c55f536c62a6efb180c1af0d5182948bff576bbfe6276e8e1359c9c7d2215d8", size = 1072675, upload-time = "2026-04-13T17:10:50.53Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ae/5ac3b7c20ce4b08f011dd2b979f96caabe64f9b10b157f211ea91bdfadca/fastar-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3082eeca59e189b9039335862f4c2780c0c8871d656bfdf559db4414a105b251", size = 1029330, upload-time = "2026-04-13T17:11:08.138Z" }, - { url = "https://files.pythonhosted.org/packages/8a/e7/37cd6a1d4e288292170b64e19d79ecce2a7de8bb76790323399a2abc4619/fastar-0.11.0-cp314-cp314-win32.whl", hash = "sha256:b201a0a4e29f9fec2a177e13154b8725ec65ab9f83bd6415483efaa2aa18344b", size = 453940, upload-time = "2026-04-13T17:11:48.713Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1c/795c878b1ee29d79021cf8ed81f18f2b25ccde58453b0d34b9bdc7e025ea/fastar-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:868fddb26072a43e870a8819134b9f80ee602931be5a76e6fb873e04da343637", size = 486334, upload-time = "2026-04-13T17:11:34.882Z" }, - { url = "https://files.pythonhosted.org/packages/ff/a4/113f104301df8bddcc0b3775b611a30cb7610baa3add933c7ccac9386467/fastar-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:3db39c9cc42abb0c780a26b299f24dfbc8be455985e969e15336d70d7b2f833b", size = 461534, upload-time = "2026-04-13T17:11:24.329Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a6/5c5f2c2c8e0c63e56a5636ebc7721589c889e94c0092cec7eb28ae7207e6/fastar-0.11.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:49c3299dec5e125e7ebaa27545714da9c7391777366015427e0ae62d548b442b", size = 707156, upload-time = "2026-04-13T17:10:02.176Z" }, - { url = "https://files.pythonhosted.org/packages/df/f7/982c01b61f0fc135ad2b16d01e6d0ee53cf8791e68827f5f7c5a65b2e5b1/fastar-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3328ed1ed56d31f5198350b17dd60449b8d6b9d47abb4688bab6aef4450a165b", size = 627032, upload-time = "2026-04-13T17:09:46.978Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c3/38f1dac77ae0c71c37b176277c96d830796b8ce2fe69705f917829b53829/fastar-0.11.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bd3eca3bbfec84a614bcb4143b4ad4f784d0895babc26cfc88436af88ca23c7a", size = 864403, upload-time = "2026-04-13T17:09:16.58Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f0/e69c363bdb3e5a5848e937b662b5469581ee6682c51bc1c0556494773929/fastar-0.11.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff86a967acb0d621dd24063dda090daa67bf4993b9570e97fe156de88a9006ca", size = 759480, upload-time = "2026-04-13T17:08:00.599Z" }, - { url = "https://files.pythonhosted.org/packages/3b/29/4d8737590c2a6357d614d7cc7288e8f68e7e449680b8922997cc4349e65e/fastar-0.11.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:86eaf7c0e985d93a7734168be2fb232b2a8cca53e41431c2782d7c12b12c03b1", size = 756219, upload-time = "2026-04-13T17:08:15.699Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ec/400de7b3b7d48801908f19cf5462177104395799472671b3e8152b2b04ca/fastar-0.11.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91f07b0b8eb67e2f177733a1f884edad7dfb9f8977ffef15927b20cb9604027d", size = 923669, upload-time = "2026-04-13T17:08:30.574Z" }, - { url = "https://files.pythonhosted.org/packages/5d/01/8926c53da923fed7ab4b96e7fbf7f73b663beb4f02095b654d6fab46f9ad/fastar-0.11.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f85c896885eb4abf1a635d54dea22cac6ae48d04fc2ea26ae652fcf1febe1220", size = 815729, upload-time = "2026-04-13T17:09:01.204Z" }, - { url = "https://files.pythonhosted.org/packages/89/f0/5fef4c7946e352651b504b1a4235dac3505e7cfd24020788ab50552e84bf/fastar-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:075c07095c8de4b774ba8f28b9c0a02b1a2cd254da50cbe464dd3bb2432e9158", size = 819812, upload-time = "2026-04-13T17:09:31.907Z" }, - { url = "https://files.pythonhosted.org/packages/b3/c8/0ebc3298b4a45e7bddc50b169ae6a6f5b80c939394d4befe6e60de535ee7/fastar-0.11.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:07f028933820c65750baf3383b807ecce1cd9385cf00ce192b79d263ad6b856c", size = 884074, upload-time = "2026-04-13T17:08:45.802Z" }, - { url = "https://files.pythonhosted.org/packages/ae/9f/7baa4cdff8d6fbca41fa5c764b48a941fed8a9ec6c4cc92de65895a28299/fastar-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:039f875efa0f01fa43c20bf4e2fc7305489c61d0ac76eda991acfba7820a0e63", size = 969450, upload-time = "2026-04-13T17:10:18.667Z" }, - { url = "https://files.pythonhosted.org/packages/d4/dc/1ebbfb58a47056ba866494f19efbcdd2ba2897096b94f36e796594b4d05b/fastar-0.11.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:fff12452a9a5c6814a012445f26365541cc3d99dcca61f09762e6a389f7a32ea", size = 1033775, upload-time = "2026-04-13T17:10:36.165Z" }, - { url = "https://files.pythonhosted.org/packages/c2/5f/ce4e3914066f08c99eb8c32952cc07c1a013e81b1db1b0f598130bf6b974/fastar-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2bf733e09f942b6fa876efe30a90508d1f4caef5630c00fb2a84fba355873712", size = 1072158, upload-time = "2026-04-13T17:10:52.497Z" }, - { url = "https://files.pythonhosted.org/packages/03/2a/6bca72992c84151c387cc6558f3867f5ebe5fb3684ee6fa9b76280ba4b8e/fastar-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d1531fa848fdd3677d2dce0a4b436ea64d9ae38fb8babe2ddbc180dd153cb7a3", size = 1028577, upload-time = "2026-04-13T17:11:09.934Z" }, - { url = "https://files.pythonhosted.org/packages/83/18/7a7c15657a3da5569b26fc51cde6a80f8d84cb54b3b1aea6d74a103db4ad/fastar-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:5744551bc67c6fc6581cbd0e34a0fd6e2cd0bd30b43e94b1c3119cf35064b162", size = 453601, upload-time = "2026-04-13T17:11:53.726Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d8/331b59a6de279f3ad75c10c02c40a12f21d64a437d9c3d6f1af2dcbd7a76/fastar-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f4ce44e3b56c47cf38244b98d29f269b259740a580c47a2552efa5b96a5458fb", size = 486436, upload-time = "2026-04-13T17:11:40.089Z" }, - { url = "https://files.pythonhosted.org/packages/6b/fd/5390ec4f49100f3ecb9968a392f9e6d039f1e3fe0ecd28443716ff01e589/fastar-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:76c1359314355eafbc6989f20fb1ad565a3d10200117923b9da765a17e2f6f11", size = 461049, upload-time = "2026-04-13T17:11:25.918Z" }, - { url = "https://files.pythonhosted.org/packages/cc/5c/9bbeffbf1905391446dd98aa520422ce7affde5c9a7c22d757cc5d7c1397/fastar-0.11.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1266d6a004f427b0d61bd6c7b544d84cc964691b2232c2f4d635a1b75f2f6d5e", size = 711644, upload-time = "2026-04-13T17:10:07.663Z" }, - { url = "https://files.pythonhosted.org/packages/7e/af/ae5cf39d4fb82d0c592705f5ec6db1b065be5265c151b108f86126ee8773/fastar-0.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:298a827ec04ade43733f6ca960d0faec38706aa1494175869ea7ea17f5bad5d3", size = 634371, upload-time = "2026-04-13T17:09:52.083Z" }, - { url = "https://files.pythonhosted.org/packages/7e/36/8d4569e26473c72ccb02d1c5df3ed710073f1c06eca09c26d52ea79fd815/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8800e2387e463a0e5799416a1cbe72dd0fde7270a20e4bde684145e7878f6516", size = 870850, upload-time = "2026-04-13T17:09:21.439Z" }, - { url = "https://files.pythonhosted.org/packages/bf/46/724dc796e1756d3977970f820d30d59bb8cab8e3671b285f1d82ab513aec/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7496def0a2befd82d429cb004ef7ca831585cc887947bd6b9abb68a5ef852b0b", size = 764469, upload-time = "2026-04-13T17:08:05.638Z" }, - { url = "https://files.pythonhosted.org/packages/99/e3/74d6859e632e8fb9339a14f652fb9f800c2bd6aa53071e311c0be3fbab8b/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:878eaf15463eb572e3538af7ca3a8534e5e279cf8196db902d24e5725c4af86e", size = 761375, upload-time = "2026-04-13T17:08:20.669Z" }, - { url = "https://files.pythonhosted.org/packages/a3/e7/cc70e2be5ef8731a7525552b1c35c1448cf9eae6a62cb3a56f12c1bf27ea/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0324ed1d1ef0186e1bbd843b17807d6d837d0906899d4c99378b02c5d86bdd9c", size = 928189, upload-time = "2026-04-13T17:08:35.663Z" }, - { url = "https://files.pythonhosted.org/packages/3c/33/c9a969e78dca323547276a6fee5f4f9588f7cd5ab45acec3778c67399589/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bdf9bd863205590beaf8ef6e66f315310196632180dceaf674985d01a876cac3", size = 820864, upload-time = "2026-04-13T17:09:06.366Z" }, - { url = "https://files.pythonhosted.org/packages/84/bd/6b9434b541fe55c125b5f2e017a565596a2d215aa09207e4555e4585064f/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59af8dbb683b24b90fb5b506de080faeab0a17a908e6c2a5d93a97260ed75d7b", size = 824060, upload-time = "2026-04-13T17:09:37.377Z" }, - { url = "https://files.pythonhosted.org/packages/24/8d/871d5f8cf4c6f13987119fb0a9ae8be131e34f2756c2524e9974adf33824/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:9f3df73a3c4292cfe15696cdf59cdb6c309ab59d30b34c733be13c6e32d9a264", size = 889217, upload-time = "2026-04-13T17:08:50.884Z" }, - { url = "https://files.pythonhosted.org/packages/d0/26/cca0fd2704f3ed20165e5613ed911549aef3aaf3b0b5b02fee0e8e23e6cc/fastar-0.11.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:aa3762cbb16e41a76b61f4a6914937a71aab3a7b6c2d82ca233bc686ebaf756b", size = 975418, upload-time = "2026-04-13T17:10:24.307Z" }, - { url = "https://files.pythonhosted.org/packages/99/94/8bbb0b13f5b6cbe2492f0b7cbba5103e6163976a3331466d010e781fa189/fastar-0.11.0-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:a8c7bc8ac74cb359bb546b199288c83236372d094b402e557c197e85527495cd", size = 1038492, upload-time = "2026-04-13T17:10:41.939Z" }, - { url = "https://files.pythonhosted.org/packages/ed/d3/5b7df222a30eac2822ffd00f82fd4c2ce84fba4b369d1e1a03732fd177fc/fastar-0.11.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:587cbd060a2699c5f66281081395bb4657b2b1e0eef5c206b1aabf740019d670", size = 1080210, upload-time = "2026-04-13T17:10:58.462Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6d/56ef943ea524784598c035ccbd42e564e937da0438ae3f55f0e76cb95571/fastar-0.11.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a1c56957ac82408be37a3f63594bc83e0919e8760492a4475e542f9f1828778", size = 1034886, upload-time = "2026-04-13T17:11:15.617Z" }, -] - -[[package]] -name = "fido2" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/cc/4529123364d41f342145f2fd775307eaed817cd22810895dea10e15a4d06/fido2-1.2.0.tar.gz", hash = "sha256:e39f95920122d64283fda5e5581d95a206e704fa42846bfa4662f86aa0d3333b", size = 266369, upload-time = "2024-11-27T09:08:21.071Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/48/e9b99d66f27d3416a619324568739fd6603e093b2f79138d6f47ccf727b6/fido2-1.2.0-py3-none-any.whl", hash = "sha256:f7c8ee62e359aa980a45773f9493965bb29ede1b237a9218169dbfe60c80e130", size = 219418, upload-time = "2024-11-27T09:08:18.932Z" }, -] - -[[package]] -name = "firebase-admin" -version = "7.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cachecontrol" }, - { name = "google-api-core", extra = ["grpc"], marker = "platform_python_implementation != 'PyPy'" }, - { name = "google-cloud-firestore", marker = "platform_python_implementation != 'PyPy'" }, - { name = "google-cloud-storage" }, - { name = "httpx", extra = ["http2"] }, - { name = "pyjwt", extra = ["crypto"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/07/ab/2c551264041ddaff7dedd53893ab4642b19008bfb986567fb6ded6380831/firebase_admin-7.4.0.tar.gz", hash = "sha256:08d7550efdba32fbd306141ce82150f9ffb91c1550e07aa4ed5757af4895d1ff", size = 201086, upload-time = "2026-04-09T20:53:08.065Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/40/87f412ce2df5e57feb456b3b9b3427b2af3a35e1eaa14addfb283f5247fb/firebase_admin-7.4.0-py3-none-any.whl", hash = "sha256:416967d1aacd30cfece2a5b0c606a52ba7614eb3093f054399be3868489cd7a8", size = 140987, upload-time = "2026-04-09T20:53:06.568Z" }, -] - -[[package]] -name = "google-api-core" -version = "2.30.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-auth" }, - { name = "googleapis-common-protos" }, - { name = "proto-plus" }, - { name = "protobuf" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/16/ce/502a57fb0ec752026d24df1280b162294b22a0afb98a326084f9a979138b/google_api_core-2.30.3.tar.gz", hash = "sha256:e601a37f148585319b26db36e219df68c5d07b6382cff2d580e83404e44d641b", size = 177001, upload-time = "2026-04-10T00:41:28.035Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/03/15/e56f351cf6ef1cfea58e6ac226a7318ed1deb2218c4b3cc9bd9e4b786c5a/google_api_core-2.30.3-py3-none-any.whl", hash = "sha256:a85761ba72c444dad5d611c2220633480b2b6be2521eca69cca2dbb3ffd6bfe8", size = 173274, upload-time = "2026-04-09T22:57:16.198Z" }, -] - -[package.optional-dependencies] -grpc = [ - { name = "grpcio" }, - { name = "grpcio-status" }, -] - -[[package]] -name = "google-auth" -version = "2.50.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, - { name = "pyasn1-modules" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5f/18/238d7021d151bdab868f23433817b027dd759135202f4dfce0670d1230ca/google_auth-2.50.0.tar.gz", hash = "sha256:f35eafb191195328e8ce10a7883970877e7aeb49c2bfaa54aa0e394316d353d0", size = 336523, upload-time = "2026-04-30T21:19:29.659Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/37/cf/4880c2137c14280b2f59975cdf12cc442bc0ae1f9ea473a26eaa0c146786/google_auth-2.50.0-py3-none-any.whl", hash = "sha256:04382175e28b94f49694977f0a792688b59a668def1499e9d8de996dc9ce5b15", size = 246495, upload-time = "2026-04-30T21:19:27.664Z" }, -] - -[[package]] -name = "google-cloud-core" -version = "2.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-api-core" }, - { name = "google-auth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dc/24/6ca08b0a03c7b0c620427503ab00353a4ae806b848b93bcea18b6b76fde6/google_cloud_core-2.5.1.tar.gz", hash = "sha256:3dc94bdec9d05a31d9f355045ed0f369fbc0d8c665076c734f065d729800f811", size = 36078, upload-time = "2026-03-30T22:50:08.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/d9/5bb050cb32826466aa9b25f79e2ca2879fe66cb76782d4ed798dd7506151/google_cloud_core-2.5.1-py3-none-any.whl", hash = "sha256:ea62cdf502c20e3e14be8a32c05ed02113d7bef454e40ff3fab6fe1ec9f1f4e7", size = 29452, upload-time = "2026-03-30T22:48:31.567Z" }, -] - -[[package]] -name = "google-cloud-firestore" -version = "2.27.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-api-core", extra = ["grpc"] }, - { name = "google-auth" }, - { name = "google-cloud-core" }, - { name = "grpcio" }, - { name = "proto-plus" }, - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4c/83/bdfd4387fadc5f44de1d0f97b456c648b6f87ec0b1818a9f7f477e6e6eab/google_cloud_firestore-2.27.0.tar.gz", hash = "sha256:5633cb164ef56ca6c73a807822191a56a98f6f10e76978c4f2eb197ae03383d2", size = 649244, upload-time = "2026-04-13T22:55:43.931Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/db/fc72c4279887cc95011f16e70200d021030f0261e6391fa52e3adfaaea25/google_cloud_firestore-2.27.0-py3-none-any.whl", hash = "sha256:cc2ea78bc2d4dcc928016d56802deacfda3c9bbda0a7d691ee73b41a2f1a80d7", size = 429806, upload-time = "2026-04-13T22:55:41.726Z" }, -] - -[[package]] -name = "google-cloud-storage" -version = "3.10.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-api-core" }, - { name = "google-auth" }, - { name = "google-cloud-core" }, - { name = "google-crc32c" }, - { name = "google-resumable-media" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4c/47/205eb8e9a1739b5345843e5a425775cbdc472cc38e7eda082ba5b8d02450/google_cloud_storage-3.10.1.tar.gz", hash = "sha256:97db9aa4460727982040edd2bd13ff3d5e2260b5331ad22895802da1fc2a5286", size = 17309950, upload-time = "2026-03-23T09:35:23.409Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/ff/ca9ab2417fa913d75aae38bf40bf856bb2749a604b2e0f701b37cfcd23cc/google_cloud_storage-3.10.1-py3-none-any.whl", hash = "sha256:a72f656759b7b99bda700f901adcb3425a828d4a29f911bc26b3ea79c5b1217f", size = 324453, upload-time = "2026-03-23T09:35:21.368Z" }, -] - -[[package]] -name = "google-crc32c" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/ac/6f7bc93886a823ab545948c2dd48143027b2355ad1944c7cf852b338dc91/google_crc32c-1.8.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0470b8c3d73b5f4e3300165498e4cf25221c7eb37f1159e221d1825b6df8a7ff", size = 31296, upload-time = "2025-12-16T00:19:07.261Z" }, - { url = "https://files.pythonhosted.org/packages/f7/97/a5accde175dee985311d949cfcb1249dcbb290f5ec83c994ea733311948f/google_crc32c-1.8.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:119fcd90c57c89f30040b47c211acee231b25a45d225e3225294386f5d258288", size = 30870, upload-time = "2025-12-16T00:29:17.669Z" }, - { url = "https://files.pythonhosted.org/packages/3d/63/bec827e70b7a0d4094e7476f863c0dbd6b5f0f1f91d9c9b32b76dcdfeb4e/google_crc32c-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f35aaffc8ccd81ba3162443fabb920e65b1f20ab1952a31b13173a67811467d", size = 33214, upload-time = "2025-12-16T00:40:19.618Z" }, - { url = "https://files.pythonhosted.org/packages/63/bc/11b70614df04c289128d782efc084b9035ef8466b3d0a8757c1b6f5cf7ac/google_crc32c-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:864abafe7d6e2c4c66395c1eb0fe12dc891879769b52a3d56499612ca93b6092", size = 33589, upload-time = "2025-12-16T00:40:20.7Z" }, - { url = "https://files.pythonhosted.org/packages/3e/00/a08a4bc24f1261cc5b0f47312d8aebfbe4b53c2e6307f1b595605eed246b/google_crc32c-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:db3fe8eaf0612fc8b20fa21a5f25bd785bc3cd5be69f8f3412b0ac2ffd49e733", size = 34437, upload-time = "2025-12-16T00:35:19.437Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ef/21ccfaab3d5078d41efe8612e0ed0bfc9ce22475de074162a91a25f7980d/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:014a7e68d623e9a4222d663931febc3033c5c7c9730785727de2a81f87d5bab8", size = 31298, upload-time = "2025-12-16T00:20:32.241Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b8/f8413d3f4b676136e965e764ceedec904fe38ae8de0cdc52a12d8eb1096e/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:86cfc00fe45a0ac7359e5214a1704e51a99e757d0272554874f419f79838c5f7", size = 30872, upload-time = "2025-12-16T00:33:58.785Z" }, - { url = "https://files.pythonhosted.org/packages/f6/fd/33aa4ec62b290477181c55bb1c9302c9698c58c0ce9a6ab4874abc8b0d60/google_crc32c-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19b40d637a54cb71e0829179f6cb41835f0fbd9e8eb60552152a8b52c36cbe15", size = 33243, upload-time = "2025-12-16T00:40:21.46Z" }, - { url = "https://files.pythonhosted.org/packages/71/03/4820b3bd99c9653d1a5210cb32f9ba4da9681619b4d35b6a052432df4773/google_crc32c-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:17446feb05abddc187e5441a45971b8394ea4c1b6efd88ab0af393fd9e0a156a", size = 33608, upload-time = "2025-12-16T00:40:22.204Z" }, - { url = "https://files.pythonhosted.org/packages/7c/43/acf61476a11437bf9733fb2f70599b1ced11ec7ed9ea760fdd9a77d0c619/google_crc32c-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:71734788a88f551fbd6a97be9668a0020698e07b2bf5b3aa26a36c10cdfb27b2", size = 34439, upload-time = "2025-12-16T00:35:20.458Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, - { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, - { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, - { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, - { url = "https://files.pythonhosted.org/packages/d1/db/000f15b41724589b0e7bc24bc7a8967898d8d3bc8caf64c513d91ef1f6c0/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3ebb04528e83b2634857f43f9bb8ef5b2bbe7f10f140daeb01b58f972d04736b", size = 31297, upload-time = "2025-12-16T00:23:20.709Z" }, - { url = "https://files.pythonhosted.org/packages/d7/0d/8ebed0c39c53a7e838e2a486da8abb0e52de135f1b376ae2f0b160eb4c1a/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:450dc98429d3e33ed2926fc99ee81001928d63460f8538f21a5d6060912a8e27", size = 30867, upload-time = "2025-12-16T00:43:14.628Z" }, - { url = "https://files.pythonhosted.org/packages/ce/42/b468aec74a0354b34c8cbf748db20d6e350a68a2b0912e128cabee49806c/google_crc32c-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b9776774b24ba76831609ffbabce8cdf6fa2bd5e9df37b594221c7e333a81fa", size = 33344, upload-time = "2025-12-16T00:40:24.742Z" }, - { url = "https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89c17d53d75562edfff86679244830599ee0a48efc216200691de8b02ab6b2b8", size = 33694, upload-time = "2025-12-16T00:40:25.505Z" }, - { url = "https://files.pythonhosted.org/packages/92/b1/d3cbd4d988afb3d8e4db94ca953df429ed6db7282ed0e700d25e6c7bfc8d/google_crc32c-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:57a50a9035b75643996fbf224d6661e386c7162d1dfdab9bc4ca790947d1007f", size = 34435, upload-time = "2025-12-16T00:35:22.107Z" }, - { url = "https://files.pythonhosted.org/packages/21/88/8ecf3c2b864a490b9e7010c84fd203ec8cf3b280651106a3a74dd1b0ca72/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:e6584b12cb06796d285d09e33f63309a09368b9d806a551d8036a4207ea43697", size = 31301, upload-time = "2025-12-16T00:24:48.527Z" }, - { url = "https://files.pythonhosted.org/packages/36/c6/f7ff6c11f5ca215d9f43d3629163727a272eabc356e5c9b2853df2bfe965/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:f4b51844ef67d6cf2e9425983274da75f18b1597bb2c998e1c0a0e8d46f8f651", size = 30868, upload-time = "2025-12-16T00:48:12.163Z" }, - { url = "https://files.pythonhosted.org/packages/56/15/c25671c7aad70f8179d858c55a6ae8404902abe0cdcf32a29d581792b491/google_crc32c-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b0d1a7afc6e8e4635564ba8aa5c0548e3173e41b6384d7711a9123165f582de2", size = 33381, upload-time = "2025-12-16T00:40:26.268Z" }, - { url = "https://files.pythonhosted.org/packages/42/fa/f50f51260d7b0ef5d4898af122d8a7ec5a84e2984f676f746445f783705f/google_crc32c-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3f68782f3cbd1bce027e48768293072813469af6a61a86f6bb4977a4380f21", size = 33734, upload-time = "2025-12-16T00:40:27.028Z" }, - { url = "https://files.pythonhosted.org/packages/08/a5/7b059810934a09fb3ccb657e0843813c1fee1183d3bc2c8041800374aa2c/google_crc32c-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:d511b3153e7011a27ab6ee6bb3a5404a55b994dc1a7322c0b87b29606d9790e2", size = 34878, upload-time = "2025-12-16T00:35:23.142Z" }, - { url = "https://files.pythonhosted.org/packages/52/c5/c171e4d8c44fec1422d801a6d2e5d7ddabd733eeda505c79730ee9607f07/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:87fa445064e7db928226b2e6f0d5304ab4cd0339e664a4e9a25029f384d9bb93", size = 28615, upload-time = "2025-12-16T00:40:29.298Z" }, - { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800, upload-time = "2025-12-16T00:40:30.322Z" }, -] - -[[package]] -name = "google-resumable-media" -version = "2.8.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-crc32c" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3f/d1/b1ea14b93b6b78f57fc580125de44e9f593ab88dd2460f1a8a8d18f74754/google_resumable_media-2.8.2.tar.gz", hash = "sha256:f3354a182ebd193ae3f42e3ef95e6c9b10f128320de23ac7637236713b1acd70", size = 2164510, upload-time = "2026-03-30T23:34:25.369Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f8/50bfaf4658431ff9de45c5c3935af7ab01157a4903c603cd0eee6e78e087/google_resumable_media-2.8.2-py3-none-any.whl", hash = "sha256:82b6d8ccd11765268cdd2a2123f417ec806b8eef3000a9a38dfe3033da5fb220", size = 81511, upload-time = "2026-03-30T23:34:09.671Z" }, -] - -[[package]] -name = "googleapis-common-protos" -version = "1.74.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/20/18/a746c8344152d368a5aac738d4c857012f2c5d1fd2eac7e17b647a7861bd/googleapis_common_protos-1.74.0.tar.gz", hash = "sha256:57971e4eeeba6aad1163c1f0fc88543f965bb49129b8bb55b2b7b26ecab084f1", size = 151254, upload-time = "2026-04-02T21:23:26.679Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/b0/be5d3329badb9230b765de6eea66b73abd5944bdeb5afb3562ddcd80ae84/googleapis_common_protos-1.74.0-py3-none-any.whl", hash = "sha256:702216f78610bb510e3f12ac3cafd281b7ac45cc5d86e90ad87e4d301a3426b5", size = 300743, upload-time = "2026-04-02T21:22:49.108Z" }, -] - -[[package]] -name = "grpcio" -version = "1.80.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/cd/bb7b7e54084a344c03d68144450da7ddd5564e51a298ae1662de65f48e2d/grpcio-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:886457a7768e408cdce226ad1ca67d2958917d306523a0e21e1a2fdaa75c9c9c", size = 6050363, upload-time = "2026-03-30T08:46:20.894Z" }, - { url = "https://files.pythonhosted.org/packages/16/02/1417f5c3460dea65f7a2e3c14e8b31e77f7ffb730e9bfadd89eda7a9f477/grpcio-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7b641fc3f1dc647bfd80bd713addc68f6d145956f64677e56d9ebafc0bd72388", size = 12026037, upload-time = "2026-03-30T08:46:25.144Z" }, - { url = "https://files.pythonhosted.org/packages/43/98/c910254eedf2cae368d78336a2de0678e66a7317d27c02522392f949b5c6/grpcio-1.80.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:33eb763f18f006dc7fee1e69831d38d23f5eccd15b2e0f92a13ee1d9242e5e02", size = 6602306, upload-time = "2026-03-30T08:46:27.593Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f8/88ca4e78c077b2b2113d95da1e1ab43efd43d723c9a0397d26529c2c1a56/grpcio-1.80.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:52d143637e3872633fc7dd7c3c6a1c84e396b359f3a72e215f8bf69fd82084fc", size = 7301535, upload-time = "2026-03-30T08:46:29.556Z" }, - { url = "https://files.pythonhosted.org/packages/f9/96/f28660fe2fe0f153288bf4a04e4910b7309d442395135c88ed4f5b3b8b40/grpcio-1.80.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c51bf8ac4575af2e0678bccfb07e47321fc7acb5049b4482832c5c195e04e13a", size = 6808669, upload-time = "2026-03-30T08:46:31.984Z" }, - { url = "https://files.pythonhosted.org/packages/47/eb/3f68a5e955779c00aeef23850e019c1c1d0e032d90633ba49c01ad5a96e0/grpcio-1.80.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:50a9871536d71c4fba24ee856abc03a87764570f0c457dd8db0b4018f379fed9", size = 7409489, upload-time = "2026-03-30T08:46:34.684Z" }, - { url = "https://files.pythonhosted.org/packages/5b/a7/d2f681a4bfb881be40659a309771f3bdfbfdb1190619442816c3f0ffc079/grpcio-1.80.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a72d84ad0514db063e21887fbacd1fd7acb4d494a564cae22227cd45c7fbf199", size = 8423167, upload-time = "2026-03-30T08:46:36.833Z" }, - { url = "https://files.pythonhosted.org/packages/97/8a/29b4589c204959aa35ce5708400a05bba72181807c45c47b3ec000c39333/grpcio-1.80.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f7691a6788ad9196872f95716df5bc643ebba13c97140b7a5ee5c8e75d1dea81", size = 7846761, upload-time = "2026-03-30T08:46:40.091Z" }, - { url = "https://files.pythonhosted.org/packages/6b/d2/ed143e097230ee121ac5848f6ff14372dba91289b10b536d54fb1b7cbae7/grpcio-1.80.0-cp310-cp310-win32.whl", hash = "sha256:46c2390b59d67f84e882694d489f5b45707c657832d7934859ceb8c33f467069", size = 4156534, upload-time = "2026-03-30T08:46:42.026Z" }, - { url = "https://files.pythonhosted.org/packages/d5/c9/df8279bb49b29409995e95efa85b72973d62f8aeff89abee58c91f393710/grpcio-1.80.0-cp310-cp310-win_amd64.whl", hash = "sha256:dc053420fc75749c961e2a4c906398d7c15725d36ccc04ae6d16093167223b58", size = 4889869, upload-time = "2026-03-30T08:46:44.219Z" }, - { url = "https://files.pythonhosted.org/packages/5d/db/1d56e5f5823257b291962d6c0ce106146c6447f405b60b234c4f222a7cde/grpcio-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:dfab85db094068ff42e2a3563f60ab3dddcc9d6488a35abf0132daec13209c8a", size = 6055009, upload-time = "2026-03-30T08:46:46.265Z" }, - { url = "https://files.pythonhosted.org/packages/6e/18/c83f3cad64c5ca63bca7e91e5e46b0d026afc5af9d0a9972472ceba294b3/grpcio-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5c07e82e822e1161354e32da2662f741a4944ea955f9f580ec8fb409dd6f6060", size = 12035295, upload-time = "2026-03-30T08:46:49.099Z" }, - { url = "https://files.pythonhosted.org/packages/0f/8e/e14966b435be2dda99fbe89db9525ea436edc79780431a1c2875a3582644/grpcio-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba0915d51fd4ced2db5ff719f84e270afe0e2d4c45a7bdb1e8d036e4502928c2", size = 6610297, upload-time = "2026-03-30T08:46:52.123Z" }, - { url = "https://files.pythonhosted.org/packages/cc/26/d5eb38f42ce0e3fdc8174ea4d52036ef8d58cc4426cb800f2610f625dd75/grpcio-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3cb8130ba457d2aa09fa6b7c3ed6b6e4e6a2685fce63cb803d479576c4d80e21", size = 7300208, upload-time = "2026-03-30T08:46:54.859Z" }, - { url = "https://files.pythonhosted.org/packages/25/51/bd267c989f85a17a5b3eea65a6feb4ff672af41ca614e5a0279cc0ea381c/grpcio-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09e5e478b3d14afd23f12e49e8b44c8684ac3c5f08561c43a5b9691c54d136ab", size = 6813442, upload-time = "2026-03-30T08:46:57.056Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d9/d80eef735b19e9169e30164bbf889b46f9df9127598a83d174eb13a48b26/grpcio-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00168469238b022500e486c1c33916acf2f2a9b2c022202cf8a1885d2e3073c1", size = 7414743, upload-time = "2026-03-30T08:46:59.682Z" }, - { url = "https://files.pythonhosted.org/packages/de/f2/567f5bd5054398ed6b0509b9a30900376dcf2786bd936812098808b49d8d/grpcio-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8502122a3cc1714038e39a0b071acb1207ca7844208d5ea0d091317555ee7106", size = 8426046, upload-time = "2026-03-30T08:47:02.474Z" }, - { url = "https://files.pythonhosted.org/packages/62/29/73ef0141b4732ff5eacd68430ff2512a65c004696997f70476a83e548e7e/grpcio-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce1794f4ea6cc3ca29463f42d665c32ba1b964b48958a66497917fe9069f26e6", size = 7851641, upload-time = "2026-03-30T08:47:05.462Z" }, - { url = "https://files.pythonhosted.org/packages/46/69/abbfa360eb229a8623bab5f5a4f8105e445bd38ce81a89514ba55d281ad0/grpcio-1.80.0-cp311-cp311-win32.whl", hash = "sha256:51b4a7189b0bef2aa30adce3c78f09c83526cf3dddb24c6a96555e3b97340440", size = 4154368, upload-time = "2026-03-30T08:47:08.027Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d4/ae92206d01183b08613e846076115f5ac5991bae358d2a749fa864da5699/grpcio-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:02e64bb0bb2da14d947a49e6f120a75e947250aebe65f9629b62bb1f5c14e6e9", size = 4894235, upload-time = "2026-03-30T08:47:10.839Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e8/a2b749265eb3415abc94f2e619bbd9e9707bebdda787e61c593004ec927a/grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0", size = 6015616, upload-time = "2026-03-30T08:47:13.428Z" }, - { url = "https://files.pythonhosted.org/packages/3e/97/b1282161a15d699d1e90c360df18d19165a045ce1c343c7f313f5e8a0b77/grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2", size = 12014204, upload-time = "2026-03-30T08:47:15.873Z" }, - { url = "https://files.pythonhosted.org/packages/6e/5e/d319c6e997b50c155ac5a8cb12f5173d5b42677510e886d250d50264949d/grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de", size = 6563866, upload-time = "2026-03-30T08:47:18.588Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f6/fdd975a2cb4d78eb67769a7b3b3830970bfa2e919f1decf724ae4445f42c/grpcio-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0cb517eb1d0d0aaf1d87af7cc5b801d686557c1d88b2619f5e31fab3c2315921", size = 7273060, upload-time = "2026-03-30T08:47:21.113Z" }, - { url = "https://files.pythonhosted.org/packages/db/f0/a3deb5feba60d9538a962913e37bd2e69a195f1c3376a3dd44fe0427e996/grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411", size = 6782121, upload-time = "2026-03-30T08:47:23.827Z" }, - { url = "https://files.pythonhosted.org/packages/ca/84/36c6dcfddc093e108141f757c407902a05085e0c328007cb090d56646cdf/grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd", size = 7383811, upload-time = "2026-03-30T08:47:26.517Z" }, - { url = "https://files.pythonhosted.org/packages/7c/ef/f3a77e3dc5b471a0ec86c564c98d6adfa3510d38f8ee99010410858d591e/grpcio-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:256507e2f524092f1473071a05e65a5b10d84b82e3ff24c5b571513cfaa61e2f", size = 8393860, upload-time = "2026-03-30T08:47:29.439Z" }, - { url = "https://files.pythonhosted.org/packages/9b/8d/9d4d27ed7f33d109c50d6b5ce578a9914aa68edab75d65869a17e630a8d1/grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f", size = 7830132, upload-time = "2026-03-30T08:47:33.254Z" }, - { url = "https://files.pythonhosted.org/packages/14/e4/9990b41c6d7a44e1e9dee8ac11d7a9802ba1378b40d77468a7761d1ad288/grpcio-1.80.0-cp312-cp312-win32.whl", hash = "sha256:c71309cfce2f22be26aa4a847357c502db6c621f1a49825ae98aa0907595b193", size = 4140904, upload-time = "2026-03-30T08:47:35.319Z" }, - { url = "https://files.pythonhosted.org/packages/2f/2c/296f6138caca1f4b92a31ace4ae1b87dab692fc16a7a3417af3bb3c805bf/grpcio-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe648599c0e37594c4809d81a9e77bd138cc82eb8baa71b6a86af65426723ff", size = 4880944, upload-time = "2026-03-30T08:47:37.831Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243, upload-time = "2026-03-30T08:47:40.075Z" }, - { url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840, upload-time = "2026-03-30T08:47:43.11Z" }, - { url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644, upload-time = "2026-03-30T08:47:46.806Z" }, - { url = "https://files.pythonhosted.org/packages/ff/40/96e07ecb604a6a67ae6ab151e3e35b132875d98bc68ec65f3e5ab3e781d7/grpcio-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:68e5851ac4b9afe07e7f84483803ad167852570d65326b34d54ca560bfa53fb6", size = 7277830, upload-time = "2026-03-30T08:47:49.643Z" }, - { url = "https://files.pythonhosted.org/packages/9b/e2/da1506ecea1f34a5e365964644b35edef53803052b763ca214ba3870c856/grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140", size = 6783216, upload-time = "2026-03-30T08:47:52.817Z" }, - { url = "https://files.pythonhosted.org/packages/44/83/3b20ff58d0c3b7f6caaa3af9a4174d4023701df40a3f39f7f1c8e7c48f9d/grpcio-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2bea16af2750fd0a899bf1abd9022244418b55d1f37da2202249ba4ba673838d", size = 7385866, upload-time = "2026-03-30T08:47:55.687Z" }, - { url = "https://files.pythonhosted.org/packages/47/45/55c507599c5520416de5eefecc927d6a0d7af55e91cfffb2e410607e5744/grpcio-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba0db34f7e1d803a878284cd70e4c63cb6ae2510ba51937bf8f45ba997cefcf7", size = 8391602, upload-time = "2026-03-30T08:47:58.303Z" }, - { url = "https://files.pythonhosted.org/packages/10/bb/dd06f4c24c01db9cf11341b547d0a016b2c90ed7dbbb086a5710df7dd1d7/grpcio-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8eb613f02d34721f1acf3626dfdb3545bd3c8505b0e52bf8b5710a28d02e8aa7", size = 7826752, upload-time = "2026-03-30T08:48:01.311Z" }, - { url = "https://files.pythonhosted.org/packages/f9/1e/9d67992ba23371fd63d4527096eb8c6b76d74d52b500df992a3343fd7251/grpcio-1.80.0-cp313-cp313-win32.whl", hash = "sha256:93b6f823810720912fd131f561f91f5fed0fda372b6b7028a2681b8194d5d294", size = 4142310, upload-time = "2026-03-30T08:48:04.594Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e6/283326a27da9e2c3038bc93eeea36fb118ce0b2d03922a9cda6688f53c5b/grpcio-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:e172cf795a3ba5246d3529e4d34c53db70e888fa582a8ffebd2e6e48bc0cba50", size = 4882833, upload-time = "2026-03-30T08:48:07.363Z" }, - { url = "https://files.pythonhosted.org/packages/c5/6d/e65307ce20f5a09244ba9e9d8476e99fb039de7154f37fb85f26978b59c3/grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e", size = 6017376, upload-time = "2026-03-30T08:48:10.005Z" }, - { url = "https://files.pythonhosted.org/packages/69/10/9cef5d9650c72625a699c549940f0abb3c4bfdb5ed45a5ce431f92f31806/grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f", size = 12018133, upload-time = "2026-03-30T08:48:12.927Z" }, - { url = "https://files.pythonhosted.org/packages/04/82/983aabaad82ba26113caceeb9091706a0696b25da004fe3defb5b346e15b/grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9", size = 6574748, upload-time = "2026-03-30T08:48:16.386Z" }, - { url = "https://files.pythonhosted.org/packages/07/d7/031666ef155aa0bf399ed7e19439656c38bbd143779ae0861b038ce82abd/grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14", size = 7277711, upload-time = "2026-03-30T08:48:19.627Z" }, - { url = "https://files.pythonhosted.org/packages/e8/43/f437a78f7f4f1d311804189e8f11fb311a01049b2e08557c1068d470cb2e/grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05", size = 6785372, upload-time = "2026-03-30T08:48:22.373Z" }, - { url = "https://files.pythonhosted.org/packages/93/3d/f6558e9c6296cb4227faa5c43c54a34c68d32654b829f53288313d16a86e/grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1", size = 7395268, upload-time = "2026-03-30T08:48:25.638Z" }, - { url = "https://files.pythonhosted.org/packages/06/21/0fdd77e84720b08843c371a2efa6f2e19dbebf56adc72df73d891f5506f0/grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f", size = 8392000, upload-time = "2026-03-30T08:48:28.974Z" }, - { url = "https://files.pythonhosted.org/packages/f5/68/67f4947ed55d2e69f2cc199ab9fd85e0a0034d813bbeef84df6d2ba4d4b7/grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e", size = 7828477, upload-time = "2026-03-30T08:48:32.054Z" }, - { url = "https://files.pythonhosted.org/packages/44/b6/8d4096691b2e385e8271911a0de4f35f0a6c7d05aff7098e296c3de86939/grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae", size = 4218563, upload-time = "2026-03-30T08:48:34.538Z" }, - { url = "https://files.pythonhosted.org/packages/e5/8c/bbe6baf2557262834f2070cf668515fa308b2d38a4bbf771f8f7872a7036/grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f", size = 5019457, upload-time = "2026-03-30T08:48:37.308Z" }, -] - -[[package]] -name = "grpcio-status" -version = "1.80.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "googleapis-common-protos" }, - { name = "grpcio" }, - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/ed/105f619bdd00cb47a49aa2feea6232ea2bbb04199d52a22cc6a7d603b5cb/grpcio_status-1.80.0.tar.gz", hash = "sha256:df73802a4c89a3ea88aa2aff971e886fccce162bc2e6511408b3d67a144381cd", size = 13901, upload-time = "2026-03-30T08:54:34.784Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/80/58cd2dfc19a07d022abe44bde7c365627f6c7cb6f692ada6c65ca437d09a/grpcio_status-1.80.0-py3-none-any.whl", hash = "sha256:4b56990363af50dbf2c2ebb80f1967185c07d87aa25aa2bea45ddb75fc181dbe", size = 14638, upload-time = "2026-03-30T08:54:01.569Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "h2" -version = "4.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "hpack" }, - { name = "hyperframe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, -] - -[[package]] -name = "hpack" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, -] - -[[package]] -name = "httptools" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/e5/c07e0bcf4ec8db8164e9f6738c048b2e66aabf30e7506f440c4cc6953f60/httptools-0.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:11d01b0ff1fe02c4c32d60af61a4d613b74fad069e47e06e9067758c01e9ac78", size = 204531, upload-time = "2025-10-10T03:54:20.887Z" }, - { url = "https://files.pythonhosted.org/packages/7e/4f/35e3a63f863a659f92ffd92bef131f3e81cf849af26e6435b49bd9f6f751/httptools-0.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d86c1e5afdc479a6fdabf570be0d3eb791df0ae727e8dbc0259ed1249998d4", size = 109408, upload-time = "2025-10-10T03:54:22.455Z" }, - { url = "https://files.pythonhosted.org/packages/f5/71/b0a9193641d9e2471ac541d3b1b869538a5fb6419d52fd2669fa9c79e4b8/httptools-0.7.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8c751014e13d88d2be5f5f14fc8b89612fcfa92a9cc480f2bc1598357a23a05", size = 440889, upload-time = "2025-10-10T03:54:23.753Z" }, - { url = "https://files.pythonhosted.org/packages/eb/d9/2e34811397b76718750fea44658cb0205b84566e895192115252e008b152/httptools-0.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:654968cb6b6c77e37b832a9be3d3ecabb243bbe7a0b8f65fbc5b6b04c8fcabed", size = 440460, upload-time = "2025-10-10T03:54:25.313Z" }, - { url = "https://files.pythonhosted.org/packages/01/3f/a04626ebeacc489866bb4d82362c0657b2262bef381d68310134be7f40bb/httptools-0.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b580968316348b474b020edf3988eecd5d6eec4634ee6561e72ae3a2a0e00a8a", size = 425267, upload-time = "2025-10-10T03:54:26.81Z" }, - { url = "https://files.pythonhosted.org/packages/a5/99/adcd4f66614db627b587627c8ad6f4c55f18881549bab10ecf180562e7b9/httptools-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d496e2f5245319da9d764296e86c5bb6fcf0cf7a8806d3d000717a889c8c0b7b", size = 424429, upload-time = "2025-10-10T03:54:28.174Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/ec8fc904a8fd30ba022dfa85f3bbc64c3c7cd75b669e24242c0658e22f3c/httptools-0.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cbf8317bfccf0fed3b5680c559d3459cccf1abe9039bfa159e62e391c7270568", size = 86173, upload-time = "2025-10-10T03:54:29.5Z" }, - { url = "https://files.pythonhosted.org/packages/9c/08/17e07e8d89ab8f343c134616d72eebfe03798835058e2ab579dcc8353c06/httptools-0.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:474d3b7ab469fefcca3697a10d11a32ee2b9573250206ba1e50d5980910da657", size = 206521, upload-time = "2025-10-10T03:54:31.002Z" }, - { url = "https://files.pythonhosted.org/packages/aa/06/c9c1b41ff52f16aee526fd10fbda99fa4787938aa776858ddc4a1ea825ec/httptools-0.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3c3b7366bb6c7b96bd72d0dbe7f7d5eead261361f013be5f6d9590465ea1c70", size = 110375, upload-time = "2025-10-10T03:54:31.941Z" }, - { url = "https://files.pythonhosted.org/packages/cc/cc/10935db22fda0ee34c76f047590ca0a8bd9de531406a3ccb10a90e12ea21/httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:379b479408b8747f47f3b253326183d7c009a3936518cdb70db58cffd369d9df", size = 456621, upload-time = "2025-10-10T03:54:33.176Z" }, - { url = "https://files.pythonhosted.org/packages/0e/84/875382b10d271b0c11aa5d414b44f92f8dd53e9b658aec338a79164fa548/httptools-0.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cad6b591a682dcc6cf1397c3900527f9affef1e55a06c4547264796bbd17cf5e", size = 454954, upload-time = "2025-10-10T03:54:34.226Z" }, - { url = "https://files.pythonhosted.org/packages/30/e1/44f89b280f7e46c0b1b2ccee5737d46b3bb13136383958f20b580a821ca0/httptools-0.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eb844698d11433d2139bbeeb56499102143beb582bd6c194e3ba69c22f25c274", size = 440175, upload-time = "2025-10-10T03:54:35.942Z" }, - { url = "https://files.pythonhosted.org/packages/6f/7e/b9287763159e700e335028bc1824359dc736fa9b829dacedace91a39b37e/httptools-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f65744d7a8bdb4bda5e1fa23e4ba16832860606fcc09d674d56e425e991539ec", size = 440310, upload-time = "2025-10-10T03:54:37.1Z" }, - { url = "https://files.pythonhosted.org/packages/b3/07/5b614f592868e07f5c94b1f301b5e14a21df4e8076215a3bccb830a687d8/httptools-0.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:135fbe974b3718eada677229312e97f3b31f8a9c8ffa3ae6f565bf808d5b6bcb", size = 86875, upload-time = "2025-10-10T03:54:38.421Z" }, - { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" }, - { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" }, - { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" }, - { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" }, - { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" }, - { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694, upload-time = "2025-10-10T03:54:45.923Z" }, - { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" }, - { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" }, - { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" }, - { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" }, - { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" }, - { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" }, - { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743, upload-time = "2025-10-10T03:54:53.448Z" }, - { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, - { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, - { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, - { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, - { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, - { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, - { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - -[package.optional-dependencies] -http2 = [ - { name = "h2" }, -] - -[[package]] -name = "hyperframe" -version = "6.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, -] - -[[package]] -name = "idna" -version = "3.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, -] - -[[package]] -name = "jmespath" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, -] - -[[package]] -name = "limits" -version = "5.8.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "deprecated" }, - { name = "packaging" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/69/826a5d1f45426c68d8f6539f8d275c0e4fcaa57f0c017ec3100986558a41/limits-5.8.0.tar.gz", hash = "sha256:c9e0d74aed837e8f6f50d1fcebcf5fd8130957287206bc3799adaee5092655da", size = 226104, upload-time = "2026-02-05T07:17:35.859Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/98/cb5ca20618d205a09d5bec7591fbc4130369c7e6308d9a676a28ff3ab22c/limits-5.8.0-py3-none-any.whl", hash = "sha256:ae1b008a43eb43073c3c579398bd4eb4c795de60952532dc24720ab45e1ac6b8", size = 60954, upload-time = "2026-02-05T07:17:34.425Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, -] - -[[package]] -name = "markupsafe" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, - { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, - { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, - { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, - { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, - { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, - { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, - { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, - { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, - { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, - { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, - { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, - { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, - { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, - { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, - { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, -] - -[[package]] -name = "motor" -version = "3.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pymongo" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/93/ae/96b88362d6a84cb372f7977750ac2a8aed7b2053eed260615df08d5c84f4/motor-3.7.1.tar.gz", hash = "sha256:27b4d46625c87928f331a6ca9d7c51c2f518ba0e270939d395bc1ddc89d64526", size = 280997, upload-time = "2025-05-14T18:56:33.653Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/9a/35e053d4f442addf751ed20e0e922476508ee580786546d699b0567c4c67/motor-3.7.1-py3-none-any.whl", hash = "sha256:8a63b9049e38eeeb56b4fdd57c3312a6d1f25d01db717fe7d82222393c410298", size = 74996, upload-time = "2025-05-14T18:56:31.665Z" }, -] - -[[package]] -name = "msgpack" -version = "1.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/a2/3b68a9e769db68668b25c6108444a35f9bd163bb848c0650d516761a59c0/msgpack-1.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0051fffef5a37ca2cd16978ae4f0aef92f164df86823871b5162812bebecd8e2", size = 81318, upload-time = "2025-10-08T09:14:38.722Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e1/2b720cc341325c00be44e1ed59e7cfeae2678329fbf5aa68f5bda57fe728/msgpack-1.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a605409040f2da88676e9c9e5853b3449ba8011973616189ea5ee55ddbc5bc87", size = 83786, upload-time = "2025-10-08T09:14:40.082Z" }, - { url = "https://files.pythonhosted.org/packages/71/e5/c2241de64bfceac456b140737812a2ab310b10538a7b34a1d393b748e095/msgpack-1.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b696e83c9f1532b4af884045ba7f3aa741a63b2bc22617293a2c6a7c645f251", size = 398240, upload-time = "2025-10-08T09:14:41.151Z" }, - { url = "https://files.pythonhosted.org/packages/b7/09/2a06956383c0fdebaef5aa9246e2356776f12ea6f2a44bd1368abf0e46c4/msgpack-1.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:365c0bbe981a27d8932da71af63ef86acc59ed5c01ad929e09a0b88c6294e28a", size = 406070, upload-time = "2025-10-08T09:14:42.821Z" }, - { url = "https://files.pythonhosted.org/packages/0e/74/2957703f0e1ef20637d6aead4fbb314330c26f39aa046b348c7edcf6ca6b/msgpack-1.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:41d1a5d875680166d3ac5c38573896453bbbea7092936d2e107214daf43b1d4f", size = 393403, upload-time = "2025-10-08T09:14:44.38Z" }, - { url = "https://files.pythonhosted.org/packages/a5/09/3bfc12aa90f77b37322fc33e7a8a7c29ba7c8edeadfa27664451801b9860/msgpack-1.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:354e81bcdebaab427c3df4281187edc765d5d76bfb3a7c125af9da7a27e8458f", size = 398947, upload-time = "2025-10-08T09:14:45.56Z" }, - { url = "https://files.pythonhosted.org/packages/4b/4f/05fcebd3b4977cb3d840f7ef6b77c51f8582086de5e642f3fefee35c86fc/msgpack-1.1.2-cp310-cp310-win32.whl", hash = "sha256:e64c8d2f5e5d5fda7b842f55dec6133260ea8f53c4257d64494c534f306bf7a9", size = 64769, upload-time = "2025-10-08T09:14:47.334Z" }, - { url = "https://files.pythonhosted.org/packages/d0/3e/b4547e3a34210956382eed1c85935fff7e0f9b98be3106b3745d7dec9c5e/msgpack-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:db6192777d943bdaaafb6ba66d44bf65aa0e9c5616fa1d2da9bb08828c6b39aa", size = 71293, upload-time = "2025-10-08T09:14:48.665Z" }, - { url = "https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c", size = 82271, upload-time = "2025-10-08T09:14:49.967Z" }, - { url = "https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0", size = 84914, upload-time = "2025-10-08T09:14:50.958Z" }, - { url = "https://files.pythonhosted.org/packages/71/46/b817349db6886d79e57a966346cf0902a426375aadc1e8e7a86a75e22f19/msgpack-1.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61c8aa3bd513d87c72ed0b37b53dd5c5a0f58f2ff9f26e1555d3bd7948fb7296", size = 416962, upload-time = "2025-10-08T09:14:51.997Z" }, - { url = "https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef", size = 426183, upload-time = "2025-10-08T09:14:53.477Z" }, - { url = "https://files.pythonhosted.org/packages/25/98/6a19f030b3d2ea906696cedd1eb251708e50a5891d0978b012cb6107234c/msgpack-1.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7bc8813f88417599564fafa59fd6f95be417179f76b40325b500b3c98409757c", size = 411454, upload-time = "2025-10-08T09:14:54.648Z" }, - { url = "https://files.pythonhosted.org/packages/b7/cd/9098fcb6adb32187a70b7ecaabf6339da50553351558f37600e53a4a2a23/msgpack-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bafca952dc13907bdfdedfc6a5f579bf4f292bdd506fadb38389afa3ac5b208e", size = 422341, upload-time = "2025-10-08T09:14:56.328Z" }, - { url = "https://files.pythonhosted.org/packages/e6/ae/270cecbcf36c1dc85ec086b33a51a4d7d08fc4f404bdbc15b582255d05ff/msgpack-1.1.2-cp311-cp311-win32.whl", hash = "sha256:602b6740e95ffc55bfb078172d279de3773d7b7db1f703b2f1323566b878b90e", size = 64747, upload-time = "2025-10-08T09:14:57.882Z" }, - { url = "https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68", size = 71633, upload-time = "2025-10-08T09:14:59.177Z" }, - { url = "https://files.pythonhosted.org/packages/73/4d/7c4e2b3d9b1106cd0aa6cb56cc57c6267f59fa8bfab7d91df5adc802c847/msgpack-1.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:86f8136dfa5c116365a8a651a7d7484b65b13339731dd6faebb9a0242151c406", size = 64755, upload-time = "2025-10-08T09:15:00.48Z" }, - { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" }, - { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" }, - { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" }, - { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" }, - { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" }, - { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" }, - { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" }, - { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" }, - { url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212, upload-time = "2025-10-08T09:15:14.552Z" }, - { url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315, upload-time = "2025-10-08T09:15:15.543Z" }, - { url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721, upload-time = "2025-10-08T09:15:16.567Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657, upload-time = "2025-10-08T09:15:17.825Z" }, - { url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668, upload-time = "2025-10-08T09:15:19.003Z" }, - { url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040, upload-time = "2025-10-08T09:15:20.183Z" }, - { url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037, upload-time = "2025-10-08T09:15:21.416Z" }, - { url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631, upload-time = "2025-10-08T09:15:22.431Z" }, - { url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" }, - { url = "https://files.pythonhosted.org/packages/22/71/201105712d0a2ff07b7873ed3c220292fb2ea5120603c00c4b634bcdafb3/msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00", size = 81127, upload-time = "2025-10-08T09:15:24.408Z" }, - { url = "https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939", size = 84981, upload-time = "2025-10-08T09:15:25.812Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a9/3536e385167b88c2cc8f4424c49e28d49a6fc35206d4a8060f136e71f94c/msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e", size = 411885, upload-time = "2025-10-08T09:15:27.22Z" }, - { url = "https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931", size = 419658, upload-time = "2025-10-08T09:15:28.4Z" }, - { url = "https://files.pythonhosted.org/packages/3b/ef/2b92e286366500a09a67e03496ee8b8ba00562797a52f3c117aa2b29514b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014", size = 403290, upload-time = "2025-10-08T09:15:29.764Z" }, - { url = "https://files.pythonhosted.org/packages/78/90/e0ea7990abea5764e4655b8177aa7c63cdfa89945b6e7641055800f6c16b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2", size = 415234, upload-time = "2025-10-08T09:15:31.022Z" }, - { url = "https://files.pythonhosted.org/packages/72/4e/9390aed5db983a2310818cd7d3ec0aecad45e1f7007e0cda79c79507bb0d/msgpack-1.1.2-cp314-cp314-win32.whl", hash = "sha256:80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717", size = 66391, upload-time = "2025-10-08T09:15:32.265Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b", size = 73787, upload-time = "2025-10-08T09:15:33.219Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b0/9d9f667ab48b16ad4115c1935d94023b82b3198064cb84a123e97f7466c1/msgpack-1.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af", size = 66453, upload-time = "2025-10-08T09:15:34.225Z" }, - { url = "https://files.pythonhosted.org/packages/16/67/93f80545eb1792b61a217fa7f06d5e5cb9e0055bed867f43e2b8e012e137/msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a", size = 85264, upload-time = "2025-10-08T09:15:35.61Z" }, - { url = "https://files.pythonhosted.org/packages/87/1c/33c8a24959cf193966ef11a6f6a2995a65eb066bd681fd085afd519a57ce/msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b", size = 89076, upload-time = "2025-10-08T09:15:36.619Z" }, - { url = "https://files.pythonhosted.org/packages/fc/6b/62e85ff7193663fbea5c0254ef32f0c77134b4059f8da89b958beb7696f3/msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245", size = 435242, upload-time = "2025-10-08T09:15:37.647Z" }, - { url = "https://files.pythonhosted.org/packages/c1/47/5c74ecb4cc277cf09f64e913947871682ffa82b3b93c8dad68083112f412/msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90", size = 432509, upload-time = "2025-10-08T09:15:38.794Z" }, - { url = "https://files.pythonhosted.org/packages/24/a4/e98ccdb56dc4e98c929a3f150de1799831c0a800583cde9fa022fa90602d/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20", size = 415957, upload-time = "2025-10-08T09:15:40.238Z" }, - { url = "https://files.pythonhosted.org/packages/da/28/6951f7fb67bc0a4e184a6b38ab71a92d9ba58080b27a77d3e2fb0be5998f/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27", size = 422910, upload-time = "2025-10-08T09:15:41.505Z" }, - { url = "https://files.pythonhosted.org/packages/f0/03/42106dcded51f0a0b5284d3ce30a671e7bd3f7318d122b2ead66ad289fed/msgpack-1.1.2-cp314-cp314t-win32.whl", hash = "sha256:1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b", size = 75197, upload-time = "2025-10-08T09:15:42.954Z" }, - { url = "https://files.pythonhosted.org/packages/15/86/d0071e94987f8db59d4eeb386ddc64d0bb9b10820a8d82bcd3e53eeb2da6/msgpack-1.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff", size = 85772, upload-time = "2025-10-08T09:15:43.954Z" }, - { url = "https://files.pythonhosted.org/packages/81/f2/08ace4142eb281c12701fc3b93a10795e4d4dc7f753911d836675050f886/msgpack-1.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46", size = 70868, upload-time = "2025-10-08T09:15:44.959Z" }, -] - -[[package]] -name = "mydevtools-backend" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "boto3" }, - { name = "fastapi", extra = ["standard"] }, - { name = "firebase-admin" }, - { name = "motor" }, - { name = "orjson" }, - { name = "pydantic-settings" }, - { name = "python-jose", extra = ["cryptography"] }, - { name = "redis" }, - { name = "resend" }, - { name = "slowapi" }, - { name = "uvicorn", extra = ["standard"] }, - { name = "webauthn" }, -] - -[package.optional-dependencies] -dev = [ - { name = "httpx" }, - { name = "pytest" }, - { name = "ruff" }, - { name = "soft-webauthn" }, - { name = "testcontainers", extra = ["redis"] }, -] - -[package.dev-dependencies] -dev = [ - { name = "pytest-asyncio" }, -] - -[package.metadata] -requires-dist = [ - { name = "boto3", specifier = ">=1.38.0" }, - { name = "fastapi", extras = ["standard"] }, - { name = "firebase-admin", specifier = ">=7.1.0" }, - { name = "httpx", marker = "extra == 'dev'", specifier = ">=0.28.1" }, - { name = "motor", specifier = ">=3.7.0" }, - { name = "orjson", specifier = ">=3.10" }, - { name = "pydantic-settings", specifier = ">=2.10.1" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.4.1" }, - { name = "python-jose", extras = ["cryptography"], specifier = ">=3.5.0" }, - { name = "redis", extras = ["asyncio"], specifier = ">=5.0" }, - { name = "resend", specifier = ">=0.7,<1.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.13.0" }, - { name = "slowapi", specifier = ">=0.1.9" }, - { name = "soft-webauthn", marker = "extra == 'dev'", specifier = ">=0.1.4" }, - { name = "testcontainers", extras = ["redis"], marker = "extra == 'dev'", specifier = ">=4.0" }, - { name = "uvicorn", extras = ["standard"], specifier = ">=0.35.0" }, - { name = "webauthn", specifier = ">=2.5.0" }, -] -provides-extras = ["dev"] - -[package.metadata.requires-dev] -dev = [{ name = "pytest-asyncio", specifier = ">=1.4.0" }] - -[[package]] -name = "orjson" -version = "3.11.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/5d/b95ca542a001135cc250a49370f282f578c8f4e46cc8617d73775297eea8/orjson-3.11.9-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:135869ef917b8704ea0a94e01620e0c05021c15c52036e4663baffe75e72f8ce", size = 228986, upload-time = "2026-05-06T15:09:14.765Z" }, - { url = "https://files.pythonhosted.org/packages/80/01/be33fbff646e22f93398429ea645f20d2097aea1a6cdc1e6628e70125f83/orjson-3.11.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115ab5f5f4a0f203cc2a5f0fb09aee503a3f771aa08392949ab5ca230c4fbdbd", size = 132558, upload-time = "2026-05-06T15:09:17.431Z" }, - { url = "https://files.pythonhosted.org/packages/4e/61/73d49333bba660a075daccca10970dc6409ce1cf42ae4046646a19468aad/orjson-3.11.9-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4da3c38a2083ca4aaf9c2a36776cce3e9328e6647b10d118948f3cfb4913ffe4", size = 128213, upload-time = "2026-05-06T15:09:18.719Z" }, - { url = "https://files.pythonhosted.org/packages/1f/7d/30e844b3dac3f74aed66b1f984daf9db3c98c0328c03d965a9e8dc06449e/orjson-3.11.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53b50b0e14084b8f7e29c5ce84c5af0f1160169b30d8a6914231d97d2fe297d4", size = 135430, upload-time = "2026-05-06T15:09:20.257Z" }, - { url = "https://files.pythonhosted.org/packages/16/64/bd815f5c610b3facc204f26ba94e87a9eb49b0d83de3d5fc1eee2402d91b/orjson-3.11.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:231742b4a11dad8d5380a435962c57e91b7c37b79be858f4ef1c0df1a259897e", size = 146178, upload-time = "2026-05-06T15:09:21.616Z" }, - { url = "https://files.pythonhosted.org/packages/c7/35/e744fd36c79b339d27beb06068b5a08a8882ef5418804d0ce545a31f718d/orjson-3.11.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34fd2317602587321faab75ab76c623a0117e80841a6413654f04e47f339a8fb", size = 133068, upload-time = "2026-05-06T15:09:23.228Z" }, - { url = "https://files.pythonhosted.org/packages/2a/56/d54152b67b63a0b3e556cfc549d6ce84f74d7f425ddeadc6c8a74d913da7/orjson-3.11.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71f3db16e69b667b132e0f305a833d5497da302d801508cbb051ed9a9819da47", size = 134217, upload-time = "2026-05-06T15:09:24.847Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ee/66154baf69f71c7164a268a5e888908aec5a0819d13c81d5e2755a257758/orjson-3.11.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0b34789fa0da61cf7bef0546b09c738fb195331e017e477096d129e9105ab03d", size = 141917, upload-time = "2026-05-06T15:09:26.647Z" }, - { url = "https://files.pythonhosted.org/packages/09/d3/c5824260ca8b9d7ba82648d042a3f8f4815d18c15bb98a1f30edd1bb2d83/orjson-3.11.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:87e4d4ab280b0c87424d47695bec2182caf8cfc17879ea78dab76680194abc13", size = 415356, upload-time = "2026-05-06T15:09:28.252Z" }, - { url = "https://files.pythonhosted.org/packages/64/cb/509c2e816fe4df641d93dc92f6a89adc8df3ada8ebdee2bd44aba3264c3c/orjson-3.11.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ace6c58523302d3b97b6ac5c38a5298a54b473762b6be82726b4265c41029f92", size = 148112, upload-time = "2026-05-06T15:09:29.783Z" }, - { url = "https://files.pythonhosted.org/packages/db/b5/3ceae56d2e4962979eedb023ba6a46a4bb65f333960379be0ca470686220/orjson-3.11.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:97d0d932803c1b164fde11cb542a9efcb1e0f63b184537cca65887147906ff48", size = 137112, upload-time = "2026-05-06T15:09:31.432Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7a/81fa3f2c7bef79b04cf2ab7838e5ac74b1f12511ceab979759b0275d6bb4/orjson-3.11.9-cp310-cp310-win32.whl", hash = "sha256:b3afcf569c15577a9fe64627292daa3e6b3a70f4fb77a5df246a87ec21681b94", size = 131706, upload-time = "2026-05-06T15:09:32.707Z" }, - { url = "https://files.pythonhosted.org/packages/ae/d8/b64600f9083c7f151ad39717a5877fccbeb0ef6d7efcb55f971ce00b6bee/orjson-3.11.9-cp310-cp310-win_amd64.whl", hash = "sha256:8697ab6a080a5c46edaad50e2bc5bd8c7ca5c66442d24104fa44ec74910a8244", size = 127282, upload-time = "2026-05-06T15:09:33.955Z" }, - { url = "https://files.pythonhosted.org/packages/1e/51/3fb9e65ae76ee97bd611869a503fa3fc0a6e81dd8b737cf3003f682df7ff/orjson-3.11.9-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f01c4818b3fc9b0da8e096722a84318071eaa118df35f6ed2344da0e73a5444f", size = 228522, upload-time = "2026-05-06T15:09:35.362Z" }, - { url = "https://files.pythonhosted.org/packages/16/fa/9d54b07cb3f3b0bfd57841478e42d7a0ece4a9f49f9907eecf5a45461687/orjson-3.11.9-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:3ebca4179031ee716ed076ffadc29428e900512f6fccee8614c9983157fcf19c", size = 128463, upload-time = "2026-05-06T15:09:37.063Z" }, - { url = "https://files.pythonhosted.org/packages/88/b1/6ceafc2eefd0a553e3be77ce6c49d107e772485d9568629376171c50e634/orjson-3.11.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48ee05097750de0ff69ed5b7bbcf0732182fd57a24043dcc2a1da780a5ead3a5", size = 132306, upload-time = "2026-05-06T15:09:38.299Z" }, - { url = "https://files.pythonhosted.org/packages/ea/76/f11311285324a40aab1e3031385c50b635a7cd0734fdaf60c7e89a696f60/orjson-3.11.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6082706765a95a6680d812e1daf1c0cfe8adec7831b3ff3b625693f3b461b1c", size = 127988, upload-time = "2026-05-06T15:09:39.597Z" }, - { url = "https://files.pythonhosted.org/packages/9e/85/0ef63bcf1337f44031ce9b91b1919563f62a37527b3ea4368bb15a22e5d7/orjson-3.11.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:277fefe9d76ee17eb14debf399e3533d4d63b5f677a4d3719eb763536af1f4bd", size = 135188, upload-time = "2026-05-06T15:09:40.957Z" }, - { url = "https://files.pythonhosted.org/packages/05/94/b0d27090ea8a2095db3c2bd1b1c96f96f19bbb494d7fef33130e846e613d/orjson-3.11.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03db380e3780fa0015ed776a90f20e8e20bb11dde13b216ce19e5718e3dfba62", size = 145937, upload-time = "2026-05-06T15:09:42.249Z" }, - { url = "https://files.pythonhosted.org/packages/09/eb/75d50c29c05b8054013e221e598820a365c8e64065312e75e202ed880709/orjson-3.11.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33d7d766701847dc6729846362dc27895d2f2d2251264f9d10e7cb9878194877", size = 132758, upload-time = "2026-05-06T15:09:43.945Z" }, - { url = "https://files.pythonhosted.org/packages/49/bd/360686f39348aa88827cb6fbf7dc606fd41c831a35235e1abf1db8e3a9e6/orjson-3.11.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147302878da387104b66bb4a8b0227d1d487e976ce41a8501916161072ed87b1", size = 133971, upload-time = "2026-05-06T15:09:45.239Z" }, - { url = "https://files.pythonhosted.org/packages/0e/30/3178eb16f3221aeef068b6f1f1ebe05f656ea5c6dffe9f6c917329fe17a3/orjson-3.11.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3513550321f8c8c811a7c3297b8a630e82dc08e4c10216d07703c997776236cd", size = 141685, upload-time = "2026-05-06T15:09:46.858Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f1/ff2f19ed0225f9680fafa42febca3570dd59444ebf190980738d376214c2/orjson-3.11.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c5d001196b89fa9cf0a4ab79766cd835b991a166e4b621ba95089edc50c429ff", size = 415167, upload-time = "2026-05-06T15:09:48.312Z" }, - { url = "https://files.pythonhosted.org/packages/9b/61/863bddf0da6e9e586765414debd54b4e58db05f560902b6d00658cb88636/orjson-3.11.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:16969c9d369c98eb084889c6e4d2d39b77c7eb38ceccf8da2a9fff62ae908980", size = 147913, upload-time = "2026-05-06T15:09:49.733Z" }, - { url = "https://files.pythonhosted.org/packages/b6/8a/4081492586d75b073d60c5271a8d0f05a0955cabf1e34c8473f6fcd84235/orjson-3.11.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:63e0efbc991250c0b3143488fa57d95affcabbfc63c99c48d625dd37779aafe2", size = 136959, upload-time = "2026-05-06T15:09:51.311Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bd/70b6ab193594d7abb875320c0a7c8335e846f28968c432c31042409c3c8d/orjson-3.11.9-cp311-cp311-win32.whl", hash = "sha256:14ed654580c1ed2bc217352ec82f91b047aef82951aa71c7f64e0dcb03c0e180", size = 131533, upload-time = "2026-05-06T15:09:52.637Z" }, - { url = "https://files.pythonhosted.org/packages/3f/17/1a1a228183d62d1b77e2c30d210f47dd4768b310ebe1607c63e3c0e3a71e/orjson-3.11.9-cp311-cp311-win_amd64.whl", hash = "sha256:57ea77fb70a448ce87d18fca050193202a3da5e54598f6501ca5476fb66cfe02", size = 127106, upload-time = "2026-05-06T15:09:54.204Z" }, - { url = "https://files.pythonhosted.org/packages/b8/95/285de5fa296d09681ee9c546cd4a8aeb773b701cf343dc125994f4d52953/orjson-3.11.9-cp311-cp311-win_arm64.whl", hash = "sha256:19b72ed11572a2ee51a67a903afbe5af504f84ed6f529c0fe44b0ab3fb5cc697", size = 126848, upload-time = "2026-05-06T15:09:55.551Z" }, - { url = "https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49", size = 228515, upload-time = "2026-05-06T15:09:57.265Z" }, - { url = "https://files.pythonhosted.org/packages/24/75/05912954c8b288f34fcf5cd4b9b071cb4f6e77b9961e175e56ebb258089f/orjson-3.11.9-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291", size = 128409, upload-time = "2026-05-06T15:09:59.063Z" }, - { url = "https://files.pythonhosted.org/packages/ab/86/1c3a47df3bc8191ea9ac51603bbb872a95167a364320c269f2557911f406/orjson-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09", size = 132106, upload-time = "2026-05-06T15:10:00.798Z" }, - { url = "https://files.pythonhosted.org/packages/d7/cf/b33b5f3e695ae7d63feef9d915c37cc3b8f465493dcd4f8e0b4c697a2366/orjson-3.11.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4", size = 127864, upload-time = "2026-05-06T15:10:02.15Z" }, - { url = "https://files.pythonhosted.org/packages/31/6a/6cf69385a58208024fcb8c014e2141b8ce838aba6492b589f8acfff97fab/orjson-3.11.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882", size = 135213, upload-time = "2026-05-06T15:10:03.515Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f8/0b1bd3e8f2efcdd376af5c8cfd79eaf13f018080c0089c80ebd724e3c7fb/orjson-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff", size = 145994, upload-time = "2026-05-06T15:10:05.083Z" }, - { url = "https://files.pythonhosted.org/packages/f3/59/dab79f61044c529d2c81aecdc589b1f833a1c8dec11ba3b1c2498a02ca7e/orjson-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe", size = 132744, upload-time = "2026-05-06T15:10:06.853Z" }, - { url = "https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61", size = 134014, upload-time = "2026-05-06T15:10:08.213Z" }, - { url = "https://files.pythonhosted.org/packages/50/c7/375e83a76851b73b2e39f3bcf0e5a19e2b89bad13e5bca97d0b293d27f24/orjson-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2", size = 141509, upload-time = "2026-05-06T15:10:09.595Z" }, - { url = "https://files.pythonhosted.org/packages/7f/7c/49d5d82a3d3097f641f094f552131f1e2723b0b8cb0fa2874ab65ecfffa6/orjson-3.11.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206", size = 415127, upload-time = "2026-05-06T15:10:11.049Z" }, - { url = "https://files.pythonhosted.org/packages/3a/dc/7446c538590d55f455647e5f3c61fc33f7108714e7afcffa6a2a033f8350/orjson-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f", size = 148025, upload-time = "2026-05-06T15:10:12.842Z" }, - { url = "https://files.pythonhosted.org/packages/df/e5/4d2d8af06f788329b4f78f8cc3679bb395392fcaa1e4d8d3c33e85308fa4/orjson-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa", size = 136943, upload-time = "2026-05-06T15:10:14.405Z" }, - { url = "https://files.pythonhosted.org/packages/06/69/850264ccf6d80f6b174620d30a87f65c9b1490aba33fe6b62798e618cad3/orjson-3.11.9-cp312-cp312-win32.whl", hash = "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470", size = 131606, upload-time = "2026-05-06T15:10:15.791Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be", size = 127101, upload-time = "2026-05-06T15:10:17.129Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" }, - { url = "https://files.pythonhosted.org/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" }, - { url = "https://files.pythonhosted.org/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" }, - { url = "https://files.pythonhosted.org/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" }, - { url = "https://files.pythonhosted.org/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" }, - { url = "https://files.pythonhosted.org/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" }, - { url = "https://files.pythonhosted.org/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" }, - { url = "https://files.pythonhosted.org/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" }, - { url = "https://files.pythonhosted.org/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" }, - { url = "https://files.pythonhosted.org/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" }, - { url = "https://files.pythonhosted.org/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" }, - { url = "https://files.pythonhosted.org/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" }, - { url = "https://files.pythonhosted.org/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" }, - { url = "https://files.pythonhosted.org/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" }, - { url = "https://files.pythonhosted.org/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" }, - { url = "https://files.pythonhosted.org/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" }, - { url = "https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e", size = 228465, upload-time = "2026-05-06T15:10:44.097Z" }, - { url = "https://files.pythonhosted.org/packages/64/62/3e0e0c14c957133bcd855395c62b55ed4e3b0af23ffea11b032cb1dcbdb1/orjson-3.11.9-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e", size = 128364, upload-time = "2026-05-06T15:10:45.839Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5a/07d8aa117211a8ed7630bda80c8c0b14d04e0f8dcf99bcf49656e4a710eb/orjson-3.11.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0", size = 132063, upload-time = "2026-05-06T15:10:47.267Z" }, - { url = "https://files.pythonhosted.org/packages/d6/ec/4acaf21483e18aa945be74a474c74b434f284b549f275a0a39b9f98956e9/orjson-3.11.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124", size = 122356, upload-time = "2026-05-06T15:10:48.765Z" }, - { url = "https://files.pythonhosted.org/packages/13/d8/5f0555e7638801323b7a75850f92e7dfa891bc84fe27a1ba4449170d1200/orjson-3.11.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c", size = 129592, upload-time = "2026-05-06T15:10:50.13Z" }, - { url = "https://files.pythonhosted.org/packages/b6/30/ed9860412a3603ceb3c5955bfd72d28b9d0e7ba6ed81add14f83d7114236/orjson-3.11.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7", size = 140491, upload-time = "2026-05-06T15:10:51.582Z" }, - { url = "https://files.pythonhosted.org/packages/d0/17/adc514dea7ac7c505527febf884934b815d34f0c7b8693c1a8b39c5c4a57/orjson-3.11.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1", size = 127309, upload-time = "2026-05-06T15:10:53.329Z" }, - { url = "https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db", size = 134030, upload-time = "2026-05-06T15:10:54.988Z" }, - { url = "https://files.pythonhosted.org/packages/c1/7a/bc82a0bb25e9faaf92dc4d9ef002732efc09737706af83e346788641d4a7/orjson-3.11.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b", size = 141482, upload-time = "2026-05-06T15:10:56.663Z" }, - { url = "https://files.pythonhosted.org/packages/01/55/e69188b939f77d5d32a9833745ace31ea5ccae3ab613a1ec185d3cd2c4fb/orjson-3.11.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972", size = 415178, upload-time = "2026-05-06T15:10:58.446Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1a/b8a5a7ac527e80b9cb11d51e3f6689b709279183264b9ec5c7bc680bb8b5/orjson-3.11.9-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0", size = 148089, upload-time = "2026-05-06T15:11:00.441Z" }, - { url = "https://files.pythonhosted.org/packages/97/4e/00503f64204bf859b37213a63927028f30fb6268cd8677fb0a5ad48155e1/orjson-3.11.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586", size = 136921, upload-time = "2026-05-06T15:11:02.176Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ba/a23b82a0a8d0ed7bed4e5f5035aae751cad4ff6a1e8d2ecd14d8860f5929/orjson-3.11.9-cp314-cp314-win32.whl", hash = "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673", size = 131638, upload-time = "2026-05-06T15:11:03.696Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl", hash = "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b", size = 127078, upload-time = "2026-05-06T15:11:05.123Z" }, - { url = "https://files.pythonhosted.org/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" }, -] - -[[package]] -name = "packaging" -version = "26.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, -] - -[[package]] -name = "proto-plus" -version = "1.27.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/81/0d/94dfe80193e79d55258345901acd2917523d56e8381bc4dee7fd38e3868a/proto_plus-1.27.2.tar.gz", hash = "sha256:b2adde53adadf75737c44d3dcb0104fde65250dfc83ad59168b4aa3e574b6a24", size = 57204, upload-time = "2026-03-26T22:18:57.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/f3/1fba73eeffafc998a25d59703b63f8be4fe8a5cb12eaff7386a0ba0f7125/proto_plus-1.27.2-py3-none-any.whl", hash = "sha256:6432f75893d3b9e70b9c412f1d2f03f65b11fb164b793d14ae2ca01821d22718", size = 50450, upload-time = "2026-03-26T22:13:42.927Z" }, -] - -[[package]] -name = "protobuf" -version = "6.33.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, - { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, - { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, - { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, - { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, - { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, -] - -[[package]] -name = "pyasn1" -version = "0.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, -] - -[[package]] -name = "pyasn1-modules" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, -] - -[[package]] -name = "pycparser" -version = "3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, -] - -[[package]] -name = "pydantic" -version = "2.13.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d9/e4/40d09941a2cebcb20609b86a559817d5b9291c49dd6f8c87e5feffbe703a/pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d", size = 844068, upload-time = "2026-04-20T14:46:43.632Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927", size = 471981, upload-time = "2026-04-20T14:46:41.402Z" }, -] - -[package.optional-dependencies] -email = [ - { name = "email-validator" }, -] - -[[package]] -name = "pydantic-core" -version = "2.46.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2a/ef/f7abb56c49382a246fd2ce9c799691e3c3e7175ec74b14d99e798bcddb1a/pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c", size = 471412, upload-time = "2026-04-20T14:40:56.672Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/98/b50eb9a411e87483b5c65dba4fa430a06bac4234d3403a40e5a9905ebcd0/pydantic_core-2.46.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:1da3786b8018e60349680720158cc19161cc3b4bdd815beb0a321cd5ce1ad5b1", size = 2108971, upload-time = "2026-04-20T14:43:51.945Z" }, - { url = "https://files.pythonhosted.org/packages/08/4b/f364b9d161718ff2217160a4b5d41ce38de60aed91c3689ebffa1c939d23/pydantic_core-2.46.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc0988cb29d21bf4a9d5cf2ef970b5c0e38d8d8e107a493278c05dc6c1dda69f", size = 1949588, upload-time = "2026-04-20T14:44:10.386Z" }, - { url = "https://files.pythonhosted.org/packages/8f/8b/30bd03ee83b2f5e29f5ba8e647ab3c456bf56f2ec72fdbcc0215484a0854/pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f9067c3bfadd04c55484b89c0d267981b2f3512850f6f66e1e74204a4e4ce3", size = 1975986, upload-time = "2026-04-20T14:43:57.106Z" }, - { url = "https://files.pythonhosted.org/packages/3c/54/13ccf954d84ec275d5d023d5786e4aa48840bc9f161f2838dc98e1153518/pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a642ac886ecf6402d9882d10c405dcf4b902abeb2972cd5fb4a48c83cd59279a", size = 2055830, upload-time = "2026-04-20T14:44:15.499Z" }, - { url = "https://files.pythonhosted.org/packages/be/0e/65f38125e660fdbd72aa858e7dfae893645cfa0e7b13d333e174a367cd23/pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79f561438481f28681584b89e2effb22855e2179880314bcddbf5968e935e807", size = 2222340, upload-time = "2026-04-20T14:41:51.353Z" }, - { url = "https://files.pythonhosted.org/packages/d1/88/f3ab7739efe0e7e80777dbb84c59eb98518e3f57ea433206194c2e425272/pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57a973eae4665352a47cf1a99b4ee864620f2fe663a217d7a8da68a1f3a5bfda", size = 2280727, upload-time = "2026-04-20T14:41:30.461Z" }, - { url = "https://files.pythonhosted.org/packages/2a/6d/c228219080817bec4982f9531cadb18da6aaa770fdeb114f49c237ac2c9f/pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83d002b97072a53ea150d63e0a3adfae5670cef5aa8a6e490240e482d3b22e57", size = 2092158, upload-time = "2026-04-20T14:44:07.305Z" }, - { url = "https://files.pythonhosted.org/packages/0f/b1/525a16711e7c6d61635fac3b0bd54600b5c5d9f60c6fc5aaab26b64a2297/pydantic_core-2.46.3-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b40ddd51e7c44b28cfaef746c9d3c506d658885e0a46f9eeef2ee815cbf8e045", size = 2116626, upload-time = "2026-04-20T14:42:34.118Z" }, - { url = "https://files.pythonhosted.org/packages/ef/7c/17d30673351439a6951bf54f564cf2443ab00ae264ec9df00e2efd710eb5/pydantic_core-2.46.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac5ec7fb9b87f04ee839af2d53bcadea57ded7d229719f56c0ed895bff987943", size = 2160691, upload-time = "2026-04-20T14:41:14.023Z" }, - { url = "https://files.pythonhosted.org/packages/86/66/af8adbcbc0886ead7f1a116606a534d75a307e71e6e08226000d51b880d2/pydantic_core-2.46.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a3b11c812f61b3129c4905781a2601dfdfdea5fe1e6c1cfb696b55d14e9c054f", size = 2182543, upload-time = "2026-04-20T14:40:48.886Z" }, - { url = "https://files.pythonhosted.org/packages/b0/37/6de71e0f54c54a4190010f57deb749e1ddf75c568ada3b1320b70067f121/pydantic_core-2.46.3-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1108da631e602e5b3c38d6d04fe5bb3bfa54349e6918e3ca6cf570b2e2b2f9d4", size = 2324513, upload-time = "2026-04-20T14:42:36.121Z" }, - { url = "https://files.pythonhosted.org/packages/51/b1/9fc74ce94f603d5ef59ff258ca9c2c8fb902fb548d340a96f77f4d1c3b7f/pydantic_core-2.46.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:de885175515bcfa98ae618c1df7a072f13d179f81376c8007112af20567fd08a", size = 2361853, upload-time = "2026-04-20T14:43:24.886Z" }, - { url = "https://files.pythonhosted.org/packages/40/d0/4c652fc592db35f100279ee751d5a145aca1b9a7984b9684ba7c1b5b0535/pydantic_core-2.46.3-cp310-cp310-win32.whl", hash = "sha256:d11058e3201527d41bc6b545c79187c9e4bf85e15a236a6007f0e991518882b7", size = 1980465, upload-time = "2026-04-20T14:44:46.239Z" }, - { url = "https://files.pythonhosted.org/packages/27/b8/a920453c38afbe1f355e1ea0b0d94a0a3e0b0879d32d793108755fa171d5/pydantic_core-2.46.3-cp310-cp310-win_amd64.whl", hash = "sha256:3612edf65c8ea67ac13616c4d23af12faef1ae435a8a93e5934c2a0cbbdd1fd6", size = 2073884, upload-time = "2026-04-20T14:43:01.201Z" }, - { url = "https://files.pythonhosted.org/packages/22/a2/1ba90a83e85a3f94c796b184f3efde9c72f2830dcda493eea8d59ba78e6d/pydantic_core-2.46.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ab124d49d0459b2373ecf54118a45c28a1e6d4192a533fbc915e70f556feb8e5", size = 2106740, upload-time = "2026-04-20T14:41:20.932Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f6/99ae893c89a0b9d3daec9f95487aa676709aa83f67643b3f0abaf4ab628a/pydantic_core-2.46.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cca67d52a5c7a16aed2b3999e719c4bcf644074eac304a5d3d62dd70ae7d4b2c", size = 1948293, upload-time = "2026-04-20T14:43:42.115Z" }, - { url = "https://files.pythonhosted.org/packages/3e/b8/2e8e636dc9e3f16c2e16bf0849e24be82c5ee82c603c65fc0326666328fc/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c024e08c0ba23e6fd68c771a521e9d6a792f2ebb0fa734296b36394dc30390e", size = 1973222, upload-time = "2026-04-20T14:41:57.841Z" }, - { url = "https://files.pythonhosted.org/packages/34/36/0e730beec4d83c5306f417afbd82ff237d9a21e83c5edf675f31ed84c1fe/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6645ce7eec4928e29a1e3b3d5c946621d105d3e79f0c9cddf07c2a9770949287", size = 2053852, upload-time = "2026-04-20T14:40:43.077Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f0/3071131f47e39136a17814576e0fada9168569f7f8c0e6ac4d1ede6a4958/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a712c7118e6c5ea96562f7b488435172abb94a3c53c22c9efc1412264a45cbbe", size = 2221134, upload-time = "2026-04-20T14:43:03.349Z" }, - { url = "https://files.pythonhosted.org/packages/2f/a9/a2dc023eec5aa4b02a467874bad32e2446957d2adcab14e107eab502e978/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69a868ef3ff206343579021c40faf3b1edc64b1cc508ff243a28b0a514ccb050", size = 2279785, upload-time = "2026-04-20T14:41:19.285Z" }, - { url = "https://files.pythonhosted.org/packages/0a/44/93f489d16fb63fbd41c670441536541f6e8cfa1e5a69f40bc9c5d30d8c90/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc7e8c32db809aa0f6ea1d6869ebc8518a65d5150fdfad8bcae6a49ae32a22e2", size = 2089404, upload-time = "2026-04-20T14:43:10.108Z" }, - { url = "https://files.pythonhosted.org/packages/2a/78/8692e3aa72b2d004f7a5d937f1dfdc8552ba26caf0bec75f342c40f00dec/pydantic_core-2.46.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:3481bd1341dc85779ee506bc8e1196a277ace359d89d28588a9468c3ecbe63fa", size = 2114898, upload-time = "2026-04-20T14:44:51.475Z" }, - { url = "https://files.pythonhosted.org/packages/6a/62/e83133f2e7832532060175cebf1f13748f4c7e7e7165cdd1f611f174494b/pydantic_core-2.46.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8690eba565c6d68ffd3a8655525cbdd5246510b44a637ee2c6c03a7ebfe64d3c", size = 2157856, upload-time = "2026-04-20T14:43:46.64Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ec/6a500e3ad7718ee50583fae79c8651f5d37e3abce1fa9ae177ae65842c53/pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4de88889d7e88d50d40ee5b39d5dac0bcaef9ba91f7e536ac064e6b2834ecccf", size = 2180168, upload-time = "2026-04-20T14:42:00.302Z" }, - { url = "https://files.pythonhosted.org/packages/d8/53/8267811054b1aa7fc1dc7ded93812372ef79a839f5e23558136a6afbfde1/pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:e480080975c1ef7f780b8f99ed72337e7cc5efea2e518a20a692e8e7b278eb8b", size = 2322885, upload-time = "2026-04-20T14:41:05.253Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c1/1c0acdb3aa0856ddc4ecc55214578f896f2de16f400cf51627eb3c26c1c4/pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de3a5c376f8cd94da9a1b8fd3dd1c16c7a7b216ed31dc8ce9fd7a22bf13b836e", size = 2360328, upload-time = "2026-04-20T14:41:43.991Z" }, - { url = "https://files.pythonhosted.org/packages/f0/d0/ef39cd0f4a926814f360e71c1adeab48ad214d9727e4deb48eedfb5bce1a/pydantic_core-2.46.3-cp311-cp311-win32.whl", hash = "sha256:fc331a5314ffddd5385b9ee9d0d2fee0b13c27e0e02dad71b1ae5d6561f51eeb", size = 1979464, upload-time = "2026-04-20T14:43:12.215Z" }, - { url = "https://files.pythonhosted.org/packages/18/9c/f41951b0d858e343f1cf09398b2a7b3014013799744f2c4a8ad6a3eec4f2/pydantic_core-2.46.3-cp311-cp311-win_amd64.whl", hash = "sha256:b5b9c6cf08a8a5e502698f5e153056d12c34b8fb30317e0c5fd06f45162a6346", size = 2070837, upload-time = "2026-04-20T14:41:47.707Z" }, - { url = "https://files.pythonhosted.org/packages/9f/1e/264a17cd582f6ed50950d4d03dd5fefd84e570e238afe1cb3e25cf238769/pydantic_core-2.46.3-cp311-cp311-win_arm64.whl", hash = "sha256:5dfd51cf457482f04ec49491811a2b8fd5b843b64b11eecd2d7a1ee596ea78a6", size = 2053647, upload-time = "2026-04-20T14:42:27.535Z" }, - { url = "https://files.pythonhosted.org/packages/4b/cb/5b47425556ecc1f3fe18ed2a0083188aa46e1dd812b06e406475b3a5d536/pydantic_core-2.46.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b11b59b3eee90a80a36701ddb4576d9ae31f93f05cb9e277ceaa09e6bf074a67", size = 2101946, upload-time = "2026-04-20T14:40:52.581Z" }, - { url = "https://files.pythonhosted.org/packages/a1/4f/2fb62c2267cae99b815bbf4a7b9283812c88ca3153ef29f7707200f1d4e5/pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089", size = 1951612, upload-time = "2026-04-20T14:42:42.996Z" }, - { url = "https://files.pythonhosted.org/packages/50/6e/b7348fd30d6556d132cddd5bd79f37f96f2601fe0608afac4f5fb01ec0b3/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a519dab6d63c514f3a81053e5266c549679e4aa88f6ec57f2b7b854aceb1b0", size = 1977027, upload-time = "2026-04-20T14:42:02.001Z" }, - { url = "https://files.pythonhosted.org/packages/82/11/31d60ee2b45540d3fb0b29302a393dbc01cd771c473f5b5147bcd353e593/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6cd87cb1575b1ad05ba98894c5b5c96411ef678fa2f6ed2576607095b8d9789", size = 2063008, upload-time = "2026-04-20T14:44:17.952Z" }, - { url = "https://files.pythonhosted.org/packages/8a/db/3a9d1957181b59258f44a2300ab0f0be9d1e12d662a4f57bb31250455c52/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f80a55484b8d843c8ada81ebf70a682f3f00a3d40e378c06cf17ecb44d280d7d", size = 2233082, upload-time = "2026-04-20T14:40:57.934Z" }, - { url = "https://files.pythonhosted.org/packages/9c/e1/3277c38792aeb5cfb18c2f0c5785a221d9ff4e149abbe1184d53d5f72273/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3861f1731b90c50a3266316b9044f5c9b405eecb8e299b0a7120596334e4fe9c", size = 2304615, upload-time = "2026-04-20T14:42:12.584Z" }, - { url = "https://files.pythonhosted.org/packages/5e/d5/e3d9717c9eba10855325650afd2a9cba8e607321697f18953af9d562da2f/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb528e295ed31570ac3dcc9bfdd6e0150bc11ce6168ac87a8082055cf1a67395", size = 2094380, upload-time = "2026-04-20T14:43:05.522Z" }, - { url = "https://files.pythonhosted.org/packages/a1/20/abac35dedcbfd66c6f0b03e4e3564511771d6c9b7ede10a362d03e110d9b/pydantic_core-2.46.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:367508faa4973b992b271ba1494acaab36eb7e8739d1e47be5035fb1ea225396", size = 2135429, upload-time = "2026-04-20T14:41:55.549Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a5/41bfd1df69afad71b5cf0535055bccc73022715ad362edbc124bc1e021d7/pydantic_core-2.46.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ad3c826fe523e4becf4fe39baa44286cff85ef137c729a2c5e269afbfd0905d", size = 2174582, upload-time = "2026-04-20T14:41:45.96Z" }, - { url = "https://files.pythonhosted.org/packages/79/65/38d86ea056b29b2b10734eb23329b7a7672ca604df4f2b6e9c02d4ee22fe/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec638c5d194ef8af27db69f16c954a09797c0dc25015ad6123eb2c73a4d271ca", size = 2187533, upload-time = "2026-04-20T14:40:55.367Z" }, - { url = "https://files.pythonhosted.org/packages/b6/55/a1129141678a2026badc539ad1dee0a71d06f54c2f06a4bd68c030ac781b/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:28ed528c45446062ee66edb1d33df5d88828ae167de76e773a3c7f64bd14e976", size = 2332985, upload-time = "2026-04-20T14:44:13.05Z" }, - { url = "https://files.pythonhosted.org/packages/d7/60/cb26f4077719f709e54819f4e8e1d43f4091f94e285eb6bd21e1190a7b7c/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aed19d0c783886d5bd86d80ae5030006b45e28464218747dcf83dabfdd092c7b", size = 2373670, upload-time = "2026-04-20T14:41:53.421Z" }, - { url = "https://files.pythonhosted.org/packages/6b/7e/c3f21882bdf1d8d086876f81b5e296206c69c6082551d776895de7801fa0/pydantic_core-2.46.3-cp312-cp312-win32.whl", hash = "sha256:06d5d8820cbbdb4147578c1fe7ffcd5b83f34508cb9f9ab76e807be7db6ff0a4", size = 1966722, upload-time = "2026-04-20T14:44:30.588Z" }, - { url = "https://files.pythonhosted.org/packages/57/be/6b5e757b859013ebfbd7adba02f23b428f37c86dcbf78b5bb0b4ffd36e99/pydantic_core-2.46.3-cp312-cp312-win_amd64.whl", hash = "sha256:c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1", size = 2072970, upload-time = "2026-04-20T14:42:54.248Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f8/a989b21cc75e9a32d24192ef700eea606521221a89faa40c919ce884f2b1/pydantic_core-2.46.3-cp312-cp312-win_arm64.whl", hash = "sha256:f1f8338dd7a7f31761f1f1a3c47503a9a3b34eea3c8b01fa6ee96408affb5e72", size = 2035963, upload-time = "2026-04-20T14:44:20.4Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3c/9b5e8eb9821936d065439c3b0fb1490ffa64163bfe7e1595985a47896073/pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37", size = 2102109, upload-time = "2026-04-20T14:41:24.219Z" }, - { url = "https://files.pythonhosted.org/packages/91/97/1c41d1f5a19f241d8069f1e249853bcce378cdb76eec8ab636d7bc426280/pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f", size = 1951820, upload-time = "2026-04-20T14:42:14.236Z" }, - { url = "https://files.pythonhosted.org/packages/30/b4/d03a7ae14571bc2b6b3c7b122441154720619afe9a336fa3a95434df5e2f/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8", size = 1977785, upload-time = "2026-04-20T14:42:31.648Z" }, - { url = "https://files.pythonhosted.org/packages/ae/0c/4086f808834b59e3c8f1aa26df8f4b6d998cdcf354a143d18ef41529d1fe/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad", size = 2062761, upload-time = "2026-04-20T14:40:37.093Z" }, - { url = "https://files.pythonhosted.org/packages/fa/71/a649be5a5064c2df0db06e0a512c2281134ed2fcc981f52a657936a7527c/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c", size = 2232989, upload-time = "2026-04-20T14:42:59.254Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/7756e75763e810b3a710f4724441d1ecc5883b94aacb07ca71c5fb5cfb69/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f", size = 2303975, upload-time = "2026-04-20T14:41:32.287Z" }, - { url = "https://files.pythonhosted.org/packages/6c/35/68a762e0c1e31f35fa0dac733cbd9f5b118042853698de9509c8e5bf128b/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35", size = 2095325, upload-time = "2026-04-20T14:42:47.685Z" }, - { url = "https://files.pythonhosted.org/packages/77/bf/1bf8c9a8e91836c926eae5e3e51dce009bf495a60ca56060689d3df3f340/pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687", size = 2133368, upload-time = "2026-04-20T14:41:22.766Z" }, - { url = "https://files.pythonhosted.org/packages/e5/50/87d818d6bab915984995157ceb2380f5aac4e563dddbed6b56f0ed057aba/pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3", size = 2173908, upload-time = "2026-04-20T14:42:52.044Z" }, - { url = "https://files.pythonhosted.org/packages/91/88/a311fb306d0bd6185db41fa14ae888fb81d0baf648a761ae760d30819d33/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022", size = 2186422, upload-time = "2026-04-20T14:43:29.55Z" }, - { url = "https://files.pythonhosted.org/packages/8f/79/28fd0d81508525ab2054fef7c77a638c8b5b0afcbbaeee493cf7c3fef7e1/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23", size = 2332709, upload-time = "2026-04-20T14:42:16.134Z" }, - { url = "https://files.pythonhosted.org/packages/b3/21/795bf5fe5c0f379308b8ef19c50dedab2e7711dbc8d0c2acf08f1c7daa05/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7", size = 2372428, upload-time = "2026-04-20T14:41:10.974Z" }, - { url = "https://files.pythonhosted.org/packages/45/b3/ed14c659cbe7605e3ef063077680a64680aec81eb1a04763a05190d49b7f/pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13", size = 1965601, upload-time = "2026-04-20T14:41:42.128Z" }, - { url = "https://files.pythonhosted.org/packages/ef/bb/adb70d9a762ddd002d723fbf1bd492244d37da41e3af7b74ad212609027e/pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0", size = 2071517, upload-time = "2026-04-20T14:43:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/52/eb/66faefabebfe68bd7788339c9c9127231e680b11906368c67ce112fdb47f/pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec", size = 2035802, upload-time = "2026-04-20T14:43:38.507Z" }, - { url = "https://files.pythonhosted.org/packages/7f/db/a7bcb4940183fda36022cd18ba8dd12f2dff40740ec7b58ce7457befa416/pydantic_core-2.46.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:afa3aa644f74e290cdede48a7b0bee37d1c35e71b05105f6b340d484af536d9b", size = 2097614, upload-time = "2026-04-20T14:44:38.374Z" }, - { url = "https://files.pythonhosted.org/packages/24/35/e4066358a22e3e99519db370494c7528f5a2aa1367370e80e27e20283543/pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ced3310e51aa425f7f77da8bbbb5212616655bedbe82c70944320bc1dbe5e018", size = 1951896, upload-time = "2026-04-20T14:40:53.996Z" }, - { url = "https://files.pythonhosted.org/packages/87/92/37cf4049d1636996e4b888c05a501f40a43ff218983a551d57f9d5e14f0d/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e29908922ce9da1a30b4da490bd1d3d82c01dcfdf864d2a74aacee674d0bfa34", size = 1979314, upload-time = "2026-04-20T14:41:49.446Z" }, - { url = "https://files.pythonhosted.org/packages/d8/36/9ff4d676dfbdfb2d591cf43f3d90ded01e15b1404fd101180ed2d62a2fd3/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c9ff69140423eea8ed2d5477df3ba037f671f5e897d206d921bc9fdc39613e7", size = 2056133, upload-time = "2026-04-20T14:42:23.574Z" }, - { url = "https://files.pythonhosted.org/packages/bc/f0/405b442a4d7ba855b06eec8b2bf9c617d43b8432d099dfdc7bf999293495/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b675ab0a0d5b1c8fdb81195dc5bcefea3f3c240871cdd7ff9a2de8aa50772eb2", size = 2228726, upload-time = "2026-04-20T14:44:22.816Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f8/65cd92dd5a0bd89ba277a98ecbfaf6fc36bbd3300973c7a4b826d6ab1391/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0087084960f209a9a4af50ecd1fb063d9ad3658c07bb81a7a53f452dacbfb2ba", size = 2301214, upload-time = "2026-04-20T14:44:48.792Z" }, - { url = "https://files.pythonhosted.org/packages/fd/86/ef96a4c6e79e7a2d0410826a68fbc0eccc0fd44aa733be199d5fcac3bb87/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed42e6cc8e1b0e2b9b96e2276bad70ae625d10d6d524aed0c93de974ae029f9f", size = 2099927, upload-time = "2026-04-20T14:41:40.196Z" }, - { url = "https://files.pythonhosted.org/packages/6d/53/269caf30e0096e0a8a8f929d1982a27b3879872cca2d917d17c2f9fdf4fe/pydantic_core-2.46.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:f1771ce258afb3e4201e67d154edbbae712a76a6081079fe247c2f53c6322c22", size = 2128789, upload-time = "2026-04-20T14:41:15.868Z" }, - { url = "https://files.pythonhosted.org/packages/00/b0/1a6d9b6a587e118482910c244a1c5acf4d192604174132efd12bf0ac486f/pydantic_core-2.46.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7610b6a5242a6c736d8ad47fd5fff87fcfe8f833b281b1c409c3d6835d9227f", size = 2173815, upload-time = "2026-04-20T14:44:25.152Z" }, - { url = "https://files.pythonhosted.org/packages/87/56/e7e00d4041a7e62b5a40815590114db3b535bf3ca0bf4dca9f16cef25246/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:ff5e7783bcc5476e1db448bf268f11cb257b1c276d3e89f00b5727be86dd0127", size = 2181608, upload-time = "2026-04-20T14:41:28.933Z" }, - { url = "https://files.pythonhosted.org/packages/e8/22/4bd23c3d41f7c185d60808a1de83c76cf5aeabf792f6c636a55c3b1ec7f9/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:9d2e32edcc143bc01e95300671915d9ca052d4f745aa0a49c48d4803f8a85f2c", size = 2326968, upload-time = "2026-04-20T14:42:03.962Z" }, - { url = "https://files.pythonhosted.org/packages/24/ac/66cd45129e3915e5ade3b292cb3bc7fd537f58f8f8dbdaba6170f7cabb74/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d83d1c6b87fa56b521479cff237e626a292f3b31b6345c15a99121b454c1", size = 2369842, upload-time = "2026-04-20T14:41:35.52Z" }, - { url = "https://files.pythonhosted.org/packages/a2/51/dd4248abb84113615473aa20d5545b7c4cd73c8644003b5259686f93996c/pydantic_core-2.46.3-cp314-cp314-win32.whl", hash = "sha256:07bc6d2a28c3adb4f7c6ae46aa4f2d2929af127f587ed44057af50bf1ce0f505", size = 1959661, upload-time = "2026-04-20T14:41:00.042Z" }, - { url = "https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl", hash = "sha256:8940562319bc621da30714617e6a7eaa6b98c84e8c685bcdc02d7ed5e7c7c44e", size = 2071686, upload-time = "2026-04-20T14:43:16.471Z" }, - { url = "https://files.pythonhosted.org/packages/8c/db/1cf77e5247047dfee34bc01fa9bca134854f528c8eb053e144298893d370/pydantic_core-2.46.3-cp314-cp314-win_arm64.whl", hash = "sha256:5dcbbcf4d22210ced8f837c96db941bdb078f419543472aca5d9a0bb7cddc7df", size = 2026907, upload-time = "2026-04-20T14:43:31.732Z" }, - { url = "https://files.pythonhosted.org/packages/57/c0/b3df9f6a543276eadba0a48487b082ca1f201745329d97dbfa287034a230/pydantic_core-2.46.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d0fe3dce1e836e418f912c1ad91c73357d03e556a4d286f441bf34fed2dbeecf", size = 2095047, upload-time = "2026-04-20T14:42:37.982Z" }, - { url = "https://files.pythonhosted.org/packages/66/57/886a938073b97556c168fd99e1a7305bb363cd30a6d2c76086bf0587b32a/pydantic_core-2.46.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9ce92e58abc722dac1bf835a6798a60b294e48eb0e625ec9fd994b932ac5feee", size = 1934329, upload-time = "2026-04-20T14:43:49.655Z" }, - { url = "https://files.pythonhosted.org/packages/0b/7c/b42eaa5c34b13b07ecb51da21761297a9b8eb43044c864a035999998f328/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03e6467f0f5ab796a486146d1b887b2dc5e5f9b3288898c1b1c3ad974e53e4a", size = 1974847, upload-time = "2026-04-20T14:42:10.737Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9b/92b42db6543e7de4f99ae977101a2967b63122d4b6cf7773812da2d7d5b5/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2798b6ba041b9d70acfb9071a2ea13c8456dd1e6a5555798e41ba7b0790e329c", size = 2041742, upload-time = "2026-04-20T14:40:44.262Z" }, - { url = "https://files.pythonhosted.org/packages/0f/19/46fbe1efabb5aa2834b43b9454e70f9a83ad9c338c1291e48bdc4fecf167/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9be3e221bdc6d69abf294dcf7aff6af19c31a5cdcc8f0aa3b14be29df4bd03b1", size = 2236235, upload-time = "2026-04-20T14:41:27.307Z" }, - { url = "https://files.pythonhosted.org/packages/77/da/b3f95bc009ad60ec53120f5d16c6faa8cabdbe8a20d83849a1f2b8728148/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13936129ce841f2a5ddf6f126fea3c43cd128807b5a59588c37cf10178c2e64", size = 2282633, upload-time = "2026-04-20T14:44:33.271Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6e/401336117722e28f32fb8220df676769d28ebdf08f2f4469646d404c43a3/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28b5f2ef03416facccb1c6ef744c69793175fd27e44ef15669201601cf423acb", size = 2109679, upload-time = "2026-04-20T14:44:41.065Z" }, - { url = "https://files.pythonhosted.org/packages/fc/53/b289f9bc8756a32fe718c46f55afaeaf8d489ee18d1a1e7be1db73f42cc4/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:830d1247d77ad23852314f069e9d7ddafeec5f684baf9d7e7065ed46a049c4e6", size = 2108342, upload-time = "2026-04-20T14:42:50.144Z" }, - { url = "https://files.pythonhosted.org/packages/10/5b/8292fc7c1f9111f1b2b7c1b0dcf1179edcd014fc3ea4517499f50b829d71/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0793c90c1a3c74966e7975eaef3ed30ebdff3260a0f815a62a22adc17e4c01c", size = 2157208, upload-time = "2026-04-20T14:42:08.133Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9e/f80044e9ec07580f057a89fc131f78dda7a58751ddf52bbe05eaf31db50f/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d2d0aead851b66f5245ec0c4fb2612ef457f8bbafefdf65a2bf9d6bac6140f47", size = 2167237, upload-time = "2026-04-20T14:42:25.412Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/6781a1b037f3b96be9227edbd1101f6d3946746056231bf4ac48cdff1a8d/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:2f40e4246676beb31c5ce77c38a55ca4e465c6b38d11ea1bd935420568e0b1ab", size = 2312540, upload-time = "2026-04-20T14:40:40.313Z" }, - { url = "https://files.pythonhosted.org/packages/3e/db/19c0839feeb728e7df03255581f198dfdf1c2aeb1e174a8420b63c5252e5/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:cf489cf8986c543939aeee17a09c04d6ffb43bfef8ca16fcbcc5cfdcbed24dba", size = 2369556, upload-time = "2026-04-20T14:41:09.427Z" }, - { url = "https://files.pythonhosted.org/packages/e0/15/3228774cb7cd45f5f721ddf1b2242747f4eb834d0c491f0c02d606f09fed/pydantic_core-2.46.3-cp314-cp314t-win32.whl", hash = "sha256:ffe0883b56cfc05798bf994164d2b2ff03efe2d22022a2bb080f3b626176dd56", size = 1949756, upload-time = "2026-04-20T14:41:25.717Z" }, - { url = "https://files.pythonhosted.org/packages/b8/2a/c79cf53fd91e5a87e30d481809f52f9a60dd221e39de66455cf04deaad37/pydantic_core-2.46.3-cp314-cp314t-win_amd64.whl", hash = "sha256:706d9d0ce9cf4593d07270d8e9f53b161f90c57d315aeec4fb4fd7a8b10240d8", size = 2051305, upload-time = "2026-04-20T14:43:18.627Z" }, - { url = "https://files.pythonhosted.org/packages/0b/db/d8182a7f1d9343a032265aae186eb063fe26ca4c40f256b21e8da4498e89/pydantic_core-2.46.3-cp314-cp314t-win_arm64.whl", hash = "sha256:77706aeb41df6a76568434701e0917da10692da28cb69d5fb6919ce5fdb07374", size = 2026310, upload-time = "2026-04-20T14:41:01.778Z" }, - { url = "https://files.pythonhosted.org/packages/66/7f/03dbad45cd3aa9083fbc93c210ae8b005af67e4136a14186950a747c6874/pydantic_core-2.46.3-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:9715525891ed524a0a1eb6d053c74d4d4ad5017677fb00af0b7c2644a31bae46", size = 2105683, upload-time = "2026-04-20T14:42:19.779Z" }, - { url = "https://files.pythonhosted.org/packages/26/22/4dc186ac8ea6b257e9855031f51b62a9637beac4d68ac06bee02f046f836/pydantic_core-2.46.3-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:9d2f400712a99a013aff420ef1eb9be077f8189a36c1e3ef87660b4e1088a874", size = 1940052, upload-time = "2026-04-20T14:43:59.274Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ca/d376391a5aff1f2e8188960d7873543608130a870961c2b6b5236627c116/pydantic_core-2.46.3-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd2aab0e2e9dc2daf36bd2686c982535d5e7b1d930a1344a7bb6e82baab42a76", size = 1988172, upload-time = "2026-04-20T14:41:17.469Z" }, - { url = "https://files.pythonhosted.org/packages/0e/6b/523b9f85c23788755d6ab949329de692a2e3a584bc6beb67fef5e035aa9d/pydantic_core-2.46.3-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e9d76736da5f362fabfeea6a69b13b7f2be405c6d6966f06b2f6bfff7e64531", size = 2128596, upload-time = "2026-04-20T14:40:41.707Z" }, - { url = "https://files.pythonhosted.org/packages/34/42/f426db557e8ab2791bc7562052299944a118655496fbff99914e564c0a94/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:b12dd51f1187c2eb489af8e20f880362db98e954b54ab792fa5d92e8bcc6b803", size = 2091877, upload-time = "2026-04-20T14:43:27.091Z" }, - { url = "https://files.pythonhosted.org/packages/5c/4f/86a832a9d14df58e663bfdf4627dc00d3317c2bd583c4fb23390b0f04b8e/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f00a0961b125f1a47af7bcc17f00782e12f4cd056f83416006b30111d941dfa3", size = 1932428, upload-time = "2026-04-20T14:40:45.781Z" }, - { url = "https://files.pythonhosted.org/packages/11/1a/fe857968954d93fb78e0d4b6df5c988c74c4aaa67181c60be7cfe327c0ca/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57697d7c056aca4bbb680200f96563e841a6386ac1129370a0102592f4dddff5", size = 1997550, upload-time = "2026-04-20T14:44:02.425Z" }, - { url = "https://files.pythonhosted.org/packages/17/eb/9d89ad2d9b0ba8cd65393d434471621b98912abb10fbe1df08e480ba57b5/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd35aa21299def8db7ef4fe5c4ff862941a9a158ca7b63d61e66fe67d30416b4", size = 2137657, upload-time = "2026-04-20T14:42:45.149Z" }, - { url = "https://files.pythonhosted.org/packages/1f/da/99d40830684f81dec901cac521b5b91c095394cc1084b9433393cde1c2df/pydantic_core-2.46.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:13afdd885f3d71280cf286b13b310ee0f7ccfefd1dbbb661514a474b726e2f25", size = 2107973, upload-time = "2026-04-20T14:42:06.175Z" }, - { url = "https://files.pythonhosted.org/packages/99/a5/87024121818d75bbb2a98ddbaf638e40e7a18b5e0f5492c9ca4b1b316107/pydantic_core-2.46.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f91c0aff3e3ee0928edd1232c57f643a7a003e6edf1860bc3afcdc749cb513f3", size = 1947191, upload-time = "2026-04-20T14:43:14.319Z" }, - { url = "https://files.pythonhosted.org/packages/60/62/0c1acfe10945b83a6a59d19fbaa92f48825381509e5701b855c08f13db76/pydantic_core-2.46.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6529d1d128321a58d30afcc97b49e98836542f68dd41b33c2e972bb9e5290536", size = 2123791, upload-time = "2026-04-20T14:43:22.766Z" }, - { url = "https://files.pythonhosted.org/packages/75/3e/3b2393b4c8f44285561dc30b00cf307a56a2eff7c483a824db3b8221ca51/pydantic_core-2.46.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:975c267cff4f7e7272eacbe50f6cc03ca9a3da4c4fbd66fffd89c94c1e311aa1", size = 2153197, upload-time = "2026-04-20T14:44:27.932Z" }, - { url = "https://files.pythonhosted.org/packages/ba/75/5af02fb35505051eee727c061f2881c555ab4f8ddb2d42da715a42c9731b/pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2b8e4f2bbdf71415c544b4b1138b8060db7b6611bc927e8064c769f64bed651c", size = 2181073, upload-time = "2026-04-20T14:43:20.729Z" }, - { url = "https://files.pythonhosted.org/packages/10/92/7e0e1bd9ca3c68305db037560ca2876f89b2647deb2f8b6319005de37505/pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e61ea8e9fff9606d09178f577ff8ccdd7206ff73d6552bcec18e1033c4254b85", size = 2315886, upload-time = "2026-04-20T14:44:04.826Z" }, - { url = "https://files.pythonhosted.org/packages/b8/d8/101655f27eaf3e44558ead736b2795d12500598beed4683f279396fa186e/pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b504bda01bafc69b6d3c7a0c7f039dcf60f47fab70e06fe23f57b5c75bdc82b8", size = 2360528, upload-time = "2026-04-20T14:40:47.431Z" }, - { url = "https://files.pythonhosted.org/packages/07/0f/1c34a74c8d07136f0d729ffe5e1fdab04fbdaa7684f61a92f92511a84a15/pydantic_core-2.46.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b00b76f7142fc60c762ce579bd29c8fa44aaa56592dd3c54fab3928d0d4ca6ff", size = 2184144, upload-time = "2026-04-20T14:42:57Z" }, -] - -[[package]] -name = "pydantic-extra-types" -version = "2.11.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/71/dba38ee2651f84f7842206adbd2233d8bbdb59fb85e9fa14232486a8c471/pydantic_extra_types-2.11.1.tar.gz", hash = "sha256:46792d2307383859e923d8fcefa82108b1a141f8a9c0198982b3832ab5ef1049", size = 172002, upload-time = "2026-03-16T08:08:03.92Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/c1/3226e6d7f5a4f736f38ac11a6fbb262d701889802595cdb0f53a885ac2e0/pydantic_extra_types-2.11.1-py3-none-any.whl", hash = "sha256:1722ea2bddae5628ace25f2aa685b69978ef533123e5638cfbddb999e0100ec1", size = 79526, upload-time = "2026-03-16T08:08:02.533Z" }, -] - -[[package]] -name = "pydantic-settings" -version = "2.14.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/98/c8345dccdc31de4228c039a98f6467a941e39558da41c1744fbe29fa5666/pydantic_settings-2.14.0.tar.gz", hash = "sha256:24285fd4b0e0c06507dd9fdfd331ee23794305352aaec8fc4eb92d4047aeb67d", size = 235709, upload-time = "2026-04-20T13:37:40.293Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/dd/bebff3040138f00ae8a102d426b27349b9a49acc310fcae7f92112d867e3/pydantic_settings-2.14.0-py3-none-any.whl", hash = "sha256:fc8d5d692eb7092e43c8647c1c35a3ecd00e040fcf02ed86f4cb5458ca62182e", size = 60940, upload-time = "2026-04-20T13:37:38.586Z" }, -] - -[[package]] -name = "pygments" -version = "2.20.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, -] - -[[package]] -name = "pyjwt" -version = "2.12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, -] - -[package.optional-dependencies] -crypto = [ - { name = "cryptography" }, -] - -[[package]] -name = "pymongo" -version = "4.17.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dnspython" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/64/50be6fbac9c79fe2e4c17401a467da2d8764d82833d83cec325afe5cab32/pymongo-4.17.0.tar.gz", hash = "sha256:70ffa08ba641468cc068cf46c06b34f01a8ce3489f6411309fcb5ceabe6b2fc0", size = 2523370, upload-time = "2026-04-20T16:39:53.524Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/77/28ebbf69772a4341d530831c7a006cdb06877ac23075cb53b0a227df4fe1/pymongo-4.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:47b021363cd923ace5edc7a1d63c0ff8a6d9d43859b8a1ba23645f5afae63221", size = 819234, upload-time = "2026-04-20T16:37:20.888Z" }, - { url = "https://files.pythonhosted.org/packages/88/cf/5a70cee503ff9a2fea20607607f14d189f4d975960ac0945ec306ee7b695/pymongo-4.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:422fa50d7d7f5c22ea0953554396c9ef95684a2d775f860bd75a7b510538dfca", size = 819969, upload-time = "2026-04-20T16:37:24.187Z" }, - { url = "https://files.pythonhosted.org/packages/23/d5/07b7e27e662c58d872efd104a0e8055eb6569aa1b6d4da436f3fdee7f897/pymongo-4.17.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:addd0498ebbdc6354227f6ed457ed9fce442d48a3bb30d5b5bad33e104996561", size = 1244510, upload-time = "2026-04-20T16:37:26.069Z" }, - { url = "https://files.pythonhosted.org/packages/fb/be/7cac5b1e89bd5a8e395067648241390321593a7c29243e36f91343c02a90/pymongo-4.17.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5c8e180cb2cabe37300e1e36c60aa4f2ff956cc579f0142135a5d2cba252243", size = 1263245, upload-time = "2026-04-20T16:37:28.003Z" }, - { url = "https://files.pythonhosted.org/packages/2e/20/40e8e99824c1fda18261411e65ce3b0cd3d9a6ed3c056cdd0a569adc870b/pymongo-4.17.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bd835cdb37a1adec359dd072c24f8bb14809e2644fde86fab4ee2fc9719b9483", size = 1304113, upload-time = "2026-04-20T16:37:30.048Z" }, - { url = "https://files.pythonhosted.org/packages/3a/94/fb7e25441dd66f2069a9b172380849b0eaa5881c18b3db217bf64a6d393c/pymongo-4.17.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c4979e7e8887862bbb44d203f00cc8263a3f27237876fa691b6beba23e40e6d8", size = 1297046, upload-time = "2026-04-20T16:37:32.054Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c9/7352e0c20fe772541556e4d283c05e07ec48f8b0d2737ad930ac4a1b6655/pymongo-4.17.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:77aa4bc164b4de60d5db193b322f0f5b6ead716e831031bfdef8e8bd92205556", size = 1265708, upload-time = "2026-04-20T16:37:33.934Z" }, - { url = "https://files.pythonhosted.org/packages/8d/e4/3df15494c2015ed297958517f0e4f6493e21b00990748068a973e66d45e0/pymongo-4.17.0-cp310-cp310-win32.whl", hash = "sha256:48bbc576677b50af043df870d84ded67cc3a9b4aa7553201beef4da5dc050a0a", size = 805533, upload-time = "2026-04-20T16:37:35.744Z" }, - { url = "https://files.pythonhosted.org/packages/22/fa/b4e71bb8cb82ad7d21bb4e8c476f2d573ba68b20368aac36ef06e4a196b4/pymongo-4.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46767f28dea610e02edf6c5d956ce615c3c7790ea396660b9b1efd5c5ead2e0", size = 815677, upload-time = "2026-04-20T16:37:37.808Z" }, - { url = "https://files.pythonhosted.org/packages/22/e2/0a4bba644f1cda3970ea1012149eeae3594ebfeed3f81fdaf32b61d90c95/pymongo-4.17.0-cp310-cp310-win_arm64.whl", hash = "sha256:757f2a4c0c2c46cab87df0333681ce69e86c9d5b45bc5203ceba5410b3489e59", size = 807293, upload-time = "2026-04-20T16:37:39.707Z" }, - { url = "https://files.pythonhosted.org/packages/c4/e2/336d86f221cf1b56b2ed9330d4a3b98f9f38f0b37829ae9a9184617d5419/pymongo-4.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4141e6c6a339789b2974efa00ecd9409101672d77a0e3ee2cc3839eedf8ec4df", size = 874668, upload-time = "2026-04-20T16:37:41.39Z" }, - { url = "https://files.pythonhosted.org/packages/34/8e/75d3c6c935d187ab59c61e9c15d9aab3f274b563eaf1706e8cae5f508dec/pymongo-4.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e68c76b84e0c132d9dbf9307f12ff8185702328187a87b9aca8c941303873433", size = 875294, upload-time = "2026-04-20T16:37:43.432Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ec/62e855744489dbcd54fd778aae4d80fa4c4819e8fb228ca0cf6f21a03997/pymongo-4.17.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ba2195d4f386f839a52a23ea1cfd60ffaaba78a3d7841db51b7e433001139918", size = 1496233, upload-time = "2026-04-20T16:37:45.518Z" }, - { url = "https://files.pythonhosted.org/packages/82/e8/93e4e5e5ce8fdf8929dabeefe24aafa5ce046028eed0dfa8eeb936e72c49/pymongo-4.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446ff4bfcb6ec2a2e50998c860986a1e992136f998b7f53e7a717fb8aa5a0b9", size = 1522927, upload-time = "2026-04-20T16:37:47.492Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ca/425dc1d21e0f17bdea0072fc463f662f7fa06d2852af52975c9eced3c07c/pymongo-4.17.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2a0d5ac205728c86e0a02192f1aa5f865b0d7d51f8df6101c01a69a7fc620d72", size = 1583468, upload-time = "2026-04-20T16:37:49.221Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9d/f08b07eeffda1a43c1759f0fa625e88ae12360996eb56d42aad832fa7dff/pymongo-4.17.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:485c8a8eaa4c739f00a331fc73757898ee7c092c214a79e63866ff76aaf282ff", size = 1572787, upload-time = "2026-04-20T16:37:51.061Z" }, - { url = "https://files.pythonhosted.org/packages/e9/c2/6855a07aafa7b894929af23675b6fb9634800ce43122b76a62f6eeb8da2a/pymongo-4.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b2dfcc795f5b9fedbe179a11fdf6051581479d196582a3fe819a92a00e9b9969", size = 1526184, upload-time = "2026-04-20T16:37:53.358Z" }, - { url = "https://files.pythonhosted.org/packages/4e/05/c952bac7db71c1942ea3559fcd308b49754cc5004b455935fb4000d1f37b/pymongo-4.17.0-cp311-cp311-win32.whl", hash = "sha256:c2292144505fb12156b981bd440f3dc994a883da06ac726c0c8692ccdbc1c510", size = 852621, upload-time = "2026-04-20T16:37:55.28Z" }, - { url = "https://files.pythonhosted.org/packages/11/c0/c04da9f4c0c6252404598f4e394b862a58a9e866822a70ae261c8a018fdf/pymongo-4.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:2e190827834fce70ecdf9d46796c6dbc0ce08ea87dc2ff5bc6f3f5579b605cb9", size = 867852, upload-time = "2026-04-20T16:37:57.233Z" }, - { url = "https://files.pythonhosted.org/packages/1d/b2/c7b4870fbeef471e947d3e014676f5910d02e0197074d692ebcf24ec049a/pymongo-4.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:a8f9c40a09bb7d4b9fc8b1da65ecf6efa79bda5cb2756f39d9b6940fac1d19ae", size = 855019, upload-time = "2026-04-20T16:37:58.983Z" }, - { url = "https://files.pythonhosted.org/packages/98/90/60bcb508840135d5ee46b51b1a950f548338aa8145a8366dbe6639ae51ac/pymongo-4.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53ffa94b2340dbf6b055e09a0090618c60482c158ecfc9565642fc996bf0944", size = 930529, upload-time = "2026-04-20T16:38:00.936Z" }, - { url = "https://files.pythonhosted.org/packages/a6/e9/313840f1e52c6dfac47f704428cbfbce59956ebe7633bffc92b03f74f0ad/pymongo-4.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6fe0de9d0f6791abce3471230b32b4817bf89d27b1182b6a550e1ec0fa72aa9a", size = 930665, upload-time = "2026-04-20T16:38:02.915Z" }, - { url = "https://files.pythonhosted.org/packages/78/35/9d3565ea45b1606f635c1e2cd2563c28d66caafdc50f7ad7d979fcd1b363/pymongo-4.17.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e537e95514dae1aaa718f481ec03151a0f0394bcd05f1322896d8fc1330cb729", size = 1762369, upload-time = "2026-04-20T16:38:05.375Z" }, - { url = "https://files.pythonhosted.org/packages/95/ee/149b0d4b1a11c38bff6f14c23d5814c9b0843fd6dc38ad40596bdb1a62d2/pymongo-4.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37a8385c29881b43eab31f584100fa0eaddedd5607adf010147ba1810118be90", size = 1798044, upload-time = "2026-04-20T16:38:07.195Z" }, - { url = "https://files.pythonhosted.org/packages/7b/d4/4cee4a7b8d8f6f0550ef6cd2fea42455c5ed619a220cb6ba4fb40d6a5bc8/pymongo-4.17.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f3ee3d241ed77a4fc99ce3cff3b289c3ebce37f61fdd7349d3592c23b82c8784", size = 1878567, upload-time = "2026-04-20T16:38:09.121Z" }, - { url = "https://files.pythonhosted.org/packages/45/ef/7fe366c84952619ee2f69973566c214775e083dd4df465751912153e4b72/pymongo-4.17.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9eb5d63a3c518cb0804ed678f5e2b875af032d89a7cf57a57360322cf6a4d222", size = 1864881, upload-time = "2026-04-20T16:38:10.896Z" }, - { url = "https://files.pythonhosted.org/packages/2f/35/b577d82c6d1be7aee7ac7e249bc86f7847998345042e5f8360de238e177b/pymongo-4.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e97e03fa13327c87e3fdc5656acd01e71817f0c1dc3221cd8f30de136bf4ec3", size = 1800349, upload-time = "2026-04-20T16:38:13.589Z" }, - { url = "https://files.pythonhosted.org/packages/b8/69/dafcf04f66e130ddd91aeb92e7a692480eda46dcd04ec1dbe82c06619e10/pymongo-4.17.0-cp312-cp312-win32.whl", hash = "sha256:6877214bff5f06f6884a9fc8d9016a4a7a5f51f537f5c51ac3a576f93e7dfb32", size = 900518, upload-time = "2026-04-20T16:38:15.541Z" }, - { url = "https://files.pythonhosted.org/packages/11/35/5c9262a459f988b4eb2605f70815240b77a0d4131136c4326d18f1822b89/pymongo-4.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:9828485f72f63c7d802e0ec41f71906f633c2692621ab3af55ca990186b091b1", size = 920335, upload-time = "2026-04-20T16:38:17.665Z" }, - { url = "https://files.pythonhosted.org/packages/8d/da/e9c7265ee176faccf4e52c4797837e794d93569a1046f6b19a4acc36e5ad/pymongo-4.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:1195370a77baf003b59b10e91ecc4706297197f0dd9d29c840cc556dc08f7cee", size = 903289, upload-time = "2026-04-20T16:38:19.33Z" }, - { url = "https://files.pythonhosted.org/packages/2a/6b/c1206879708b94e82fcd8b9653440ec271f79a3674d122192df383047f5a/pymongo-4.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:809ec74de3b9148ae43fa8df9faf53470f511c8d384f13b99d6f671f2a379f15", size = 985829, upload-time = "2026-04-20T16:38:21.031Z" }, - { url = "https://files.pythonhosted.org/packages/cb/cf/bb044ed85160e5c40f568c7c4f4e8ea16f40764ff5d302e5befbe8f6f814/pymongo-4.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a431b737816bf4cddd4fa0fcef04e424ad36b7692734a64150f872fb8f3208be", size = 985899, upload-time = "2026-04-20T16:38:23.409Z" }, - { url = "https://files.pythonhosted.org/packages/74/0a/f6dfd5ea3901e5d6888da8de8ba728971a1d447debab681cfc56f90d1208/pymongo-4.17.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e4fab10f8403169ce92f3cea921609d9ee81107306caae06c08f592d4b8ad2b5", size = 2028569, upload-time = "2026-04-20T16:38:25.343Z" }, - { url = "https://files.pythonhosted.org/packages/4a/c5/081f59a1c02ae8c0dc73ae58e563838c44eec81aeafa7d0b93a637841c9b/pymongo-4.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20323b0b1c1d33770ad1fc68d429c757734ce9ad3594421c3d6618f10572b1b9", size = 2072916, upload-time = "2026-04-20T16:38:27.291Z" }, - { url = "https://files.pythonhosted.org/packages/31/42/6e41d434297ffe8b30d9c3717916591a4a7be9075a0dcc2fafdfaaaa62ed/pymongo-4.17.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5a5de048e6da5c18e27cc2437e8c15b3b0cdc8385c15b41178b0caa3322a09c2", size = 2173234, upload-time = "2026-04-20T16:38:29.474Z" }, - { url = "https://files.pythonhosted.org/packages/3d/cf/1e4a7db352ef9485831c7268dfe8402f0117b32a9ad54b16e810699e3617/pymongo-4.17.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dff3de1294fbbc1db0ba6b511f77b8e540601d092538a31312e99c8a91a78b1e", size = 2156784, upload-time = "2026-04-20T16:38:32.134Z" }, - { url = "https://files.pythonhosted.org/packages/12/10/6195be29962a61ebb5f4bd9e4c7519890b172f7968a0a0d880398c6ddb02/pymongo-4.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:faf03e4c2aafd6de626dbd30ba246d369ae33f47f10629d1bbe40f72115027a6", size = 2074446, upload-time = "2026-04-20T16:38:34.004Z" }, - { url = "https://files.pythonhosted.org/packages/37/48/33410b8819837ed370c738587306bdf060b59cef11823be212f4a07703c5/pymongo-4.17.0-cp313-cp313-win32.whl", hash = "sha256:c9786665926a09630c5d420c79762cfadbff35a9438bcbc4c81a9fb5ab9228b7", size = 948435, upload-time = "2026-04-20T16:38:35.922Z" }, - { url = "https://files.pythonhosted.org/packages/6f/77/c0ed522f798a286b99acaa7914ed8d9c80ab091f97f57c59ffed72906e5e/pymongo-4.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:5960519b4d7168f1ecdd3ea10c81b2aedeb9423651aca953cfbc8e76705d3b38", size = 972847, upload-time = "2026-04-20T16:38:37.888Z" }, - { url = "https://files.pythonhosted.org/packages/97/f0/c39480a2db385fde23861d0c8acda41cdaf1d43e46579db72c5c013a2e81/pymongo-4.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:0ff6bd2f735ab5356541e3e57d5b7dbfbc3f2ee1ccb10b6b0f82d58af69d1d8e", size = 951575, upload-time = "2026-04-20T16:38:40.544Z" }, - { url = "https://files.pythonhosted.org/packages/da/49/2b0250762a89737ed6f9cea238331baca061b89a8ddd10dd17fee52c3970/pymongo-4.17.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ff5aa3f1c7e3f08eb0e7a016c91ba468b1850ccfd63d9b1f12f56350f4974cef", size = 1040945, upload-time = "2026-04-20T16:38:42.783Z" }, - { url = "https://files.pythonhosted.org/packages/89/1c/7a9b5447a08be20e84b6e5b17330917e8d6d9507daa3cd099a9309f11ad7/pymongo-4.17.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e816db649ba5d7de0568cf3a9f287a9dc9aad21cf0ca667ab156a7ef47fca0b0", size = 1041187, upload-time = "2026-04-20T16:38:45.358Z" }, - { url = "https://files.pythonhosted.org/packages/78/a1/71704f61632dfc90407a5834fe5f6132854937c4a3648f6c05c351d85a45/pymongo-4.17.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c4fded3a9f1d6a687e36ebd384ac6d00b9b00de1969aa74048e7051ec2a713", size = 2294806, upload-time = "2026-04-20T16:38:47.734Z" }, - { url = "https://files.pythonhosted.org/packages/ad/b9/aff42be75108b96c2469b1d9329b912c15108f3e7ef32fdc86da8423c330/pymongo-4.17.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2db66aa8dd253a0fc1fad3b0d23d5b3993f7ebde02fbbd7727128debf2853675", size = 2348231, upload-time = "2026-04-20T16:38:50.371Z" }, - { url = "https://files.pythonhosted.org/packages/f2/30/44c115b8ba1479942c15fd9480eb29a7da0ba68acd56983423ba0deb4a94/pymongo-4.17.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3987e96e7c7be4083d42e8ac2cc6c0d5b78db9973c90fce42ae800b616ca6b20", size = 2467614, upload-time = "2026-04-20T16:38:52.665Z" }, - { url = "https://files.pythonhosted.org/packages/d2/84/21ee95c8bf0ca7acae7ec7eb365d740bf8fc0156c194baf2c3bdfcb85ec0/pymongo-4.17.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cee36b3c0d0354f880fa7a7fdcdaf2bb5e542c2281e25c1bfadf8cfe21eba7d2", size = 2445970, upload-time = "2026-04-20T16:38:55.175Z" }, - { url = "https://files.pythonhosted.org/packages/06/89/081d7f1809d5ca09d1e47e49f2111b245f5694de3a7af32cd3a353a6f43f/pymongo-4.17.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:320b34457b20bbcc79997801f95d25ce00472915ca5241167242b42c4359e027", size = 2348605, upload-time = "2026-04-20T16:38:57.557Z" }, - { url = "https://files.pythonhosted.org/packages/ea/c3/0d949f9d3f2a341c1f635c398c16615e96f89f51ff424ed81e914cf1a4de/pymongo-4.17.0-cp314-cp314-win32.whl", hash = "sha256:df4a644af9ae132d4bfdb2e9516ea51a615fd881caddfbfbd071cf1354844479", size = 1004119, upload-time = "2026-04-20T16:39:00.309Z" }, - { url = "https://files.pythonhosted.org/packages/f7/55/5c3a3db1048054c695c75c5964cc8bedc2247fdb5a75ef6fab4ec8bb013e/pymongo-4.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:c797f8a80957134f6dd9690367a0f8f5906d672119af2c6aa55f0c527b656bed", size = 1032314, upload-time = "2026-04-20T16:39:02.665Z" }, - { url = "https://files.pythonhosted.org/packages/e0/19/e235f39906134cb0ffd5574c5a59c355ef5380f0499644ab94994afbb109/pymongo-4.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:68fca71e05ee5da23a8d73cee8379dfb3d26e609a377cae731d742771ed96946", size = 1007627, upload-time = "2026-04-20T16:39:04.678Z" }, - { url = "https://files.pythonhosted.org/packages/1e/e0/c4c1a86791415b14c684fa0908f9da96de91594a3fd1fa1b8dc689fbb800/pymongo-4.17.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b4384700cffc3f1dd98e088bc0072dedf6d7d68a230bb4b972665cf69c071c1e", size = 1099151, upload-time = "2026-04-20T16:39:06.969Z" }, - { url = "https://files.pythonhosted.org/packages/81/4b/69c67f3e23fd9b23b9bedc7ebd23754881cc9d5c5d5b2a9811e96b07f475/pymongo-4.17.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:93641192644fa1ee0f34030e774fd31022a27ad11ba22cb1716142231524f8bd", size = 1099346, upload-time = "2026-04-20T16:39:08.996Z" }, - { url = "https://files.pythonhosted.org/packages/a2/19/a5208f62f9508a26d73acc69bd3821b8c8adae253679a3c26d2f9652f0d5/pymongo-4.17.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:75bc3aa5b94fdb7138d357ec6ca61cd97e0c79f4f7f0bd3efe9639b15cc50942", size = 2619034, upload-time = "2026-04-20T16:39:11.049Z" }, - { url = "https://files.pythonhosted.org/packages/77/27/426cba1ec5973082a56d4150798529bfdf4151c31391ed1fbbecb23ef2ac/pymongo-4.17.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e8f8e23c6df7c6d6929f5e734980b227706e73ee847517c9ba5af90f7fc466", size = 2689939, upload-time = "2026-04-20T16:39:13.617Z" }, - { url = "https://files.pythonhosted.org/packages/ef/2e/f70993d1255e33f6ee59a4ec4371cc65bff7a7e3fda7d55c3386f25287e8/pymongo-4.17.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:15d3f3d732aecac1f8d481bde4029755615639bd3076f258a2147210aec8515a", size = 2824994, upload-time = "2026-04-20T16:39:16.057Z" }, - { url = "https://files.pythonhosted.org/packages/b3/eb/87b0e988ba889e1fcc3430c2cfc166b251872c813e92b43174298bee17ff/pymongo-4.17.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5f62862d0f87be481fa1fe8cb811994486773c94a2b61e509285e3f2890763", size = 2801745, upload-time = "2026-04-20T16:39:18.476Z" }, - { url = "https://files.pythonhosted.org/packages/67/4c/3f83412d086f682d4d468761d66ddc49cf161e786ea74073045eb4491c60/pymongo-4.17.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64837adbbd72073301af51bb0fc80e3d7707fe5527cea1033ba0320f0b2f881b", size = 2684636, upload-time = "2026-04-20T16:39:20.878Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d8/b75f6f4ab6c8beb50b0270a4f1e2530b5774f5e116563440e1677ca1820f/pymongo-4.17.0-cp314-cp314t-win32.whl", hash = "sha256:b93b22eedc62598cf5ee9d8c8007a8e9121c50fd88137012d8985500e9dc3151", size = 1056356, upload-time = "2026-04-20T16:39:22.996Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5e/648c8a238eef18a25ed8a169ea6542d4a860bbec3e95b3d9badac2935c71/pymongo-4.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3689ea34f6b647c7d1e7bdc60fcfb214b2789ed1359a7fb96569c69f50e5f18f", size = 1090964, upload-time = "2026-04-20T16:39:24.989Z" }, - { url = "https://files.pythonhosted.org/packages/dc/cb/d9780b66939c4fc1f024bcc7be23a2abcfe06a9745ca8fa76dc73395482e/pymongo-4.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9543d8f84c2e5608565c08ac679774811e6730770d8a645439b073422a4276fb", size = 1058526, upload-time = "2026-04-20T16:39:27.924Z" }, -] - -[[package]] -name = "pyopenssl" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/04/8c/cd89ad05804f8e3c17dea8f178c3f40eeab5694c30e0c9f5bcd49f576fc3/pyopenssl-25.1.0.tar.gz", hash = "sha256:8d031884482e0c67ee92bf9a4d8cceb08d92aba7136432ffb0703c5280fc205b", size = 179937, upload-time = "2025-05-17T16:28:31.31Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/80/28/2659c02301b9500751f8d42f9a6632e1508aa5120de5e43042b8b30f8d5d/pyopenssl-25.1.0-py3-none-any.whl", hash = "sha256:2b11f239acc47ac2e5aca04fd7fa829800aeee22a2eb30d744572a157bd8a1ab", size = 56771, upload-time = "2025-05-17T16:28:29.197Z" }, -] - -[[package]] -name = "pytest" -version = "9.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, -] - -[[package]] -name = "pytest-asyncio" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, - { name = "pytest" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, -] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - -[[package]] -name = "python-dotenv" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, -] - -[[package]] -name = "python-jose" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ecdsa" }, - { name = "pyasn1" }, - { name = "rsa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c6/77/3a1c9039db7124eb039772b935f2244fbb73fc8ee65b9acf2375da1c07bf/python_jose-3.5.0.tar.gz", hash = "sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b", size = 92726, upload-time = "2025-05-28T17:31:54.288Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/c3/0bd11992072e6a1c513b16500a5d07f91a24017c5909b02c72c62d7ad024/python_jose-3.5.0-py2.py3-none-any.whl", hash = "sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771", size = 34624, upload-time = "2025-05-28T17:31:52.802Z" }, -] - -[package.optional-dependencies] -cryptography = [ - { name = "cryptography" }, -] - -[[package]] -name = "python-multipart" -version = "0.0.27" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/9b/f23807317a113dc36e74e75eb265a02dd1a4d9082abc3c1064acd22997c4/python_multipart-0.0.27.tar.gz", hash = "sha256:9870a6a8c5a20a5bf4f07c017bd1489006ff8836cff097b6933355ee2b49b602", size = 44043, upload-time = "2026-04-27T10:51:26.649Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/78/4126abcbdbd3c559d43e0db7f7b9173fc6befe45d39a2856cc0b8ec2a5a6/python_multipart-0.0.27-py3-none-any.whl", hash = "sha256:6fccfad17a27334bd0193681b369f476eda3409f17381a2d65aa7df3f7275645", size = 29254, upload-time = "2026-04-27T10:51:24.997Z" }, -] - -[[package]] -name = "pywin32" -version = "312" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/1b/9cfdeac80ee45bebbbcb31f1b7b99a0d81a1c72de48d837be984e0e88b1d/pywin32-312-cp310-cp310-win32.whl", hash = "sha256:772235332b5d1024c696f11cea1ae4be7930f0a8b894bb43db14e3f435f1ff7e", size = 6361387, upload-time = "2026-06-04T07:49:14.329Z" }, - { url = "https://files.pythonhosted.org/packages/33/b1/7afc96d041d982c27bc2df6f853d43f01fd273e3d39d04be3647ddeb533d/pywin32-312-cp310-cp310-win_amd64.whl", hash = "sha256:5dbc35d2b5320dc07f25fa31269cfb767471002b17de5eb067d03da68c7cb2db", size = 6926780, upload-time = "2026-06-04T07:49:16.881Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3a/4140da9ad54108e517f4a16b2d83da3033e08662144623e1239587cb7db6/pywin32-312-cp310-cp310-win_arm64.whl", hash = "sha256:3020656e34f1cf7faeb7bccd2b84653a607c6ff0c55ada85e6487d61716deabd", size = 4307203, upload-time = "2026-06-04T07:49:18.993Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f5/10a6e845a00fc5e7afd0a988b744f403d4d57162a28d160a093c4d9322f0/pywin32-312-cp311-cp311-win32.whl", hash = "sha256:17948aeadbdb091f0ced6ef0841620794e68327b94ee415571c1203594b7215c", size = 6362659, upload-time = "2026-06-04T07:49:21.349Z" }, - { url = "https://files.pythonhosted.org/packages/35/c4/dcd2d62b5944b6d5db53413a5899016ccd57ffcb7278f3f81655d25d2027/pywin32-312-cp311-cp311-win_amd64.whl", hash = "sha256:d11417d84412f859b722fad0841b3614459ed0047f7542d8362e77884f6b6e8a", size = 6928825, upload-time = "2026-06-04T07:49:23.934Z" }, - { url = "https://files.pythonhosted.org/packages/b7/56/3cbb433fe4501cdba2eb9040f56a4e1a8243faa4186b25295564d1a7a79d/pywin32-312-cp311-cp311-win_arm64.whl", hash = "sha256:b2200a054ca6d6625c4842fc56a4976a4b47f96b73dbe5538c3f813a80359f47", size = 6721875, upload-time = "2026-06-04T07:49:26.416Z" }, - { url = "https://files.pythonhosted.org/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877, upload-time = "2026-06-04T07:49:28.836Z" }, - { url = "https://files.pythonhosted.org/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841, upload-time = "2026-06-04T07:49:31.85Z" }, - { url = "https://files.pythonhosted.org/packages/e3/cc/7b1ec671775756020a0ee7f4feeaf3c568f0ab86bd3900088cf986937a92/pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950", size = 6727901, upload-time = "2026-06-04T07:49:34.244Z" }, - { url = "https://files.pythonhosted.org/packages/2d/41/12fbfd7f36ed2146d8bc9de96c2741296bf0d490b98508496cff322e274c/pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c", size = 6370184, upload-time = "2026-06-04T07:49:36.253Z" }, - { url = "https://files.pythonhosted.org/packages/ba/db/36a78e3403099d31d9746d13fdcde5accc43c1155f375a34d15983a479a7/pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9", size = 6914298, upload-time = "2026-06-04T07:49:38.876Z" }, - { url = "https://files.pythonhosted.org/packages/84/37/c1697194092b76de9ed47ca124323f02c57ffc8a45c06f88a3d5acaf01eb/pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831", size = 6727640, upload-time = "2026-06-04T07:49:41.083Z" }, - { url = "https://files.pythonhosted.org/packages/fc/2b/1f3cded5822fd49c02f40544cbb5f58c7cfd6b1694869fd476cb6170ee97/pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b", size = 6468928, upload-time = "2026-06-04T07:49:43.188Z" }, - { url = "https://files.pythonhosted.org/packages/21/82/3bf86d2e2808902013132e1ce905a7da0da53790f3836c64bf44d55e24f3/pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e", size = 7024157, upload-time = "2026-06-04T07:49:45.34Z" }, - { url = "https://files.pythonhosted.org/packages/a4/0e/73f6d6800b4f27655abd9e9f6aaeaefcddb2b946e4674efa2bab184a7f7b/pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa", size = 6839598, upload-time = "2026-06-04T07:49:47.613Z" }, - { url = "https://files.pythonhosted.org/packages/eb/61/caa39686032d2ebdd04ff0ab5cbe163126c0066d98e00c9018646e42393b/pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed", size = 6471159, upload-time = "2026-06-04T07:49:50.035Z" }, - { url = "https://files.pythonhosted.org/packages/0f/cd/7e1de64a4a6f69c04214169657ccab0d93a670ea50e35eb8f489d7378249/pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5", size = 7025293, upload-time = "2026-06-04T07:49:54.857Z" }, - { url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" }, -] - -[[package]] -name = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, - { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, - { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, - { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, - { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, - { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, - { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, - { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, - { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, -] - -[[package]] -name = "redis" -version = "8.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/53/ae/ed461cca5780b5fc8b9fe8ca0ed98d89508645fb9d880c24cc42c087678f/redis-8.0.0.tar.gz", hash = "sha256:a00c5355432051ac14e593b8b197fc76c887ee12d55a0984f69328a1115fdc49", size = 5101591, upload-time = "2026-05-28T12:45:13.5Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/e3/b519734372d305bd547534a9f32e4ce9f98552af753dce72cf3483a0ff0b/redis-8.0.0-py3-none-any.whl", hash = "sha256:c938c18338585009f0bc310f4c7e4e4b4d37639356c4ac072cedf3af570c8dc7", size = 499870, upload-time = "2026-05-28T12:45:11.697Z" }, -] - -[[package]] -name = "requests" -version = "2.31.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794, upload-time = "2023-05-22T15:12:44.175Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574, upload-time = "2023-05-22T15:12:42.313Z" }, -] - -[[package]] -name = "resend" -version = "0.8.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3c/44/42b53c594056acd28002b5898ef0f503ebd6cbb577c45837dc539e7f4858/resend-0.8.0.tar.gz", hash = "sha256:94142394701724dbcfcd8f760f675c662a1025013e741dd7cc773ca885526257", size = 7138, upload-time = "2024-03-06T02:49:10.644Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/60/b0263aa0edab4b35c13f1bc37de6b034a212ba57f94bb167db32aaecd278/resend-0.8.0-py2.py3-none-any.whl", hash = "sha256:adc1515dadf4f4fc6b90db55a237f0f37fc56fd74287a986519a8a187fdb661d", size = 8760, upload-time = "2024-03-06T02:49:07.732Z" }, -] - -[[package]] -name = "rich" -version = "15.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, -] - -[[package]] -name = "rich-toolkit" -version = "0.19.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "rich" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/ba/dae9e3096651042754da419a4042bc1c75e07d615f9b15066d738838e4df/rich_toolkit-0.19.7.tar.gz", hash = "sha256:133c0915872da91d4c25d85342d5ec1dfacc69b63448af1a08a0d4b4f23ef46e", size = 195877, upload-time = "2026-02-24T16:06:20.555Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/3c/c923619f6d2f5fafcc96fec0aaf9550a46cd5b6481f06e0c6b66a2a4fed0/rich_toolkit-0.19.7-py3-none-any.whl", hash = "sha256:0288e9203728c47c5a4eb60fd2f0692d9df7455a65901ab6f898437a2ba5989d", size = 32963, upload-time = "2026-02-24T16:06:22.066Z" }, -] - -[[package]] -name = "rignore" -version = "0.7.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e5/f5/8bed2310abe4ae04b67a38374a4d311dd85220f5d8da56f47ae9361be0b0/rignore-0.7.6.tar.gz", hash = "sha256:00d3546cd793c30cb17921ce674d2c8f3a4b00501cb0e3dd0e82217dbeba2671", size = 57140, upload-time = "2025-11-05T21:41:21.968Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/7a/b970cd0138b0ece72eb28f086e933f9ed75b795716ad3de5ab22994b3b54/rignore-0.7.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f3c74a7e5ee77aea669c95fdb3933f2a6c7549893700082e759128a29cf67e45", size = 884999, upload-time = "2025-11-05T20:42:38.373Z" }, - { url = "https://files.pythonhosted.org/packages/ca/05/23faca29616d8966ada63fb0e13c214107811fa9a0aba2275e4c7ca63bd5/rignore-0.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7202404958f5fe3474bac91f65350f0b1dde1a5e05089f2946549b7e91e79ec", size = 824824, upload-time = "2025-11-05T20:42:22.1Z" }, - { url = "https://files.pythonhosted.org/packages/fa/2e/05a1e61f04cf2548524224f0b5f21ca19ea58f7273a863bac10846b8ff69/rignore-0.7.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bde7c5835fa3905bfb7e329a4f1d7eccb676de63da7a3f934ddd5c06df20597", size = 899121, upload-time = "2025-11-05T20:40:48.94Z" }, - { url = "https://files.pythonhosted.org/packages/ff/35/71518847e10bdbf359badad8800e4681757a01f4777b3c5e03dbde8a42d8/rignore-0.7.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:626c3d4ba03af266694d25101bc1d8d16eda49c5feb86cedfec31c614fceca7d", size = 873813, upload-time = "2025-11-05T20:41:04.71Z" }, - { url = "https://files.pythonhosted.org/packages/f6/c8/32ae405d3e7fd4d9f9b7838f2fcca0a5005bb87fa514b83f83fd81c0df22/rignore-0.7.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a43841e651e7a05a4274b9026cc408d1912e64016ede8cd4c145dae5d0635be", size = 1168019, upload-time = "2025-11-05T20:41:20.723Z" }, - { url = "https://files.pythonhosted.org/packages/25/98/013c955982bc5b4719bf9a5bea58be317eea28aa12bfd004025e3cd7c000/rignore-0.7.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7978c498dbf7f74d30cdb8859fe612167d8247f0acd377ae85180e34490725da", size = 942822, upload-time = "2025-11-05T20:41:36.99Z" }, - { url = "https://files.pythonhosted.org/packages/90/fb/9a3f3156c6ed30bcd597e63690353edac1fcffe9d382ad517722b56ac195/rignore-0.7.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d22f72ab695c07d2d96d2a645208daff17084441b5d58c07378c9dd6f9c4c87", size = 959820, upload-time = "2025-11-05T20:42:06.364Z" }, - { url = "https://files.pythonhosted.org/packages/5e/b2/93bf609633021e9658acaff24cfb055d8cdaf7f5855d10ebb35307900dda/rignore-0.7.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5bd8e1a91ed1a789b2cbe39eeea9204a6719d4f2cf443a9544b521a285a295f", size = 985050, upload-time = "2025-11-05T20:41:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/69/bc/ec2d040469bdfd7b743df10f2201c5d285009a4263d506edbf7a06a090bb/rignore-0.7.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fc03efad5789365018e94ac4079f851a999bc154d1551c45179f7fcf45322", size = 1079164, upload-time = "2025-11-05T21:40:10.368Z" }, - { url = "https://files.pythonhosted.org/packages/df/26/4b635f4ea5baf4baa8ba8eee06163f6af6e76dfbe72deb57da34bb24b19d/rignore-0.7.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ce2617fe28c51367fd8abfd4eeea9e61664af63c17d4ea00353d8ef56dfb95fa", size = 1139028, upload-time = "2025-11-05T21:40:27.977Z" }, - { url = "https://files.pythonhosted.org/packages/6a/54/a3147ebd1e477b06eb24e2c2c56d951ae5faa9045b7b36d7892fec5080d9/rignore-0.7.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7c4ad2cee85068408e7819a38243043214e2c3047e9bd4c506f8de01c302709e", size = 1119024, upload-time = "2025-11-05T21:40:45.148Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f4/27475db769a57cff18fe7e7267b36e6cdb5b1281caa185ba544171106cba/rignore-0.7.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:02cd240bfd59ecc3907766f4839cbba20530a2e470abca09eaa82225e4d946fb", size = 1128531, upload-time = "2025-11-05T21:41:02.734Z" }, - { url = "https://files.pythonhosted.org/packages/97/32/6e782d3b352e4349fa0e90bf75b13cb7f11d8908b36d9e2b262224b65d9a/rignore-0.7.6-cp310-cp310-win32.whl", hash = "sha256:fe2bd8fa1ff555259df54c376abc73855cb02628a474a40d51b358c3a1ddc55b", size = 646817, upload-time = "2025-11-05T21:41:47.51Z" }, - { url = "https://files.pythonhosted.org/packages/c0/8a/53185c69abb3bb362e8a46b8089999f820bf15655629ff8395107633c8ab/rignore-0.7.6-cp310-cp310-win_amd64.whl", hash = "sha256:d80afd6071c78baf3765ec698841071b19e41c326f994cfa69b5a1df676f5d39", size = 727001, upload-time = "2025-11-05T21:41:32.778Z" }, - { url = "https://files.pythonhosted.org/packages/25/41/b6e2be3069ef3b7f24e35d2911bd6deb83d20ed5642ad81d5a6d1c015473/rignore-0.7.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:40be8226e12d6653abbebaffaea2885f80374c1c8f76fe5ca9e0cadd120a272c", size = 885285, upload-time = "2025-11-05T20:42:39.763Z" }, - { url = "https://files.pythonhosted.org/packages/52/66/ba7f561b6062402022887706a7f2b2c2e2e2a28f1e3839202b0a2f77e36d/rignore-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182f4e5e4064d947c756819446a7d4cdede8e756b8c81cf9e509683fe38778d7", size = 823882, upload-time = "2025-11-05T20:42:23.488Z" }, - { url = "https://files.pythonhosted.org/packages/f5/81/4087453df35a90b07370647b19017029324950c1b9137d54bf1f33843f17/rignore-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16b63047648a916a87be1e51bb5c009063f1b8b6f5afe4f04f875525507e63dc", size = 899362, upload-time = "2025-11-05T20:40:51.111Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c9/390a8fdfabb76d71416be773bd9f162977bd483084f68daf19da1dec88a6/rignore-0.7.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba5524f5178deca4d7695e936604ebc742acb8958f9395776e1fcb8133f8257a", size = 873633, upload-time = "2025-11-05T20:41:06.193Z" }, - { url = "https://files.pythonhosted.org/packages/df/c9/79404fcb0faa76edfbc9df0901f8ef18568d1104919ebbbad6d608c888d1/rignore-0.7.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62020dbb89a1dd4b84ab3d60547b3b2eb2723641d5fb198463643f71eaaed57d", size = 1167633, upload-time = "2025-11-05T20:41:22.491Z" }, - { url = "https://files.pythonhosted.org/packages/6e/8d/b3466d32d445d158a0aceb80919085baaae495b1f540fb942f91d93b5e5b/rignore-0.7.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b34acd532769d5a6f153a52a98dcb81615c949ab11697ce26b2eb776af2e174d", size = 941434, upload-time = "2025-11-05T20:41:38.151Z" }, - { url = "https://files.pythonhosted.org/packages/e8/40/9cd949761a7af5bc27022a939c91ff622d29c7a0b66d0c13a863097dde2d/rignore-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c5e53b752f9de44dff7b3be3c98455ce3bf88e69d6dc0cf4f213346c5e3416c", size = 959461, upload-time = "2025-11-05T20:42:08.476Z" }, - { url = "https://files.pythonhosted.org/packages/b5/87/1e1a145731f73bdb7835e11f80da06f79a00d68b370d9a847de979575e6d/rignore-0.7.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25b3536d13a5d6409ce85f23936f044576eeebf7b6db1d078051b288410fc049", size = 985323, upload-time = "2025-11-05T20:41:52.735Z" }, - { url = "https://files.pythonhosted.org/packages/6c/31/1ecff992fc3f59c4fcdcb6c07d5f6c1e6dfb55ccda19c083aca9d86fa1c6/rignore-0.7.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e01cad2b0b92f6b1993f29fc01f23f2d78caf4bf93b11096d28e9d578eb08ce", size = 1079173, upload-time = "2025-11-05T21:40:12.007Z" }, - { url = "https://files.pythonhosted.org/packages/17/18/162eedadb4c2282fa4c521700dbf93c9b14b8842e8354f7d72b445b8d593/rignore-0.7.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5991e46ab9b4868334c9e372ab0892b0150f3f586ff2b1e314272caeb38aaedb", size = 1139012, upload-time = "2025-11-05T21:40:29.399Z" }, - { url = "https://files.pythonhosted.org/packages/78/96/a9ca398a8af74bb143ad66c2a31303c894111977e28b0d0eab03867f1b43/rignore-0.7.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6c8ae562e5d1246cba5eaeb92a47b2a279e7637102828dde41dcbe291f529a3e", size = 1118827, upload-time = "2025-11-05T21:40:46.6Z" }, - { url = "https://files.pythonhosted.org/packages/9f/22/1c1a65047df864def9a047dbb40bc0b580b8289a4280e62779cd61ae21f2/rignore-0.7.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aaf938530dcc0b47c4cfa52807aa2e5bfd5ca6d57a621125fe293098692f6345", size = 1128182, upload-time = "2025-11-05T21:41:04.239Z" }, - { url = "https://files.pythonhosted.org/packages/bd/f4/1526eb01fdc2235aca1fd9d0189bee4021d009a8dcb0161540238c24166e/rignore-0.7.6-cp311-cp311-win32.whl", hash = "sha256:166ebce373105dd485ec213a6a2695986346e60c94ff3d84eb532a237b24a4d5", size = 646547, upload-time = "2025-11-05T21:41:49.439Z" }, - { url = "https://files.pythonhosted.org/packages/7c/c8/dda0983e1845706beb5826459781549a840fe5a7eb934abc523e8cd17814/rignore-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:44f35ee844b1a8cea50d056e6a595190ce9d42d3cccf9f19d280ae5f3058973a", size = 727139, upload-time = "2025-11-05T21:41:34.367Z" }, - { url = "https://files.pythonhosted.org/packages/e3/47/eb1206b7bf65970d41190b879e1723fc6bbdb2d45e53565f28991a8d9d96/rignore-0.7.6-cp311-cp311-win_arm64.whl", hash = "sha256:14b58f3da4fa3d5c3fa865cab49821675371f5e979281c683e131ae29159a581", size = 657598, upload-time = "2025-11-05T21:41:23.758Z" }, - { url = "https://files.pythonhosted.org/packages/0b/0e/012556ef3047a2628842b44e753bb15f4dc46806780ff090f1e8fe4bf1eb/rignore-0.7.6-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:03e82348cb7234f8d9b2834f854400ddbbd04c0f8f35495119e66adbd37827a8", size = 883488, upload-time = "2025-11-05T20:42:41.359Z" }, - { url = "https://files.pythonhosted.org/packages/93/b0/d4f1f3fe9eb3f8e382d45ce5b0547ea01c4b7e0b4b4eb87bcd66a1d2b888/rignore-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9e624f6be6116ea682e76c5feb71ea91255c67c86cb75befe774365b2931961", size = 820411, upload-time = "2025-11-05T20:42:24.782Z" }, - { url = "https://files.pythonhosted.org/packages/4a/c8/dea564b36dedac8de21c18e1851789545bc52a0c22ece9843444d5608a6a/rignore-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bda49950d405aa8d0ebe26af807c4e662dd281d926530f03f29690a2e07d649a", size = 897821, upload-time = "2025-11-05T20:40:52.613Z" }, - { url = "https://files.pythonhosted.org/packages/b3/2b/ee96db17ac1835e024c5d0742eefb7e46de60020385ac883dd3d1cde2c1f/rignore-0.7.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5fd5ab3840b8c16851d327ed06e9b8be6459702a53e5ab1fc4073b684b3789e", size = 873963, upload-time = "2025-11-05T20:41:07.49Z" }, - { url = "https://files.pythonhosted.org/packages/a5/8c/ad5a57bbb9d14d5c7e5960f712a8a0b902472ea3f4a2138cbf70d1777b75/rignore-0.7.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ced2a248352636a5c77504cb755dc02c2eef9a820a44d3f33061ce1bb8a7f2d2", size = 1169216, upload-time = "2025-11-05T20:41:23.73Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/5b00bc2a6bc1701e6878fca798cf5d9125eb3113193e33078b6fc0d99123/rignore-0.7.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04a3b73b75ddc12c9c9b21efcdaab33ca3832941d6f1d67bffd860941cd448a", size = 942942, upload-time = "2025-11-05T20:41:39.393Z" }, - { url = "https://files.pythonhosted.org/packages/85/e5/7f99bd0cc9818a91d0e8b9acc65b792e35750e3bdccd15a7ee75e64efca4/rignore-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24321efac92140b7ec910ac7c53ab0f0c86a41133d2bb4b0e6a7c94967f44dd", size = 959787, upload-time = "2025-11-05T20:42:09.765Z" }, - { url = "https://files.pythonhosted.org/packages/55/54/2ffea79a7c1eabcede1926347ebc2a81bc6b81f447d05b52af9af14948b9/rignore-0.7.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c7aa109d41e593785c55fdaa89ad80b10330affa9f9d3e3a51fa695f739b20", size = 984245, upload-time = "2025-11-05T20:41:54.062Z" }, - { url = "https://files.pythonhosted.org/packages/41/f7/e80f55dfe0f35787fa482aa18689b9c8251e045076c35477deb0007b3277/rignore-0.7.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1734dc49d1e9501b07852ef44421f84d9f378da9fbeda729e77db71f49cac28b", size = 1078647, upload-time = "2025-11-05T21:40:13.463Z" }, - { url = "https://files.pythonhosted.org/packages/d4/cf/2c64f0b6725149f7c6e7e5a909d14354889b4beaadddaa5fff023ec71084/rignore-0.7.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5719ea14ea2b652c0c0894be5dfde954e1853a80dea27dd2fbaa749618d837f5", size = 1139186, upload-time = "2025-11-05T21:40:31.27Z" }, - { url = "https://files.pythonhosted.org/packages/75/95/a86c84909ccc24af0d094b50d54697951e576c252a4d9f21b47b52af9598/rignore-0.7.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8e23424fc7ce35726854f639cb7968151a792c0c3d9d082f7f67e0c362cfecca", size = 1117604, upload-time = "2025-11-05T21:40:48.07Z" }, - { url = "https://files.pythonhosted.org/packages/7f/5e/13b249613fd5d18d58662490ab910a9f0be758981d1797789913adb4e918/rignore-0.7.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3efdcf1dd84d45f3e2bd2f93303d9be103888f56dfa7c3349b5bf4f0657ec696", size = 1127725, upload-time = "2025-11-05T21:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/c7/28/fa5dcd1e2e16982c359128664e3785f202d3eca9b22dd0b2f91c4b3d242f/rignore-0.7.6-cp312-cp312-win32.whl", hash = "sha256:ccca9d1a8b5234c76b71546fc3c134533b013f40495f394a65614a81f7387046", size = 646145, upload-time = "2025-11-05T21:41:51.096Z" }, - { url = "https://files.pythonhosted.org/packages/26/87/69387fb5dd81a0f771936381431780b8cf66fcd2cfe9495e1aaf41548931/rignore-0.7.6-cp312-cp312-win_amd64.whl", hash = "sha256:c96a285e4a8bfec0652e0bfcf42b1aabcdda1e7625f5006d188e3b1c87fdb543", size = 726090, upload-time = "2025-11-05T21:41:36.485Z" }, - { url = "https://files.pythonhosted.org/packages/24/5f/e8418108dcda8087fb198a6f81caadbcda9fd115d61154bf0df4d6d3619b/rignore-0.7.6-cp312-cp312-win_arm64.whl", hash = "sha256:a64a750e7a8277a323f01ca50b7784a764845f6cce2fe38831cb93f0508d0051", size = 656317, upload-time = "2025-11-05T21:41:25.305Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8a/a4078f6e14932ac7edb171149c481de29969d96ddee3ece5dc4c26f9e0c3/rignore-0.7.6-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2bdab1d31ec9b4fb1331980ee49ea051c0d7f7bb6baa28b3125ef03cdc48fdaf", size = 883057, upload-time = "2025-11-05T20:42:42.741Z" }, - { url = "https://files.pythonhosted.org/packages/f9/8f/f8daacd177db4bf7c2223bab41e630c52711f8af9ed279be2058d2fe4982/rignore-0.7.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:90f0a00ce0c866c275bf888271f1dc0d2140f29b82fcf33cdbda1e1a6af01010", size = 820150, upload-time = "2025-11-05T20:42:26.545Z" }, - { url = "https://files.pythonhosted.org/packages/36/31/b65b837e39c3f7064c426754714ac633b66b8c2290978af9d7f513e14aa9/rignore-0.7.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ad295537041dc2ed4b540fb1a3906bd9ede6ccdad3fe79770cd89e04e3c73c", size = 897406, upload-time = "2025-11-05T20:40:53.854Z" }, - { url = "https://files.pythonhosted.org/packages/ca/58/1970ce006c427e202ac7c081435719a076c478f07b3a23f469227788dc23/rignore-0.7.6-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f782dbd3a65a5ac85adfff69e5c6b101285ef3f845c3a3cae56a54bebf9fe116", size = 874050, upload-time = "2025-11-05T20:41:08.922Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/eb45db9f90137329072a732273be0d383cb7d7f50ddc8e0bceea34c1dfdf/rignore-0.7.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65cece3b36e5b0826d946494734c0e6aaf5a0337e18ff55b071438efe13d559e", size = 1167835, upload-time = "2025-11-05T20:41:24.997Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f1/6f1d72ddca41a64eed569680587a1236633587cc9f78136477ae69e2c88a/rignore-0.7.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7e4bb66c13cd7602dc8931822c02dfbbd5252015c750ac5d6152b186f0a8be0", size = 941945, upload-time = "2025-11-05T20:41:40.628Z" }, - { url = "https://files.pythonhosted.org/packages/48/6f/2f178af1c1a276a065f563ec1e11e7a9e23d4996fd0465516afce4b5c636/rignore-0.7.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297e500c15766e196f68aaaa70e8b6db85fa23fdc075b880d8231fdfba738cd7", size = 959067, upload-time = "2025-11-05T20:42:11.09Z" }, - { url = "https://files.pythonhosted.org/packages/5b/db/423a81c4c1e173877c7f9b5767dcaf1ab50484a94f60a0b2ed78be3fa765/rignore-0.7.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a07084211a8d35e1a5b1d32b9661a5ed20669970b369df0cf77da3adea3405de", size = 984438, upload-time = "2025-11-05T20:41:55.443Z" }, - { url = "https://files.pythonhosted.org/packages/31/eb/c4f92cc3f2825d501d3c46a244a671eb737fc1bcf7b05a3ecd34abb3e0d7/rignore-0.7.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:181eb2a975a22256a1441a9d2f15eb1292839ea3f05606620bd9e1938302cf79", size = 1078365, upload-time = "2025-11-05T21:40:15.148Z" }, - { url = "https://files.pythonhosted.org/packages/26/09/99442f02794bd7441bfc8ed1c7319e890449b816a7493b2db0e30af39095/rignore-0.7.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7bbcdc52b5bf9f054b34ce4af5269df5d863d9c2456243338bc193c28022bd7b", size = 1139066, upload-time = "2025-11-05T21:40:32.771Z" }, - { url = "https://files.pythonhosted.org/packages/2c/88/bcfc21e520bba975410e9419450f4b90a2ac8236b9a80fd8130e87d098af/rignore-0.7.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f2e027a6da21a7c8c0d87553c24ca5cc4364def18d146057862c23a96546238e", size = 1118036, upload-time = "2025-11-05T21:40:49.646Z" }, - { url = "https://files.pythonhosted.org/packages/e2/25/d37215e4562cda5c13312636393aea0bafe38d54d4e0517520a4cc0753ec/rignore-0.7.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee4a18b82cbbc648e4aac1510066682fe62beb5dc88e2c67c53a83954e541360", size = 1127550, upload-time = "2025-11-05T21:41:07.648Z" }, - { url = "https://files.pythonhosted.org/packages/dc/76/a264ab38bfa1620ec12a8ff1c07778da89e16d8c0f3450b0333020d3d6dc/rignore-0.7.6-cp313-cp313-win32.whl", hash = "sha256:a7d7148b6e5e95035d4390396895adc384d37ff4e06781a36fe573bba7c283e5", size = 646097, upload-time = "2025-11-05T21:41:53.201Z" }, - { url = "https://files.pythonhosted.org/packages/62/44/3c31b8983c29ea8832b6082ddb1d07b90379c2d993bd20fce4487b71b4f4/rignore-0.7.6-cp313-cp313-win_amd64.whl", hash = "sha256:b037c4b15a64dced08fc12310ee844ec2284c4c5c1ca77bc37d0a04f7bff386e", size = 726170, upload-time = "2025-11-05T21:41:38.131Z" }, - { url = "https://files.pythonhosted.org/packages/aa/41/e26a075cab83debe41a42661262f606166157df84e0e02e2d904d134c0d8/rignore-0.7.6-cp313-cp313-win_arm64.whl", hash = "sha256:e47443de9b12fe569889bdbe020abe0e0b667516ee2ab435443f6d0869bd2804", size = 656184, upload-time = "2025-11-05T21:41:27.396Z" }, - { url = "https://files.pythonhosted.org/packages/9a/b9/1f5bd82b87e5550cd843ceb3768b4a8ef274eb63f29333cf2f29644b3d75/rignore-0.7.6-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:8e41be9fa8f2f47239ded8920cc283699a052ac4c371f77f5ac017ebeed75732", size = 882632, upload-time = "2025-11-05T20:42:44.063Z" }, - { url = "https://files.pythonhosted.org/packages/e9/6b/07714a3efe4a8048864e8a5b7db311ba51b921e15268b17defaebf56d3db/rignore-0.7.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6dc1e171e52cefa6c20e60c05394a71165663b48bca6c7666dee4f778f2a7d90", size = 820760, upload-time = "2025-11-05T20:42:27.885Z" }, - { url = "https://files.pythonhosted.org/packages/ac/0f/348c829ea2d8d596e856371b14b9092f8a5dfbb62674ec9b3f67e4939a9d/rignore-0.7.6-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ce2268837c3600f82ab8db58f5834009dc638ee17103582960da668963bebc5", size = 899044, upload-time = "2025-11-05T20:40:55.336Z" }, - { url = "https://files.pythonhosted.org/packages/f0/30/2e1841a19b4dd23878d73edd5d82e998a83d5ed9570a89675f140ca8b2ad/rignore-0.7.6-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:690a3e1b54bfe77e89c4bacb13f046e642f8baadafc61d68f5a726f324a76ab6", size = 874144, upload-time = "2025-11-05T20:41:10.195Z" }, - { url = "https://files.pythonhosted.org/packages/c2/bf/0ce9beb2e5f64c30e3580bef09f5829236889f01511a125f98b83169b993/rignore-0.7.6-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09d12ac7a0b6210c07bcd145007117ebd8abe99c8eeb383e9e4673910c2754b2", size = 1168062, upload-time = "2025-11-05T20:41:26.511Z" }, - { url = "https://files.pythonhosted.org/packages/b9/8b/571c178414eb4014969865317da8a02ce4cf5241a41676ef91a59aab24de/rignore-0.7.6-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a2b2b74a8c60203b08452479b90e5ce3dbe96a916214bc9eb2e5af0b6a9beb0", size = 942542, upload-time = "2025-11-05T20:41:41.838Z" }, - { url = "https://files.pythonhosted.org/packages/19/62/7a3cf601d5a45137a7e2b89d10c05b5b86499190c4b7ca5c3c47d79ee519/rignore-0.7.6-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc5a531ef02131e44359419a366bfac57f773ea58f5278c2cdd915f7d10ea94", size = 958739, upload-time = "2025-11-05T20:42:12.463Z" }, - { url = "https://files.pythonhosted.org/packages/5f/1f/4261f6a0d7caf2058a5cde2f5045f565ab91aa7badc972b57d19ce58b14e/rignore-0.7.6-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7a1f77d9c4cd7e76229e252614d963442686bfe12c787a49f4fe481df49e7a9", size = 984138, upload-time = "2025-11-05T20:41:56.775Z" }, - { url = "https://files.pythonhosted.org/packages/2b/bf/628dfe19c75e8ce1f45f7c248f5148b17dfa89a817f8e3552ab74c3ae812/rignore-0.7.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ead81f728682ba72b5b1c3d5846b011d3e0174da978de87c61645f2ed36659a7", size = 1079299, upload-time = "2025-11-05T21:40:16.639Z" }, - { url = "https://files.pythonhosted.org/packages/af/a5/be29c50f5c0c25c637ed32db8758fdf5b901a99e08b608971cda8afb293b/rignore-0.7.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:12ffd50f520c22ffdabed8cd8bfb567d9ac165b2b854d3e679f4bcaef11a9441", size = 1139618, upload-time = "2025-11-05T21:40:34.507Z" }, - { url = "https://files.pythonhosted.org/packages/2a/40/3c46cd7ce4fa05c20b525fd60f599165e820af66e66f2c371cd50644558f/rignore-0.7.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e5a16890fbe3c894f8ca34b0fcacc2c200398d4d46ae654e03bc9b3dbf2a0a72", size = 1117626, upload-time = "2025-11-05T21:40:51.494Z" }, - { url = "https://files.pythonhosted.org/packages/8c/b9/aea926f263b8a29a23c75c2e0d8447965eb1879d3feb53cfcf84db67ed58/rignore-0.7.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3abab3bf99e8a77488ef6c7c9a799fac22224c28fe9f25cc21aa7cc2b72bfc0b", size = 1128144, upload-time = "2025-11-05T21:41:09.169Z" }, - { url = "https://files.pythonhosted.org/packages/a4/f6/0d6242f8d0df7f2ecbe91679fefc1f75e7cd2072cb4f497abaab3f0f8523/rignore-0.7.6-cp314-cp314-win32.whl", hash = "sha256:eeef421c1782953c4375aa32f06ecae470c1285c6381eee2a30d2e02a5633001", size = 646385, upload-time = "2025-11-05T21:41:55.105Z" }, - { url = "https://files.pythonhosted.org/packages/d5/38/c0dcd7b10064f084343d6af26fe9414e46e9619c5f3224b5272e8e5d9956/rignore-0.7.6-cp314-cp314-win_amd64.whl", hash = "sha256:6aeed503b3b3d5af939b21d72a82521701a4bd3b89cd761da1e7dc78621af304", size = 725738, upload-time = "2025-11-05T21:41:39.736Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7a/290f868296c1ece914d565757ab363b04730a728b544beb567ceb3b2d96f/rignore-0.7.6-cp314-cp314-win_arm64.whl", hash = "sha256:104f215b60b3c984c386c3e747d6ab4376d5656478694e22c7bd2f788ddd8304", size = 656008, upload-time = "2025-11-05T21:41:29.028Z" }, - { url = "https://files.pythonhosted.org/packages/ca/d2/3c74e3cd81fe8ea08a8dcd2d755c09ac2e8ad8fe409508904557b58383d3/rignore-0.7.6-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bb24a5b947656dd94cb9e41c4bc8b23cec0c435b58be0d74a874f63c259549e8", size = 882835, upload-time = "2025-11-05T20:42:45.443Z" }, - { url = "https://files.pythonhosted.org/packages/77/61/a772a34b6b63154877433ac2d048364815b24c2dd308f76b212c408101a2/rignore-0.7.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b1e33c9501cefe24b70a1eafd9821acfd0ebf0b35c3a379430a14df089993e3", size = 820301, upload-time = "2025-11-05T20:42:29.226Z" }, - { url = "https://files.pythonhosted.org/packages/71/30/054880b09c0b1b61d17eeb15279d8bf729c0ba52b36c3ada52fb827cbb3c/rignore-0.7.6-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bec3994665a44454df86deb762061e05cd4b61e3772f5b07d1882a8a0d2748d5", size = 897611, upload-time = "2025-11-05T20:40:56.475Z" }, - { url = "https://files.pythonhosted.org/packages/1e/40/b2d1c169f833d69931bf232600eaa3c7998ba4f9a402e43a822dad2ea9f2/rignore-0.7.6-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26cba2edfe3cff1dfa72bddf65d316ddebf182f011f2f61538705d6dbaf54986", size = 873875, upload-time = "2025-11-05T20:41:11.561Z" }, - { url = "https://files.pythonhosted.org/packages/55/59/ca5ae93d83a1a60e44b21d87deb48b177a8db1b85e82fc8a9abb24a8986d/rignore-0.7.6-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffa86694fec604c613696cb91e43892aa22e1fec5f9870e48f111c603e5ec4e9", size = 1167245, upload-time = "2025-11-05T20:41:28.29Z" }, - { url = "https://files.pythonhosted.org/packages/a5/52/cf3dce392ba2af806cba265aad6bcd9c48bb2a6cb5eee448d3319f6e505b/rignore-0.7.6-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48efe2ed95aa8104145004afb15cdfa02bea5cdde8b0344afeb0434f0d989aa2", size = 941750, upload-time = "2025-11-05T20:41:43.111Z" }, - { url = "https://files.pythonhosted.org/packages/ec/be/3f344c6218d779395e785091d05396dfd8b625f6aafbe502746fcd880af2/rignore-0.7.6-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dcae43eb44b7f2457fef7cc87f103f9a0013017a6f4e62182c565e924948f21", size = 958896, upload-time = "2025-11-05T20:42:13.784Z" }, - { url = "https://files.pythonhosted.org/packages/c9/34/d3fa71938aed7d00dcad87f0f9bcb02ad66c85d6ffc83ba31078ce53646a/rignore-0.7.6-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2cd649a7091c0dad2f11ef65630d30c698d505cbe8660dd395268e7c099cc99f", size = 983992, upload-time = "2025-11-05T20:41:58.022Z" }, - { url = "https://files.pythonhosted.org/packages/24/a4/52a697158e9920705bdbd0748d59fa63e0f3233fb92e9df9a71afbead6ca/rignore-0.7.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42de84b0289d478d30ceb7ae59023f7b0527786a9a5b490830e080f0e4ea5aeb", size = 1078181, upload-time = "2025-11-05T21:40:18.151Z" }, - { url = "https://files.pythonhosted.org/packages/ac/65/aa76dbcdabf3787a6f0fd61b5cc8ed1e88580590556d6c0207960d2384bb/rignore-0.7.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:875a617e57b53b4acbc5a91de418233849711c02e29cc1f4f9febb2f928af013", size = 1139232, upload-time = "2025-11-05T21:40:35.966Z" }, - { url = "https://files.pythonhosted.org/packages/08/44/31b31a49b3233c6842acc1c0731aa1e7fb322a7170612acf30327f700b44/rignore-0.7.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8703998902771e96e49968105207719f22926e4431b108450f3f430b4e268b7c", size = 1117349, upload-time = "2025-11-05T21:40:53.013Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ae/1b199a2302c19c658cf74e5ee1427605234e8c91787cfba0015f2ace145b/rignore-0.7.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:602ef33f3e1b04c1e9a10a3c03f8bc3cef2d2383dcc250d309be42b49923cabc", size = 1127702, upload-time = "2025-11-05T21:41:10.881Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d3/18210222b37e87e36357f7b300b7d98c6dd62b133771e71ae27acba83a4f/rignore-0.7.6-cp314-cp314t-win32.whl", hash = "sha256:c1d8f117f7da0a4a96a8daef3da75bc090e3792d30b8b12cfadc240c631353f9", size = 647033, upload-time = "2025-11-05T21:42:00.095Z" }, - { url = "https://files.pythonhosted.org/packages/3e/87/033eebfbee3ec7d92b3bb1717d8f68c88e6fc7de54537040f3b3a405726f/rignore-0.7.6-cp314-cp314t-win_amd64.whl", hash = "sha256:ca36e59408bec81de75d307c568c2d0d410fb880b1769be43611472c61e85c96", size = 725647, upload-time = "2025-11-05T21:41:44.449Z" }, - { url = "https://files.pythonhosted.org/packages/79/62/b88e5879512c55b8ee979c666ee6902adc4ed05007226de266410ae27965/rignore-0.7.6-cp314-cp314t-win_arm64.whl", hash = "sha256:b83adabeb3e8cf662cabe1931b83e165b88c526fa6af6b3aa90429686e474896", size = 656035, upload-time = "2025-11-05T21:41:31.13Z" }, - { url = "https://files.pythonhosted.org/packages/85/12/62d690b4644c330d7ac0f739b7f078190ab4308faa909a60842d0e4af5b2/rignore-0.7.6-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3d3a523af1cd4ed2c0cba8d277a32d329b0c96ef9901fb7ca45c8cfaccf31a5", size = 887462, upload-time = "2025-11-05T20:42:50.804Z" }, - { url = "https://files.pythonhosted.org/packages/05/bc/6528a0e97ed2bd7a7c329183367d1ffbc5b9762ae8348d88dae72cc9d1f5/rignore-0.7.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:990853566e65184a506e1e2af2d15045afad3ebaebb8859cb85b882081915110", size = 826918, upload-time = "2025-11-05T20:42:33.689Z" }, - { url = "https://files.pythonhosted.org/packages/3e/2c/7d7bad116e09a04e9e1688c6f891fa2d4fd33f11b69ac0bd92419ddebeae/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cab9ff2e436ce7240d7ee301c8ef806ed77c1fd6b8a8239ff65f9bbbcb5b8a3", size = 900922, upload-time = "2025-11-05T20:41:00.361Z" }, - { url = "https://files.pythonhosted.org/packages/09/ba/e5ea89fbde8e37a90ce456e31c5e9d85512cef5ae38e0f4d2426eb776a19/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1a6671b2082c13bfd9a5cf4ce64670f832a6d41470556112c4ab0b6519b2fc4", size = 876987, upload-time = "2025-11-05T20:41:16.219Z" }, - { url = "https://files.pythonhosted.org/packages/d0/fb/93d14193f0ec0c3d35b763f0a000e9780f63b2031f3d3756442c2152622d/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2468729b4c5295c199d084ab88a40afcb7c8b974276805105239c07855bbacee", size = 1171110, upload-time = "2025-11-05T20:41:32.631Z" }, - { url = "https://files.pythonhosted.org/packages/9e/46/08436312ff96ffa29cfa4e1a987efc37e094531db46ba5e9fda9bb792afd/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:775710777fd71e5fdf54df69cdc249996a1d6f447a2b5bfb86dbf033fddd9cf9", size = 943339, upload-time = "2025-11-05T20:41:47.128Z" }, - { url = "https://files.pythonhosted.org/packages/34/28/3b3c51328f505cfaf7e53f408f78a1e955d561135d02f9cb0341ea99f69a/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4565407f4a77f72cf9d91469e75d15d375f755f0a01236bb8aaa176278cc7085", size = 961680, upload-time = "2025-11-05T20:42:18.061Z" }, - { url = "https://files.pythonhosted.org/packages/5c/9e/cbff75c8676d4f4a90bd58a1581249d255c7305141b0868f0abc0324836b/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc44c33f8fb2d5c9da748de7a6e6653a78aa740655e7409895e94a247ffa97c8", size = 987045, upload-time = "2025-11-05T20:42:02.315Z" }, - { url = "https://files.pythonhosted.org/packages/8c/25/d802d1d369502a7ddb8816059e7c79d2d913e17df975b863418e0aca4d8a/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8f32478f05540513c11923e8838afab9efef0131d66dca7f67f0e1bbd118af6a", size = 1080310, upload-time = "2025-11-05T21:40:23.184Z" }, - { url = "https://files.pythonhosted.org/packages/43/f0/250b785c2e473b1ab763eaf2be820934c2a5409a722e94b279dddac21c7d/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:1b63a3dd76225ea35b01dd6596aa90b275b5d0f71d6dc28fce6dd295d98614aa", size = 1140998, upload-time = "2025-11-05T21:40:40.603Z" }, - { url = "https://files.pythonhosted.org/packages/f5/d6/bb42fd2a8bba6aea327962656e20621fd495523259db40cfb4c5f760f05c/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:fe6c41175c36554a4ef0994cd1b4dbd6d73156fca779066456b781707402048e", size = 1121178, upload-time = "2025-11-05T21:40:57.585Z" }, - { url = "https://files.pythonhosted.org/packages/97/f4/aeb548374129dce3dc191a4bb598c944d9ed663f467b9af830315d86059c/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a0c6792406ae36f4e7664dc772da909451d46432ff8485774526232d4885063", size = 1130190, upload-time = "2025-11-05T21:41:16.403Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/a6250ff0c49a3cdb943910ada4116e708118e9b901c878cfae616c80a904/rignore-0.7.6-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a20b6fb61bcced9a83dfcca6599ad45182b06ba720cff7c8d891e5b78db5b65f", size = 886470, upload-time = "2025-11-05T20:42:52.314Z" }, - { url = "https://files.pythonhosted.org/packages/35/af/c69c0c51b8f9f7914d95c4ea91c29a2ac067572048cae95dd6d2efdbe05d/rignore-0.7.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:392dcabfecbe176c9ebbcb40d85a5e86a5989559c4f988c2741da7daf1b5be25", size = 825976, upload-time = "2025-11-05T20:42:35.118Z" }, - { url = "https://files.pythonhosted.org/packages/f1/d2/1b264f56132264ea609d3213ab603d6a27016b19559a1a1ede1a66a03dcd/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22baa462abdc36fdd5a5e2dae423107723351b85ff093762f9261148b9d0a04a", size = 899739, upload-time = "2025-11-05T20:41:01.518Z" }, - { url = "https://files.pythonhosted.org/packages/55/e4/b3c5dfdd8d8a10741dfe7199ef45d19a0e42d0c13aa377c83bd6caf65d90/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53fb28882d2538cb2d231972146c4927a9d9455e62b209f85d634408c4103538", size = 874843, upload-time = "2025-11-05T20:41:17.687Z" }, - { url = "https://files.pythonhosted.org/packages/cc/10/d6f3750233881a2a154cefc9a6a0a9b19da526b19f7f08221b552c6f827d/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87409f7eeb1103d6b77f3472a3a0d9a5953e3ae804a55080bdcb0120ee43995b", size = 1170348, upload-time = "2025-11-05T20:41:34.21Z" }, - { url = "https://files.pythonhosted.org/packages/6e/10/ad98ca05c9771c15af734cee18114a3c280914b6e34fde9ffea2e61e88aa/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684014e42e4341ab3ea23a203551857fcc03a7f8ae96ca3aefb824663f55db32", size = 942315, upload-time = "2025-11-05T20:41:48.508Z" }, - { url = "https://files.pythonhosted.org/packages/de/00/ab5c0f872acb60d534e687e629c17e0896c62da9b389c66d3aa16b817aa8/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77356ebb01ba13f8a425c3d30fcad40e57719c0e37670d022d560884a30e4767", size = 961047, upload-time = "2025-11-05T20:42:19.403Z" }, - { url = "https://files.pythonhosted.org/packages/b8/86/3030fdc363a8f0d1cd155b4c453d6db9bab47a24fcc64d03f61d9d78fe6a/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6cbd8a48abbd3747a6c830393cd578782fab5d43f4deea48c5f5e344b8fed2b0", size = 986090, upload-time = "2025-11-05T20:42:03.581Z" }, - { url = "https://files.pythonhosted.org/packages/33/b8/133aa4002cee0ebbb39362f94e4898eec7fbd09cec9fcbce1cd65b355b7f/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2673225dcec7f90497e79438c35e34638d0d0391ccea3cbb79bfb9adc0dc5bd7", size = 1079656, upload-time = "2025-11-05T21:40:24.89Z" }, - { url = "https://files.pythonhosted.org/packages/67/56/36d5d34210e5e7dfcd134eed8335b19e80ae940ee758f493e4f2b344dd70/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:c081f17290d8a2b96052b79207622aa635686ea39d502b976836384ede3d303c", size = 1139789, upload-time = "2025-11-05T21:40:42.119Z" }, - { url = "https://files.pythonhosted.org/packages/6b/5b/bb4f9420802bf73678033a4a55ab1bede36ce2e9b41fec5f966d83d932b3/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:57e8327aacc27f921968cb2a174f9e47b084ce9a7dd0122c8132d22358f6bd79", size = 1120308, upload-time = "2025-11-05T21:40:59.402Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8b/a1299085b28a2f6135e30370b126e3c5055b61908622f2488ade67641479/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:d8955b57e42f2a5434670d5aa7b75eaf6e74602ccd8955dddf7045379cd762fb", size = 1129444, upload-time = "2025-11-05T21:41:17.906Z" }, -] - -[[package]] -name = "rsa" -version = "4.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, -] - -[[package]] -name = "ruff" -version = "0.15.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, - { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, - { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, - { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, - { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, - { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, - { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, - { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, - { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, - { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, - { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, - { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, - { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, - { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, -] - -[[package]] -name = "s3transfer" -version = "0.17.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "botocore" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/ec/7c692cde9125b77e84b307354d4fb705f98b8ccad59a036d5957ca75bfc3/s3transfer-0.17.0.tar.gz", hash = "sha256:9edeb6d1c3c2f89d6050348548834ad8289610d886e5bf7b7207728bd43ce33a", size = 155337, upload-time = "2026-04-29T22:07:36.33Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/72/c6c32d2b657fa3dad1de340254e14390b1e334ce38268b7ad51abda3c8c2/s3transfer-0.17.0-py3-none-any.whl", hash = "sha256:ce3801712acf4ad3e89fb9990df97b4972e93f4b3b0004d214be5bce12814c20", size = 86811, upload-time = "2026-04-29T22:07:34.966Z" }, -] - -[[package]] -name = "sentry-sdk" -version = "2.59.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/65/e0/9bf5e5fc7442b10880f3ec0eff0ef4208b84a099606f343ec4f5445227fb/sentry_sdk-2.59.0.tar.gz", hash = "sha256:cd265808ef8bf3f3edf69b527c0a0b2b6b1322762679e55b8987db2e9584aec1", size = 447331, upload-time = "2026-05-04T12:19:06.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/00/b8cc413748fb6383d1582e7cda51314f99743351c462a92dc690d5b5853b/sentry_sdk-2.59.0-py2.py3-none-any.whl", hash = "sha256:abcf65ee9a9d9cdebf9ad369782408ecca9c1c792686ef06ba34f5ab233527fe", size = 468432, upload-time = "2026-05-04T12:19:04.741Z" }, -] - -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, -] - -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, -] - -[[package]] -name = "slowapi" -version = "0.1.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "limits" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a0/99/adfc7f94ca024736f061257d39118e1542bade7a52e86415a4c4ae92d8ff/slowapi-0.1.9.tar.gz", hash = "sha256:639192d0f1ca01b1c6d95bf6c71d794c3a9ee189855337b4821f7f457dddad77", size = 14028, upload-time = "2024-02-05T12:11:52.13Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/bb/f71c4b7d7e7eb3fc1e8c0458a8979b912f40b58002b9fbf37729b8cb464b/slowapi-0.1.9-py3-none-any.whl", hash = "sha256:cfad116cfb84ad9d763ee155c1e5c5cbf00b0d47399a769b227865f5df576e36", size = 14670, upload-time = "2024-02-05T12:11:50.898Z" }, -] - -[[package]] -name = "soft-webauthn" -version = "0.1.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, - { name = "fido2" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/61/31/65cf69054dced6c143ca8631ce3075cdbb9d096739b8a5254467baa38ec8/soft-webauthn-0.1.4.tar.gz", hash = "sha256:e96513342e69bbfde5a60adcb03ab0d562d70d5d2e20ee7203fcfbdf4f1b06d0", size = 3467, upload-time = "2022-07-08T12:48:29.572Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/5b/cd4b1738509cdb9382d632c503be0b242d589e4dfe39dcc75d74fcf1618c/soft_webauthn-0.1.4-py3-none-any.whl", hash = "sha256:eacb98e406b0bc170455f9fd00977400d6129e7c557f872cf118e9431d1cb9e1", size = 4389, upload-time = "2022-07-08T12:48:27.817Z" }, -] - -[[package]] -name = "starlette" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, -] - -[[package]] -name = "testcontainers" -version = "4.14.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docker" }, - { name = "python-dotenv" }, - { name = "typing-extensions" }, - { name = "urllib3" }, - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/ac/a597c3a0e02b26cbed6dd07df68be1e57684766fd1c381dee9b170a99690/testcontainers-4.14.2.tar.gz", hash = "sha256:1340ccf16fe3acd9389a6c9e1d9ab21d9fe99a8afdf8165f89c3e69c1967d239", size = 166841, upload-time = "2026-03-18T05:19:16.696Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/2d/26b8b30067d94339afee62c3edc9b803a6eb9332f521ba77d8aaab5de873/testcontainers-4.14.2-py3-none-any.whl", hash = "sha256:0d0522c3cd8f8d9627cda41f7a6b51b639fa57bdc492923c045117933c668d68", size = 125712, upload-time = "2026-03-18T05:19:15.29Z" }, -] - -[package.optional-dependencies] -redis = [ - { name = "redis" }, -] - -[[package]] -name = "tomli" -version = "2.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, - { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, - { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, - { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, - { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, - { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, - { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, - { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, - { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, - { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, - { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, - { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, - { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, - { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, - { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, - { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, - { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, - { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, - { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, - { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, - { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, - { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, - { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, - { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, - { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, - { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, - { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, - { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, - { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, - { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, - { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, - { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, - { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, - { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, - { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, - { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, - { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, - { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, - { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, -] - -[[package]] -name = "typer" -version = "0.25.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-doc" }, - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, -] - -[[package]] -name = "urllib3" -version = "2.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, -] - -[[package]] -name = "uvicorn" -version = "0.46.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "h11" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1f/93/041fca8274050e40e6791f267d82e0e2e27dd165627bd640d3e0e378d877/uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d", size = 88758, upload-time = "2026-04-23T07:16:00.151Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/a3/5b1562db76a5a488274b2332a97199b32d0442aca0ed193697fd47786316/uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048", size = 70926, upload-time = "2026-04-23T07:15:58.355Z" }, -] - -[package.optional-dependencies] -standard = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "httptools" }, - { name = "python-dotenv" }, - { name = "pyyaml" }, - { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, - { name = "watchfiles" }, - { name = "websockets" }, -] - -[[package]] -name = "uvloop" -version = "0.22.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/14/ecceb239b65adaaf7fde510aa8bd534075695d1e5f8dadfa32b5723d9cfb/uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c", size = 1343335, upload-time = "2025-10-16T22:16:11.43Z" }, - { url = "https://files.pythonhosted.org/packages/ba/ae/6f6f9af7f590b319c94532b9567409ba11f4fa71af1148cab1bf48a07048/uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792", size = 742903, upload-time = "2025-10-16T22:16:12.979Z" }, - { url = "https://files.pythonhosted.org/packages/09/bd/3667151ad0702282a1f4d5d29288fce8a13c8b6858bf0978c219cd52b231/uvloop-0.22.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86", size = 3648499, upload-time = "2025-10-16T22:16:14.451Z" }, - { url = "https://files.pythonhosted.org/packages/b3/f6/21657bb3beb5f8c57ce8be3b83f653dd7933c2fd00545ed1b092d464799a/uvloop-0.22.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd", size = 3700133, upload-time = "2025-10-16T22:16:16.272Z" }, - { url = "https://files.pythonhosted.org/packages/09/e0/604f61d004ded805f24974c87ddd8374ef675644f476f01f1df90e4cdf72/uvloop-0.22.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2", size = 3512681, upload-time = "2025-10-16T22:16:18.07Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ce/8491fd370b0230deb5eac69c7aae35b3be527e25a911c0acdffb922dc1cd/uvloop-0.22.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec", size = 3615261, upload-time = "2025-10-16T22:16:19.596Z" }, - { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" }, - { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" }, - { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" }, - { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" }, - { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" }, - { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, - { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, - { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, - { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, - { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, - { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, - { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, - { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, - { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, - { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, - { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, - { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, - { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, - { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, - { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, - { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, - { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, - { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, - { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, - { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, - { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, -] - -[[package]] -name = "watchfiles" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/1a/206e8cf2dd86fddf939165a57b4df61607a1e0add2785f170a3f616b7d9f/watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c", size = 407318, upload-time = "2025-10-14T15:04:18.753Z" }, - { url = "https://files.pythonhosted.org/packages/b3/0f/abaf5262b9c496b5dad4ed3c0e799cbecb1f8ea512ecb6ddd46646a9fca3/watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43", size = 394478, upload-time = "2025-10-14T15:04:20.297Z" }, - { url = "https://files.pythonhosted.org/packages/b1/04/9cc0ba88697b34b755371f5ace8d3a4d9a15719c07bdc7bd13d7d8c6a341/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31", size = 449894, upload-time = "2025-10-14T15:04:21.527Z" }, - { url = "https://files.pythonhosted.org/packages/d2/9c/eda4615863cd8621e89aed4df680d8c3ec3da6a4cf1da113c17decd87c7f/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac", size = 459065, upload-time = "2025-10-14T15:04:22.795Z" }, - { url = "https://files.pythonhosted.org/packages/84/13/f28b3f340157d03cbc8197629bc109d1098764abe1e60874622a0be5c112/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d", size = 488377, upload-time = "2025-10-14T15:04:24.138Z" }, - { url = "https://files.pythonhosted.org/packages/86/93/cfa597fa9389e122488f7ffdbd6db505b3b915ca7435ecd7542e855898c2/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d", size = 595837, upload-time = "2025-10-14T15:04:25.057Z" }, - { url = "https://files.pythonhosted.org/packages/57/1e/68c1ed5652b48d89fc24d6af905d88ee4f82fa8bc491e2666004e307ded1/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863", size = 473456, upload-time = "2025-10-14T15:04:26.497Z" }, - { url = "https://files.pythonhosted.org/packages/d5/dc/1a680b7458ffa3b14bb64878112aefc8f2e4f73c5af763cbf0bd43100658/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab", size = 455614, upload-time = "2025-10-14T15:04:27.539Z" }, - { url = "https://files.pythonhosted.org/packages/61/a5/3d782a666512e01eaa6541a72ebac1d3aae191ff4a31274a66b8dd85760c/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82", size = 630690, upload-time = "2025-10-14T15:04:28.495Z" }, - { url = "https://files.pythonhosted.org/packages/9b/73/bb5f38590e34687b2a9c47a244aa4dd50c56a825969c92c9c5fc7387cea1/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4", size = 622459, upload-time = "2025-10-14T15:04:29.491Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ac/c9bb0ec696e07a20bd58af5399aeadaef195fb2c73d26baf55180fe4a942/watchfiles-1.1.1-cp310-cp310-win32.whl", hash = "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844", size = 272663, upload-time = "2025-10-14T15:04:30.435Z" }, - { url = "https://files.pythonhosted.org/packages/11/a0/a60c5a7c2ec59fa062d9a9c61d02e3b6abd94d32aac2d8344c4bdd033326/watchfiles-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e", size = 287453, upload-time = "2025-10-14T15:04:31.53Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", size = 406529, upload-time = "2025-10-14T15:04:32.899Z" }, - { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384, upload-time = "2025-10-14T15:04:33.761Z" }, - { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789, upload-time = "2025-10-14T15:04:34.679Z" }, - { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521, upload-time = "2025-10-14T15:04:35.963Z" }, - { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", size = 488722, upload-time = "2025-10-14T15:04:37.091Z" }, - { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088, upload-time = "2025-10-14T15:04:38.39Z" }, - { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923, upload-time = "2025-10-14T15:04:39.666Z" }, - { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080, upload-time = "2025-10-14T15:04:40.643Z" }, - { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432, upload-time = "2025-10-14T15:04:41.789Z" }, - { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046, upload-time = "2025-10-14T15:04:42.718Z" }, - { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", size = 271473, upload-time = "2025-10-14T15:04:43.624Z" }, - { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", size = 287598, upload-time = "2025-10-14T15:04:44.516Z" }, - { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", size = 277210, upload-time = "2025-10-14T15:04:45.883Z" }, - { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, - { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, - { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, - { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, - { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, - { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, - { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, - { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, - { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, - { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, - { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, - { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, - { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, - { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, - { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, - { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, - { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, - { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, - { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, - { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, - { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, - { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, - { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, - { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, - { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, - { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, - { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, - { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, - { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, - { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, - { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, - { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, - { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, - { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, - { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, - { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, - { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, - { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, - { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, - { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, - { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, - { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, - { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, - { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, - { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, - { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, - { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, - { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, - { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, - { url = "https://files.pythonhosted.org/packages/ba/4c/a888c91e2e326872fa4705095d64acd8aa2fb9c1f7b9bd0588f33850516c/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3", size = 409611, upload-time = "2025-10-14T15:06:05.809Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c7/5420d1943c8e3ce1a21c0a9330bcf7edafb6aa65d26b21dbb3267c9e8112/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2", size = 396889, upload-time = "2025-10-14T15:06:07.035Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e5/0072cef3804ce8d3aaddbfe7788aadff6b3d3f98a286fdbee9fd74ca59a7/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d", size = 451616, upload-time = "2025-10-14T15:06:08.072Z" }, - { url = "https://files.pythonhosted.org/packages/83/4e/b87b71cbdfad81ad7e83358b3e447fedd281b880a03d64a760fe0a11fc2e/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b", size = 458413, upload-time = "2025-10-14T15:06:09.209Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", size = 408250, upload-time = "2025-10-14T15:06:10.264Z" }, - { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", size = 396117, upload-time = "2025-10-14T15:06:11.28Z" }, - { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", size = 450493, upload-time = "2025-10-14T15:06:12.321Z" }, - { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546, upload-time = "2025-10-14T15:06:13.372Z" }, -] - -[[package]] -name = "webauthn" -version = "2.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cbor2" }, - { name = "cryptography" }, - { name = "pyasn1" }, - { name = "pyopenssl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6c/f4/9529bcf85ef46c76842b84c66ffa8ec31f18e3aacd1330b62f440077b45b/webauthn-2.7.1.tar.gz", hash = "sha256:2a1ebbfffc4a83e31d3db5d69113944bc49d05fae77770c2d4e388386cb9656e", size = 124256, upload-time = "2026-02-11T23:36:02.302Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/5b/f73513367a9d34b199de916b44306acfa4027b57f7e22200421212b1f763/webauthn-2.7.1-py3-none-any.whl", hash = "sha256:d57e9613c65e0c6a4db7ee715fb49ebdf3c4a6eb3979729eeb497c99105e8181", size = 71684, upload-time = "2026-02-11T23:36:00.864Z" }, -] - -[[package]] -name = "websockets" -version = "16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/74/221f58decd852f4b59cc3354cccaf87e8ef695fede361d03dc9a7396573b/websockets-16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04cdd5d2d1dacbad0a7bf36ccbcd3ccd5a30ee188f2560b7a62a30d14107b31a", size = 177343, upload-time = "2026-01-10T09:22:21.28Z" }, - { url = "https://files.pythonhosted.org/packages/19/0f/22ef6107ee52ab7f0b710d55d36f5a5d3ef19e8a205541a6d7ffa7994e5a/websockets-16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ff32bb86522a9e5e31439a58addbb0166f0204d64066fb955265c4e214160f0", size = 175021, upload-time = "2026-01-10T09:22:22.696Z" }, - { url = "https://files.pythonhosted.org/packages/10/40/904a4cb30d9b61c0e278899bf36342e9b0208eb3c470324a9ecbaac2a30f/websockets-16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:583b7c42688636f930688d712885cf1531326ee05effd982028212ccc13e5957", size = 175320, upload-time = "2026-01-10T09:22:23.94Z" }, - { url = "https://files.pythonhosted.org/packages/9d/2f/4b3ca7e106bc608744b1cdae041e005e446124bebb037b18799c2d356864/websockets-16.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7d837379b647c0c4c2355c2499723f82f1635fd2c26510e1f587d89bc2199e72", size = 183815, upload-time = "2026-01-10T09:22:25.469Z" }, - { url = "https://files.pythonhosted.org/packages/86/26/d40eaa2a46d4302becec8d15b0fc5e45bdde05191e7628405a19cf491ccd/websockets-16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df57afc692e517a85e65b72e165356ed1df12386ecb879ad5693be08fac65dde", size = 185054, upload-time = "2026-01-10T09:22:27.101Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ba/6500a0efc94f7373ee8fefa8c271acdfd4dca8bd49a90d4be7ccabfc397e/websockets-16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2b9f1e0d69bc60a4a87349d50c09a037a2607918746f07de04df9e43252c77a3", size = 184565, upload-time = "2026-01-10T09:22:28.293Z" }, - { url = "https://files.pythonhosted.org/packages/04/b4/96bf2cee7c8d8102389374a2616200574f5f01128d1082f44102140344cc/websockets-16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:335c23addf3d5e6a8633f9f8eda77efad001671e80b95c491dd0924587ece0b3", size = 183848, upload-time = "2026-01-10T09:22:30.394Z" }, - { url = "https://files.pythonhosted.org/packages/02/8e/81f40fb00fd125357814e8c3025738fc4ffc3da4b6b4a4472a82ba304b41/websockets-16.0-cp310-cp310-win32.whl", hash = "sha256:37b31c1623c6605e4c00d466c9d633f9b812ea430c11c8a278774a1fde1acfa9", size = 178249, upload-time = "2026-01-10T09:22:32.083Z" }, - { url = "https://files.pythonhosted.org/packages/b4/5f/7e40efe8df57db9b91c88a43690ac66f7b7aa73a11aa6a66b927e44f26fa/websockets-16.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e1dab317b6e77424356e11e99a432b7cb2f3ec8c5ab4dabbcee6add48f72b35", size = 178685, upload-time = "2026-01-10T09:22:33.345Z" }, - { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, - { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, - { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, - { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, - { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, - { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, - { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, - { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, - { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, - { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, - { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, - { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, - { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, - { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, - { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, - { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, - { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, - { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, - { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, - { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, - { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, - { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, - { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, - { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, - { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, - { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, - { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, - { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, - { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, - { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, - { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, - { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, - { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, - { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, - { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, - { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, - { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, - { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, -] - -[[package]] -name = "wrapt" -version = "2.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/64/925f213fdcbb9baeb1530449ac71a4d57fc361c053d06bf78d0c5c7cd80c/wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e", size = 81678, upload-time = "2026-03-06T02:53:25.134Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/d2/387594fb592d027366645f3d7cc9b4d7ca7be93845fbaba6d835a912ef3c/wrapt-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a86d99a14f76facb269dc148590c01aaf47584071809a70da30555228158c", size = 60669, upload-time = "2026-03-06T02:52:40.671Z" }, - { url = "https://files.pythonhosted.org/packages/c9/18/3f373935bc5509e7ac444c8026a56762e50c1183e7061797437ca96c12ce/wrapt-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a819e39017f95bf7aede768f75915635aa8f671f2993c036991b8d3bfe8dbb6f", size = 61603, upload-time = "2026-03-06T02:54:21.032Z" }, - { url = "https://files.pythonhosted.org/packages/c2/7a/32758ca2853b07a887a4574b74e28843919103194bb47001a304e24af62f/wrapt-2.1.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5681123e60aed0e64c7d44f72bbf8b4ce45f79d81467e2c4c728629f5baf06eb", size = 113632, upload-time = "2026-03-06T02:53:54.121Z" }, - { url = "https://files.pythonhosted.org/packages/1d/d5/eeaa38f670d462e97d978b3b0d9ce06d5b91e54bebac6fbed867809216e7/wrapt-2.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8b28e97a44d21836259739ae76284e180b18abbb4dcfdff07a415cf1016c3e", size = 115644, upload-time = "2026-03-06T02:54:53.33Z" }, - { url = "https://files.pythonhosted.org/packages/e3/09/2a41506cb17affb0bdf9d5e2129c8c19e192b388c4c01d05e1b14db23c00/wrapt-2.1.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cef91c95a50596fcdc31397eb6955476f82ae8a3f5a8eabdc13611b60ee380ba", size = 112016, upload-time = "2026-03-06T02:54:43.274Z" }, - { url = "https://files.pythonhosted.org/packages/64/15/0e6c3f5e87caadc43db279724ee36979246d5194fa32fed489c73643ba59/wrapt-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dad63212b168de8569b1c512f4eac4b57f2c6934b30df32d6ee9534a79f1493f", size = 114823, upload-time = "2026-03-06T02:54:29.392Z" }, - { url = "https://files.pythonhosted.org/packages/56/b2/0ad17c8248f4e57bedf44938c26ec3ee194715f812d2dbbd9d7ff4be6c06/wrapt-2.1.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d307aa6888d5efab2c1cde09843d48c843990be13069003184b67d426d145394", size = 111244, upload-time = "2026-03-06T02:54:02.149Z" }, - { url = "https://files.pythonhosted.org/packages/ff/04/bcdba98c26f2c6522c7c09a726d5d9229120163493620205b2f76bd13c01/wrapt-2.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c87cf3f0c85e27b3ac7d9ad95da166bf8739ca215a8b171e8404a2d739897a45", size = 113307, upload-time = "2026-03-06T02:54:12.428Z" }, - { url = "https://files.pythonhosted.org/packages/0e/1b/5e2883c6bc14143924e465a6fc5a92d09eeabe35310842a481fb0581f832/wrapt-2.1.2-cp310-cp310-win32.whl", hash = "sha256:d1c5fea4f9fe3762e2b905fdd67df51e4be7a73b7674957af2d2ade71a5c075d", size = 57986, upload-time = "2026-03-06T02:54:26.823Z" }, - { url = "https://files.pythonhosted.org/packages/42/5a/4efc997bccadd3af5749c250b49412793bc41e13a83a486b2b54a33e240c/wrapt-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:d8f7740e1af13dff2684e4d56fe604a7e04d6c94e737a60568d8d4238b9a0c71", size = 60336, upload-time = "2026-03-06T02:54:18Z" }, - { url = "https://files.pythonhosted.org/packages/c1/f5/a2bb833e20181b937e87c242645ed5d5aa9c373006b0467bfe1a35c727d0/wrapt-2.1.2-cp310-cp310-win_arm64.whl", hash = "sha256:1c6cc827c00dc839350155f316f1f8b4b0c370f52b6a19e782e2bda89600c7dc", size = 58757, upload-time = "2026-03-06T02:53:51.545Z" }, - { url = "https://files.pythonhosted.org/packages/c7/81/60c4471fce95afa5922ca09b88a25f03c93343f759aae0f31fb4412a85c7/wrapt-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96159a0ee2b0277d44201c3b5be479a9979cf154e8c82fa5df49586a8e7679bb", size = 60666, upload-time = "2026-03-06T02:52:58.934Z" }, - { url = "https://files.pythonhosted.org/packages/6b/be/80e80e39e7cb90b006a0eaf11c73ac3a62bbfb3068469aec15cc0bc795de/wrapt-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98ba61833a77b747901e9012072f038795de7fc77849f1faa965464f3f87ff2d", size = 61601, upload-time = "2026-03-06T02:53:00.487Z" }, - { url = "https://files.pythonhosted.org/packages/b0/be/d7c88cd9293c859fc74b232abdc65a229bb953997995d6912fc85af18323/wrapt-2.1.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:767c0dbbe76cae2a60dd2b235ac0c87c9cccf4898aef8062e57bead46b5f6894", size = 114057, upload-time = "2026-03-06T02:52:44.08Z" }, - { url = "https://files.pythonhosted.org/packages/ea/25/36c04602831a4d685d45a93b3abea61eca7fe35dab6c842d6f5d570ef94a/wrapt-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c691a6bc752c0cc4711cc0c00896fcd0f116abc253609ef64ef930032821842", size = 116099, upload-time = "2026-03-06T02:54:56.74Z" }, - { url = "https://files.pythonhosted.org/packages/5c/4e/98a6eb417ef551dc277bec1253d5246b25003cf36fdf3913b65cb7657a56/wrapt-2.1.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f3b7d73012ea75aee5844de58c88f44cf62d0d62711e39da5a82824a7c4626a8", size = 112457, upload-time = "2026-03-06T02:53:52.842Z" }, - { url = "https://files.pythonhosted.org/packages/cb/a6/a6f7186a5297cad8ec53fd7578533b28f795fdf5372368c74bd7e6e9841c/wrapt-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:577dff354e7acd9d411eaf4bfe76b724c89c89c8fc9b7e127ee28c5f7bcb25b6", size = 115351, upload-time = "2026-03-06T02:53:32.684Z" }, - { url = "https://files.pythonhosted.org/packages/97/6f/06e66189e721dbebd5cf20e138acc4d1150288ce118462f2fcbff92d38db/wrapt-2.1.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d7b6fd105f8b24e5bd23ccf41cb1d1099796524bcc6f7fbb8fe576c44befbc9", size = 111748, upload-time = "2026-03-06T02:53:08.455Z" }, - { url = "https://files.pythonhosted.org/packages/ef/43/4808b86f499a51370fbdbdfa6cb91e9b9169e762716456471b619fca7a70/wrapt-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:866abdbf4612e0b34764922ef8b1c5668867610a718d3053d59e24a5e5fcfc15", size = 113783, upload-time = "2026-03-06T02:53:02.02Z" }, - { url = "https://files.pythonhosted.org/packages/91/2c/a3f28b8fa7ac2cefa01cfcaca3471f9b0460608d012b693998cd61ef43df/wrapt-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5a0a0a3a882393095573344075189eb2d566e0fd205a2b6414e9997b1b800a8b", size = 57977, upload-time = "2026-03-06T02:53:27.844Z" }, - { url = "https://files.pythonhosted.org/packages/3f/c3/2b1c7bd07a27b1db885a2fab469b707bdd35bddf30a113b4917a7e2139d2/wrapt-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:64a07a71d2730ba56f11d1a4b91f7817dc79bc134c11516b75d1921a7c6fcda1", size = 60336, upload-time = "2026-03-06T02:54:28.104Z" }, - { url = "https://files.pythonhosted.org/packages/ec/5c/76ece7b401b088daa6503d6264dd80f9a727df3e6042802de9a223084ea2/wrapt-2.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:b89f095fe98bc12107f82a9f7d570dc83a0870291aeb6b1d7a7d35575f55d98a", size = 58756, upload-time = "2026-03-06T02:53:16.319Z" }, - { url = "https://files.pythonhosted.org/packages/4c/b6/1db817582c49c7fcbb7df6809d0f515af29d7c2fbf57eb44c36e98fb1492/wrapt-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff2aad9c4cda28a8f0653fc2d487596458c2a3f475e56ba02909e950a9efa6a9", size = 61255, upload-time = "2026-03-06T02:52:45.663Z" }, - { url = "https://files.pythonhosted.org/packages/a2/16/9b02a6b99c09227c93cd4b73acc3678114154ec38da53043c0ddc1fba0dc/wrapt-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6433ea84e1cfacf32021d2a4ee909554ade7fd392caa6f7c13f1f4bf7b8e8748", size = 61848, upload-time = "2026-03-06T02:53:48.728Z" }, - { url = "https://files.pythonhosted.org/packages/af/aa/ead46a88f9ec3a432a4832dfedb84092fc35af2d0ba40cd04aea3889f247/wrapt-2.1.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c20b757c268d30d6215916a5fa8461048d023865d888e437fab451139cad6c8e", size = 121433, upload-time = "2026-03-06T02:54:40.328Z" }, - { url = "https://files.pythonhosted.org/packages/3a/9f/742c7c7cdf58b59085a1ee4b6c37b013f66ac33673a7ef4aaed5e992bc33/wrapt-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79847b83eb38e70d93dc392c7c5b587efe65b3e7afcc167aa8abd5d60e8761c8", size = 123013, upload-time = "2026-03-06T02:53:26.58Z" }, - { url = "https://files.pythonhosted.org/packages/e8/44/2c3dd45d53236b7ed7c646fcf212251dc19e48e599debd3926b52310fafb/wrapt-2.1.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f8fba1bae256186a83d1875b2b1f4e2d1242e8fac0f58ec0d7e41b26967b965c", size = 117326, upload-time = "2026-03-06T02:53:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/74/e2/b17d66abc26bd96f89dec0ecd0ef03da4a1286e6ff793839ec431b9fae57/wrapt-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e3d3b35eedcf5f7d022291ecd7533321c4775f7b9cd0050a31a68499ba45757c", size = 121444, upload-time = "2026-03-06T02:54:09.5Z" }, - { url = "https://files.pythonhosted.org/packages/3c/62/e2977843fdf9f03daf1586a0ff49060b1b2fc7ff85a7ea82b6217c1ae36e/wrapt-2.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6f2c5390460de57fa9582bc8a1b7a6c86e1a41dfad74c5225fc07044c15cc8d1", size = 116237, upload-time = "2026-03-06T02:54:03.884Z" }, - { url = "https://files.pythonhosted.org/packages/88/dd/27fc67914e68d740bce512f11734aec08696e6b17641fef8867c00c949fc/wrapt-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7dfa9f2cf65d027b951d05c662cc99ee3bd01f6e4691ed39848a7a5fffc902b2", size = 120563, upload-time = "2026-03-06T02:53:20.412Z" }, - { url = "https://files.pythonhosted.org/packages/ec/9f/b750b3692ed2ef4705cb305bd68858e73010492b80e43d2a4faa5573cbe7/wrapt-2.1.2-cp312-cp312-win32.whl", hash = "sha256:eba8155747eb2cae4a0b913d9ebd12a1db4d860fc4c829d7578c7b989bd3f2f0", size = 58198, upload-time = "2026-03-06T02:53:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/8e/b2/feecfe29f28483d888d76a48f03c4c4d8afea944dbee2b0cd3380f9df032/wrapt-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1c51c738d7d9faa0b3601708e7e2eda9bf779e1b601dce6c77411f2a1b324a63", size = 60441, upload-time = "2026-03-06T02:52:47.138Z" }, - { url = "https://files.pythonhosted.org/packages/44/e1/e328f605d6e208547ea9fd120804fcdec68536ac748987a68c47c606eea8/wrapt-2.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:c8e46ae8e4032792eb2f677dbd0d557170a8e5524d22acc55199f43efedd39bf", size = 58836, upload-time = "2026-03-06T02:53:22.053Z" }, - { url = "https://files.pythonhosted.org/packages/4c/7a/d936840735c828b38d26a854e85d5338894cda544cb7a85a9d5b8b9c4df7/wrapt-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd6f4d67befa6fe2abdffcbd3de2d82dfc6fb8a6d850407c53332709d030b", size = 61259, upload-time = "2026-03-06T02:53:41.922Z" }, - { url = "https://files.pythonhosted.org/packages/5e/88/9a9b9a90ac8ca11c2fdb6a286cb3a1fc7dd774c00ed70929a6434f6bc634/wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e", size = 61851, upload-time = "2026-03-06T02:52:48.672Z" }, - { url = "https://files.pythonhosted.org/packages/03/a9/5b7d6a16fd6533fed2756900fc8fc923f678179aea62ada6d65c92718c00/wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb", size = 121446, upload-time = "2026-03-06T02:54:14.013Z" }, - { url = "https://files.pythonhosted.org/packages/45/bb/34c443690c847835cfe9f892be78c533d4f32366ad2888972c094a897e39/wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca", size = 123056, upload-time = "2026-03-06T02:54:10.829Z" }, - { url = "https://files.pythonhosted.org/packages/93/b9/ff205f391cb708f67f41ea148545f2b53ff543a7ac293b30d178af4d2271/wrapt-2.1.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:162e4e2ba7542da9027821cb6e7c5e068d64f9a10b5f15512ea28e954893a267", size = 117359, upload-time = "2026-03-06T02:53:03.623Z" }, - { url = "https://files.pythonhosted.org/packages/1f/3d/1ea04d7747825119c3c9a5e0874a40b33594ada92e5649347c457d982805/wrapt-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f29c827a8d9936ac320746747a016c4bc66ef639f5cd0d32df24f5eacbf9c69f", size = 121479, upload-time = "2026-03-06T02:53:45.844Z" }, - { url = "https://files.pythonhosted.org/packages/78/cc/ee3a011920c7a023b25e8df26f306b2484a531ab84ca5c96260a73de76c0/wrapt-2.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9dd9813825f7ecb018c17fd147a01845eb330254dff86d3b5816f20f4d6aaf8", size = 116271, upload-time = "2026-03-06T02:54:46.356Z" }, - { url = "https://files.pythonhosted.org/packages/98/fd/e5ff7ded41b76d802cf1191288473e850d24ba2e39a6ec540f21ae3b57cb/wrapt-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f8dbdd3719e534860d6a78526aafc220e0241f981367018c2875178cf83a413", size = 120573, upload-time = "2026-03-06T02:52:50.163Z" }, - { url = "https://files.pythonhosted.org/packages/47/c5/242cae3b5b080cd09bacef0591691ba1879739050cc7c801ff35c8886b66/wrapt-2.1.2-cp313-cp313-win32.whl", hash = "sha256:5c35b5d82b16a3bc6e0a04349b606a0582bc29f573786aebe98e0c159bc48db6", size = 58205, upload-time = "2026-03-06T02:53:47.494Z" }, - { url = "https://files.pythonhosted.org/packages/12/69/c358c61e7a50f290958809b3c61ebe8b3838ea3e070d7aac9814f95a0528/wrapt-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f8bc1c264d8d1cf5b3560a87bbdd31131573eb25f9f9447bb6252b8d4c44a3a1", size = 60452, upload-time = "2026-03-06T02:53:30.038Z" }, - { url = "https://files.pythonhosted.org/packages/8e/66/c8a6fcfe321295fd8c0ab1bd685b5a01462a9b3aa2f597254462fc2bc975/wrapt-2.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:3beb22f674550d5634642c645aba4c72a2c66fb185ae1aebe1e955fae5a13baf", size = 58842, upload-time = "2026-03-06T02:52:52.114Z" }, - { url = "https://files.pythonhosted.org/packages/da/55/9c7052c349106e0b3f17ae8db4b23a691a963c334de7f9dbd60f8f74a831/wrapt-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fc04bc8664a8bc4c8e00b37b5355cffca2535209fba1abb09ae2b7c76ddf82b", size = 63075, upload-time = "2026-03-06T02:53:19.108Z" }, - { url = "https://files.pythonhosted.org/packages/09/a8/ce7b4006f7218248dd71b7b2b732d0710845a0e49213b18faef64811ffef/wrapt-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a9b9d50c9af998875a1482a038eb05755dfd6fe303a313f6a940bb53a83c3f18", size = 63719, upload-time = "2026-03-06T02:54:33.452Z" }, - { url = "https://files.pythonhosted.org/packages/e4/e5/2ca472e80b9e2b7a17f106bb8f9df1db11e62101652ce210f66935c6af67/wrapt-2.1.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d3ff4f0024dd224290c0eabf0240f1bfc1f26363431505fb1b0283d3b08f11d", size = 152643, upload-time = "2026-03-06T02:52:42.721Z" }, - { url = "https://files.pythonhosted.org/packages/36/42/30f0f2cefca9d9cbf6835f544d825064570203c3e70aa873d8ae12e23791/wrapt-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3278c471f4468ad544a691b31bb856374fbdefb7fee1a152153e64019379f015", size = 158805, upload-time = "2026-03-06T02:54:25.441Z" }, - { url = "https://files.pythonhosted.org/packages/bb/67/d08672f801f604889dcf58f1a0b424fe3808860ede9e03affc1876b295af/wrapt-2.1.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8914c754d3134a3032601c6984db1c576e6abaf3fc68094bb8ab1379d75ff92", size = 145990, upload-time = "2026-03-06T02:53:57.456Z" }, - { url = "https://files.pythonhosted.org/packages/68/a7/fd371b02e73babec1de6ade596e8cd9691051058cfdadbfd62a5898f3295/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff95d4264e55839be37bafe1536db2ab2de19da6b65f9244f01f332b5286cfbf", size = 155670, upload-time = "2026-03-06T02:54:55.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/2d/9fe0095dfdb621009f40117dcebf41d7396c2c22dca6eac779f4c007b86c/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:76405518ca4e1b76fbb1b9f686cff93aebae03920cc55ceeec48ff9f719c5f67", size = 144357, upload-time = "2026-03-06T02:54:24.092Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b6/ec7b4a254abbe4cde9fa15c5d2cca4518f6b07d0f1b77d4ee9655e30280e/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0be8b5a74c5824e9359b53e7e58bef71a729bacc82e16587db1c4ebc91f7c5a", size = 150269, upload-time = "2026-03-06T02:53:31.268Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6b/2fabe8ebf148f4ee3c782aae86a795cc68ffe7d432ef550f234025ce0cfa/wrapt-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:f01277d9a5fc1862f26f7626da9cf443bebc0abd2f303f41c5e995b15887dabd", size = 59894, upload-time = "2026-03-06T02:54:15.391Z" }, - { url = "https://files.pythonhosted.org/packages/ca/fb/9ba66fc2dedc936de5f8073c0217b5d4484e966d87723415cc8262c5d9c2/wrapt-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:84ce8f1c2104d2f6daa912b1b5b039f331febfeee74f8042ad4e04992bd95c8f", size = 63197, upload-time = "2026-03-06T02:54:41.943Z" }, - { url = "https://files.pythonhosted.org/packages/c0/1c/012d7423c95d0e337117723eb8ecf73c622ce15a97847e84cf3f8f26cd7e/wrapt-2.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a93cd767e37faeddbe07d8fc4212d5cba660af59bdb0f6372c93faaa13e6e679", size = 60363, upload-time = "2026-03-06T02:54:48.093Z" }, - { url = "https://files.pythonhosted.org/packages/39/25/e7ea0b417db02bb796182a5316398a75792cd9a22528783d868755e1f669/wrapt-2.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1370e516598854e5b4366e09ce81e08bfe94d42b0fd569b88ec46cc56d9164a9", size = 61418, upload-time = "2026-03-06T02:53:55.706Z" }, - { url = "https://files.pythonhosted.org/packages/ec/0f/fa539e2f6a770249907757eaeb9a5ff4deb41c026f8466c1c6d799088a9b/wrapt-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6de1a3851c27e0bd6a04ca993ea6f80fc53e6c742ee1601f486c08e9f9b900a9", size = 61914, upload-time = "2026-03-06T02:52:53.37Z" }, - { url = "https://files.pythonhosted.org/packages/53/37/02af1867f5b1441aaeda9c82deed061b7cd1372572ddcd717f6df90b5e93/wrapt-2.1.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de9f1a2bbc5ac7f6012ec24525bdd444765a2ff64b5985ac6e0692144838542e", size = 120417, upload-time = "2026-03-06T02:54:30.74Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b7/0138a6238c8ba7476c77cf786a807f871672b37f37a422970342308276e7/wrapt-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:970d57ed83fa040d8b20c52fe74a6ae7e3775ae8cff5efd6a81e06b19078484c", size = 122797, upload-time = "2026-03-06T02:54:51.539Z" }, - { url = "https://files.pythonhosted.org/packages/e1/ad/819ae558036d6a15b7ed290d5b14e209ca795dd4da9c58e50c067d5927b0/wrapt-2.1.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3969c56e4563c375861c8df14fa55146e81ac11c8db49ea6fb7f2ba58bc1ff9a", size = 117350, upload-time = "2026-03-06T02:54:37.651Z" }, - { url = "https://files.pythonhosted.org/packages/8b/2d/afc18dc57a4600a6e594f77a9ae09db54f55ba455440a54886694a84c71b/wrapt-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:57d7c0c980abdc5f1d98b11a2aa3bb159790add80258c717fa49a99921456d90", size = 121223, upload-time = "2026-03-06T02:54:35.221Z" }, - { url = "https://files.pythonhosted.org/packages/b9/5b/5ec189b22205697bc56eb3b62aed87a1e0423e9c8285d0781c7a83170d15/wrapt-2.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:776867878e83130c7a04237010463372e877c1c994d449ca6aaafeab6aab2586", size = 116287, upload-time = "2026-03-06T02:54:19.654Z" }, - { url = "https://files.pythonhosted.org/packages/f7/2d/f84939a7c9b5e6cdd8a8d0f6a26cabf36a0f7e468b967720e8b0cd2bdf69/wrapt-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fab036efe5464ec3291411fabb80a7a39e2dd80bae9bcbeeca5087fdfa891e19", size = 119593, upload-time = "2026-03-06T02:54:16.697Z" }, - { url = "https://files.pythonhosted.org/packages/0b/fe/ccd22a1263159c4ac811ab9374c061bcb4a702773f6e06e38de5f81a1bdc/wrapt-2.1.2-cp314-cp314-win32.whl", hash = "sha256:e6ed62c82ddf58d001096ae84ce7f833db97ae2263bff31c9b336ba8cfe3f508", size = 58631, upload-time = "2026-03-06T02:53:06.498Z" }, - { url = "https://files.pythonhosted.org/packages/65/0a/6bd83be7bff2e7efaac7b4ac9748da9d75a34634bbbbc8ad077d527146df/wrapt-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:467e7c76315390331c67073073d00662015bb730c566820c9ca9b54e4d67fd04", size = 60875, upload-time = "2026-03-06T02:53:50.252Z" }, - { url = "https://files.pythonhosted.org/packages/6c/c0/0b3056397fe02ff80e5a5d72d627c11eb885d1ca78e71b1a5c1e8c7d45de/wrapt-2.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:da1f00a557c66225d53b095a97eace0fc5349e3bfda28fa34ffae238978ee575", size = 59164, upload-time = "2026-03-06T02:53:59.128Z" }, - { url = "https://files.pythonhosted.org/packages/71/ed/5d89c798741993b2371396eb9d4634f009ff1ad8a6c78d366fe2883ea7a6/wrapt-2.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:62503ffbc2d3a69891cf29beeaccdb4d5e0a126e2b6a851688d4777e01428dbb", size = 63163, upload-time = "2026-03-06T02:52:54.873Z" }, - { url = "https://files.pythonhosted.org/packages/c6/8c/05d277d182bf36b0a13d6bd393ed1dec3468a25b59d01fba2dd70fe4d6ae/wrapt-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7e6cd120ef837d5b6f860a6ea3745f8763805c418bb2f12eeb1fa6e25f22d22", size = 63723, upload-time = "2026-03-06T02:52:56.374Z" }, - { url = "https://files.pythonhosted.org/packages/f4/27/6c51ec1eff4413c57e72d6106bb8dec6f0c7cdba6503d78f0fa98767bcc9/wrapt-2.1.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3769a77df8e756d65fbc050333f423c01ae012b4f6731aaf70cf2bef61b34596", size = 152652, upload-time = "2026-03-06T02:53:23.79Z" }, - { url = "https://files.pythonhosted.org/packages/db/4c/d7dd662d6963fc7335bfe29d512b02b71cdfa23eeca7ab3ac74a67505deb/wrapt-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a76d61a2e851996150ba0f80582dd92a870643fa481f3b3846f229de88caf044", size = 158807, upload-time = "2026-03-06T02:53:35.742Z" }, - { url = "https://files.pythonhosted.org/packages/b4/4d/1e5eea1a78d539d346765727422976676615814029522c76b87a95f6bcdd/wrapt-2.1.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6f97edc9842cf215312b75fe737ee7c8adda75a89979f8e11558dfff6343cc4b", size = 146061, upload-time = "2026-03-06T02:52:57.574Z" }, - { url = "https://files.pythonhosted.org/packages/89/bc/62cabea7695cd12a288023251eeefdcb8465056ddaab6227cb78a2de005b/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4006c351de6d5007aa33a551f600404ba44228a89e833d2fadc5caa5de8edfbf", size = 155667, upload-time = "2026-03-06T02:53:39.422Z" }, - { url = "https://files.pythonhosted.org/packages/e9/99/6f2888cd68588f24df3a76572c69c2de28287acb9e1972bf0c83ce97dbc1/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9372fc3639a878c8e7d87e1556fa209091b0a66e912c611e3f833e2c4202be2", size = 144392, upload-time = "2026-03-06T02:54:22.41Z" }, - { url = "https://files.pythonhosted.org/packages/40/51/1dfc783a6c57971614c48e361a82ca3b6da9055879952587bc99fe1a7171/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3144b027ff30cbd2fca07c0a87e67011adb717eb5f5bd8496325c17e454257a3", size = 150296, upload-time = "2026-03-06T02:54:07.848Z" }, - { url = "https://files.pythonhosted.org/packages/6c/38/cbb8b933a0201076c1f64fc42883b0023002bdc14a4964219154e6ff3350/wrapt-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:3b8d15e52e195813efe5db8cec156eebe339aaf84222f4f4f051a6c01f237ed7", size = 60539, upload-time = "2026-03-06T02:54:00.594Z" }, - { url = "https://files.pythonhosted.org/packages/82/dd/e5176e4b241c9f528402cebb238a36785a628179d7d8b71091154b3e4c9e/wrapt-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:08ffa54146a7559f5b8df4b289b46d963a8e74ed16ba3687f99896101a3990c5", size = 63969, upload-time = "2026-03-06T02:54:39Z" }, - { url = "https://files.pythonhosted.org/packages/5c/99/79f17046cf67e4a95b9987ea129632ba8bcec0bc81f3fb3d19bdb0bd60cd/wrapt-2.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:72aaa9d0d8e4ed0e2e98019cea47a21f823c9dd4b43c7b77bba6679ffcca6a00", size = 60554, upload-time = "2026-03-06T02:53:14.132Z" }, - { url = "https://files.pythonhosted.org/packages/1a/c7/8528ac2dfa2c1e6708f647df7ae144ead13f0a31146f43c7264b4942bf12/wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8", size = 43993, upload-time = "2026-03-06T02:53:12.905Z" }, -] diff --git a/apps/desktop-ui/messages/af.json b/apps/desktop-ui/messages/af.json index 5a337a72..1223018e 100644 --- a/apps/desktop-ui/messages/af.json +++ b/apps/desktop-ui/messages/af.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Wat is MyDevTools?", "description": "'n Vanlyn werkspasie vir ontwikkelaars: 'n paneelbord en meer as 80 gereedskap wat heeltemal op jou toestel loop.", - "p1": "MyDevTools loop heeltemal vanlyn. Ná 'n eenmalige aktivering in jou blaaier werk alles op hierdie toestel, sonder rekeninginteken en sonder internetverbinding. Die sybalk lys al die programme; gereedskap wat jy nie gebruik nie, kan jy versteek by", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Instellings", "p1After": ".", - "p2": "Sommige funksies gebruik 'n meesterwagwoord (of globale kluissleutel) sodat geheime soos wagwoorde en databasisverbindingstringe op jou toestel geënkripteer word. Jou data word in 'n plaaslike geënkripteerde databasis gestoor en word nooit na 'n bediener gestuur nie." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Vinnige skakels", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Aktivering en vanlyn toegang", - "description": "Hoe die program die eerste keer ontsluit en daarna vanlyn loop.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Eenmalige aktivering gebeur in jou blaaier die eerste keer wat jy die program oopmaak. Dit registreer jou plan en ontsluit die program op hierdie toestel.", - "offline": "Geen aantekensessies nie. Ná aktivering is daar geen aanmelding per sessie en geen periodieke herkontrole nie—die program bly werk sonder internetverbinding.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Plaaslike berging. Al jou data lê in 'n geënkripteerde SQLCipher-databasis op jou toestel, beveilig met 'n lukrake sleutel in jou bedryfstelsel se sleutelhouer." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Instellings", - "subtitle": "Bestuur jou rekeninginstellings en voorkeure.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Gebruikerprofiel", - "description": "Jou persoonlike inligting en rekeningbesonderhede.", - "avatarAlt": "Gebruiker", - "anonymousUser": "Anonieme gebruiker", - "noEmailProvided": "Geen e-pos verskaf nie", - "notLoggedIn": "Nie aangemeld nie." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Gereedskap en funksies", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Wys werfikone in die kluis", "helpText": "Standaard af. Om 'n ikoon te haal vertel 'n derdeparty-diens by watter werwe jy rekeninge het." + }, + "telemetry": { + "label": "Deel anonieme gebruiksdata", + "helpText": "Standaard af. Stuur watter gereedskap jy oopmaak, die weergawe van die program en jou taal. Geen rekening, geen toestel-ID, en niks wat jy in 'n hulpmiddel tik nie." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Jou data in ’n oogopslag", - "subtitle": "Tellinge gesinkroniseer met jou rekening op die bediener.", + "subtitle": "Counts recorded on this device.", "loadError": "Kon nie ontleding laai nie.", "retry": "Probeer weer", - "loginHint": "Meld aan om gebruik-ontleding en koppelvlakvoorkeure te sien.", "passwordEntries": "Wagwoord-inskrywings", "bookmarks": "Boekmerke", "bookmarkFolders": "Boekmerkvouers", @@ -664,9 +629,8 @@ }, "ui": { "title": "Koppelvlakvoorkeure", - "subtitle": "Tema, taal en sigbare gereedskap in die sybalk — vanaf jou rekeninginstellings.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Kon nie voorkeure laai nie.", - "loginHint": "Meld aan om gestoorde koppelvlakvoorkeure te sien.", "theme": "Tema", "accent": "Aksentkleur", "locale": "Taal", @@ -674,13 +638,9 @@ "favoriteTools": "Gunsteling-gereedskap", "toolsWithUsage": "Gereedskap met gebruikstatistiek" }, - "signIn": "Meld aan", "noResults": "Geen gereedskap stem ooreen met jou soektog nie", "noResultsHint": "Probeer 'n ander sleutelwoord of maak die filter skoon.", "filterAll": "Alles", - "loginCta": { - "description": "Meld aan om jou onlangs gebruikte gereedskap te volg." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Voer uit" }, "context": { - "authRequiredError": "Gebruiker nie geverifieer nie", "defaultTitle": "Sonder titel", - "cloudSyncImageError": "Skakel Wolksinkronisasie aan om notaprente te stoor. Teksnotas bly plaaslik." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Kanselleer", "confirmDelete": "Vee uit", "deleteDialogTitle": "Omgewingstel uitvee?", - "deleteDialogDescription": "Dit verwyder die geënkripteerde data uit jou rekening. Dit kan nie ongedaan gemaak word nie.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2656,7 +2615,6 @@ "toastCopiedText": "Tekspaneel gekopieer", "toastCopiedTree": "Boompaneel gekopieer", "toastCopyFailed": "Kon nie JSON kopieer nie", - "toastLoginRequired": "Meld asseblief aan om dokumente te stoor", "toastSaved": "Dokument gestoor", "toastSaveFailed": "Kon nie dokument stoor nie", "format": "Formateer", @@ -5160,5 +5118,13 @@ "close": "Sluit", "pill": "Word bygewerk…", "pillPct": "Word bygewerk… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Help besluit wat volgende gebou word?", + "body": "Deel anonieme gebruik: watter gereedskap jy oopmaak, die weergawe van die program en jou taal. Geen rekening, geen toestel-ID, en niks wat jy in 'n hulpmiddel tik nie. Jy kan dit enige tyd in Instellings verander.", + "accept": "Deel anoniem", + "decline": "Nee dankie" + } } } diff --git a/apps/desktop-ui/messages/ar.json b/apps/desktop-ui/messages/ar.json index af916e22..38d21da9 100644 --- a/apps/desktop-ui/messages/ar.json +++ b/apps/desktop-ui/messages/ar.json @@ -91,10 +91,10 @@ "whatIs": { "title": "ما هو MyDevTools؟", "description": "مساحة عمل للمطورين تعمل دون اتصال: لوحة تحكم وأكثر من 80 أداة تعمل بالكامل على جهازك.", - "p1": "يعمل MyDevTools بالكامل دون اتصال بالإنترنت. بعد تفعيل لمرة واحدة في متصفحك، يعمل كل شيء على هذا الجهاز دون الحاجة إلى تسجيل الدخول إلى حساب أو اتصال بالإنترنت. يعرض الشريط الجانبي جميع التطبيقات؛ ويمكنك إخفاء الأدوات التي لا تستخدمها من", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "الإعدادات", "p1After": ".", - "p2": "تستخدم بعض الميزات كلمة مرور رئيسية (أو مفتاح خزنة عام) بحيث تُشفَّر الأسرار مثل كلمات المرور وسلاسل اتصال قواعد البيانات على جهازك. تُخزَّن بياناتك في قاعدة بيانات محلية مشفَّرة ولا تُرسَل أبدًا إلى خادم." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "روابط سريعة", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "التفعيل والوصول دون اتصال", - "description": "كيف يُفتح قفل التطبيق في المرة الأولى، ثم يعمل دون اتصال.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "تفعيل لمرة واحدة يحدث في متصفحك عند فتح التطبيق للمرة الأولى. فهو يسجّل خطتك ويفتح قفل التطبيق على هذا الجهاز.", - "offline": "لا جلسات تسجيل دخول. بعد التفعيل لا يوجد تسجيل دخول لكل جلسة ولا إعادة فحص دورية، ويستمر التطبيق في العمل دون اتصال بالإنترنت.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "تخزين محلي. تعيش جميع بياناتك في قاعدة بيانات SQLCipher مشفَّرة على جهازك، ومؤمَّنة بمفتاح عشوائي محفوظ في سلسلة مفاتيح نظام التشغيل لديك." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "الإعدادات", - "subtitle": "إدارة إعدادات حسابك وتفضيلاتك.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "الملف الشخصي", - "description": "معلوماتك الشخصية وتفاصيل الحساب.", - "avatarAlt": "المستخدم", - "anonymousUser": "مستخدم مجهول", - "noEmailProvided": "لم يُقدَّم بريد إلكتروني", - "notLoggedIn": "لم يتم تسجيل الدخول." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "الأدوات والميزات", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "إظهار أيقونات المواقع في الخزنة", "helpText": "معطّل افتراضيًا. جلب الأيقونة يُخبر خدمة خارجية بالمواقع التي تملك فيها حسابات." + }, + "telemetry": { + "label": "مشاركة بيانات الاستخدام المجهولة", + "helpText": "معطّل افتراضيًا. يُرسل الأدوات التي تفتحها وإصدار التطبيق ولغتك. بدون حساب، وبدون معرّف جهاز، وبدون أي شيء تكتبه داخل الأدوات." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "بياناتك في لمحة", - "subtitle": "أعداد متزامنة مع حسابك على الخادم.", + "subtitle": "Counts recorded on this device.", "loadError": "تعذّر تحميل التحليلات.", "retry": "إعادة المحاولة", - "loginHint": "سجّل الدخول لعرض تحليلات الاستخدام وتفضيلات الواجهة.", "passwordEntries": "إدخالات كلمات المرور", "bookmarks": "الإشارات المرجعية", "bookmarkFolders": "مجلدات الإشارات", @@ -664,9 +629,8 @@ }, "ui": { "title": "تفضيلات الواجهة", - "subtitle": "المظهر واللغة والأدوات الظاهرة في الشريط الجانبي — من إعدادات الحساب.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "تعذّر تحميل التفضيلات.", - "loginHint": "سجّل الدخول لعرض تفضيلات الواجهة المحفوظة.", "theme": "المظهر", "accent": "لون التمييز", "locale": "اللغة", @@ -674,13 +638,9 @@ "favoriteTools": "الأدوات المفضلة", "toolsWithUsage": "أدوات مع تتبع الاستخدام" }, - "signIn": "تسجيل الدخول", "noResults": "لا توجد أدوات تطابق بحثك", "noResultsHint": "جرّب كلمة مفتاحية مختلفة أو امسح الفلتر.", "filterAll": "الكل", - "loginCta": { - "description": "سجّل الدخول لتتبع أدواتك المستخدمة مؤخرًا." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "تصدير" }, "context": { - "authRequiredError": "المستخدم غير مصادق", "defaultTitle": "بدون عنوان", - "cloudSyncImageError": "فعّل المزامنة السحابية لتخزين صور الملاحظات. تبقى الملاحظات النصية محلية." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "إلغاء", "confirmDelete": "حذف", "deleteDialogTitle": "حذف مجموعة البيئة؟", - "deleteDialogDescription": "يُزال النص المشفّر من حسابك. لا يمكن التراجع.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "تم نسخ لوحة النص", "toastCopiedTree": "تم نسخ لوحة الشجرة", "toastCopyFailed": "فشل نسخ JSON", - "toastLoginRequired": "يرجى تسجيل الدخول لحفظ المستندات", "toastSaved": "تم حفظ المستند", "toastSaveFailed": "فشل حفظ المستند", "format": "تنسيق", @@ -5149,5 +5107,13 @@ "close": "إغلاق", "pill": "جارٍ التحديث…", "pillPct": "جارٍ التحديث… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "هل تساعدنا في تحديد ما نبنيه لاحقًا؟", + "body": "شارك الاستخدام بشكل مجهول: الأدوات التي تفتحها وإصدار التطبيق ولغتك. بدون حساب، وبدون معرّف جهاز، وبدون أي شيء تكتبه داخل الأدوات. يمكنك تغيير ذلك في أي وقت من الإعدادات.", + "accept": "المشاركة بشكل مجهول", + "decline": "لا، شكرًا" + } } } diff --git a/apps/desktop-ui/messages/ca.json b/apps/desktop-ui/messages/ca.json index 3e2d13b7..44bedc74 100644 --- a/apps/desktop-ui/messages/ca.json +++ b/apps/desktop-ui/messages/ca.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Què és MyDevTools?", "description": "Un espai de treball per a desenvolupadors sense connexió: un tauler i més de 80 eines que s'executen completament al teu dispositiu.", - "p1": "MyDevTools funciona completament sense connexió. Després d'una activació única al navegador, tot funciona en aquest dispositiu, sense inici de sessió amb compte i sense connexió a internet. La barra lateral mostra totes les aplicacions; les eines que no facis servir les pots amagar des de", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Configuració", "p1After": ".", - "p2": "Algunes funcions fan servir una contrasenya mestra (o clau global de la caixa forta) perquè els secrets com ara contrasenyes i cadenes de connexió a bases de dades s'encriptin al teu dispositiu. Les teves dades es desen en una base de dades local encriptada i mai no s'envien a cap servidor." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Enllaços ràpids", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Activació i accés sense connexió", - "description": "Com es desbloqueja l'aplicació la primera vegada i després s'executa sense connexió.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "L'activació única es fa al navegador el primer cop que obres l'aplicació. Registra el teu pla i desbloqueja l'aplicació en aquest dispositiu.", - "offline": "Sense sessions d'inici. Després de l'activació no hi ha cap inici de sessió per sessió ni cap comprovació periòdica: l'aplicació continua funcionant sense connexió a internet.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Emmagatzematge local. Totes les teves dades resideixen en una base de dades SQLCipher encriptada al teu dispositiu, protegida amb una clau aleatòria guardada al clauer del teu sistema operatiu." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Configuració", - "subtitle": "Gestiona la configuració i les preferències del teu compte.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Perfil d'usuari", - "description": "La teva informació personal i els detalls del compte.", - "avatarAlt": "Usuari", - "anonymousUser": "Usuari anònim", - "noEmailProvided": "No s'ha proporcionat cap correu", - "notLoggedIn": "No has iniciat sessió." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Eines i funcions", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Mostra icones dels llocs a la caixa forta", "helpText": "Desactivat per defecte. Baixar una icona revela a un servei extern a quins llocs tens comptes." + }, + "telemetry": { + "label": "Comparteix dades d'ús anònimes", + "helpText": "Desactivat per defecte. Envia quines eines obres, la versió de l'aplicació i el teu idioma. Sense compte, sense identificador de dispositiu i sense res del que escrius a les eines." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Les teves dades d’un cop d’ull", - "subtitle": "Recomptes sincronitzats amb el teu compte al servidor.", + "subtitle": "Counts recorded on this device.", "loadError": "No s’han pogut carregar les estadístiques.", "retry": "Torna-ho a provar", - "loginHint": "Inicia la sessió per veure l’anàlisi d’ús i les preferències d’interfície.", "passwordEntries": "Entrades de contrasenyes", "bookmarks": "Marcadors", "bookmarkFolders": "Carpetes de marcadors", @@ -664,9 +629,8 @@ }, "ui": { "title": "Preferències d’interfície", - "subtitle": "Tema, idioma i eines visibles a la barra lateral — des de la configuració del compte.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "No s’han pogut carregar les preferències.", - "loginHint": "Inicia la sessió per veure les preferències d’interfície desades.", "theme": "Tema", "accent": "Color d’accent", "locale": "Idioma", @@ -674,13 +638,9 @@ "favoriteTools": "Eines favorites", "toolsWithUsage": "Eines amb ús registrat" }, - "signIn": "Inicia la sessió", "noResults": "Cap eina coincideix amb la vostra cerca", "noResultsHint": "Proveu una paraula clau diferent o elimineu el filtre.", "filterAll": "Tot", - "loginCta": { - "description": "Inicieu sessió per fer un seguiment de les eines utilitzades recentment." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Exporta" }, "context": { - "authRequiredError": "Usuari no autenticat", "defaultTitle": "Sense títol", - "cloudSyncImageError": "Activa la sincronització al núvol per desar imatges de les notes. Les notes de text es queden localment." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Cancel·la", "confirmDelete": "Suprimeix", "deleteDialogTitle": "Vols suprimir el conjunt d'entorn?", - "deleteDialogDescription": "Això elimina el blob xifrat del teu compte. No es pot desfer.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "Panell de text copiat", "toastCopiedTree": "Panell d'arbre copiat", "toastCopyFailed": "No s'ha pogut copiar el JSON", - "toastLoginRequired": "Inicia sessió per desar documents", "toastSaved": "Document desat", "toastSaveFailed": "No s'ha pogut desar el document", "format": "Formata", @@ -5149,5 +5107,13 @@ "close": "Tanca", "pill": "S'està actualitzant…", "pillPct": "S'està actualitzant… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Vols ajudar a decidir què construïm després?", + "body": "Comparteix l'ús de manera anònima: quines eines obres, la versió de l'aplicació i el teu idioma. Sense compte, sense identificador de dispositiu i sense res del que escrius a les eines. Ho pots canviar en qualsevol moment a Configuració.", + "accept": "Comparteix anònimament", + "decline": "No, gràcies" + } } } diff --git a/apps/desktop-ui/messages/cs.json b/apps/desktop-ui/messages/cs.json index 1d9021db..28dc5e22 100644 --- a/apps/desktop-ui/messages/cs.json +++ b/apps/desktop-ui/messages/cs.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Co je MyDevTools?", "description": "Offline vývojářské prostředí: řídicí panel a více než 80 nástrojů, které běží zcela na vašem zařízení.", - "p1": "MyDevTools funguje plně offline. Po jednorázové aktivaci v prohlížeči vše funguje na tomto zařízení bez přihlašování k účtu a bez připojení k internetu. V postranním panelu jsou uvedeny všechny aplikace; nástroje, které nepoužíváte, můžete skrýt v sekci", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Nastavení", "p1After": ".", - "p2": "Některé funkce používají hlavní heslo (nebo globální klíč trezoru), aby byly tajné údaje jako hesla a připojovací řetězce k databázím zašifrovány na vašem zařízení. Vaše data jsou uložena v lokální šifrované databázi a nikdy nejsou odesílána na server." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Rychlé odkazy", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Aktivace a offline přístup", - "description": "Jak se aplikace poprvé odemkne a poté běží offline.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Jednorázová aktivace proběhne v prohlížeči při prvním otevření aplikace. Zaznamená váš plán a odemkne aplikaci na tomto zařízení.", - "offline": "Žádné přihlašovací relace. Po aktivaci není potřeba přihlašování při každé relaci ani pravidelné opětovné ověřování — aplikace funguje dál bez připojení k internetu.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Lokální úložiště. Všechna vaše data se nacházejí v šifrované databázi SQLCipher na vašem zařízení, zabezpečené náhodným klíčem uloženým v klíčence vašeho operačního systému." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Nastavení", - "subtitle": "Spravujte nastavení účtu a předvolby.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Uživatelský profil", - "description": "Vaše osobní údaje a informace o účtu.", - "avatarAlt": "Uživatel", - "anonymousUser": "Anonymní uživatel", - "noEmailProvided": "E-mail není uveden", - "notLoggedIn": "Nejste přihlášeni." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Nástroje a funkce", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Zobrazovat ikony webů v trezoru", "helpText": "Ve výchozím stavu vypnuto. Načtení ikony prozradí externí službě, na kterých webech máte účty." + }, + "telemetry": { + "label": "Sdílet anonymní data o používání", + "helpText": "Ve výchozím nastavení vypnuto. Odesílá, které nástroje otevíráte, verzi aplikace a váš jazyk. Žádný účet, žádné ID zařízení a nic, co v nástroji napíšete." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Vaše data na první pohled", - "subtitle": "Počty synchronizované s vaším účtem na serveru.", + "subtitle": "Counts recorded on this device.", "loadError": "Nelze načíst analytiku.", "retry": "Zkusit znovu", - "loginHint": "Přihlaste se pro zobrazení analytiky a předvoleb rozhraní.", "passwordEntries": "Záznamů hesel", "bookmarks": "Záložky", "bookmarkFolders": "Složky záložek", @@ -664,9 +629,8 @@ }, "ui": { "title": "Předvolby rozhraní", - "subtitle": "Motiv, jazyk a viditelné nástroje v postranním panelu — z nastavení účtu.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Nelze načíst předvolby.", - "loginHint": "Přihlaste se pro zobrazení uložených předvoleb rozhraní.", "theme": "Motiv", "accent": "Barva zvýraznění", "locale": "Jazyk", @@ -674,13 +638,9 @@ "favoriteTools": "Oblíbené nástroje", "toolsWithUsage": "Nástroje se sledováním použití" }, - "signIn": "Přihlásit se", "noResults": "Žádné nástroje neodpovídají vašemu hledání", "noResultsHint": "Zkuste jiné klíčové slovo nebo vymažte filtr.", "filterAll": "Vše", - "loginCta": { - "description": "Přihlaste se a sledujte naposledy použité nástroje." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Exportovat" }, "context": { - "authRequiredError": "Uživatel není přihlášen", "defaultTitle": "Bez názvu", - "cloudSyncImageError": "Zapněte cloudovou synchronizaci pro ukládání obrázků poznámek. Textové poznámky zůstávají místní." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Zrušit", "confirmDelete": "Smazat", "deleteDialogTitle": "Smazat sadu prostředí?", - "deleteDialogDescription": "Tím se odstraní šifrovaná data z účtu. Akci nelze vrátit zpět.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "Textový panel zkopírován", "toastCopiedTree": "Stromový panel zkopírován", "toastCopyFailed": "Nepodařilo se zkopírovat JSON", - "toastLoginRequired": "Pro uložení dokumentů se přihlaste", "toastSaved": "Dokument uložen", "toastSaveFailed": "Nepodařilo se uložit dokument", "format": "Formátovat", @@ -5149,5 +5107,13 @@ "close": "Zavřít", "pill": "Aktualizace…", "pillPct": "Aktualizace… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Pomůžete rozhodnout, co vznikne příště?", + "body": "Sdílejte používání anonymně: které nástroje otevíráte, verzi aplikace a váš jazyk. Žádný účet, žádné ID zařízení a nic, co v nástroji napíšete. Kdykoli to můžete změnit v Nastavení.", + "accept": "Sdílet anonymně", + "decline": "Ne, děkuji" + } } } diff --git a/apps/desktop-ui/messages/da.json b/apps/desktop-ui/messages/da.json index 384a8985..5e24bb60 100644 --- a/apps/desktop-ui/messages/da.json +++ b/apps/desktop-ui/messages/da.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Hvad er MyDevTools?", "description": "Et offline-arbejdsrum for udviklere: et dashboard og over 80 værktøjer, der kører helt på din enhed.", - "p1": "MyDevTools kører helt offline. Efter en engangsaktivering i din browser fungerer alt på denne enhed, uden kontologin og uden internetforbindelse. Sidepanelet viser alle apps; værktøjer, du ikke bruger, kan du skjule under", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Indstillinger", "p1After": ".", - "p2": "Nogle funktioner bruger en hovedadgangskode (eller en global boksnøgle), så hemmeligheder som adgangskoder og forbindelsesstrenge til databaser krypteres på din enhed. Dine data gemmes i en lokal krypteret database og sendes aldrig til en server." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Genveje", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Aktivering og offline-adgang", - "description": "Sådan låses appen op første gang og kører derefter offline.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Engangsaktivering sker i din browser, første gang du åbner appen. Den registrerer dit abonnement og låser appen op på denne enhed.", - "offline": "Ingen login-sessioner. Efter aktivering er der ingen login pr. session og ingen periodisk kontrol—appen fortsætter med at fungere uden internetforbindelse.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Lokal lagring. Alle dine data ligger i en krypteret SQLCipher-database på din enhed, sikret med en tilfældig nøgle i dit operativsystems nøglering." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Indstillinger", - "subtitle": "Administrer dine kontoindstillinger og præferencer.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Brugerprofil", - "description": "Dine personlige oplysninger og kontooplysninger.", - "avatarAlt": "Bruger", - "anonymousUser": "Anonym bruger", - "noEmailProvided": "Ingen e-mail angivet", - "notLoggedIn": "Ikke logget ind." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Værktøjer og funktioner", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Vis webstedsikoner i boksen", "helpText": "Slået fra som standard. At hente et ikon fortæller en tredjepartstjeneste, hvilke websteder du har konti på." + }, + "telemetry": { + "label": "Del anonyme brugsdata", + "helpText": "Slået fra som standard. Sender hvilke værktøjer du åbner, appens version og dit sprog. Ingen konto, intet enheds-id og intet af det, du skriver i et værktøj." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Dine data på et øjeblik", - "subtitle": "Antal synkroniseret med din konto på serveren.", + "subtitle": "Counts recorded on this device.", "loadError": "Kunne ikke indlæse analyse.", "retry": "Prøv igen", - "loginHint": "Log ind for at se brugsanalyse og grænsefladeindstillinger.", "passwordEntries": "Adgangskodeposter", "bookmarks": "Bogmærker", "bookmarkFolders": "Bogmærkemapper", @@ -664,9 +629,8 @@ }, "ui": { "title": "Brugerfladeindstillinger", - "subtitle": "Tema, sprog og synlige værktøjer i sidebjælken — fra dine kontoindstillinger.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Kunne ikke indlæse indstillinger.", - "loginHint": "Log ind for at se dine gemte grænsefladeindstillinger.", "theme": "Tema", "accent": "Accentfarve", "locale": "Sprog", @@ -674,13 +638,9 @@ "favoriteTools": "Foretrukne værktøjer", "toolsWithUsage": "Værktøjer med brugsstatistik" }, - "signIn": "Log ind", "noResults": "Ingen værktøjer matcher din søgning", "noResultsHint": "Prøv et andet nøgleord, eller ryd filteret.", "filterAll": "Alle", - "loginCta": { - "description": "Log ind for at spore dine senest brugte værktøjer." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Eksportér" }, "context": { - "authRequiredError": "Brugeren er ikke godkendt", "defaultTitle": "Uden titel", - "cloudSyncImageError": "Slå cloudsynkronisering til for at gemme notebilleder. Tekstnoter forbliver lokale." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Annuller", "confirmDelete": "Slet", "deleteDialogTitle": "Slet miljøsæt?", - "deleteDialogDescription": "Dette fjerner den krypterede blob fra din konto. Kan ikke fortrydes.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "Tekstpanel kopieret", "toastCopiedTree": "Træpanel kopieret", "toastCopyFailed": "Kunne ikke kopiere JSON", - "toastLoginRequired": "Log ind for at gemme dokumenter", "toastSaved": "Dokument gemt", "toastSaveFailed": "Kunne ikke gemme dokument", "format": "Formatér", @@ -5149,5 +5107,13 @@ "close": "Luk", "pill": "Opdaterer…", "pillPct": "Opdaterer… {pct} %" + }, + "Telemetry": { + "consent": { + "title": "Vil du hjælpe med at bestemme, hvad der bygges næst?", + "body": "Del brug anonymt: hvilke værktøjer du åbner, appens version og dit sprog. Ingen konto, intet enheds-id og intet af det, du skriver i et værktøj. Du kan altid ændre det under Indstillinger.", + "accept": "Del anonymt", + "decline": "Nej tak" + } } } diff --git a/apps/desktop-ui/messages/de.json b/apps/desktop-ui/messages/de.json index 904cf6cb..b36a0801 100644 --- a/apps/desktop-ui/messages/de.json +++ b/apps/desktop-ui/messages/de.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Was ist MyDevTools?", "description": "Ein Offline-Arbeitsbereich für Entwickler: ein Dashboard und über 80 Tools, die vollständig auf Ihrem Gerät laufen.", - "p1": "MyDevTools läuft vollständig offline. Nach einer einmaligen Aktivierung in Ihrem Browser funktioniert alles auf diesem Gerät – ohne Konto-Anmeldung und ohne Internetverbindung. In der Seitenleiste sind alle Apps aufgeführt; Tools, die Sie nicht verwenden, können Sie in den", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Einstellungen", "p1After": "ausblenden.", - "p2": "Einige Funktionen verwenden ein Master-Passwort (oder einen globalen Tresor-Schlüssel), damit vertrauliche Daten wie Passwörter und Datenbank-Verbindungszeichenfolgen auf Ihrem Gerät verschlüsselt werden. Ihre Daten werden in einer lokalen, verschlüsselten Datenbank gespeichert und niemals an einen Server gesendet." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Schnellzugriff", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Aktivierung & Offline-Zugriff", - "description": "Wie die App beim ersten Mal freigeschaltet wird und danach offline läuft.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Die einmalige Aktivierung erfolgt beim ersten Öffnen der App in Ihrem Browser. Sie erfasst Ihren Plan und schaltet die App auf diesem Gerät frei.", - "offline": "Keine Anmeldesitzungen. Nach der Aktivierung gibt es keine Anmeldung pro Sitzung und keine regelmäßige Überprüfung – die App funktioniert weiter, ganz ohne Internetverbindung.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Lokale Speicherung. Alle Ihre Daten liegen in einer verschlüsselten SQLCipher-Datenbank auf Ihrem Gerät, gesichert durch einen zufälligen Schlüssel, der im Schlüsselbund Ihres Betriebssystems hinterlegt ist." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Einstellungen", - "subtitle": "Verwalten Sie Ihre Kontoeinstellungen und Präferenzen.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Benutzerprofil", - "description": "Ihre persönlichen Daten und Kontoinformationen.", - "avatarAlt": "Benutzer", - "anonymousUser": "Anonymer Benutzer", - "noEmailProvided": "Keine E-Mail angegeben", - "notLoggedIn": "Nicht angemeldet." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Tools & Funktionen", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Website-Symbole im Tresor anzeigen", "helpText": "Standardmäßig aus. Das Laden eines Symbols verrät einem Drittanbieter, bei welchen Seiten Sie Konten haben." + }, + "telemetry": { + "label": "Anonyme Nutzungsdaten teilen", + "helpText": "Standardmäßig aus. Sendet, welche Tools Sie öffnen, die App-Version und Ihre Sprache. Kein Konto, keine Geräte-ID und nichts, was Sie in ein Tool eingeben." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Ihre Daten auf einen Blick", - "subtitle": "Zählerstand mit Ihrem Konto auf dem Server synchronisiert.", + "subtitle": "Counts recorded on this device.", "loadError": "Analysen konnten nicht geladen werden.", "retry": "Erneut versuchen", - "loginHint": "Melden Sie sich an, um Nutzungsanalysen und Oberflächeneinstellungen zu sehen.", "passwordEntries": "Passwort-Einträge", "bookmarks": "Lesezeichen", "bookmarkFolders": "Lesezeichen-Ordner", @@ -664,9 +629,8 @@ }, "ui": { "title": "Oberflächeneinstellungen", - "subtitle": "Design, Sprache und sichtbare Tools in der Seitenleiste — aus Ihren Kontoeinstellungen.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Einstellungen konnten nicht geladen werden.", - "loginHint": "Melden Sie sich an, um Ihre gespeicherten Oberflächeneinstellungen zu sehen.", "theme": "Design", "accent": "Akzentfarbe", "locale": "Sprache", @@ -674,13 +638,9 @@ "favoriteTools": "Favoriten-Tools", "toolsWithUsage": "Tools mit Nutzungsstatistik" }, - "signIn": "Anmelden", "noResults": "Keine Werkzeuge entsprechen deiner Suche", "noResultsHint": "Versuche ein anderes Stichwort oder lösche den Filter.", "filterAll": "Alle", - "loginCta": { - "description": "Melde dich an, um deine zuletzt verwendeten Werkzeuge zu verfolgen." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Exportieren" }, "context": { - "authRequiredError": "Benutzer nicht angemeldet", "defaultTitle": "Ohne Titel", - "cloudSyncImageError": "Aktiviere die Cloud-Synchronisierung, um Notizbilder zu speichern. Textnotizen bleiben lokal." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Abbrechen", "confirmDelete": "Löschen", "deleteDialogTitle": "Umgebungsset löschen?", - "deleteDialogDescription": "Der verschlüsselte Datensatz wird aus Ihrem Konto entfernt. Dies lässt sich nicht rückgängig machen.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "Textbereich kopiert", "toastCopiedTree": "Baumansicht kopiert", "toastCopyFailed": "JSON konnte nicht kopiert werden", - "toastLoginRequired": "Bitte melden Sie sich an, um Dokumente zu speichern", "toastSaved": "Dokument gespeichert", "toastSaveFailed": "Dokument konnte nicht gespeichert werden", "format": "Formatieren", @@ -5149,5 +5107,13 @@ "close": "Schließen", "pill": "Wird aktualisiert…", "pillPct": "Wird aktualisiert… {pct} %" + }, + "Telemetry": { + "consent": { + "title": "Möchten Sie mitentscheiden, was als Nächstes entsteht?", + "body": "Nutzung anonym teilen: welche Tools Sie öffnen, die App-Version und Ihre Sprache. Kein Konto, keine Geräte-ID und nichts, was Sie in ein Tool eingeben. Sie können das jederzeit in den Einstellungen ändern.", + "accept": "Anonym teilen", + "decline": "Nein, danke" + } } } diff --git a/apps/desktop-ui/messages/el.json b/apps/desktop-ui/messages/el.json index 3f083b32..dfbb9a66 100644 --- a/apps/desktop-ui/messages/el.json +++ b/apps/desktop-ui/messages/el.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Τι είναι το MyDevTools;", "description": "Ένας χώρος εργασίας για προγραμματιστές εκτός σύνδεσης: ένας πίνακας ελέγχου και πάνω από 80 εργαλεία που εκτελούνται εξ ολοκλήρου στη συσκευή σας.", - "p1": "Το MyDevTools λειτουργεί πλήρως εκτός σύνδεσης. Μετά από μια εφάπαξ ενεργοποίηση στο πρόγραμμα περιήγησής σας, όλα λειτουργούν σε αυτή τη συσκευή χωρίς σύνδεση σε λογαριασμό και χωρίς να απαιτείται σύνδεση στο διαδίκτυο. Η πλαϊνή γραμμή παραθέτει όλες τις εφαρμογές· μπορείτε να αποκρύψετε τα εργαλεία που δεν χρησιμοποιείτε από τις", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Ρυθμίσεις", "p1After": ".", - "p2": "Ορισμένες λειτουργίες χρησιμοποιούν έναν κύριο κωδικό πρόσβασης (ή καθολικό κλειδί θησαυροφυλακίου), ώστε μυστικά όπως κωδικοί πρόσβασης και συμβολοσειρές σύνδεσης βάσεων δεδομένων να κρυπτογραφούνται στη συσκευή σας. Τα δεδομένα σας αποθηκεύονται σε μια τοπική κρυπτογραφημένη βάση δεδομένων και δεν αποστέλλονται ποτέ σε διακομιστή." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Γρήγοροι σύνδεσμοι", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Ενεργοποίηση και πρόσβαση εκτός σύνδεσης", - "description": "Πώς ξεκλειδώνει η εφαρμογή την πρώτη φορά και στη συνέχεια λειτουργεί εκτός σύνδεσης.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Η εφάπαξ ενεργοποίηση πραγματοποιείται στο πρόγραμμα περιήγησής σας την πρώτη φορά που ανοίγετε την εφαρμογή. Καταγράφει το πρόγραμμά σας και ξεκλειδώνει την εφαρμογή σε αυτή τη συσκευή.", - "offline": "Καμία σύνδεση ανά συνεδρία. Μετά την ενεργοποίηση δεν υπάρχει σύνδεση ανά συνεδρία ούτε περιοδικός επανέλεγχος—η εφαρμογή συνεχίζει να λειτουργεί χωρίς σύνδεση στο διαδίκτυο.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Τοπική αποθήκευση. Όλα τα δεδομένα σας βρίσκονται σε μια κρυπτογραφημένη βάση δεδομένων SQLCipher στη συσκευή σας, με κλειδί ένα τυχαίο κλειδί που φυλάσσεται στην κλειδοθήκη του λειτουργικού σας συστήματος." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Ρυθμίσεις", - "subtitle": "Διαχειριστείτε τις ρυθμίσεις και τις προτιμήσεις του λογαριασμού σας.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Προφίλ χρήστη", - "description": "Τα προσωπικά σας στοιχεία και τα στοιχεία του λογαριασμού σας.", - "avatarAlt": "Χρήστης", - "anonymousUser": "Ανώνυμος χρήστης", - "noEmailProvided": "Δεν παρέχεται email", - "notLoggedIn": "Δεν έχετε συνδεθεί." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Εργαλεία & Χαρακτηριστικά", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Εμφάνιση εικονιδίων ιστότοπων", "helpText": "Ανενεργό από προεπιλογή. Η λήψη εικονιδίου αποκαλύπτει σε τρίτη υπηρεσία σε ποιους ιστότοπους έχετε λογαριασμούς." + }, + "telemetry": { + "label": "Κοινοποίηση ανώνυμων δεδομένων χρήσης", + "helpText": "Απενεργοποιημένο από προεπιλογή. Στέλνει ποια εργαλεία ανοίγετε, την έκδοση της εφαρμογής και τη γλώσσα σας. Χωρίς λογαριασμό, χωρίς αναγνωριστικό συσκευής και χωρίς τίποτα από όσα πληκτρολογείτε σε ένα εργαλείο." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Τα δεδομένα σας με μια ματιά", - "subtitle": "Μετρήσεις συγχρονισμένες με τον λογαριασμό σας στον διακομιστή.", + "subtitle": "Counts recorded on this device.", "loadError": "Δεν ήταν δυνατή η φόρτωση αναλυτικών.", "retry": "Επανάληψη", - "loginHint": "Συνδεθείτε για να δείτε αναλυτικά χρήσης και προτιμήσεις διεπαφής.", "passwordEntries": "Καταχωρήσεις κωδικών", "bookmarks": "Σελιδοδείκτες", "bookmarkFolders": "Φάκελοι σελιδοδεικτών", @@ -664,9 +629,8 @@ }, "ui": { "title": "Προτιμήσεις διεπαφής", - "subtitle": "Θέμα, γλώσσα και ορατά εργαλεία στην πλευρική γραμμή — από τις ρυθμίσεις λογαριασμού.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Δεν ήταν δυνατή η φόρτωση προτιμήσεων.", - "loginHint": "Συνδεθείτε για να δείτε τις αποθηκευμένες προτιμήσεις διεπαφής.", "theme": "Θέμα", "accent": "Χρώμα έμφασης", "locale": "Γλώσσα", @@ -674,13 +638,9 @@ "favoriteTools": "Αγαπημένα εργαλεία", "toolsWithUsage": "Εργαλεία με παρακολούθηση χρήσης" }, - "signIn": "Σύνδεση", "noResults": "Κανένα εργαλείο δεν ταιριάζει με την αναζήτησή σας", "noResultsHint": "Δοκιμάστε διαφορετική λέξη-κλειδί ή καθαρίστε το φίλτρο.", "filterAll": "Όλα", - "loginCta": { - "description": "Συνδεθείτε για να παρακολουθείτε τα πρόσφατα χρησιμοποιημένα εργαλεία σας." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Εξαγωγή" }, "context": { - "authRequiredError": "Ο χρήστης δεν έχει επικυρωθεί", "defaultTitle": "Χωρίς τίτλο", - "cloudSyncImageError": "Ενεργοποιήστε τον συγχρονισμό cloud για αποθήκευση εικόνων σημειώσεων. Οι σημειώσεις κειμένου παραμένουν τοπικές." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Ακύρωση", "confirmDelete": "Διαγραφή", "deleteDialogTitle": "Διαγραφή συνόλου περιβάλλοντος;", - "deleteDialogDescription": "Αφαιρεί το κρυπτογραφημένο αρχείο από τον λογαριασμό σας. Δεν μπορεί να αναιρεθεί.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "Το πάνελ κειμένου αντιγράφηκε", "toastCopiedTree": "Το πάνελ δέντρου αντιγράφηκε", "toastCopyFailed": "Αποτυχία αντιγραφής JSON", - "toastLoginRequired": "Συνδεθείτε για να αποθηκεύσετε έγγραφα", "toastSaved": "Το έγγραφο αποθηκεύτηκε", "toastSaveFailed": "Αποτυχία αποθήκευσης εγγράφου", "format": "Μορφοποίηση", @@ -5149,5 +5107,13 @@ "close": "Κλείσιμο", "pill": "Ενημέρωση…", "pillPct": "Ενημέρωση… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Θέλετε να βοηθήσετε να αποφασιστεί τι θα φτιαχτεί στη συνέχεια;", + "body": "Μοιραστείτε ανώνυμα τη χρήση: ποια εργαλεία ανοίγετε, την έκδοση της εφαρμογής και τη γλώσσα σας. Χωρίς λογαριασμό, χωρίς αναγνωριστικό συσκευής και χωρίς τίποτα από όσα πληκτρολογείτε σε ένα εργαλείο. Μπορείτε να το αλλάξετε οποιαδήποτε στιγμή στις Ρυθμίσεις.", + "accept": "Ανώνυμη κοινοποίηση", + "decline": "Όχι, ευχαριστώ" + } } } diff --git a/apps/desktop-ui/messages/en.json b/apps/desktop-ui/messages/en.json index b7172781..ee10a0be 100644 --- a/apps/desktop-ui/messages/en.json +++ b/apps/desktop-ui/messages/en.json @@ -1,19 +1,4 @@ { - "Activate": { - "title": "Activate MyDevTools", - "subtitle": "Sign in once with your browser to activate the app. After that, everything works fully offline.", - "signInWithBrowser": "Sign in with your browser", - "waitingForBrowser": "Waiting for the browser…", - "finishing": "Finishing activation…", - "authSuccess": "Authentication successful", - "offlineNote": "Yours forever — the app works fully offline from here on.", - "start": "Start using MyDevTools", - "privacyNote": "Your data stays on this device. The account is only used to activate the app." - }, - "AccountCard": { - "title": "Account", - "manage": "Manage account on mydevtools.tech" - }, "Navigation": { "dashboard": "Dashboard", "settings": "Settings", @@ -106,10 +91,10 @@ "whatIs": { "title": "What is MyDevTools?", "description": "An offline developer workspace: a dashboard and 80+ tools that run entirely on your device.", - "p1": "MyDevTools runs fully offline. After a one-time activation in your browser, everything works on this device with no account login and no internet connection required. The sidebar lists every app; you can hide tools you do not use from", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Settings", "p1After": ".", - "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted on your device. Your data is stored in a local encrypted database and is never sent to a server." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Quick links", @@ -125,11 +110,11 @@ }, "security": { "access": { - "title": "Activation & offline access", - "description": "How the app unlocks the first time, then runs offline.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "One-time activation happens in your browser the first time you open the app. It records your plan and unlocks the app on this device.", - "offline": "No login sessions. After activation there is no per-session sign-in and no periodic re-check—the app keeps working with no internet connection.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Local storage. All your data lives in an encrypted SQLCipher database on your device, keyed by a random key held in your operating system keychain." } }, @@ -188,62 +173,24 @@ }, "SettingsPage": { "title": "Settings", - "subtitle": "Manage your account settings and preferences.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "User Profile", - "description": "Your personal information and account details.", - "avatarAlt": "User", - "anonymousUser": "Anonymous User", - "noEmailProvided": "No email provided", - "notLoggedIn": "Not logged in." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Tools & Features", "description": "Choose which tools appear in your navigation sidebar." }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } - }, "appearance": { "title": "Appearance", "description": "Customize how the application looks on your device.", @@ -269,6 +216,10 @@ "vaultIcons": { "label": "Show site icons in the vault", "helpText": "Off by default. Fetching an icon tells a third-party service which sites you hold accounts on." + }, + "telemetry": { + "label": "Share anonymous usage data", + "helpText": "Off by default. Sends which tools you open, the app version and your language. No account, no device ID, and nothing you type in a tool." } }, "language": { @@ -314,9 +265,6 @@ "popularTools": "Popular tools", "filterAll": "All", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", - "loginCta": { - "description": "Sign in to track your recently used tools." - }, "greeting": { "morning": "Good morning", "afternoon": "Good afternoon", @@ -328,7 +276,6 @@ "tagline": "What would you like to build today?", "brandName": "MyDevTools", "viewActivity": "Recent activity", - "signIn": "Sign in", "stats": { "tools": "Tools", "favorites": "Favorites", @@ -351,7 +298,7 @@ }, "analytics": { "title": "Your data at a glance", - "subtitle": "Counts synced with your account on the server.", + "subtitle": "Counts recorded on this device.", "heroBadge": "Live sync", "totalLabel": "Tracked items", "totalHint": "Sum of every category below", @@ -363,7 +310,6 @@ "loadingLabel": "Syncing your stats…", "loadError": "Could not load analytics.", "retry": "Retry", - "loginHint": "Sign in to see usage analytics and interface preferences.", "passwordEntries": "Password entries", "bookmarks": "Bookmarks", "bookmarkFolders": "Bookmark folders", @@ -387,9 +333,8 @@ }, "ui": { "title": "Interface preferences", - "subtitle": "Theme, language, and which tools appear in the sidebar — from your account settings.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Could not load preferences.", - "loginHint": "Sign in to see your saved interface preferences.", "theme": "Theme", "accent": "Accent color", "locale": "Language", @@ -972,9 +917,8 @@ "export": "Export" }, "context": { - "authRequiredError": "User not authenticated", "defaultTitle": "Untitled", - "cloudSyncImageError": "Turn on Cloud Sync to store note images. Text notes stay local." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1230,7 +1174,7 @@ "cancel": "Cancel", "confirmDelete": "Delete", "deleteDialogTitle": "Delete environment set?", - "deleteDialogDescription": "This removes the encrypted blob from your account. You cannot undo this.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "noResultsTitle": "No results", "noResultsHint": "No sets match your search or filter. Try adjusting your query.", "sort": { @@ -2681,7 +2625,6 @@ "toastCopiedText": "Text pane copied", "toastCopiedTree": "Tree pane copied", "toastCopyFailed": "Failed to copy JSON", - "toastLoginRequired": "Please sign in to save documents", "toastSaved": "Document saved", "toastSaveFailed": "Failed to save document", "format": "Format", @@ -5185,5 +5128,13 @@ "close": "Close", "pill": "Updating…", "pillPct": "Updating… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Help decide what gets built next?", + "body": "Share anonymous usage: which tools you open, the app version and your language. No account, no device ID, and nothing you type in a tool. You can change this any time in Settings.", + "accept": "Share anonymously", + "decline": "No thanks" + } } } diff --git a/apps/desktop-ui/messages/es.json b/apps/desktop-ui/messages/es.json index e375ca3d..247e3ca5 100644 --- a/apps/desktop-ui/messages/es.json +++ b/apps/desktop-ui/messages/es.json @@ -91,10 +91,10 @@ "whatIs": { "title": "¿Qué es MyDevTools?", "description": "Un espacio de trabajo para desarrolladores sin conexión: un panel y más de 80 herramientas que se ejecutan por completo en tu dispositivo.", - "p1": "MyDevTools funciona totalmente sin conexión. Tras una activación única en tu navegador, todo funciona en este dispositivo sin necesidad de iniciar sesión con una cuenta ni de conexión a Internet. La barra lateral muestra todas las aplicaciones; puedes ocultar las herramientas que no uses desde", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Ajustes", "p1After": ".", - "p2": "Algunas funciones usan una contraseña maestra (o clave global de la bóveda) para que los datos sensibles, como contraseñas y cadenas de conexión a bases de datos, se cifren en tu dispositivo. Tus datos se guardan en una base de datos local cifrada y nunca se envían a un servidor." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Enlaces rápidos", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Activación y acceso sin conexión", - "description": "Cómo se desbloquea la aplicación la primera vez y luego funciona sin conexión.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "La activación única se realiza en tu navegador la primera vez que abres la aplicación. Registra tu plan y desbloquea la aplicación en este dispositivo.", - "offline": "Sin sesiones de inicio de sesión. Tras la activación no hay inicio de sesión por sesión ni comprobaciones periódicas: la aplicación sigue funcionando sin conexión a Internet.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Almacenamiento local. Todos tus datos residen en una base de datos SQLCipher cifrada en tu dispositivo, protegida por una clave aleatoria guardada en el llavero de tu sistema operativo." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Configuraciones", - "subtitle": "Administra la configuración y preferencias de tu cuenta.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Perfil de usuario", - "description": "Tu información personal y los detalles de tu cuenta.", - "avatarAlt": "Usuario", - "anonymousUser": "Usuario anónimo", - "noEmailProvided": "No se proporcionó correo electrónico", - "notLoggedIn": "No has iniciado sesión." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Herramientas y funciones", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Mostrar iconos de sitios en la bóveda", "helpText": "Desactivado por defecto. Descargar un icono revela a un servicio externo en qué sitios tienes cuentas." + }, + "telemetry": { + "label": "Compartir datos de uso anónimos", + "helpText": "Desactivado por defecto. Envía qué herramientas abres, la versión de la aplicación y tu idioma. Sin cuenta, sin identificador de dispositivo y sin nada de lo que escribes en una herramienta." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Tus datos de un vistazo", - "subtitle": "Recuentos sincronizados con tu cuenta en el servidor.", + "subtitle": "Counts recorded on this device.", "loadError": "No se pudieron cargar las estadísticas.", "retry": "Reintentar", - "loginHint": "Inicia sesión para ver el análisis de uso y las preferencias de interfaz.", "passwordEntries": "Entradas de contraseñas", "bookmarks": "Marcadores", "bookmarkFolders": "Carpetas de marcadores", @@ -664,9 +629,8 @@ }, "ui": { "title": "Preferencias de interfaz", - "subtitle": "Tema, idioma y herramientas visibles en la barra lateral — desde la configuración de tu cuenta.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "No se pudieron cargar las preferencias.", - "loginHint": "Inicia sesión para ver tus preferencias de interfaz guardadas.", "theme": "Tema", "accent": "Color de acento", "locale": "Idioma", @@ -674,13 +638,9 @@ "favoriteTools": "Herramientas favoritas", "toolsWithUsage": "Herramientas con uso registrado" }, - "signIn": "Iniciar sesión", "noResults": "Ninguna herramienta coincide con tu búsqueda", "noResultsHint": "Prueba con otra palabra clave o elimina el filtro.", "filterAll": "Todo", - "loginCta": { - "description": "Inicia sesión para rastrear tus herramientas usadas recientemente." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Exportar" }, "context": { - "authRequiredError": "Usuario no autenticado", "defaultTitle": "Sin título", - "cloudSyncImageError": "Activa la sincronización en la nube para guardar imágenes de notas. Las notas de texto permanecen locales." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Cancelar", "confirmDelete": "Eliminar", "deleteDialogTitle": "¿Eliminar conjunto de entorno?", - "deleteDialogDescription": "Esto elimina el blob cifrado de tu cuenta. No se puede deshacer.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2656,7 +2615,6 @@ "toastCopiedText": "Panel de texto copiado", "toastCopiedTree": "Panel de árbol copiado", "toastCopyFailed": "Error al copiar JSON", - "toastLoginRequired": "Inicia sesión para guardar documentos", "toastSaved": "Documento guardado", "toastSaveFailed": "Error al guardar el documento", "format": "Formatear", @@ -5160,5 +5118,13 @@ "close": "Cerrar", "pill": "Actualizando…", "pillPct": "Actualizando… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "¿Nos ayudas a decidir qué construir a continuación?", + "body": "Comparte el uso de forma anónima: qué herramientas abres, la versión de la aplicación y tu idioma. Sin cuenta, sin identificador de dispositivo y sin nada de lo que escribes en una herramienta. Puedes cambiarlo cuando quieras en Ajustes.", + "accept": "Compartir de forma anónima", + "decline": "No, gracias" + } } } diff --git a/apps/desktop-ui/messages/fa.json b/apps/desktop-ui/messages/fa.json index b51051d3..fd960552 100644 --- a/apps/desktop-ui/messages/fa.json +++ b/apps/desktop-ui/messages/fa.json @@ -91,10 +91,10 @@ "whatIs": { "title": "MyDevTools چیست؟", "description": "یک فضای کاری آفلاین برای توسعه‌دهندگان: یک داشبورد و بیش از ۸۰ ابزار که کاملاً روی دستگاه شما اجرا می‌شوند.", - "p1": "MyDevTools کاملاً آفلاین اجرا می‌شود. پس از یک فعال‌سازی یک‌باره در مرورگر شما، همه چیز روی این دستگاه کار می‌کند، بدون نیاز به ورود به حساب کاربری و بدون نیاز به اتصال اینترنت. نوار کناری همه‌ی برنامه‌ها را فهرست می‌کند؛ می‌توانید ابزارهایی را که استفاده نمی‌کنید از", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "تنظیمات", "p1After": " پنهان کنید.", - "p2": "برخی از قابلیت‌ها از یک رمز اصلی (یا کلید سراسری گاوصندوق) استفاده می‌کنند تا اسراری مانند رمزهای عبور و رشته‌های اتصال پایگاه داده روی دستگاه شما رمزنگاری شوند. داده‌های شما در یک پایگاه داده‌ی محلی رمزنگاری‌شده ذخیره می‌شوند و هرگز به سرور ارسال نمی‌شوند." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "پیوندهای سریع", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "فعال‌سازی و دسترسی آفلاین", - "description": "چگونه برنامه بار اول باز می‌شود و سپس آفلاین اجرا می‌شود.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "فعال‌سازی یک‌باره نخستین باری که برنامه را باز می‌کنید در مرورگر شما انجام می‌شود. این کار پلن شما را ثبت کرده و قفل برنامه را روی این دستگاه باز می‌کند.", - "offline": "بدون نشست‌های ورود. پس از فعال‌سازی، هیچ ورود جداگانه برای هر نشست و هیچ بازبینی دوره‌ای وجود ندارد؛ برنامه بدون اتصال اینترنت به کار خود ادامه می‌دهد.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "ذخیره‌سازی محلی. همه‌ی داده‌های شما در یک پایگاه داده‌ی رمزنگاری‌شده‌ی SQLCipher روی دستگاه شما قرار دارند که با یک کلید تصادفی نگهداری‌شده در کی‌چین سیستم‌عامل شما ایمن شده است." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "تنظیمات", - "subtitle": "تنظیمات و تنظیمات برگزیده حساب خود را مدیریت کنید.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "نمایه کاربر", - "description": "اطلاعات شخصی و جزئیات حساب شما.", - "avatarAlt": "کاربر", - "anonymousUser": "کاربر ناشناس", - "noEmailProvided": "ایمیلی ارائه نشده است", - "notLoggedIn": "وارد نشده اید." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "ابزارها و ویژگی ها", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "نمایش نمادهای سایت در گاوصندوق", "helpText": "به‌طور پیش‌فرض خاموش. دریافت نماد به سرویس ثالث می‌گوید در چه سایت‌هایی حساب دارید." + }, + "telemetry": { + "label": "اشتراک‌گذاری داده‌های ناشناس استفاده", + "helpText": "به‌طور پیش‌فرض خاموش است. ارسال می‌کند که چه ابزارهایی را باز می‌کنید، نسخهٔ برنامه و زبان شما. بدون حساب کاربری، بدون شناسهٔ دستگاه و بدون هیچ چیزی که در ابزار می‌نویسید." } }, "language": { @@ -248,49 +257,6 @@ "uk": "Українська", "fa": "فارسی" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "داده‌های شما در یک نگاه", - "subtitle": "شمارش‌ها همگام‌شده با حساب شما روی سرور.", + "subtitle": "Counts recorded on this device.", "loadError": "بارگذاری تحلیل ممکن نشد.", "retry": "تلاش دوباره", - "loginHint": "برای دیدن تحلیل استفاده و ترجیحات رابط وارد شوید.", "passwordEntries": "ورودی‌های گذرواژه", "bookmarks": "نشانک‌ها", "bookmarkFolders": "پوشه‌های نشانک", @@ -664,9 +629,8 @@ }, "ui": { "title": "ترجیحات رابط", - "subtitle": "پوسته، زبان و ابزارهای نوار کناری — از تنظیمات حساب.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "بارگذاری ترجیحات ممکن نشد.", - "loginHint": "برای دیدن ترجیحات ذخیره‌شده وارد شوید.", "theme": "پوسته", "accent": "رنگ تأکید", "locale": "زبان", @@ -674,13 +638,9 @@ "favoriteTools": "ابزارهای مورد علاقه", "toolsWithUsage": "ابزارها با آمار استفاده" }, - "signIn": "ورود", "noResults": "هیچ ابزاری با جستجوی شما مطابقت ندارد", "noResultsHint": "یک کلیدواژه متفاوت امتحان کنید یا فیلتر را پاک کنید.", "filterAll": "همه", - "loginCta": { - "description": "وارد شوید تا ابزارهای اخیراً استفاده شده خود را دنبال کنید." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "خروجی گرفتن" }, "context": { - "authRequiredError": "کاربر احراز هویت نشده است", "defaultTitle": "بدون عنوان", - "cloudSyncImageError": "برای ذخیره تصاویر یادداشت، همگام‌سازی ابری را روشن کنید. یادداشت‌های متنی محلی می‌مانند." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "لغو", "confirmDelete": "حذف", "deleteDialogTitle": "حذف مجموعه محیط؟", - "deleteDialogDescription": "بلاب رمزگذاری‌شده از حساب شما حذف می‌شود. قابل بازگشت نیست.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2656,7 +2615,6 @@ "toastCopiedText": "پنل متن کپی شد", "toastCopiedTree": "پنل درختی کپی شد", "toastCopyFailed": "کپی JSON ناموفق بود", - "toastLoginRequired": "برای ذخیره اسناد وارد شوید", "toastSaved": "سند ذخیره شد", "toastSaveFailed": "ذخیره سند ناموفق بود", "format": "قالب‌بندی", @@ -5160,5 +5118,13 @@ "close": "بستن", "pill": "در حال به‌روزرسانی…", "pillPct": "در حال به‌روزرسانی… {pct}٪" + }, + "Telemetry": { + "consent": { + "title": "کمک می‌کنید تصمیم بگیریم بعد چه چیزی ساخته شود؟", + "body": "استفاده را به‌صورت ناشناس به اشتراک بگذارید: چه ابزارهایی را باز می‌کنید، نسخهٔ برنامه و زبان شما. بدون حساب کاربری، بدون شناسهٔ دستگاه و بدون هیچ چیزی که در ابزار می‌نویسید. هر زمان می‌توانید این را در تنظیمات تغییر دهید.", + "accept": "اشتراک‌گذاری ناشناس", + "decline": "نه، ممنون" + } } } diff --git a/apps/desktop-ui/messages/fr.json b/apps/desktop-ui/messages/fr.json index d81dc1bd..dd2b7081 100644 --- a/apps/desktop-ui/messages/fr.json +++ b/apps/desktop-ui/messages/fr.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Qu'est-ce que MyDevTools ?", "description": "Un espace de travail hors ligne pour les développeurs : un tableau de bord et plus de 80 outils qui s'exécutent entièrement sur votre appareil.", - "p1": "MyDevTools fonctionne entièrement hors ligne. Après une activation unique dans votre navigateur, tout fonctionne sur cet appareil, sans connexion à un compte ni connexion Internet. La barre latérale répertorie toutes les applications ; vous pouvez masquer les outils que vous n'utilisez pas depuis les", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Paramètres", "p1After": ".", - "p2": "Certaines fonctionnalités utilisent un mot de passe maître (ou une clé de coffre globale) afin que les données sensibles comme les mots de passe et les chaînes de connexion aux bases de données soient chiffrées sur votre appareil. Vos données sont stockées dans une base de données locale chiffrée et ne sont jamais envoyées à un serveur." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Liens rapides", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Activation & accès hors ligne", - "description": "Comment l'application se déverrouille la première fois, puis fonctionne hors ligne.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "L'activation unique a lieu dans votre navigateur lors de la première ouverture de l'application. Elle enregistre votre forfait et déverrouille l'application sur cet appareil.", - "offline": "Aucune session de connexion. Après l'activation, il n'y a pas de connexion par session ni de nouvelle vérification périodique : l'application continue de fonctionner sans connexion Internet.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Stockage local. Toutes vos données résident dans une base de données SQLCipher chiffrée sur votre appareil, protégée par une clé aléatoire conservée dans le trousseau de votre système d'exploitation." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Paramètres", - "subtitle": "Gérez les paramètres et préférences de votre compte.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Profil utilisateur", - "description": "Vos informations personnelles et détails du compte.", - "avatarAlt": "Utilisateur", - "anonymousUser": "Utilisateur anonyme", - "noEmailProvided": "Aucun e-mail fourni", - "notLoggedIn": "Non connecté." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Outils et fonctionnalités", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Afficher les icônes de sites dans le coffre", "helpText": "Désactivé par défaut. Récupérer une icône indique à un service tiers sur quels sites vous avez des comptes." + }, + "telemetry": { + "label": "Partager des données d'utilisation anonymes", + "helpText": "Désactivé par défaut. Envoie les outils que vous ouvrez, la version de l'application et votre langue. Aucun compte, aucun identifiant d'appareil et rien de ce que vous saisissez dans un outil." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Vos données en un coup d’œil", - "subtitle": "Compteurs synchronisés avec votre compte sur le serveur.", + "subtitle": "Counts recorded on this device.", "loadError": "Impossible de charger les statistiques.", "retry": "Réessayer", - "loginHint": "Connectez-vous pour voir les analyses d’utilisation et les préférences d’interface.", "passwordEntries": "Entrées de mots de passe", "bookmarks": "Signets", "bookmarkFolders": "Dossiers de signets", @@ -664,9 +629,8 @@ }, "ui": { "title": "Préférences d’interface", - "subtitle": "Thème, langue et outils visibles dans la barre latérale — depuis les paramètres du compte.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Impossible de charger les préférences.", - "loginHint": "Connectez-vous pour voir vos préférences d’interface enregistrées.", "theme": "Thème", "accent": "Couleur d’accent", "locale": "Langue", @@ -674,13 +638,9 @@ "favoriteTools": "Outils favoris", "toolsWithUsage": "Outils avec suivi d’utilisation" }, - "signIn": "Se connecter", "noResults": "Aucun outil ne correspond à votre recherche", "noResultsHint": "Essayez un autre mot-clé ou effacez le filtre.", "filterAll": "Tout", - "loginCta": { - "description": "Connectez-vous pour suivre vos outils récemment utilisés." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Exporter" }, "context": { - "authRequiredError": "Utilisateur non authentifié", "defaultTitle": "Sans titre", - "cloudSyncImageError": "Activez la synchronisation cloud pour stocker les images des notes. Les notes texte restent locales." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Annuler", "confirmDelete": "Supprimer", "deleteDialogTitle": "Supprimer le jeu d'environnement ?", - "deleteDialogDescription": "Cela supprime le blob chiffré de votre compte. Action irréversible.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2656,7 +2615,6 @@ "toastCopiedText": "Panneau texte copié", "toastCopiedTree": "Panneau arborescence copié", "toastCopyFailed": "Échec de la copie du JSON", - "toastLoginRequired": "Connectez-vous pour enregistrer des documents", "toastSaved": "Document enregistré", "toastSaveFailed": "Échec de l'enregistrement du document", "format": "Formater", @@ -5160,5 +5118,13 @@ "close": "Fermer", "pill": "Mise à jour…", "pillPct": "Mise à jour… {pct} %" + }, + "Telemetry": { + "consent": { + "title": "Vous voulez aider à décider de la suite ?", + "body": "Partagez votre utilisation de façon anonyme : les outils que vous ouvrez, la version de l'application et votre langue. Aucun compte, aucun identifiant d'appareil et rien de ce que vous saisissez dans un outil. Vous pouvez changer d'avis à tout moment dans les Paramètres.", + "accept": "Partager anonymement", + "decline": "Non merci" + } } } diff --git a/apps/desktop-ui/messages/id.json b/apps/desktop-ui/messages/id.json index e7bb6f71..f04b3d09 100644 --- a/apps/desktop-ui/messages/id.json +++ b/apps/desktop-ui/messages/id.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Apa itu MyDevTools?", "description": "Ruang kerja pengembang yang offline: sebuah dasbor dan 80+ alat yang berjalan sepenuhnya di perangkat Anda.", - "p1": "MyDevTools berjalan sepenuhnya secara offline. Setelah aktivasi sekali saja di browser Anda, semuanya bekerja di perangkat ini tanpa login akun dan tanpa memerlukan koneksi internet. Bilah samping menampilkan setiap aplikasi; Anda dapat menyembunyikan alat yang tidak Anda gunakan dari", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Pengaturan", "p1After": ".", - "p2": "Beberapa fitur menggunakan kata sandi utama (atau kunci brankas global) sehingga rahasia seperti kata sandi dan string koneksi basis data terenkripsi di perangkat Anda. Data Anda disimpan dalam basis data terenkripsi lokal dan tidak pernah dikirim ke server." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Tautan cepat", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Aktivasi & akses offline", - "description": "Bagaimana aplikasi terbuka pertama kali, lalu berjalan secara offline.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Aktivasi sekali saja terjadi di browser Anda saat pertama kali membuka aplikasi. Aktivasi mencatat paket Anda dan membuka kunci aplikasi di perangkat ini.", - "offline": "Tanpa sesi login. Setelah aktivasi, tidak ada login per sesi dan tidak ada pemeriksaan ulang berkala—aplikasi tetap bekerja tanpa koneksi internet.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Penyimpanan lokal. Semua data Anda berada dalam basis data SQLCipher terenkripsi di perangkat Anda, dikunci dengan kunci acak yang disimpan di keychain sistem operasi Anda." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Pengaturan", - "subtitle": "Kelola pengaturan dan preferensi akun Anda.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Profil Mitra", - "description": "Informasi pribadi dan detail akun Anda.", - "avatarAlt": "Pengguna", - "anonymousUser": "Pengguna Anonim", - "noEmailProvided": "Tidak ada email yang diberikan", - "notLoggedIn": "Belum masuk" + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Alat & Fitur", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Tampilkan ikon situs di brankas", "helpText": "Mati secara bawaan. Mengambil ikon memberi tahu layanan pihak ketiga di situs mana Anda punya akun." + }, + "telemetry": { + "label": "Bagikan data penggunaan anonim", + "helpText": "Nonaktif secara bawaan. Mengirim alat apa yang Anda buka, versi aplikasi, dan bahasa Anda. Tanpa akun, tanpa ID perangkat, dan tanpa apa pun yang Anda ketik di dalam alat." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Data Anda sekilas", - "subtitle": "Jumlah disinkronkan dengan akun Anda di server.", + "subtitle": "Counts recorded on this device.", "loadError": "Tidak dapat memuat analitik.", "retry": "Coba lagi", - "loginHint": "Masuk untuk melihat analitik penggunaan dan preferensi antarmuka.", "passwordEntries": "Entri kata sandi", "bookmarks": "Penanda", "bookmarkFolders": "Folder penanda", @@ -664,9 +629,8 @@ }, "ui": { "title": "Preferensi antarmuka", - "subtitle": "Tema, bahasa, dan alat yang terlihat di bilah sisi — dari pengaturan akun.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Tidak dapat memuat preferensi.", - "loginHint": "Masuk untuk melihat preferensi antarmuka yang disimpan.", "theme": "Tema", "accent": "Warna aksen", "locale": "Bahasa", @@ -674,13 +638,9 @@ "favoriteTools": "Alat favorit", "toolsWithUsage": "Alat dengan pelacakan penggunaan" }, - "signIn": "Masuk", "noResults": "Tidak ada alat yang cocok dengan pencarian Anda", "noResultsHint": "Coba kata kunci lain atau hapus filter.", "filterAll": "Semua", - "loginCta": { - "description": "Masuk untuk melacak alat yang baru saja Anda gunakan." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Ekspor" }, "context": { - "authRequiredError": "Pengguna tidak diautentikasi", "defaultTitle": "Tanpa judul", - "cloudSyncImageError": "Aktifkan Sinkronisasi Cloud untuk menyimpan gambar catatan. Catatan teks tetap lokal." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Batal", "confirmDelete": "Hapus", "deleteDialogTitle": "Hapus set lingkungan?", - "deleteDialogDescription": "Ini menghapus data terenkripsi dari akun Anda. Tidak dapat dibatalkan.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "Panel teks disalin", "toastCopiedTree": "Panel pohon disalin", "toastCopyFailed": "Gagal menyalin JSON", - "toastLoginRequired": "Silakan masuk untuk menyimpan dokumen", "toastSaved": "Dokumen disimpan", "toastSaveFailed": "Gagal menyimpan dokumen", "format": "Format", @@ -5149,5 +5107,13 @@ "close": "Tutup", "pill": "Memperbarui…", "pillPct": "Memperbarui… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Bantu tentukan apa yang dibuat berikutnya?", + "body": "Bagikan penggunaan secara anonim: alat apa yang Anda buka, versi aplikasi, dan bahasa Anda. Tanpa akun, tanpa ID perangkat, dan tanpa apa pun yang Anda ketik di dalam alat. Anda dapat mengubahnya kapan saja di Pengaturan.", + "accept": "Bagikan secara anonim", + "decline": "Tidak, terima kasih" + } } } diff --git a/apps/desktop-ui/messages/it.json b/apps/desktop-ui/messages/it.json index 628ee78c..a5d4201b 100644 --- a/apps/desktop-ui/messages/it.json +++ b/apps/desktop-ui/messages/it.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Che cos'è MyDevTools?", "description": "Uno spazio di lavoro offline per sviluppatori: una dashboard e oltre 80 strumenti che vengono eseguiti interamente sul tuo dispositivo.", - "p1": "MyDevTools funziona completamente offline. Dopo un'attivazione una tantum nel browser, tutto funziona su questo dispositivo senza accesso a un account e senza connessione a Internet. La barra laterale elenca tutte le app; puoi nascondere gli strumenti che non usi dalle", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Impostazioni", "p1After": ".", - "p2": "Alcune funzionalità utilizzano una password principale (o chiave globale della cassaforte) affinché i dati sensibili come password e stringhe di connessione ai database siano cifrati sul tuo dispositivo. I tuoi dati vengono archiviati in un database locale cifrato e non vengono mai inviati a un server." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Collegamenti rapidi", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Attivazione e accesso offline", - "description": "Come si sblocca l'app la prima volta e come funziona poi offline.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "L'attivazione una tantum avviene nel browser la prima volta che apri l'app. Registra il tuo piano e sblocca l'app su questo dispositivo.", - "offline": "Nessuna sessione di accesso. Dopo l'attivazione non c'è alcun accesso per sessione né controlli periodici: l'app continua a funzionare senza connessione a Internet.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Archiviazione locale. Tutti i tuoi dati risiedono in un database SQLCipher cifrato sul tuo dispositivo, protetto da una chiave casuale conservata nel portachiavi del tuo sistema operativo." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Impostazioni", - "subtitle": "Gestisci le impostazioni e le preferenze del tuo account.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Profilo utente", - "description": "Le tue informazioni personali e i dettagli dell'account.", - "avatarAlt": "Utente", - "anonymousUser": "Utente anonimo", - "noEmailProvided": "Nessuna e-mail fornita", - "notLoggedIn": "Non effettuato l'accesso." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Strumenti e funzionalità", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Mostra icone dei siti nella cassaforte", "helpText": "Disattivato per impostazione predefinita. Scaricare un'icona rivela a un servizio esterno su quali siti hai account." + }, + "telemetry": { + "label": "Condividi dati di utilizzo anonimi", + "helpText": "Disattivato per impostazione predefinita. Invia quali strumenti apri, la versione dell'app e la tua lingua. Nessun account, nessun ID dispositivo e nulla di ciò che digiti in uno strumento." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "I tuoi dati a colpo d’occhio", - "subtitle": "Conteggi sincronizzati con il tuo account sul server.", + "subtitle": "Counts recorded on this device.", "loadError": "Impossibile caricare le statistiche.", "retry": "Riprova", - "loginHint": "Accedi per vedere analisi d’uso e preferenze di interfaccia.", "passwordEntries": "Voci password", "bookmarks": "Segnalibri", "bookmarkFolders": "Cartelle segnalibri", @@ -664,9 +629,8 @@ }, "ui": { "title": "Preferenze interfaccia", - "subtitle": "Tema, lingua e strumenti visibili nella barra laterale — dalle impostazioni account.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Impossibile caricare le preferenze.", - "loginHint": "Accedi per vedere le preferenze di interfaccia salvate.", "theme": "Tema", "accent": "Colore accento", "locale": "Lingua", @@ -674,13 +638,9 @@ "favoriteTools": "Strumenti preferiti", "toolsWithUsage": "Strumenti con utilizzo tracciato" }, - "signIn": "Accedi", "noResults": "Nessuno strumento corrisponde alla tua ricerca", "noResultsHint": "Prova una parola chiave diversa o cancella il filtro.", "filterAll": "Tutti", - "loginCta": { - "description": "Accedi per tenere traccia degli strumenti usati di recente." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Esporta" }, "context": { - "authRequiredError": "Utente non autenticato", "defaultTitle": "Senza titolo", - "cloudSyncImageError": "Attiva la sincronizzazione cloud per salvare le immagini delle note. Le note di testo restano locali." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Annulla", "confirmDelete": "Elimina", "deleteDialogTitle": "Eliminare il set di ambiente?", - "deleteDialogDescription": "Rimuove il blob crittografato dal tuo account. Operazione irreversibile.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2656,7 +2615,6 @@ "toastCopiedText": "Pannello testo copiato", "toastCopiedTree": "Pannello albero copiato", "toastCopyFailed": "Impossibile copiare il JSON", - "toastLoginRequired": "Accedi per salvare i documenti", "toastSaved": "Documento salvato", "toastSaveFailed": "Impossibile salvare il documento", "format": "Formatta", @@ -5160,5 +5118,13 @@ "close": "Chiudi", "pill": "Aggiornamento…", "pillPct": "Aggiornamento… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Vuoi aiutarci a decidere cosa costruire dopo?", + "body": "Condividi l'utilizzo in forma anonima: quali strumenti apri, la versione dell'app e la tua lingua. Nessun account, nessun ID dispositivo e nulla di ciò che digiti in uno strumento. Puoi cambiare idea in qualsiasi momento nelle Impostazioni.", + "accept": "Condividi in forma anonima", + "decline": "No, grazie" + } } } diff --git a/apps/desktop-ui/messages/ja.json b/apps/desktop-ui/messages/ja.json index 7e4e29dd..049c0a75 100644 --- a/apps/desktop-ui/messages/ja.json +++ b/apps/desktop-ui/messages/ja.json @@ -91,10 +91,10 @@ "whatIs": { "title": "MyDevTools とは?", "description": "オフラインの開発者ワークスペース。ダッシュボードと、すべてデバイス上で動作する 80 以上のツールを備えています。", - "p1": "MyDevTools は完全にオフラインで動作します。ブラウザで一度アクティベーションを行えば、アカウントへのログインもインターネット接続も不要で、すべてがこのデバイス上で動作します。サイドバーにはすべてのアプリが表示されます。使わないツールは", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "設定", "p1After": "から非表示にできます。", - "p2": "一部の機能ではマスターパスワード(またはグローバルボルトキー)を使用し、パスワードやデータベース接続文字列などの機密情報をデバイス上で暗号化します。データはローカルの暗号化データベースに保存され、サーバーへ送信されることは一切ありません。" + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "クイックリンク", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "アクティベーションとオフラインアクセス", - "description": "アプリが初回に解除される仕組みと、その後オフラインで動作する方法。", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "アプリを初めて開くとき、ブラウザで一度限りのアクティベーションが行われます。これによりプランが記録され、このデバイス上でアプリのロックが解除されます。", - "offline": "ログインセッションはありません。アクティベーション後は、セッションごとのサインインも定期的な再チェックもありません。アプリはインターネット接続なしで動作し続けます。", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "ローカルストレージ。すべてのデータはデバイス上の暗号化された SQLCipher データベースに保存され、オペレーティングシステムのキーチェーンに保持されたランダムキーで暗号化されます。" } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "設定", - "subtitle": "アカウント設定と環境設定を管理します。", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "ユーザープロフィール", - "description": "個人情報とアカウントの詳細です。", - "avatarAlt": "ユーザー", - "anonymousUser": "匿名ユーザー", - "noEmailProvided": "メールアドレス未設定", - "notLoggedIn": "ログインしていません。" + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "ツールと機能", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "保管庫でサイトアイコンを表示", "helpText": "既定はオフ。アイコンの取得は、どのサイトにアカウントがあるかを外部サービスに知らせます。" + }, + "telemetry": { + "label": "匿名の利用データを共有", + "helpText": "既定はオフ。開いたツール、アプリのバージョン、言語を送信します。アカウントも端末IDもなく、ツールに入力した内容は一切送信されません。" } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "データの概要", - "subtitle": "サーバー上のアカウントと同期された件数です。", + "subtitle": "Counts recorded on this device.", "loadError": "分析を読み込めませんでした。", "retry": "再試行", - "loginHint": "ログインすると利用状況の分析とインターフェース設定を表示できます。", "passwordEntries": "パスワード項目", "bookmarks": "ブックマーク", "bookmarkFolders": "ブックマークフォルダ", @@ -664,9 +629,8 @@ }, "ui": { "title": "インターフェース設定", - "subtitle": "テーマ・言語・サイドバーに表示するツール — アカウント設定から。", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "設定を読み込めませんでした。", - "loginHint": "ログインすると保存したインターフェース設定を表示できます。", "theme": "テーマ", "accent": "アクセントカラー", "locale": "言語", @@ -674,13 +638,9 @@ "favoriteTools": "お気に入りのツール", "toolsWithUsage": "利用状況が記録されたツール" }, - "signIn": "ログイン", "noResults": "検索に一致するツールがありません", "noResultsHint": "別のキーワードを試すか、フィルターをクリアしてください。", "filterAll": "すべて", - "loginCta": { - "description": "ログインして最近使用したツールを追跡しましょう。" - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "エクスポート" }, "context": { - "authRequiredError": "ユーザーが認証されていません", "defaultTitle": "無題", - "cloudSyncImageError": "ノート画像を保存するにはクラウド同期をオンにしてください。テキストノートはローカルに保存されます。" + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "キャンセル", "confirmDelete": "削除", "deleteDialogTitle": "環境セットを削除しますか?", - "deleteDialogDescription": "アカウントから暗号化データが削除されます。元に戻せません。", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "テキストパネルをコピーしました", "toastCopiedTree": "ツリーパネルをコピーしました", "toastCopyFailed": "JSONのコピーに失敗しました", - "toastLoginRequired": "ドキュメントを保存するにはログインしてください", "toastSaved": "ドキュメントを保存しました", "toastSaveFailed": "ドキュメントの保存に失敗しました", "format": "整形", @@ -5149,5 +5107,13 @@ "close": "閉じる", "pill": "更新中…", "pillPct": "更新中… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "次に作るものを決めるのを手伝いませんか?", + "body": "利用状況を匿名で共有します。送るのは、開いたツール、アプリのバージョン、言語だけです。アカウントも端末IDもなく、ツールに入力した内容は一切送信されません。設定からいつでも変更できます。", + "accept": "匿名で共有", + "decline": "共有しない" + } } } diff --git a/apps/desktop-ui/messages/ko.json b/apps/desktop-ui/messages/ko.json index a457f19a..57c45243 100644 --- a/apps/desktop-ui/messages/ko.json +++ b/apps/desktop-ui/messages/ko.json @@ -91,10 +91,10 @@ "whatIs": { "title": "MyDevTools란?", "description": "오프라인 개발자 작업 공간입니다. 대시보드와 함께 전적으로 기기에서 실행되는 80개 이상의 도구를 제공합니다.", - "p1": "MyDevTools는 완전히 오프라인으로 작동합니다. 브라우저에서 한 번만 활성화하면 계정 로그인이나 인터넷 연결 없이 이 기기에서 모든 것이 작동합니다. 사이드바에는 모든 앱이 나열되며, 사용하지 않는 도구는", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "설정", "p1After": "에서 숨길 수 있습니다.", - "p2": "일부 기능은 마스터 비밀번호(또는 전역 볼트 키)를 사용하여 비밀번호나 데이터베이스 연결 문자열과 같은 비밀 정보를 기기에서 암호화합니다. 데이터는 로컬 암호화 데이터베이스에 저장되며 서버로 전송되지 않습니다." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "바로 가기", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "활성화 및 오프라인 접근", - "description": "앱이 처음 잠금 해제되는 방식과 이후 오프라인으로 실행되는 방법.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "앱을 처음 열 때 브라우저에서 일회성 활성화가 진행됩니다. 이를 통해 요금제가 기록되고 이 기기에서 앱의 잠금이 해제됩니다.", - "offline": "로그인 세션이 없습니다. 활성화 이후에는 세션마다 로그인하거나 주기적으로 재확인할 필요가 없으며, 앱은 인터넷 연결 없이도 계속 작동합니다.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "로컬 저장소. 모든 데이터는 기기의 암호화된 SQLCipher 데이터베이스에 저장되며, 운영 체제 키체인에 보관된 무작위 키로 암호화됩니다." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "설정", - "subtitle": "계정 설정 및 환경설정을 관리하세요.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "사용자 프로필", - "description": "개인 정보 및 계정 세부 사항입니다.", - "avatarAlt": "사용자", - "anonymousUser": "익명 사용자", - "noEmailProvided": "이메일이 제공되지 않았습니다", - "notLoggedIn": "로그인되지 않았습니다." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "도구 및 기능", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "보관함에 사이트 아이콘 표시", "helpText": "기본값은 끔. 아이콘을 가져오면 어떤 사이트에 계정이 있는지 외부 서비스에 알려집니다." + }, + "telemetry": { + "label": "익명 사용 데이터 공유", + "helpText": "기본적으로 꺼져 있습니다. 어떤 도구를 열었는지, 앱 버전, 언어를 전송합니다. 계정도 기기 ID도 없으며, 도구에 입력한 내용은 전송되지 않습니다." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "데이터 한눈에 보기", - "subtitle": "서버의 계정과 동기화된 개수입니다.", + "subtitle": "Counts recorded on this device.", "loadError": "분석을 불러올 수 없습니다.", "retry": "다시 시도", - "loginHint": "로그인하면 사용 분석과 인터페이스 설정을 볼 수 있습니다.", "passwordEntries": "비밀번호 항목", "bookmarks": "북마크", "bookmarkFolders": "북마크 폴더", @@ -664,9 +629,8 @@ }, "ui": { "title": "인터페이스 설정", - "subtitle": "테마, 언어, 사이드바에 표시할 도구 — 계정 설정에서.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "설정을 불러올 수 없습니다.", - "loginHint": "로그인하면 저장된 인터페이스 설정을 볼 수 있습니다.", "theme": "테마", "accent": "강조 색", "locale": "언어", @@ -674,13 +638,9 @@ "favoriteTools": "즐겨찾는 도구", "toolsWithUsage": "사용량이 추적된 도구" }, - "signIn": "로그인", "noResults": "검색과 일치하는 도구가 없습니다", "noResultsHint": "다른 키워드를 시도하거나 필터를 지우세요.", "filterAll": "전체", - "loginCta": { - "description": "로그인하여 최근 사용한 도구를 추적하세요." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "내보내기" }, "context": { - "authRequiredError": "사용자가 인증되지 않았습니다", "defaultTitle": "제목 없음", - "cloudSyncImageError": "노트 이미지를 저장하려면 클라우드 동기화를 켜세요. 텍스트 노트는 로컬에 유지됩니다." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "취소", "confirmDelete": "삭제", "deleteDialogTitle": "환경 세트를 삭제할까요?", - "deleteDialogDescription": "계정에서 암호화된 데이터가 삭제됩니다. 되돌릴 수 없습니다.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "텍스트 패널 복사됨", "toastCopiedTree": "트리 패널 복사됨", "toastCopyFailed": "JSON 복사 실패", - "toastLoginRequired": "문서를 저장하려면 로그인하세요", "toastSaved": "문서가 저장되었습니다", "toastSaveFailed": "문서 저장에 실패했습니다", "format": "포맷", @@ -5149,5 +5107,13 @@ "close": "닫기", "pill": "업데이트 중…", "pillPct": "업데이트 중… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "다음에 무엇을 만들지 정하는 데 도움을 주시겠어요?", + "body": "사용 정보를 익명으로 공유합니다: 어떤 도구를 열었는지, 앱 버전, 언어. 계정도 기기 ID도 없으며, 도구에 입력한 내용은 전송되지 않습니다. 설정에서 언제든지 변경할 수 있습니다.", + "accept": "익명으로 공유", + "decline": "괜찮습니다" + } } } diff --git a/apps/desktop-ui/messages/ms.json b/apps/desktop-ui/messages/ms.json index c1fbc121..098a4975 100644 --- a/apps/desktop-ui/messages/ms.json +++ b/apps/desktop-ui/messages/ms.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Apakah itu MyDevTools?", "description": "Ruang kerja pembangun yang luar talian: sebuah papan pemuka dan 80+ alat yang berjalan sepenuhnya pada peranti anda.", - "p1": "MyDevTools berjalan sepenuhnya secara luar talian. Selepas pengaktifan sekali sahaja dalam pelayar anda, segalanya berfungsi pada peranti ini tanpa log masuk akaun dan tanpa memerlukan sambungan internet. Bar sisi menyenaraikan setiap aplikasi; anda boleh menyembunyikan alat yang tidak anda gunakan daripada", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Tetapan", "p1After": ".", - "p2": "Sesetengah ciri menggunakan kata laluan induk (atau kunci peti besi global) supaya rahsia seperti kata laluan dan rentetan sambungan pangkalan data disulitkan pada peranti anda. Data anda disimpan dalam pangkalan data tersulit setempat dan tidak pernah dihantar ke pelayan." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Pautan pantas", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Pengaktifan & akses luar talian", - "description": "Bagaimana aplikasi dibuka kunci buat kali pertama, kemudian berjalan secara luar talian.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Pengaktifan sekali sahaja berlaku dalam pelayar anda pada kali pertama anda membuka aplikasi. Ia merekod pelan anda dan membuka kunci aplikasi pada peranti ini.", - "offline": "Tiada sesi log masuk. Selepas pengaktifan, tiada log masuk bagi setiap sesi dan tiada semakan semula berkala—aplikasi terus berfungsi tanpa sambungan internet.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Storan setempat. Semua data anda berada dalam pangkalan data SQLCipher tersulit pada peranti anda, dikunci dengan kunci rawak yang disimpan dalam keychain sistem pengendalian anda." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Tetapan", - "subtitle": "Urus tetapan akaun dan keutamaan anda.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Profil Pengguna", - "description": "Maklumat peribadi dan butiran akaun anda.", - "avatarAlt": "Pengguna", - "anonymousUser": "Pengguna Tanpa Nama", - "noEmailProvided": "Tiada e-mel disediakan", - "notLoggedIn": "Belum log masuk." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Alat & Ciri", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Tunjukkan ikon tapak dalam peti besi", "helpText": "Dimatikan secara lalai. Mengambil ikon memberitahu perkhidmatan pihak ketiga tapak mana anda mempunyai akaun." + }, + "telemetry": { + "label": "Kongsi data penggunaan tanpa nama", + "helpText": "Dimatikan secara lalai. Menghantar alat yang anda buka, versi aplikasi dan bahasa anda. Tiada akaun, tiada ID peranti, dan tiada apa-apa yang anda taip dalam alat." } }, "language": { @@ -248,58 +257,12 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { "noResults": "Tiada alat yang sepadan dengan carian anda", "noResultsHint": "Cuba kata kunci lain atau kosongkan penapis.", "filterAll": "Semua", - "loginCta": { - "description": "Log masuk untuk menjejaki alat yang baru-baru ini digunakan." - }, "greeting": { "morning": "Selamat pagi", "afternoon": "Selamat petang", @@ -634,10 +597,9 @@ }, "analytics": { "title": "Data anda sekilas", - "subtitle": "Kiraan disegerakkan dengan akaun anda pada pelayan.", + "subtitle": "Counts recorded on this device.", "loadError": "Tidak dapat memuatkan analitik.", "retry": "Cuba lagi", - "loginHint": "Log masuk untuk melihat analitik penggunaan dan pilihan antara muka.", "passwordEntries": "Entri kata laluan", "bookmarks": "Penanda halaman", "bookmarkFolders": "Folder penanda", @@ -670,9 +632,8 @@ }, "ui": { "title": "Pilihan antara muka", - "subtitle": "Tema, bahasa dan alat yang kelihatan di bar sisi — daripada tetapan akaun.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Tidak dapat memuatkan pilihan.", - "loginHint": "Log masuk untuk melihat pilihan antara muka yang disimpan.", "theme": "Tema", "accent": "Warna aksen", "locale": "Bahasa", @@ -680,7 +641,6 @@ "favoriteTools": "Alat kegemaran", "toolsWithUsage": "Alat dengan penjejakan penggunaan" }, - "signIn": "Log masuk", "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Eksport" }, "context": { - "authRequiredError": "Pengguna tidak disahkan", "defaultTitle": "Tanpa Tajuk", - "cloudSyncImageError": "Hidupkan Penyegerakan Awan untuk menyimpan imej nota. Nota teks kekal setempat." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Batal", "confirmDelete": "Padam", "deleteDialogTitle": "Padam set persekitaran?", - "deleteDialogDescription": "Ini memadam blob disulitkan daripada akaun anda. Tidak boleh dibuat asal.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2656,7 +2615,6 @@ "toastCopiedText": "Panel teks disalin", "toastCopiedTree": "Panel pokok disalin", "toastCopyFailed": "Gagal menyalin JSON", - "toastLoginRequired": "Sila log masuk untuk menyimpan dokumen", "toastSaved": "Dokumen disimpan", "toastSaveFailed": "Gagal menyimpan dokumen", "format": "Format", @@ -5160,5 +5118,13 @@ "close": "Tutup", "pill": "Mengemas kini…", "pillPct": "Mengemas kini… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Bantu tentukan apa yang dibina seterusnya?", + "body": "Kongsi penggunaan tanpa nama: alat yang anda buka, versi aplikasi dan bahasa anda. Tiada akaun, tiada ID peranti, dan tiada apa-apa yang anda taip dalam alat. Anda boleh mengubahnya bila-bila masa dalam Tetapan.", + "accept": "Kongsi tanpa nama", + "decline": "Tidak, terima kasih" + } } } diff --git a/apps/desktop-ui/messages/nb.json b/apps/desktop-ui/messages/nb.json index 0bd15dcf..ea489d08 100644 --- a/apps/desktop-ui/messages/nb.json +++ b/apps/desktop-ui/messages/nb.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Hva er MyDevTools?", "description": "Et offline arbeidsområde for utviklere: et dashbord og over 80 verktøy som kjører helt på enheten din.", - "p1": "MyDevTools kjører helt offline. Etter en engangsaktivering i nettleseren fungerer alt på denne enheten, uten kontoinnlogging og uten internettforbindelse. Sidefeltet viser alle appene; verktøy du ikke bruker, kan du skjule under", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Innstillinger", "p1After": ".", - "p2": "Noen funksjoner bruker et hovedpassord (eller en global hvelvnøkkel) slik at hemmeligheter som passord og tilkoblingsstrenger til databaser krypteres på enheten din. Dataene dine lagres i en lokal kryptert database og sendes aldri til en server." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Hurtiglenker", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Aktivering og offline tilgang", - "description": "Slik låses appen opp første gang og kjører deretter offline.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Engangsaktivering skjer i nettleseren første gang du åpner appen. Den registrerer abonnementet ditt og låser opp appen på denne enheten.", - "offline": "Ingen innloggingsøkter. Etter aktivering finnes ingen innlogging per økt og ingen periodisk ny sjekk—appen fortsetter å fungere uten internettforbindelse.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Lokal lagring. Alle dataene dine ligger i en kryptert SQLCipher-database på enheten din, sikret med en tilfeldig nøkkel i operativsystemets nøkkelring." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Innstillinger", - "subtitle": "Administrer kontoinnstillinger og preferanser.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Brukerprofil", - "description": "Din personlige informasjon og kontodetaljer.", - "avatarAlt": "Bruker", - "anonymousUser": "Anonym bruker", - "noEmailProvided": "Ingen e-post oppgitt", - "notLoggedIn": "Ikke innlogget." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Verktøy og funksjoner", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Vis nettstedsikoner i hvelvet", "helpText": "Av som standard. Å hente et ikon forteller en tredjepartstjeneste hvilke nettsteder du har kontoer på." + }, + "telemetry": { + "label": "Del anonyme bruksdata", + "helpText": "Av som standard. Sender hvilke verktøy du åpner, appversjonen og språket ditt. Ingen konto, ingen enhets-ID og ingenting av det du skriver i et verktøy." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Dataene dine i et øyeblikk", - "subtitle": "Tall synkronisert med kontoen din på serveren.", + "subtitle": "Counts recorded on this device.", "loadError": "Kunne ikke laste analyse.", "retry": "Prøv igjen", - "loginHint": "Logg inn for å se bruksanalyse og grensesnittpreferanser.", "passwordEntries": "Passordoppføringer", "bookmarks": "Bokmerker", "bookmarkFolders": "Bokmerkemapper", @@ -664,9 +629,8 @@ }, "ui": { "title": "Grensesnittpreferanser", - "subtitle": "Tema, språk og synlige verktøy i sidefeltet — fra kontoinnstillingene.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Kunne ikke laste preferanser.", - "loginHint": "Logg inn for å se lagrede grensesnittpreferanser.", "theme": "Tema", "accent": "Aksentfarge", "locale": "Språk", @@ -674,13 +638,9 @@ "favoriteTools": "Favorittverktøy", "toolsWithUsage": "Verktøy med bruksstatistikk" }, - "signIn": "Logg inn", "noResults": "Ingen verktøy samsvarer med søket ditt", "noResultsHint": "Prøv et annet søkeord eller fjern filteret.", "filterAll": "Alle", - "loginCta": { - "description": "Logg inn for å spore verktøyene du nylig har brukt." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Eksporter" }, "context": { - "authRequiredError": "Bruker ikke autentisert", "defaultTitle": "Uten tittel", - "cloudSyncImageError": "Slå på skysynkronisering for å lagre notatbilder. Tekstnotater forblir lokale." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Avbryt", "confirmDelete": "Slett", "deleteDialogTitle": "Slette miljøsett?", - "deleteDialogDescription": "Dette fjerner den krypterte bloben fra kontoen din. Kan ikke angres.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "Tekstpanel kopiert", "toastCopiedTree": "Trepanel kopiert", "toastCopyFailed": "Kunne ikke kopiere JSON", - "toastLoginRequired": "Logg inn for å lagre dokumenter", "toastSaved": "Dokument lagret", "toastSaveFailed": "Kunne ikke lagre dokument", "format": "Formater", @@ -5149,5 +5107,13 @@ "close": "Lukk", "pill": "Oppdaterer…", "pillPct": "Oppdaterer… {pct} %" + }, + "Telemetry": { + "consent": { + "title": "Vil du hjelpe med å bestemme hva som lages neste gang?", + "body": "Del bruk anonymt: hvilke verktøy du åpner, appversjonen og språket ditt. Ingen konto, ingen enhets-ID og ingenting av det du skriver i et verktøy. Du kan endre dette når som helst i Innstillinger.", + "accept": "Del anonymt", + "decline": "Nei takk" + } } } diff --git a/apps/desktop-ui/messages/nl.json b/apps/desktop-ui/messages/nl.json index 9f40ae87..4c09bf9c 100644 --- a/apps/desktop-ui/messages/nl.json +++ b/apps/desktop-ui/messages/nl.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Wat is MyDevTools?", "description": "Een offline ontwikkelaarswerkplek: een dashboard en meer dan 80 tools die volledig op je apparaat draaien.", - "p1": "MyDevTools werkt volledig offline. Na een eenmalige activering in je browser werkt alles op dit apparaat, zonder accountaanmelding en zonder internetverbinding. In de zijbalk staan alle apps; tools die je niet gebruikt, kun je verbergen via", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Instellingen", "p1After": ".", - "p2": "Sommige functies gebruiken een hoofdwachtwoord (of globale kluissleutel) zodat geheimen zoals wachtwoorden en databaseverbindingsstrings versleuteld worden op je apparaat. Je gegevens worden opgeslagen in een lokale versleutelde database en worden nooit naar een server gestuurd." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Snelkoppelingen", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Activering en offline toegang", - "description": "Hoe de app de eerste keer wordt ontgrendeld en daarna offline draait.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Eenmalige activering vindt plaats in je browser wanneer je de app voor het eerst opent. Hierbij wordt je abonnement geregistreerd en de app op dit apparaat ontgrendeld.", - "offline": "Geen aanmeldsessies. Na activering is er geen aanmelding per sessie en geen periodieke hercontrole—de app blijft werken zonder internetverbinding.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Lokale opslag. Al je gegevens staan in een versleutelde SQLCipher-database op je apparaat, beveiligd met een willekeurige sleutel in de sleutelhanger van je besturingssysteem." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Instellingen", - "subtitle": "Beheer je accountinstellingen en voorkeuren.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Gebruikersprofiel", - "description": "Je persoonlijke gegevens en accountdetails.", - "avatarAlt": "Gebruiker", - "anonymousUser": "Anonieme gebruiker", - "noEmailProvided": "Geen e-mailadres opgegeven", - "notLoggedIn": "Niet ingelogd." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Tools & functies", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Site-iconen in de kluis tonen", "helpText": "Standaard uit. Een icoon ophalen vertelt een externe dienst bij welke sites je accounts hebt." + }, + "telemetry": { + "label": "Anonieme gebruiksgegevens delen", + "helpText": "Standaard uit. Verstuurt welke tools je opent, de app-versie en je taal. Geen account, geen apparaat-ID en niets van wat je in een tool typt." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Jouw data in één oogopslag", - "subtitle": "Tellingen gesynchroniseerd met je account op de server.", + "subtitle": "Counts recorded on this device.", "loadError": "Kan statistieken niet laden.", "retry": "Opnieuw", - "loginHint": "Log in om gebruiksanalyses en interfacevoorkeuren te zien.", "passwordEntries": "Wachtwoorditems", "bookmarks": "Bladwijzers", "bookmarkFolders": "Bladwijzermappen", @@ -664,9 +629,8 @@ }, "ui": { "title": "Interfacevoorkeuren", - "subtitle": "Thema, taal en zichtbare tools in de zijbalk — vanuit je accountinstellingen.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Kan voorkeuren niet laden.", - "loginHint": "Log in om je opgeslagen interfacevoorkeuren te zien.", "theme": "Thema", "accent": "Accentkleur", "locale": "Taal", @@ -674,13 +638,9 @@ "favoriteTools": "Favoriete tools", "toolsWithUsage": "Tools met gebruiksstatistieken" }, - "signIn": "Inloggen", "noResults": "Geen tools komen overeen met je zoekopdracht", "noResultsHint": "Probeer een ander trefwoord of wis het filter.", "filterAll": "Alles", - "loginCta": { - "description": "Log in om je recent gebruikte tools bij te houden." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Exporteren" }, "context": { - "authRequiredError": "Gebruiker niet geauthenticeerd", "defaultTitle": "Naamloos", - "cloudSyncImageError": "Schakel cloudsynchronisatie in om notitieafbeeldingen op te slaan. Tekstnotities blijven lokaal." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Annuleren", "confirmDelete": "Verwijderen", "deleteDialogTitle": "Omgevingsset verwijderen?", - "deleteDialogDescription": "Hiermee wordt de versleutelde blob uit uw account verwijderd. Kan niet ongedaan worden gemaakt.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "Tekstpaneel gekopieerd", "toastCopiedTree": "Boompaneel gekopieerd", "toastCopyFailed": "Kan JSON niet kopiëren", - "toastLoginRequired": "Log in om documenten op te slaan", "toastSaved": "Document opgeslagen", "toastSaveFailed": "Document opslaan mislukt", "format": "Formatteren", @@ -5149,5 +5107,13 @@ "close": "Sluiten", "pill": "Bijwerken…", "pillPct": "Bijwerken… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Help je bepalen wat we hierna bouwen?", + "body": "Deel je gebruik anoniem: welke tools je opent, de app-versie en je taal. Geen account, geen apparaat-ID en niets van wat je in een tool typt. Je kunt dit altijd wijzigen in Instellingen.", + "accept": "Anoniem delen", + "decline": "Nee, bedankt" + } } } diff --git a/apps/desktop-ui/messages/pl.json b/apps/desktop-ui/messages/pl.json index 7d290e31..a2e52692 100644 --- a/apps/desktop-ui/messages/pl.json +++ b/apps/desktop-ui/messages/pl.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Czym jest MyDevTools?", "description": "Offline'owe środowisko dla programistów: panel oraz ponad 80 narzędzi działających w całości na Twoim urządzeniu.", - "p1": "MyDevTools działa w pełni offline. Po jednorazowej aktywacji w przeglądarce wszystko działa na tym urządzeniu bez logowania do konta i bez połączenia z internetem. Na pasku bocznym wymienione są wszystkie aplikacje; nieużywane narzędzia możesz ukryć w sekcji", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Ustawienia", "p1After": ".", - "p2": "Niektóre funkcje korzystają z hasła głównego (lub globalnego klucza sejfu), aby sekrety takie jak hasła i ciągi połączeń z bazami danych były szyfrowane na Twoim urządzeniu. Twoje dane są przechowywane w lokalnej, zaszyfrowanej bazie danych i nigdy nie są wysyłane na serwer." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Szybkie odnośniki", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Aktywacja i dostęp offline", - "description": "Jak aplikacja odblokowuje się za pierwszym razem, a następnie działa offline.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Jednorazowa aktywacja odbywa się w przeglądarce przy pierwszym uruchomieniu aplikacji. Rejestruje ona Twój plan i odblokowuje aplikację na tym urządzeniu.", - "offline": "Brak sesji logowania. Po aktywacji nie ma logowania przy każdej sesji ani okresowej ponownej weryfikacji — aplikacja działa dalej bez połączenia z internetem.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Pamięć lokalna. Wszystkie Twoje dane znajdują się w zaszyfrowanej bazie danych SQLCipher na Twoim urządzeniu, zabezpieczonej losowym kluczem przechowywanym w pęku kluczy Twojego systemu operacyjnego." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Settings", - "subtitle": "Manage your account settings and preferences.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "User Profile", - "description": "Your personal information and account details.", - "avatarAlt": "User", - "anonymousUser": "Anonymous User", - "noEmailProvided": "No email provided", - "notLoggedIn": "Not logged in." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Tools & Features", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Pokazuj ikony witryn w sejfie", "helpText": "Domyślnie wyłączone. Pobranie ikony ujawnia zewnętrznej usłudze, na jakich witrynach masz konta." + }, + "telemetry": { + "label": "Udostępniaj anonimowe dane o użyciu", + "helpText": "Domyślnie wyłączone. Wysyła, które narzędzia otwierasz, wersję aplikacji i Twój język. Bez konta, bez identyfikatora urządzenia i bez niczego, co wpisujesz w narzędziu." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Twoje dane w skrócie", - "subtitle": "Liczby zsynchronizowane z kontem na serwerze.", + "subtitle": "Counts recorded on this device.", "loadError": "Nie udało się wczytać statystyk.", "retry": "Ponów", - "loginHint": "Zaloguj się, aby zobaczyć analitykę i preferencje interfejsu.", "passwordEntries": "Wpisy haseł", "bookmarks": "Zakładki", "bookmarkFolders": "Foldery zakładek", @@ -664,9 +629,8 @@ }, "ui": { "title": "Preferencje interfejsu", - "subtitle": "Motyw, język i widoczne narzędzia na pasku bocznym — z ustawień konta.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Nie udało się wczytać preferencji.", - "loginHint": "Zaloguj się, aby zobaczyć zapisane preferencje interfejsu.", "theme": "Motyw", "accent": "Kolor akcentu", "locale": "Język", @@ -674,13 +638,9 @@ "favoriteTools": "Ulubione narzędzia", "toolsWithUsage": "Narzędzia ze statystyką użycia" }, - "signIn": "Zaloguj się", "noResults": "Żadne narzędzie nie pasuje do Twojego wyszukiwania", "noResultsHint": "Spróbuj innego słowa kluczowego lub wyczyść filtr.", "filterAll": "Wszystkie", - "loginCta": { - "description": "Zaloguj się, aby śledzić ostatnio używane narzędzia." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Eksportuj" }, "context": { - "authRequiredError": "User not authenticated", "defaultTitle": "Untitled", - "cloudSyncImageError": "Włącz synchronizację w chmurze, aby przechowywać obrazy notatek. Notatki tekstowe pozostają lokalne." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Anuluj", "confirmDelete": "Usuń", "deleteDialogTitle": "Usunąć zestaw środowisk?", - "deleteDialogDescription": "To usuwa zaszyfrowane dane z konta. Operacji nie cofniesz.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "Skopiowano panel tekstowy", "toastCopiedTree": "Skopiowano panel drzewa", "toastCopyFailed": "Nie udało się skopiować JSON", - "toastLoginRequired": "Zaloguj się, aby zapisywać dokumenty", "toastSaved": "Dokument zapisany", "toastSaveFailed": "Nie udało się zapisać dokumentu", "format": "Formatuj", @@ -5149,5 +5107,13 @@ "close": "Zamknij", "pill": "Aktualizowanie…", "pillPct": "Aktualizowanie… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Pomożesz zdecydować, co powstanie w następnej kolejności?", + "body": "Udostępnij anonimowe dane o użyciu: które narzędzia otwierasz, wersję aplikacji i Twój język. Bez konta, bez identyfikatora urządzenia i bez niczego, co wpisujesz w narzędziu. Możesz to zmienić w każdej chwili w Ustawieniach.", + "accept": "Udostępnij anonimowo", + "decline": "Nie, dziękuję" + } } } diff --git a/apps/desktop-ui/messages/pt-BR.json b/apps/desktop-ui/messages/pt-BR.json index 41b8a1bf..b683c90e 100644 --- a/apps/desktop-ui/messages/pt-BR.json +++ b/apps/desktop-ui/messages/pt-BR.json @@ -91,10 +91,10 @@ "whatIs": { "title": "O que é o MyDevTools?", "description": "Um espaço de trabalho offline para desenvolvedores: um painel e mais de 80 ferramentas que rodam inteiramente no seu dispositivo.", - "p1": "O MyDevTools funciona totalmente offline. Após uma ativação única no seu navegador, tudo funciona neste dispositivo, sem login em conta e sem conexão com a internet. A barra lateral lista todos os aplicativos; você pode ocultar as ferramentas que não usa nas", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Configurações", "p1After": ".", - "p2": "Alguns recursos usam uma senha mestra (ou chave global do cofre) para que dados sensíveis, como senhas e strings de conexão de banco de dados, sejam criptografados no seu dispositivo. Seus dados são armazenados em um banco de dados local criptografado e nunca são enviados para um servidor." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Links rápidos", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Ativação e acesso offline", - "description": "Como o aplicativo é desbloqueado na primeira vez e depois funciona offline.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "A ativação única ocorre no seu navegador na primeira vez que você abre o aplicativo. Ela registra seu plano e desbloqueia o aplicativo neste dispositivo.", - "offline": "Sem sessões de login. Após a ativação não há login por sessão nem verificações periódicas — o aplicativo continua funcionando sem conexão com a internet.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Armazenamento local. Todos os seus dados ficam em um banco de dados SQLCipher criptografado no seu dispositivo, protegido por uma chave aleatória guardada no chaveiro do seu sistema operacional." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Configurações", - "subtitle": "Gerencie as configurações e preferências da sua conta.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Perfil do Usuário", - "description": "Suas informações pessoais e detalhes da conta.", - "avatarAlt": "Usuário", - "anonymousUser": "Usuário Anônimo", - "noEmailProvided": "E-mail não fornecido", - "notLoggedIn": "Não conectado." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Ferramentas e Recursos", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Mostrar ícones dos sites no cofre", "helpText": "Desativado por padrão. Buscar um ícone informa a um serviço externo em quais sites você tem contas." + }, + "telemetry": { + "label": "Compartilhar dados de uso anônimos", + "helpText": "Desativado por padrão. Envia quais ferramentas você abre, a versão do app e seu idioma. Sem conta, sem ID de dispositivo e sem nada do que você digita em uma ferramenta." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Seus dados em resumo", - "subtitle": "Contagens sincronizadas com sua conta no servidor.", + "subtitle": "Counts recorded on this device.", "loadError": "Não foi possível carregar as análises.", "retry": "Tentar de novo", - "loginHint": "Entre para ver análises de uso e preferências de interface.", "passwordEntries": "Entradas de senhas", "bookmarks": "Favoritos", "bookmarkFolders": "Pastas de favoritos", @@ -664,9 +629,8 @@ }, "ui": { "title": "Preferências de interface", - "subtitle": "Tema, idioma e ferramentas visíveis na barra lateral — nas configurações da conta.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Não foi possível carregar as preferências.", - "loginHint": "Entre para ver suas preferências de interface salvas.", "theme": "Tema", "accent": "Cor de destaque", "locale": "Idioma", @@ -674,13 +638,9 @@ "favoriteTools": "Ferramentas favoritas", "toolsWithUsage": "Ferramentas com uso rastreado" }, - "signIn": "Entrar", "noResults": "Nenhuma ferramenta corresponde à sua pesquisa", "noResultsHint": "Tente uma palavra-chave diferente ou limpe o filtro.", "filterAll": "Tudo", - "loginCta": { - "description": "Faça login para rastrear suas ferramentas usadas recentemente." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Exportar" }, "context": { - "authRequiredError": "Usuário não autenticado", "defaultTitle": "Sem Título", - "cloudSyncImageError": "Ative a Sincronização na Nuvem para armazenar imagens das notas. As notas de texto permanecem locais." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Cancelar", "confirmDelete": "Excluir", "deleteDialogTitle": "Excluir conjunto de ambiente?", - "deleteDialogDescription": "Isso remove o blob criptografado da sua conta. Não pode ser desfeito.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "Painel de texto copiado", "toastCopiedTree": "Painel de árvore copiado", "toastCopyFailed": "Falha ao copiar JSON", - "toastLoginRequired": "Faça login para salvar documentos", "toastSaved": "Documento salvo", "toastSaveFailed": "Falha ao salvar documento", "format": "Formatar", @@ -5149,5 +5107,13 @@ "close": "Fechar", "pill": "Atualizando…", "pillPct": "Atualizando… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Quer ajudar a decidir o que construir em seguida?", + "body": "Compartilhe o uso de forma anônima: quais ferramentas você abre, a versão do app e seu idioma. Sem conta, sem ID de dispositivo e sem nada do que você digita em uma ferramenta. Você pode mudar isso quando quiser em Configurações.", + "accept": "Compartilhar anonimamente", + "decline": "Não, obrigado" + } } } diff --git a/apps/desktop-ui/messages/pt.json b/apps/desktop-ui/messages/pt.json index d54101f6..0ebe342f 100644 --- a/apps/desktop-ui/messages/pt.json +++ b/apps/desktop-ui/messages/pt.json @@ -91,10 +91,10 @@ "whatIs": { "title": "O que é o MyDevTools?", "description": "Um espaço de trabalho offline para programadores: um painel e mais de 80 ferramentas que são executadas inteiramente no seu dispositivo.", - "p1": "O MyDevTools funciona totalmente offline. Após uma ativação única no seu navegador, tudo funciona neste dispositivo, sem início de sessão numa conta e sem ligação à Internet. A barra lateral apresenta todas as aplicações; pode ocultar as ferramentas que não utiliza nas", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Definições", "p1After": ".", - "p2": "Algumas funcionalidades utilizam uma palavra-passe mestra (ou chave global do cofre) para que dados sensíveis, como palavras-passe e cadeias de ligação a bases de dados, sejam cifrados no seu dispositivo. Os seus dados são guardados numa base de dados local cifrada e nunca são enviados para um servidor." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Ligações rápidas", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Ativação e acesso offline", - "description": "Como a aplicação é desbloqueada da primeira vez e depois funciona offline.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "A ativação única ocorre no seu navegador na primeira vez que abre a aplicação. Regista o seu plano e desbloqueia a aplicação neste dispositivo.", - "offline": "Sem sessões de início de sessão. Após a ativação não há início de sessão por sessão nem verificações periódicas — a aplicação continua a funcionar sem ligação à Internet.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Armazenamento local. Todos os seus dados residem numa base de dados SQLCipher cifrada no seu dispositivo, protegida por uma chave aleatória guardada no porta-chaves do seu sistema operativo." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Definições", - "subtitle": "Gira as definições e preferências da tua conta.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Perfil do Utilizador", - "description": "As tuas informações pessoais e detalhes da conta.", - "avatarAlt": "Utilizador", - "anonymousUser": "Utilizador Anónimo", - "noEmailProvided": "E-mail não fornecido", - "notLoggedIn": "Sessão não iniciada." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Ferramentas e Funcionalidades", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Mostrar ícones dos sites no cofre", "helpText": "Desativado por predefinição. Obter um ícone revela a um serviço externo em que sites tem contas." + }, + "telemetry": { + "label": "Partilhar dados de utilização anónimos", + "helpText": "Desativado por predefinição. Envia que ferramentas abre, a versão da aplicação e o seu idioma. Sem conta, sem identificador de dispositivo e sem nada do que escreve numa ferramenta." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Os seus dados num relance", - "subtitle": "Contagens sincronizadas com a sua conta no servidor.", + "subtitle": "Counts recorded on this device.", "loadError": "Não foi possível carregar as estatísticas.", "retry": "Tentar novamente", - "loginHint": "Inicie sessão para ver análises de utilização e preferências de interface.", "passwordEntries": "Entradas de palavras-passe", "bookmarks": "Marcadores", "bookmarkFolders": "Pastas de marcadores", @@ -664,9 +629,8 @@ }, "ui": { "title": "Preferências de interface", - "subtitle": "Tema, idioma e ferramentas visíveis na barra lateral — nas definições da conta.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Não foi possível carregar as preferências.", - "loginHint": "Inicie sessão para ver as preferências de interface guardadas.", "theme": "Tema", "accent": "Cor de destaque", "locale": "Idioma", @@ -674,13 +638,9 @@ "favoriteTools": "Ferramentas favoritas", "toolsWithUsage": "Ferramentas com utilização registada" }, - "signIn": "Iniciar sessão", "noResults": "Nenhuma ferramenta corresponde à sua pesquisa", "noResultsHint": "Experimente uma palavra-chave diferente ou limpe o filtro.", "filterAll": "Tudo", - "loginCta": { - "description": "Inicie sessão para acompanhar as ferramentas utilizadas recentemente." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Exportar" }, "context": { - "authRequiredError": "Utilizador não autenticado", "defaultTitle": "Sem Título", - "cloudSyncImageError": "Ative a Sincronização na Nuvem para armazenar imagens das notas. As notas de texto permanecem locais." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Cancelar", "confirmDelete": "Eliminar", "deleteDialogTitle": "Eliminar conjunto de ambiente?", - "deleteDialogDescription": "Isto remove o blob encriptado da sua conta. Não pode ser anulado.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "Painel de texto copiado", "toastCopiedTree": "Painel de árvore copiado", "toastCopyFailed": "Falha ao copiar JSON", - "toastLoginRequired": "Inicie sessão para guardar documentos", "toastSaved": "Documento guardado", "toastSaveFailed": "Falha ao guardar documento", "format": "Formatar", @@ -5149,5 +5107,13 @@ "close": "Fechar", "pill": "A atualizar…", "pillPct": "A atualizar… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Quer ajudar a decidir o que se constrói a seguir?", + "body": "Partilhe a utilização de forma anónima: que ferramentas abre, a versão da aplicação e o seu idioma. Sem conta, sem identificador de dispositivo e sem nada do que escreve numa ferramenta. Pode alterar isto a qualquer momento nas Definições.", + "accept": "Partilhar anonimamente", + "decline": "Não, obrigado" + } } } diff --git a/apps/desktop-ui/messages/ru.json b/apps/desktop-ui/messages/ru.json index 75468f61..279dd21f 100644 --- a/apps/desktop-ui/messages/ru.json +++ b/apps/desktop-ui/messages/ru.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Что такое MyDevTools?", "description": "Офлайн-среда для разработчиков: панель управления и более 80 инструментов, работающих полностью на вашем устройстве.", - "p1": "MyDevTools работает полностью офлайн. После однократной активации в браузере всё работает на этом устройстве без входа в учётную запись и без подключения к интернету. На боковой панели перечислены все приложения; неиспользуемые инструменты можно скрыть в разделе", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Настройки", "p1After": ".", - "p2": "Некоторые функции используют мастер-пароль (или глобальный ключ хранилища), чтобы такие секреты, как пароли и строки подключения к базам данных, были зашифрованы на вашем устройстве. Ваши данные хранятся в локальной зашифрованной базе данных и никогда не отправляются на сервер." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Быстрые ссылки", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Активация и офлайн-доступ", - "description": "Как приложение разблокируется в первый раз, а затем работает офлайн.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Однократная активация выполняется в браузере при первом запуске приложения. Она фиксирует ваш тарифный план и разблокирует приложение на этом устройстве.", - "offline": "Никаких сеансов входа. После активации нет входа в систему при каждом сеансе и периодических повторных проверок — приложение продолжает работать без подключения к интернету.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Локальное хранилище. Все ваши данные находятся в зашифрованной базе данных SQLCipher на вашем устройстве, ключом к которой служит случайный ключ, хранящийся в связке ключей вашей операционной системы." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Настройки", - "subtitle": "Управление настройками учётной записи и предпочтениями.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Профиль пользователя", - "description": "Ваша личная информация и данные учётной записи.", - "avatarAlt": "Пользователь", - "anonymousUser": "Анонимный пользователь", - "noEmailProvided": "Email не указан", - "notLoggedIn": "Не авторизован." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Инструменты и функции", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Показывать значки сайтов в хранилище", "helpText": "По умолчанию выключено. Загрузка значка сообщает стороннему сервису, на каких сайтах у вас есть аккаунты." + }, + "telemetry": { + "label": "Делиться анонимными данными об использовании", + "helpText": "По умолчанию выключено. Отправляет, какие инструменты вы открываете, версию приложения и ваш язык. Без аккаунта, без идентификатора устройства и без того, что вы вводите в инструментах." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Ваши данные кратко", - "subtitle": "Счётчики синхронизированы с вашей учётной записью на сервере.", + "subtitle": "Counts recorded on this device.", "loadError": "Не удалось загрузить аналитику.", "retry": "Повторить", - "loginHint": "Войдите, чтобы видеть аналитику использования и настройки интерфейса.", "passwordEntries": "Записей паролей", "bookmarks": "Закладки", "bookmarkFolders": "Папки закладок", @@ -664,9 +629,8 @@ }, "ui": { "title": "Настройки интерфейса", - "subtitle": "Тема, язык и видимые инструменты на боковой панели — из настроек аккаунта.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Не удалось загрузить настройки.", - "loginHint": "Войдите, чтобы видеть сохранённые настройки интерфейса.", "theme": "Тема", "accent": "Цвет акцента", "locale": "Язык", @@ -674,13 +638,9 @@ "favoriteTools": "Избранные инструменты", "toolsWithUsage": "Инструменты со статистикой" }, - "signIn": "Войти", "noResults": "Ни один инструмент не соответствует вашему поиску", "noResultsHint": "Попробуйте другое ключевое слово или очистите фильтр.", "filterAll": "Все", - "loginCta": { - "description": "Войдите, чтобы отслеживать недавно используемые инструменты." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Экспорт" }, "context": { - "authRequiredError": "Пользователь не авторизован", "defaultTitle": "Без названия", - "cloudSyncImageError": "Включите облачную синхронизацию, чтобы сохранять изображения заметок. Текстовые заметки остаются локальными." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Отмена", "confirmDelete": "Удалить", "deleteDialogTitle": "Удалить набор окружения?", - "deleteDialogDescription": "Зашифрованные данные будут удалены из аккаунта. Действие необратимо.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "Текстовая панель скопирована", "toastCopiedTree": "Панель дерева скопирована", "toastCopyFailed": "Не удалось скопировать JSON", - "toastLoginRequired": "Войдите, чтобы сохранять документы", "toastSaved": "Документ сохранён", "toastSaveFailed": "Не удалось сохранить документ", "format": "Форматировать", @@ -5149,5 +5107,13 @@ "close": "Закрыть", "pill": "Обновление…", "pillPct": "Обновление… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Поможете решить, что делать дальше?", + "body": "Делитесь анонимными данными: какие инструменты вы открываете, версия приложения и ваш язык. Без аккаунта, без идентификатора устройства и без того, что вы вводите в инструментах. Изменить это можно в любой момент в настройках.", + "accept": "Делиться анонимно", + "decline": "Нет, спасибо" + } } } diff --git a/apps/desktop-ui/messages/sv.json b/apps/desktop-ui/messages/sv.json index 84349c34..f44fa205 100644 --- a/apps/desktop-ui/messages/sv.json +++ b/apps/desktop-ui/messages/sv.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Vad är MyDevTools?", "description": "En offline-arbetsyta för utvecklare: en instrumentpanel och över 80 verktyg som körs helt på din enhet.", - "p1": "MyDevTools körs helt offline. Efter en engångsaktivering i din webbläsare fungerar allt på den här enheten, utan kontoinloggning och utan internetanslutning. I sidofältet listas alla appar; verktyg du inte använder kan du dölja via", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Inställningar", "p1After": ".", - "p2": "Vissa funktioner använder ett huvudlösenord (eller en global valvnyckel) så att hemligheter som lösenord och anslutningssträngar till databaser krypteras på din enhet. Dina data lagras i en lokal krypterad databas och skickas aldrig till en server." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Snabblänkar", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Aktivering och offlineåtkomst", - "description": "Hur appen låses upp första gången och sedan körs offline.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Engångsaktivering sker i din webbläsare första gången du öppnar appen. Den registrerar din prenumeration och låser upp appen på den här enheten.", - "offline": "Inga inloggningssessioner. Efter aktiveringen finns ingen inloggning per session och ingen periodisk kontroll—appen fortsätter fungera utan internetanslutning.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Lokal lagring. Alla dina data finns i en krypterad SQLCipher-databas på din enhet, skyddad med en slumpmässig nyckel i operativsystemets nyckelring." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Inställningar", - "subtitle": "Hantera dina kontoinställningar och preferenser.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Användarprofil", - "description": "Din personliga information och kontouppgifter.", - "avatarAlt": "Användare", - "anonymousUser": "Anonym användare", - "noEmailProvided": "Ingen e-post angiven", - "notLoggedIn": "Inte inloggad." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Verktyg och funktioner", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Visa webbplatsikoner i valvet", "helpText": "Av som standard. Att hämta en ikon berättar för en tredjepartstjänst vilka webbplatser du har konton på." + }, + "telemetry": { + "label": "Dela anonym användningsdata", + "helpText": "Av som standard. Skickar vilka verktyg du öppnar, appens version och ditt språk. Inget konto, inget enhets-ID och inget av det du skriver i ett verktyg." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Dina data i korthet", - "subtitle": "Antal synkroniserade med ditt konto på servern.", + "subtitle": "Counts recorded on this device.", "loadError": "Kunde inte ladda analys.", "retry": "Försök igen", - "loginHint": "Logga in för att se användningsanalys och gränssnittsinställningar.", "passwordEntries": "Lösenordsposter", "bookmarks": "Bokmärken", "bookmarkFolders": "Bokmärkesmappar", @@ -664,9 +629,8 @@ }, "ui": { "title": "Gränssnittsinställningar", - "subtitle": "Tema, språk och synliga verktyg i sidofältet — från dina kontoinställningar.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Kunde inte ladda inställningar.", - "loginHint": "Logga in för att se dina sparade gränssnittsinställningar.", "theme": "Tema", "accent": "Accentfärg", "locale": "Språk", @@ -674,13 +638,9 @@ "favoriteTools": "Favoritverktyg", "toolsWithUsage": "Verktyg med användningsstatistik" }, - "signIn": "Logga in", "noResults": "Inga verktyg matchar din sökning", "noResultsHint": "Prova ett annat sökord eller rensa filtret.", "filterAll": "Alla", - "loginCta": { - "description": "Logga in för att spåra dina senast använda verktyg." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Exportera" }, "context": { - "authRequiredError": "Användaren är inte autentiserad", "defaultTitle": "Namnlös", - "cloudSyncImageError": "Aktivera molnsynkronisering för att lagra anteckningsbilder. Textanteckningar förblir lokala." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Avbryt", "confirmDelete": "Ta bort", "deleteDialogTitle": "Ta bort miljöuppsättning?", - "deleteDialogDescription": "Detta tar bort den krypterade blobben från ditt konto. Kan inte ångras.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2656,7 +2615,6 @@ "toastCopiedText": "Textpanelen kopierad", "toastCopiedTree": "Trädpanelen kopierad", "toastCopyFailed": "Kunde inte kopiera JSON", - "toastLoginRequired": "Logga in för att spara dokument", "toastSaved": "Dokument sparat", "toastSaveFailed": "Kunde inte spara dokument", "format": "Formatera", @@ -5160,5 +5118,13 @@ "close": "Stäng", "pill": "Uppdaterar…", "pillPct": "Uppdaterar… {pct} %" + }, + "Telemetry": { + "consent": { + "title": "Vill du hjälpa till att avgöra vad som byggs härnäst?", + "body": "Dela användning anonymt: vilka verktyg du öppnar, appens version och ditt språk. Inget konto, inget enhets-ID och inget av det du skriver i ett verktyg. Du kan ändra detta när som helst i Inställningar.", + "accept": "Dela anonymt", + "decline": "Nej tack" + } } } diff --git a/apps/desktop-ui/messages/tr.json b/apps/desktop-ui/messages/tr.json index 7eb13661..796be883 100644 --- a/apps/desktop-ui/messages/tr.json +++ b/apps/desktop-ui/messages/tr.json @@ -91,10 +91,10 @@ "whatIs": { "title": "MyDevTools nedir?", "description": "Çevrimdışı bir geliştirici çalışma alanı: tamamen cihazınızda çalışan bir pano ve 80+ araç.", - "p1": "MyDevTools tamamen çevrimdışı çalışır. Tarayıcınızda tek seferlik bir etkinleştirmenin ardından her şey bu cihazda çalışır; hesap girişi veya internet bağlantısı gerekmez. Kenar çubuğu tüm uygulamaları listeler; kullanmadığınız araçları şuradan gizleyebilirsiniz:", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Ayarlar", "p1After": ".", - "p2": "Bazı özellikler bir ana parola (veya genel kasa anahtarı) kullanır; böylece parolalar ve veritabanı bağlantı dizeleri gibi gizli bilgiler cihazınızda şifrelenir. Verileriniz yerel bir şifreli veritabanında saklanır ve asla bir sunucuya gönderilmez." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Hızlı bağlantılar", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Etkinleştirme ve çevrimdışı erişim", - "description": "Uygulamanın ilk seferde nasıl kilidinin açıldığı ve ardından çevrimdışı nasıl çalıştığı.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Tek seferlik etkinleştirme, uygulamayı ilk açtığınızda tarayıcınızda gerçekleşir. Planınızı kaydeder ve uygulamanın bu cihazdaki kilidini açar.", - "offline": "Oturum girişi yok. Etkinleştirmeden sonra oturum başına giriş veya periyodik yeniden kontrol yoktur; uygulama internet bağlantısı olmadan çalışmaya devam eder.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Yerel depolama. Tüm verileriniz cihazınızdaki şifreli bir SQLCipher veritabanında yaşar ve işletim sistemi anahtarlığınızda tutulan rastgele bir anahtarla anahtarlanır." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Ayarlar", - "subtitle": "Hesap ayarlarınızı ve tercihlerinizi yönetin.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Kullanıcı Profili", - "description": "Kişisel bilgileriniz ve hesap detaylarınız.", - "avatarAlt": "Kullanıcı", - "anonymousUser": "Anonim Kullanıcı", - "noEmailProvided": "E-posta belirtilmedi", - "notLoggedIn": "Giriş yapılmadı." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Araçlar ve Özellikler", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Kasada site simgelerini göster", "helpText": "Varsayılan olarak kapalı. Simge indirmek, hangi sitelerde hesabınız olduğunu üçüncü taraf bir hizmete bildirir." + }, + "telemetry": { + "label": "Anonim kullanım verilerini paylaş", + "helpText": "Varsayılan olarak kapalı. Hangi araçları açtığınızı, uygulama sürümünü ve dilinizi gönderir. Hesap yok, cihaz kimliği yok ve bir araca yazdığınız hiçbir şey gönderilmez." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Verilerinize genel bakış", - "subtitle": "Sunucudaki hesabınızla senkronize sayılar.", + "subtitle": "Counts recorded on this device.", "loadError": "Analitik yüklenemedi.", "retry": "Yeniden dene", - "loginHint": "Kullanım analitiği ve arayüz tercihleri için giriş yapın.", "passwordEntries": "Parola kayıtları", "bookmarks": "Yer imleri", "bookmarkFolders": "Yer imi klasörleri", @@ -664,9 +629,8 @@ }, "ui": { "title": "Arayüz tercihleri", - "subtitle": "Tema, dil ve kenar çubuğundaki araçlar — hesap ayarlarınızdan.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Tercihler yüklenemedi.", - "loginHint": "Kayıtlı arayüz tercihlerinizi görmek için giriş yapın.", "theme": "Tema", "accent": "Vurgu rengi", "locale": "Dil", @@ -674,13 +638,9 @@ "favoriteTools": "Favori araçlar", "toolsWithUsage": "Kullanımı izlenen araçlar" }, - "signIn": "Giriş yap", "noResults": "Aramanızla eşleşen araç bulunamadı", "noResultsHint": "Farklı bir anahtar kelime deneyin veya filtreyi temizleyin.", "filterAll": "Tümü", - "loginCta": { - "description": "Son kullandığınız araçları takip etmek için giriş yapın." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Dışa aktar" }, "context": { - "authRequiredError": "Kullanıcı kimliği doğrulanmadı", "defaultTitle": "Başlıksız", - "cloudSyncImageError": "Not görsellerini depolamak için Bulut Eşitlemeyi açın. Metin notları yerel kalır." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "İptal", "confirmDelete": "Sil", "deleteDialogTitle": "Ortam kümesi silinsin mi?", - "deleteDialogDescription": "Şifreli veri hesabınızdan kaldırılır. Geri alınamaz.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2656,7 +2615,6 @@ "toastCopiedText": "Metin paneli kopyalandı", "toastCopiedTree": "Ağaç paneli kopyalandı", "toastCopyFailed": "JSON kopyalanamadı", - "toastLoginRequired": "Belgeleri kaydetmek için giriş yapın", "toastSaved": "Belge kaydedildi", "toastSaveFailed": "Belge kaydedilemedi", "format": "Biçimlendir", @@ -5160,5 +5118,13 @@ "close": "Kapat", "pill": "Güncelleniyor…", "pillPct": "Güncelleniyor… %{pct}" + }, + "Telemetry": { + "consent": { + "title": "Sırada ne yapılacağına karar vermeye yardım eder misiniz?", + "body": "Kullanımı anonim olarak paylaşın: hangi araçları açtığınız, uygulama sürümü ve diliniz. Hesap yok, cihaz kimliği yok ve bir araca yazdığınız hiçbir şey gönderilmez. Bunu istediğiniz zaman Ayarlar'dan değiştirebilirsiniz.", + "accept": "Anonim olarak paylaş", + "decline": "Hayır, teşekkürler" + } } } diff --git a/apps/desktop-ui/messages/uk.json b/apps/desktop-ui/messages/uk.json index 39a67e43..ec3ee262 100644 --- a/apps/desktop-ui/messages/uk.json +++ b/apps/desktop-ui/messages/uk.json @@ -91,10 +91,10 @@ "whatIs": { "title": "Що таке MyDevTools?", "description": "Офлайн-середовище для розробників: панель керування та понад 80 інструментів, що працюють повністю на вашому пристрої.", - "p1": "MyDevTools працює повністю офлайн. Після одноразової активації в браузері все працює на цьому пристрої без входу в обліковий запис і без підключення до інтернету. На бічній панелі перелічено всі застосунки; інструменти, якими ви не користуєтесь, можна приховати в розділі", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Налаштування", "p1After": ".", - "p2": "Деякі функції використовують майстер-пароль (або глобальний ключ сховища), щоб такі секрети, як паролі та рядки підключення до баз даних, були зашифровані на вашому пристрої. Ваші дані зберігаються в локальній зашифрованій базі даних і ніколи не надсилаються на сервер." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Швидкі посилання", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Активація та офлайн-доступ", - "description": "Як застосунок розблоковується вперше, а потім працює офлайн.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Одноразова активація відбувається у браузері під час першого запуску застосунку. Вона фіксує ваш тарифний план і розблоковує застосунок на цьому пристрої.", - "offline": "Жодних сеансів входу. Після активації немає входу під час кожного сеансу та періодичних повторних перевірок — застосунок продовжує працювати без підключення до інтернету.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Локальне сховище. Усі ваші дані зберігаються в зашифрованій базі даних SQLCipher на вашому пристрої, ключем до якої є випадковий ключ, що зберігається у зв'язці ключів вашої операційної системи." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Налаштування", - "subtitle": "Керуйте налаштуваннями облікового запису та вподобаннями.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Профіль користувача", - "description": "Ваша особиста інформація та дані облікового запису.", - "avatarAlt": "Користувач", - "anonymousUser": "Анонімний користувач", - "noEmailProvided": "Email не вказано", - "notLoggedIn": "Не авторизовано." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Інструменти та функції", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Показувати значки сайтів у сховищі", "helpText": "Типово вимкнено. Завантаження значка повідомляє сторонньому сервісу, на яких сайтах у вас є облікові записи." + }, + "telemetry": { + "label": "Ділитися анонімними даними про використання", + "helpText": "Типово вимкнено. Надсилає, які інструменти ви відкриваєте, версію застосунку та вашу мову. Без облікового запису, без ідентифікатора пристрою й без того, що ви вводите в інструментах." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Ваші дані коротко", - "subtitle": "Лічильники синхронізовано з обліковим записом на сервері.", + "subtitle": "Counts recorded on this device.", "loadError": "Не вдалося завантажити аналітику.", "retry": "Повторити", - "loginHint": "Увійдіть, щоб бачити аналітику та налаштування інтерфейсу.", "passwordEntries": "Записів паролів", "bookmarks": "Закладки", "bookmarkFolders": "Папки закладок", @@ -664,9 +629,8 @@ }, "ui": { "title": "Налаштування інтерфейсу", - "subtitle": "Тема, мова та видимі інструменти на бічній панелі — з налаштувань облікового запису.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Не вдалося завантажити налаштування.", - "loginHint": "Увійдіть, щоб бачити збережені налаштування інтерфейсу.", "theme": "Тема", "accent": "Колір акценту", "locale": "Мова", @@ -674,13 +638,9 @@ "favoriteTools": "Обрані інструменти", "toolsWithUsage": "Інструменти зі статистикою" }, - "signIn": "Увійти", "noResults": "Жоден інструмент не відповідає вашому пошуку", "noResultsHint": "Спробуйте інше ключове слово або очистіть фільтр.", "filterAll": "Усі", - "loginCta": { - "description": "Увійдіть, щоб відстежувати нещодавно використані інструменти." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Експорт" }, "context": { - "authRequiredError": "Користувач не автентифікований", "defaultTitle": "Без назви", - "cloudSyncImageError": "Увімкніть хмарну синхронізацію, щоб зберігати зображення нотаток. Текстові нотатки залишаються локальними." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Скасувати", "confirmDelete": "Видалити", "deleteDialogTitle": "Видалити набір середовища?", - "deleteDialogDescription": "Зашифровані дані буде видалено з облікового запису. Це незворотно.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2656,7 +2615,6 @@ "toastCopiedText": "Текстову панель скопійовано", "toastCopiedTree": "Панель дерева скопійовано", "toastCopyFailed": "Не вдалося скопіювати JSON", - "toastLoginRequired": "Увійдіть, щоб зберігати документи", "toastSaved": "Документ збережено", "toastSaveFailed": "Не вдалося зберегти документ", "format": "Форматувати", @@ -5160,5 +5118,13 @@ "close": "Закрити", "pill": "Оновлення…", "pillPct": "Оновлення… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Допоможете вирішити, що створити далі?", + "body": "Діліться анонімними даними: які інструменти ви відкриваєте, версія застосунку та ваша мова. Без облікового запису, без ідентифікатора пристрою й без того, що ви вводите в інструментах. Це можна змінити будь-коли в налаштуваннях.", + "accept": "Ділитися анонімно", + "decline": "Ні, дякую" + } } } diff --git a/apps/desktop-ui/messages/vi.json b/apps/desktop-ui/messages/vi.json index 225a33db..e1f27695 100644 --- a/apps/desktop-ui/messages/vi.json +++ b/apps/desktop-ui/messages/vi.json @@ -91,10 +91,10 @@ "whatIs": { "title": "MyDevTools là gì?", "description": "Một không gian làm việc ngoại tuyến dành cho lập trình viên: một bảng điều khiển và hơn 80 công cụ chạy hoàn toàn trên thiết bị của bạn.", - "p1": "MyDevTools chạy hoàn toàn ngoại tuyến. Sau một lần kích hoạt trong trình duyệt, mọi thứ đều hoạt động trên thiết bị này mà không cần đăng nhập tài khoản và không cần kết nối internet. Thanh bên liệt kê mọi ứng dụng; bạn có thể ẩn những công cụ không dùng đến trong phần", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "Cài đặt", "p1After": ".", - "p2": "Một số tính năng sử dụng mật khẩu chính (hoặc khóa kho toàn cục) để các thông tin bí mật như mật khẩu và chuỗi kết nối cơ sở dữ liệu được mã hóa ngay trên thiết bị của bạn. Dữ liệu của bạn được lưu trong một cơ sở dữ liệu mã hóa cục bộ và không bao giờ được gửi đến máy chủ." + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "Liên kết nhanh", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "Kích hoạt & truy cập ngoại tuyến", - "description": "Cách ứng dụng mở khóa lần đầu, sau đó chạy ngoại tuyến.", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "Kích hoạt một lần diễn ra trong trình duyệt của bạn khi bạn mở ứng dụng lần đầu tiên. Nó ghi lại gói dịch vụ của bạn và mở khóa ứng dụng trên thiết bị này.", - "offline": "Không có phiên đăng nhập. Sau khi kích hoạt, không cần đăng nhập theo từng phiên và không có việc kiểm tra lại định kỳ — ứng dụng vẫn tiếp tục hoạt động mà không cần kết nối internet.", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "Lưu trữ cục bộ. Toàn bộ dữ liệu của bạn nằm trong một cơ sở dữ liệu SQLCipher được mã hóa trên thiết bị, được khóa bằng một khóa ngẫu nhiên lưu giữ trong keychain của hệ điều hành." } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "Cài đặt", - "subtitle": "Quản lý cài đặt tài khoản và tùy chọn của bạn.", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "Hồ sơ người dùng", - "description": "Thông tin cá nhân và chi tiết tài khoản của bạn.", - "avatarAlt": "Người dùng", - "anonymousUser": "Người dùng ẩn danh", - "noEmailProvided": "Chưa cung cấp email", - "notLoggedIn": "Chưa đăng nhập." + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "Công cụ & Tính năng", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "Hiện biểu tượng trang web trong kho", "helpText": "Mặc định tắt. Việc tải biểu tượng cho dịch vụ bên thứ ba biết bạn có tài khoản ở những trang nào." + }, + "telemetry": { + "label": "Chia sẻ dữ liệu sử dụng ẩn danh", + "helpText": "Mặc định tắt. Gửi những công cụ bạn mở, phiên bản ứng dụng và ngôn ngữ của bạn. Không tài khoản, không ID thiết bị và không có bất kỳ nội dung nào bạn nhập trong công cụ." } }, "language": { @@ -248,49 +257,6 @@ "tr": "Türkçe", "uk": "Українська" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "Dữ liệu của bạn trong nháy mắt", - "subtitle": "Số liệu đồng bộ với tài khoản trên máy chủ.", + "subtitle": "Counts recorded on this device.", "loadError": "Không tải được phân tích.", "retry": "Thử lại", - "loginHint": "Đăng nhập để xem phân tích sử dụng và tùy chọn giao diện.", "passwordEntries": "Mục mật khẩu", "bookmarks": "Dấu trang", "bookmarkFolders": "Thư mục dấu trang", @@ -664,9 +629,8 @@ }, "ui": { "title": "Tùy chọn giao diện", - "subtitle": "Chủ đề, ngôn ngữ và công cụ hiển thị trên thanh bên — từ cài đặt tài khoản.", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "Không tải được tùy chọn.", - "loginHint": "Đăng nhập để xem tùy chọn giao diện đã lưu.", "theme": "Chủ đề", "accent": "Màu nhấn", "locale": "Ngôn ngữ", @@ -674,13 +638,9 @@ "favoriteTools": "Công cụ yêu thích", "toolsWithUsage": "Công cụ có theo dõi sử dụng" }, - "signIn": "Đăng nhập", "noResults": "Không có công cụ nào phù hợp với tìm kiếm của bạn", "noResultsHint": "Thử từ khóa khác hoặc xóa bộ lọc.", "filterAll": "Tất cả", - "loginCta": { - "description": "Đăng nhập để theo dõi các công cụ bạn sử dụng gần đây." - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "Xuất" }, "context": { - "authRequiredError": "Người dùng chưa xác thực", "defaultTitle": "Không tiêu đề", - "cloudSyncImageError": "Bật Đồng bộ Đám mây để lưu trữ hình ảnh ghi chú. Ghi chú văn bản vẫn ở cục bộ." + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "Hủy", "confirmDelete": "Xóa", "deleteDialogTitle": "Xóa bộ môi trường?", - "deleteDialogDescription": "Dữ liệu mã hóa sẽ bị xóa khỏi tài khoản. Không hoàn tác được.", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "Đã sao chép bảng văn bản", "toastCopiedTree": "Đã sao chép bảng cây", "toastCopyFailed": "Không thể sao chép JSON", - "toastLoginRequired": "Vui lòng đăng nhập để lưu tài liệu", "toastSaved": "Đã lưu tài liệu", "toastSaveFailed": "Không thể lưu tài liệu", "format": "Định dạng", @@ -5149,5 +5107,13 @@ "close": "Đóng", "pill": "Đang cập nhật…", "pillPct": "Đang cập nhật… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "Bạn muốn giúp quyết định điều gì sẽ được xây dựng tiếp theo?", + "body": "Chia sẻ ẩn danh cách bạn dùng ứng dụng: những công cụ bạn mở, phiên bản ứng dụng và ngôn ngữ. Không tài khoản, không ID thiết bị và không có bất kỳ nội dung nào bạn nhập trong công cụ. Bạn có thể thay đổi bất cứ lúc nào trong Cài đặt.", + "accept": "Chia sẻ ẩn danh", + "decline": "Không, cảm ơn" + } } } diff --git a/apps/desktop-ui/messages/zh.json b/apps/desktop-ui/messages/zh.json index aea85c0c..3e9414ec 100644 --- a/apps/desktop-ui/messages/zh.json +++ b/apps/desktop-ui/messages/zh.json @@ -91,10 +91,10 @@ "whatIs": { "title": "什么是 MyDevTools?", "description": "一个离线的开发者工作空间:包含一个仪表盘和 80 多款完全在您设备上运行的工具。", - "p1": "MyDevTools 完全离线运行。在浏览器中完成一次性激活后,一切都在本设备上运行,无需登录账户,也无需联网。侧边栏列出了每一款应用;您可以在", + "p1": "MyDevTools runs fully offline. There is no account, no sign-in and no server — everything works on this device with no internet connection required. The sidebar lists every app; you can hide tools you do not use from", "settingsLink": "设置", "p1After": "中隐藏您不使用的工具。", - "p2": "部分功能使用主密码(或全局保险库密钥),以便在您的设备上加密密码、数据库连接字符串等机密信息。您的数据存储在本地加密数据库中,绝不会发送到任何服务器。" + "p2": "Some features use a master password (or global vault key) so that secrets like passwords and database connection strings are encrypted at rest on your device." }, "quickLinks": { "title": "快速链接", @@ -110,11 +110,11 @@ }, "security": { "access": { - "title": "激活与离线访问", - "description": "应用首次如何解锁,之后如何离线运行。", + "title": "Offline access", + "description": "How the app opens, and where your data lives.", "items": { - "activation": "首次打开应用时,会在您的浏览器中进行一次性激活。它会记录您的套餐并在本设备上解锁应用。", - "offline": "无需登录会话。激活后,无需每次会话登录,也没有定期重新校验——应用在无网络连接的情况下持续运行。", + "activation": "No account. Install the app and open it — there is nothing to register, activate or sign in to.", + "offline": "No sessions. There is no sign-in, no token refresh and no periodic re-check, so the app keeps working with no internet connection.", "storage": "本地存储。您的所有数据都保存在设备上一个加密的 SQLCipher 数据库中,并由保存在您操作系统钥匙串中的随机密钥加密。" } }, @@ -173,14 +173,19 @@ }, "SettingsPage": { "title": "设置", - "subtitle": "管理你的账户设置与偏好。", + "subtitle": "Manage your app settings and preferences.", "userProfile": { - "title": "用户资料", - "description": "你的个人信息与账户详情。", - "avatarAlt": "用户", - "anonymousUser": "匿名用户", - "noEmailProvided": "未提供邮箱", - "notLoggedIn": "未登录。" + "title": "Profile", + "description": "The name and avatar this app shows you. Stored on this device — there is no account.", + "avatarAlt": "Avatar", + "anonymousUser": "You", + "nameLabel": "Display name", + "namePlaceholder": "e.g. Alex", + "avatarLabel": "Avatar URL", + "avatarPlaceholder": "https://…", + "save": "Save", + "saved": "Profile saved", + "saveError": "Could not save your profile" }, "tools": { "title": "工具与功能", @@ -211,6 +216,10 @@ "vaultIcons": { "label": "在保险库中显示网站图标", "helpText": "默认关闭。获取图标会让第三方服务知道你在哪些网站有账户。" + }, + "telemetry": { + "label": "共享匿名使用数据", + "helpText": "默认关闭。发送你打开了哪些工具、应用版本和语言。没有账号,没有设备 ID,也不会发送你在工具中输入的任何内容。" } }, "language": { @@ -248,49 +257,6 @@ "sv": "Svenska", "tr": "Türkçe" } - }, - "passkeys": { - "title": "Passkeys", - "description": "Sign in faster and more securely with Face ID, Touch ID, Windows Hello, or a hardware key.", - "addButton": "Add passkey", - "loading": "Loading…", - "none": "No passkeys yet.", - "count": "{count, plural, one {# passkey registered.} other {# passkeys registered.}}", - "addedOn": "Added {date}", - "lastUsedOn": "Last used {date}", - "synced": "Synced", - "rename": "Rename", - "renamePrompt": "Rename passkey", - "addedToast": "Passkey added.", - "renamedToast": "Passkey renamed.", - "removedToast": "Passkey removed.", - "loadError": "Could not load passkeys.", - "addError": "Could not add passkey.", - "renameError": "Could not rename passkey.", - "deleteError": "Could not delete passkey.", - "dialog": { - "title": "Add a passkey", - "description": "Your device will prompt you for a biometric or PIN. Give the passkey a name you'll recognize later.", - "nameLabel": "Device name (optional)", - "namePlaceholder": "e.g. MacBook Touch ID", - "cancel": "Cancel", - "continue": "Continue", - "waiting": "Waiting for device…" - }, - "delete": { - "title": "Remove passkey?", - "description": "You won't be able to sign in with this passkey anymore. You can always add a new one later.", - "cancel": "Cancel", - "confirm": "Remove" - }, - "prompt": { - "title": "Sign in faster next time", - "description": "Set up a passkey on this device. Use Face ID, Touch ID, Windows Hello, or a hardware key to sign in — no password or OAuth round-trip needed.", - "skip": "Maybe later", - "setup": "Set up passkey", - "successToast": "Passkey added — try signing out and back in to test it.", - "errorToast": "Could not add passkey. You can try again from Settings." - } } }, "Dashboard": { @@ -628,10 +594,9 @@ }, "analytics": { "title": "数据一览", - "subtitle": "与服务器账户同步的计数。", + "subtitle": "Counts recorded on this device.", "loadError": "无法加载分析数据。", "retry": "重试", - "loginHint": "登录后可查看使用分析与界面偏好。", "passwordEntries": "密码条目", "bookmarks": "书签", "bookmarkFolders": "书签文件夹", @@ -664,9 +629,8 @@ }, "ui": { "title": "界面偏好", - "subtitle": "主题、语言与侧边栏显示的工具 — 来自账户设置。", + "subtitle": "Theme, language, and which tools appear in the sidebar.", "loadError": "无法加载偏好设置。", - "loginHint": "登录后可查看已保存的界面偏好。", "theme": "主题", "accent": "强调色", "locale": "语言", @@ -674,13 +638,9 @@ "favoriteTools": "收藏的工具", "toolsWithUsage": "已记录使用情况的工具" }, - "signIn": "登录", "noResults": "没有工具与您的搜索匹配", "noResultsHint": "尝试不同的关键词或清除筛选条件。", "filterAll": "全部", - "loginCta": { - "description": "登录以跟踪您最近使用的工具。" - }, "popularTools": "Popular tools", "pinnedEmpty": "Pin your most-used tools — hover a card and click the pin icon.", "viewActivity": "Recent activity", @@ -957,9 +917,8 @@ "export": "导出" }, "context": { - "authRequiredError": "用户未登录", "defaultTitle": "无标题", - "cloudSyncImageError": "开启云同步以存储笔记图片。文本笔记保留在本地。" + "imageTooLargeError": "That image is too large to embed. Images are stored inside the note and are capped at 2 MB." } }, "PasswordManager": { @@ -1210,7 +1169,7 @@ "cancel": "取消", "confirmDelete": "删除", "deleteDialogTitle": "删除环境集合?", - "deleteDialogDescription": "将从账户中移除加密数据,无法撤销。", + "deleteDialogDescription": "This removes the encrypted blob from this device. You cannot undo this.", "view": "View", "viewDialogSubtitle": "Decrypted variables in .env format. Do not share your screen in untrusted environments.", "viewNoVariables": "No variables in this set.", @@ -2645,7 +2604,6 @@ "toastCopiedText": "已复制文本面板", "toastCopiedTree": "已复制树形面板", "toastCopyFailed": "复制 JSON 失败", - "toastLoginRequired": "请登录以保存文档", "toastSaved": "文档已保存", "toastSaveFailed": "文档保存失败", "format": "格式化", @@ -5149,5 +5107,13 @@ "close": "关闭", "pill": "正在更新…", "pillPct": "正在更新… {pct}%" + }, + "Telemetry": { + "consent": { + "title": "帮我们决定下一步做什么?", + "body": "匿名共享使用情况:你打开了哪些工具、应用版本和语言。没有账号,没有设备 ID,也不会发送你在工具中输入的任何内容。你可以随时在设置中更改。", + "accept": "匿名共享", + "decline": "不用了" + } } } diff --git a/apps/desktop-ui/package.json b/apps/desktop-ui/package.json index a9148ede..edc4b60e 100644 --- a/apps/desktop-ui/package.json +++ b/apps/desktop-ui/package.json @@ -19,7 +19,6 @@ "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@hookform/resolvers": "^3", - "@microsoft/clarity": "^1.0.2", "@milkdown/crepe": "7.21.3", "@milkdown/kit": "7.21.3", "@monaco-editor/react": "^4.7.0", @@ -48,7 +47,6 @@ "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-visually-hidden": "^1.2.7", - "@simplewebauthn/browser": "^11.0.0", "@space-man/react-theme-animation": "^1.1.1", "@tabler/icons-react": "^3.35.0", "@tanstack/react-query": "^5", @@ -58,8 +56,6 @@ "@tauri-apps/plugin-opener": "^2.5.4", "@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-updater": "^2.10.1", - "@vercel/analytics": "^1.5.0", - "@vercel/speed-insights": "^1.2.0", "@zxcvbn-ts/core": "^4.1.2", "@zxcvbn-ts/language-common": "^4.1.3", "aws4fetch": "^1.0.20", @@ -76,7 +72,6 @@ "exifr": "^7.1.3", "fast-xml-parser": "^5.6.0", "file-saver": "^2.0.5", - "firebase": "^12.5.0", "framer-motion": "^12.23.24", "geist": "^1.5.1", "gpt-tokenizer": "^3.4.0", @@ -109,7 +104,6 @@ "qrcode": "^1.5.4", "react": "^19.2.0", "react-dom": "^19.2.0", - "react-firebase-hooks": "^5.1.1", "react-github-calendar": "^5.0.6", "react-hook-form": "^7", "react-resizable-panels": "^3.0.6", diff --git a/apps/desktop-ui/src/app/activate/page.tsx b/apps/desktop-ui/src/app/activate/page.tsx deleted file mode 100644 index ad781313..00000000 --- a/apps/desktop-ui/src/app/activate/page.tsx +++ /dev/null @@ -1,120 +0,0 @@ -'use client' - -import { useCallback, useEffect, useRef, useState } from 'react' -import { useRouter } from 'next/navigation' -import { useTranslations } from 'next-intl' -import { BadgeCheck, Globe, Loader2 } from 'lucide-react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { completeActivation, getActivation, type ActivationRecord } from '@/lib/desktop/activation' - -type Phase = 'idle' | 'waiting' | 'finishing' | 'done' | 'error' - -export default function ActivatePage() { - const t = useTranslations('Activate') - const router = useRouter() - const [phase, setPhase] = useState('idle') - const [record, setRecord] = useState(null) - const [error, setError] = useState(null) - const finishing = useRef(false) - - // Already activated (back-navigation etc.) → straight to the app. - useEffect(() => { - void getActivation().then((r) => { - if (r) router.replace('/dashboard') - }).catch(() => {}) - }, [router]) - - const finish = useCallback(async () => { - if (finishing.current) return - finishing.current = true - setPhase('finishing') - try { - setRecord(await completeActivation()) - setPhase('done') - } catch (e) { - setError(e instanceof Error ? e.message : String(e)) - setPhase('error') - } finally { - finishing.current = false - } - }, []) - - // Deep-link sign-in path (mydevtools://auth) completes outside this page's - // startCloudSignIn call — listen for it and finish the activation here. - useEffect(() => { - const onAuthed = () => void finish() - window.addEventListener('mydevtools:desktop-authed', onAuthed) - return () => window.removeEventListener('mydevtools:desktop-authed', onAuthed) - }, [finish]) - - const signIn = async () => { - setError(null) - setPhase('waiting') - try { - const { startCloudSignIn } = await import('@/lib/desktop/cloud-signin') - await startCloudSignIn() - await finish() - } catch (e) { - if (finishing.current || phase === 'done') return - setError(e instanceof Error ? e.message : String(e)) - setPhase('error') - } - } - - if (phase === 'done' && record) { - return ( -
- - -
- -
- {t('authSuccess')} - - {record.display_name || record.email} - -
- -
- - {t('offlineNote')} -
- -
-
-
- ) - } - - return ( -
- - - {t('title')} - {t('subtitle')} - - - - {error &&

{error}

} -

{t('privacyNote')}

-
-
-
- ) -} diff --git a/apps/desktop-ui/src/app/app/app-content.tsx b/apps/desktop-ui/src/app/app/app-content.tsx index 205d2d94..1002879d 100644 --- a/apps/desktop-ui/src/app/app/app-content.tsx +++ b/apps/desktop-ui/src/app/app/app-content.tsx @@ -2,19 +2,16 @@ import React, { useEffect, useRef } from 'react'; import { ClientLayout } from '../../components/sidebar/client-layout'; -import { RequireAuth } from '@/components/require-auth'; import { MasterPasswordGate } from '@/components/master-password-gate'; import { useMasterKeyStore } from '@/store/master-key-store'; import { clearMasterKey } from '@/lib/key-storage'; import { getMasterVaultOrNull } from '@/lib/global-vault-api'; import { restoreVault } from '@/lib/restore-vault'; import { useIdleLock } from '@/lib/use-idle-lock'; -import useAuth from '@/utils/useAuth'; -// Single restoration path. Runs once per signed-in user mount. Mutates the -// store with the final state — modal and pages read from store only. +// Single restoration path. Runs once per mount. Mutates the store with the +// final state — modal and pages read from store only. function VaultKeyRestorer() { - const { user } = useAuth(false); const { vaultStatus, setKey, setVaultStatus, setVault, setRestoreError } = useMasterKeyStore(); const ranRef = useRef(false); @@ -24,7 +21,7 @@ function VaultKeyRestorer() { ranRef.current = false; return; } - if (!user || ranRef.current) return; + if (ranRef.current) return; ranRef.current = true; (async () => { @@ -53,7 +50,7 @@ function VaultKeyRestorer() { } })(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [user, vaultStatus]); + }, [vaultStatus]); return null; } @@ -65,11 +62,11 @@ function IdleLock() { export function AppContent({ children }: { children: React.ReactNode }) { return ( - + <> {children} - + ); } diff --git a/apps/desktop-ui/src/app/app/notes/context/NotesContext.tsx b/apps/desktop-ui/src/app/app/notes/context/NotesContext.tsx index 39eff7f0..c80c09ec 100644 --- a/apps/desktop-ui/src/app/app/notes/context/NotesContext.tsx +++ b/apps/desktop-ui/src/app/app/notes/context/NotesContext.tsx @@ -2,19 +2,13 @@ import React, { createContext, useContext, useEffect, useMemo, useState, useCallback, useRef } from "react"; import { Note } from "../types/Note"; -import { useAuthState } from "react-firebase-hooks/auth"; -import { auth } from "@/database/firebase"; import { useTranslations } from "next-intl"; import { fetchAllPages } from "@/lib/fetch-all-pages"; -import { proxyJsonAuthed } from "@/lib/backend-auth"; +import { apiRequestRaw } from "@/lib/backend-api"; import { useCipherKey, cipherKeyErrorMessage } from "@/lib/use-cipher-key"; import { encryptField, decryptField, isEnvelope, type ContentEnvelope } from "@/lib/content-envelope"; import { setCachedPlainText, deleteCachedPlainText, clearPlainTextCache } from "@/components/notes/notes-helpers"; -const BACKEND_BASE_URL: string = - process.env.NEXT_PUBLIC_FASTAPI_BASE_URL || - process.env.NEXT_PUBLIC_BACKEND_BASE_URL || - "http://localhost:8000"; const NOTES_PAGE_SIZE = 500; const EMPTY_CONTENT: Map = new Map(); @@ -66,7 +60,6 @@ const NotesActionsContext = createContext(undefined); export function NotesProvider({ children }: { children: React.ReactNode }) { const t = useTranslations("Notes.context"); - const [user] = useAuthState(auth); const [notes, setNotes] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isContentLoading, setIsContentLoading] = useState(false); @@ -100,11 +93,9 @@ export function NotesProvider({ children }: { children: React.ReactNode }) { // Refs to read latest state inside stable action callbacks const notesRef = useRef(notes); const activeNoteIdRef = useRef(activeNoteId); - const userRef = useRef(user); const cipherKeyRef = useRef(cipherKey); useEffect(() => { notesRef.current = notes; }, [notes]); useEffect(() => { activeNoteIdRef.current = activeNoteId; }, [activeNoteId]); - useEffect(() => { userRef.current = user; }, [user]); useEffect(() => { cipherKeyRef.current = cipherKey; }, [cipherKey]); const setContent = useCallback((id: string, content: unknown) => { @@ -141,10 +132,8 @@ export function NotesProvider({ children }: { children: React.ReactNode }) { const apiRequest = useCallback( async (method: string, path: string, body?: unknown): Promise => { - const currentUser = userRef.current; - if (!currentUser) throw new Error(t("authRequiredError")); - const { status, data } = await proxyJsonAuthed(BACKEND_BASE_URL, method, path, body); + const { status, data } = await apiRequestRaw(method, path, body); if (status < 200 || status >= 300) { throw new Error(`API ${method} ${path} failed (${status})`); } @@ -154,7 +143,6 @@ export function NotesProvider({ children }: { children: React.ReactNode }) { ); const refreshNotes = useCallback(async () => { - if (!userRef.current) return; // Metadata only: the list endpoint nulls bodies. Bodies load per opened // note, and in bulk on the first search (warmSearchIndex). No crypto here. const allNotes = await fetchAllPages({ @@ -203,13 +191,6 @@ export function NotesProvider({ children }: { children: React.ReactNode }) { }, [activeNoteId]); useEffect(() => { - if (!user) { - setNotes([]); - setIsLoading(false); - setActiveNoteId(null); - return; - } - (async () => { try { setIsLoading(true); @@ -220,7 +201,7 @@ export function NotesProvider({ children }: { children: React.ReactNode }) { })(); // No cipherKey dep: metadata is plaintext on the wire and doesn't depend // on the vault key. Bodies are handled by the effect below. - }, [refreshNotes, user]); + }, [refreshNotes]); // Vault lock/unlock: bodies and the search index invalidate themselves (both // are tagged with their key), but the plain-text cache is module state — @@ -264,7 +245,6 @@ export function NotesProvider({ children }: { children: React.ReactNode }) { }, [apiRequest, decryptBody]); const createNote = useCallback(async (parentId: string | null = null) => { - if (!userRef.current) throw new Error(t("authRequiredError")); const created = await apiRequest("POST", "/api/v1/notes", { title: t("defaultTitle"), content: await encryptContent({}), @@ -281,7 +261,6 @@ export function NotesProvider({ children }: { children: React.ReactNode }) { }, [apiRequest, t, encryptContent, setContent]); const updateNote = useCallback(async (id: string, updates: Partial) => { - if (!userRef.current) return; // Block body writes while locked — persisting would clobber the real // (undecryptable) content with an empty one. if (updates.content !== undefined && (lockedIds.current.has(id) || !cipherKeyRef.current)) { @@ -365,7 +344,6 @@ export function NotesProvider({ children }: { children: React.ReactNode }) { }, [updateNote]); const deleteNote = useCallback(async (id: string) => { - if (!userRef.current) return; await apiRequest("DELETE", `/api/v1/notes/${id}?recursive=true`); // Local mutation: compute descendant set, prune in one pass — skip full refetch. diff --git a/apps/desktop-ui/src/app/app/to-do/GoogleLoginButton.tsx b/apps/desktop-ui/src/app/app/to-do/GoogleLoginButton.tsx deleted file mode 100644 index c1ae59d3..00000000 --- a/apps/desktop-ui/src/app/app/to-do/GoogleLoginButton.tsx +++ /dev/null @@ -1,37 +0,0 @@ -// components/GoogleLoginButton.js -'use client'; -import { GoogleAuthProvider, signInWithPopup } from 'firebase/auth'; -import { auth } from '../../../database/firebase'; -import { Button } from '../../../components/ui/button'; -import Image from 'next/image'; -import { useRouter } from 'next/navigation'; -import { establishBackendSession } from '@/lib/backend-auth'; - -const GoogleLoginButton = () => { - const router = useRouter(); - const signInWithGoogle = async () => { - const provider = new GoogleAuthProvider(); - try { - const result = await signInWithPopup(auth, provider); - const idToken = await result.user.getIdToken(); - await establishBackendSession(idToken, { checkRevoked: true }); - router.push('/dashboard'); - } catch (error) { - console.error('Error during Google sign-in or API session:', error); - } - }; - - return ( -
- -
- ); - - -}; - -export default GoogleLoginButton; - diff --git a/apps/desktop-ui/src/app/app/to-do/context/ProjectContext.tsx b/apps/desktop-ui/src/app/app/to-do/context/ProjectContext.tsx index 6cce573d..011fa6e4 100644 --- a/apps/desktop-ui/src/app/app/to-do/context/ProjectContext.tsx +++ b/apps/desktop-ui/src/app/app/to-do/context/ProjectContext.tsx @@ -3,7 +3,7 @@ import React, { createContext, useContext, useState, useEffect, useCallback } from "react"; import { Project, NewProject } from "@/app/app/to-do/types/Project"; import useAuth from "@/utils/useAuth"; -import { backendFetch } from "@/lib/backend-auth"; +import { apiFetch } from "@/lib/desktop/api-fetch"; import { toast } from "sonner"; import { useTranslations } from "next-intl"; @@ -26,7 +26,7 @@ export function ProjectProvider({ children }: { children: React.ReactNode }) { const authedFetch = useCallback( async (path: string, init?: RequestInit) => { if (!user) throw new Error("Not authenticated"); - const res = await backendFetch(path, { + const res = await apiFetch(path, { ...init, headers: { "Content-Type": "application/json", diff --git a/apps/desktop-ui/src/app/app/to-do/context/TaskContext.tsx b/apps/desktop-ui/src/app/app/to-do/context/TaskContext.tsx index d72d7ac9..f8edc65a 100644 --- a/apps/desktop-ui/src/app/app/to-do/context/TaskContext.tsx +++ b/apps/desktop-ui/src/app/app/to-do/context/TaskContext.tsx @@ -13,7 +13,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { Task, NewTask } from "@/app/app/to-do/types/Task"; import { format } from "date-fns"; import useAuth, { AuthState } from "@/utils/useAuth"; -import { backendFetch } from "@/lib/backend-auth"; +import { apiFetch } from "@/lib/desktop/api-fetch"; import { fetchAllPages } from "@/lib/fetch-all-pages"; import { toast } from "sonner"; import { useTranslations } from "next-intl"; @@ -136,7 +136,7 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { const authedFetch = useCallback( async (path: string, init?: RequestInit) => { if (!user) throw new Error("Not authenticated"); - const res = await backendFetch(path, { + const res = await apiFetch(path, { ...init, headers: { "Content-Type": "application/json", diff --git a/apps/desktop-ui/src/app/app/to-do/login-page.tsx b/apps/desktop-ui/src/app/app/to-do/login-page.tsx deleted file mode 100644 index a564ad7d..00000000 --- a/apps/desktop-ui/src/app/app/to-do/login-page.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import type { Metadata } from "next" -import { FileJson, ListTodo, StickyNote, FileCode, Key, Hash } from "lucide-react" -import { LegalAgreementFooter } from "@/components/legal-agreement-footer" -import { Card } from "@/components/ui/card" -import { LoginForm } from "../../../components/login-form" - -export const metadata: Metadata = { - title: "Login - MyDevTools", - description: "Login to access your developer tools and workspace", -} - -export default function LoginPage() { - return ( -
-
-
-
-
-

Essential Developer Tools

-
- {[ - { - icon: ListTodo, - title: "To-Do App", - description: "Organize tasks", - }, - { - icon: StickyNote, - title: "Note Taking", - description: "Capture ideas", - }, - { - icon: FileJson, - title: "JSON/YAML Tools", - description: "Convert & format", - }, - { - icon: FileCode, - title: "YAML to TOML", - description: "Easy conversion", - }, - { - icon: Key, - title: "Bcrypt Generator", - description: "Secure hashing", - }, - { - icon: Hash, - title: "UUID Generator", - description: "Unique identifiers", - }, - ].map((tool, i) => ( - -
- -
-

{tool.title}

-

{tool.description}

-
-
-
- ))} -
-
-
-
-

- Streamline your development workflow with our comprehensive suite of tools. -

-
-
-
-
-
-

Welcome back

-

Sign in to your account to continue

-
- - -
-
-
- ) -} - diff --git a/apps/desktop-ui/src/app/dashboard/dashboard-client-layout.tsx b/apps/desktop-ui/src/app/dashboard/dashboard-client-layout.tsx index 930d87a3..860f4c92 100644 --- a/apps/desktop-ui/src/app/dashboard/dashboard-client-layout.tsx +++ b/apps/desktop-ui/src/app/dashboard/dashboard-client-layout.tsx @@ -1,22 +1,15 @@ 'use client' import React from 'react'; import { ClientLayout } from '../../components/sidebar/client-layout'; -import { RequireAuth } from '@/components/require-auth'; import { MasterPasswordGate } from '@/components/master-password-gate'; import { OnboardingGate } from '@/components/onboarding-gate'; -import { PasskeyPromptGate } from '@/components/passkey-prompt-gate'; -import { isDesktop } from '@/lib/desktop/is-desktop'; export default function DashboardClientLayout({ children }: { children: React.ReactNode }) { - // Onboarding/passkey are web-account flows; the offline desktop app has no - // per-session auth backend for them (local API 501s on /auth/*). - const web = !isDesktop(); return ( - - {web && } + <> + - {web && } {children} - + ); } diff --git a/apps/desktop-ui/src/app/layout.tsx b/apps/desktop-ui/src/app/layout.tsx index 18252dda..e3d7234a 100644 --- a/apps/desktop-ui/src/app/layout.tsx +++ b/apps/desktop-ui/src/app/layout.tsx @@ -114,11 +114,6 @@ export default async function RootLayout({ courierPrime.variable )}> - {/* Firebase Auth & token refresh */} - - - {/* Firebase Storage */} - {/* Vercel Speed Insights */} diff --git a/apps/desktop-ui/src/app/login/page.tsx b/apps/desktop-ui/src/app/login/page.tsx deleted file mode 100644 index b8dd304f..00000000 --- a/apps/desktop-ui/src/app/login/page.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import type { Metadata } from "next"; -import Link from "next/link"; -import { ArrowLeft, Cloud, ShieldCheck, Zap } from "lucide-react"; -import { LegalAgreementFooter } from "@/components/legal-agreement-footer"; -import { LoginForm } from "@/components/login-form"; -import { LoginRedirectIfAuthed } from "@/components/login-redirect-if-authed"; -import { HideOnDesktop } from "@/components/desktop/hide-on-desktop"; -import { Logo } from "@/components/logo"; -import { Magnetic } from "@/components/mdt-magnetic"; -import MdtAurora from "@/components/mdt-aurora"; - -const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://mydevtools.tech"; - -export const metadata: Metadata = { - title: "Login - MyDevTools", - description: "Login to access your developer tools and workspace", - alternates: { canonical: `${baseUrl}/login` }, -}; - -const FEATURES = [ - { - icon: Zap, - title: "80+ tools, one app", - body: "Formatters, generators, API client, SQL, crypto — all in one place.", - }, - { - icon: Cloud, - title: "Completely offline", - body: "Runs fully on your device. No network needed once you're in.", - }, - { - icon: ShieldCheck, - title: "Client-side AES encryption", - body: "Data is AES-256 encrypted and never leaves your device — unless you back it up.", - }, -]; - -const TOOL_PILLS = [ - "JSON", - "JWT", - "Regex", - "Base64", - "UUID", - "API Client", - "SQL", - "GraphQL", -]; - -export default function LoginPage() { - return ( -
- - - {/* deck backdrop */} -
- -
-
-
-
- - - - - - - - - -
- -
- - - - - Back to home - - - -
- -
- {/* ── Showcase (desktop only) ── */} -
-

Welcome back

-

- Your entire dev toolkit,{" "} - one app away. -

-

- Sign in to sync your work across devices and pick up exactly where you left off. -

- -
    - {FEATURES.map(({ icon: Icon, title, body }) => ( -
  • - - - -
    -

    {title}

    -

    {body}

    -
    -
  • - ))} -
- -
- {TOOL_PILLS.map((pill) => ( - - {pill} - - ))} -
-
- - {/* ── Login card ── */} -
-
-
- -
- -

- Sign in to MyDevTools -

-

- Continue with your preferred provider to open your workspace. -

-
- - - -

- No passwords. One-click OAuth — we never see your credentials. -

-
- - -
-
-
- ); -} diff --git a/apps/desktop-ui/src/app/page.tsx b/apps/desktop-ui/src/app/page.tsx index 4c811955..f2791d09 100644 --- a/apps/desktop-ui/src/app/page.tsx +++ b/apps/desktop-ui/src/app/page.tsx @@ -4,18 +4,13 @@ import { useEffect } from "react"; import { useRouter } from "next/navigation"; /** - * Desktop launch route: no marketing surface in the app bundle. Route to the - * one-time activation screen until the app is activated, then straight to the - * tools. Offline-first — no network in this decision. + * Launch route: no marketing surface in the app bundle, and nothing to unlock — + * the app has no accounts. Straight to the tools. */ export default function Page() { const router = useRouter(); useEffect(() => { - void (async () => { - const { localApi } = await import("@/lib/desktop/bridge"); - const activated = (await localApi("GET", "/desktop/activation")).status === 200; - router.replace(activated ? "/dashboard" : "/activate"); - })(); + router.replace("/dashboard"); }, [router]); return
; } diff --git a/apps/desktop-ui/src/app/settings/page.tsx b/apps/desktop-ui/src/app/settings/page.tsx index a749b9dc..ee664f2d 100644 --- a/apps/desktop-ui/src/app/settings/page.tsx +++ b/apps/desktop-ui/src/app/settings/page.tsx @@ -8,12 +8,13 @@ import { Sun, Globe, Palette, Check, Lock } from 'lucide-react' import { IDLE_TIMEOUT_KEY, getIdleTimeoutMinutes } from '@/lib/use-idle-lock' import { Switch } from '@/components/ui/switch' import { getVaultIconsEnabled, setVaultIconsEnabled } from '@/lib/vault-icon-pref' +import { getConsent, setConsent } from '@/lib/telemetry' import { useTheme } from 'next-themes' import { cn } from '@/lib/utils' import { useLocale, useTranslations } from 'next-intl' import { useRouter } from 'next/navigation' import { COLOR_THEME_OPTIONS, type ColorTheme, useColorTheme } from '@/hooks/use-color-theme' -import { DesktopPlanSettings } from '@/components/desktop/desktop-plan-settings' +import { ProfileCard } from '@/components/settings/profile-card' import { AppVersionLabel } from '@/components/desktop/app-version-label' import { useActiveWorkspace } from '@/store/workspace-store' import { Briefcase } from 'lucide-react' @@ -38,11 +39,13 @@ export default function SettingsPage() { const [mounted, setMounted] = useState(false) const [idleTimeout, setIdleTimeout] = useState('15') const [vaultIcons, setVaultIcons] = useState(false) + const [telemetry, setTelemetry] = useState(false) useEffect(() => { setMounted(true) setIdleTimeout(String(getIdleTimeoutMinutes())) setVaultIcons(getVaultIconsEnabled()) + setTelemetry(getConsent() === 'granted') }, []) const handleIdleTimeoutChange = (value: string) => { @@ -79,7 +82,7 @@ export default function SettingsPage() {
- + @@ -196,6 +199,23 @@ export default function SettingsPage() { }} />
+ + {/* Off until explicitly granted — see lib/telemetry.ts for what + a granted event actually contains. */} +
+
+ +

{t('security.telemetry.helpText')}

+
+ { + setConsent(next ? 'granted' : 'denied') + setTelemetry(next) + }} + /> +
diff --git a/apps/desktop-ui/src/components/__tests__/migration-banner.test.ts b/apps/desktop-ui/src/components/__tests__/migration-banner.test.ts deleted file mode 100644 index 8c09e414..00000000 --- a/apps/desktop-ui/src/components/__tests__/migration-banner.test.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * Tests for MigrationBanner (task 25 — boot wiring + first-login migration banner). - * - * Environment: jest-environment-node — no DOM, no React rendering. - * Strategy: verify module structure + polling logic contracts via state - * simulation, matching the pattern of other component tests in this project. - */ - -// ── Mocks ──────────────────────────────────────────────────────────────────── - -jest.mock("@/lib/backend-auth", () => ({ - backendFetch: jest.fn(), -})) - -// ── Imports ────────────────────────────────────────────────────────────────── - -import * as backendAuth from "@/lib/backend-auth" - -// ── Helpers ────────────────────────────────────────────────────────────────── - -type MeResponse = { - migration_status?: string - migrated_at?: number | null - migrated_fast?: boolean -} - -/** Simulate one MigrationBanner poll tick and return the derived status. */ -async function simulateTick(me: MeResponse): Promise<"pending" | "done" | null> { - ;(backendAuth.backendFetch as jest.Mock).mockResolvedValueOnce({ - ok: true, - json: async () => me, - }) - - const res = await (backendAuth.backendFetch as jest.Mock)("/api/backend/auth/me") - if (!res.ok) return null - const data = await res.json() - - if (data.migrated_at || data.migrated_fast === true) return "done" - if (data.migration_status === "pending") return "pending" - return "done" -} - -// ── Tests ──────────────────────────────────────────────────────────────────── - -describe("MigrationBanner — module exports", () => { - it("exports a MigrationBanner named function component", () => { - const mod = require("../migration-banner") - expect(typeof mod.MigrationBanner).toBe("function") - }) -}) - -describe("MigrationBanner — polling logic contracts", () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - it("resolves to 'done' immediately when migrated_at is set", async () => { - const status = await simulateTick({ migrated_at: 1234567890, migration_status: "done" }) - expect(status).toBe("done") - }) - - it("resolves to 'done' when migrated_fast flag is true", async () => { - const status = await simulateTick({ migrated_fast: true, migration_status: "done" }) - expect(status).toBe("done") - }) - - it("resolves to 'pending' when migration_status is 'pending' and migrated_at is absent", async () => { - const status = await simulateTick({ migration_status: "pending" }) - expect(status).toBe("pending") - }) - - it("resolves to 'done' when migration_status is neither 'pending' nor 'done' (unknown value)", async () => { - const status = await simulateTick({ migration_status: "unknown" }) - expect(status).toBe("done") - }) - - it("resolves to 'done' when migration_status is absent (field not returned)", async () => { - const status = await simulateTick({}) - expect(status).toBe("done") - }) - - it("resolves to 'done' when migrated_at is set even if migration_status is still 'pending'", async () => { - // migrated_at takes priority over migration_status - const status = await simulateTick({ migrated_at: 1700000000, migration_status: "pending" }) - expect(status).toBe("done") - }) - - it("polls the correct endpoint", async () => { - ;(backendAuth.backendFetch as jest.Mock).mockResolvedValueOnce({ - ok: true, - json: async () => ({ migration_status: "done" }), - }) - await (backendAuth.backendFetch as jest.Mock)("/api/backend/auth/me") - expect(backendAuth.backendFetch).toHaveBeenCalledWith("/api/backend/auth/me") - }) - - it("returns null when the fetch response is not ok", async () => { - ;(backendAuth.backendFetch as jest.Mock).mockResolvedValueOnce({ ok: false }) - const res = await (backendAuth.backendFetch as jest.Mock)("/api/backend/auth/me") - const status = res.ok ? "done" : null - expect(status).toBeNull() - }) -}) - -describe("MigrationBanner — banner render guard (status contract)", () => { - it("banner must NOT render when status is null", () => { - // Mirrors: if (status !== 'pending') return null - const status = null - const wouldRender = status === "pending" - expect(wouldRender).toBe(false) - }) - - it("banner must NOT render when status is 'done'", () => { - const status: string = "done" - const wouldRender = status === "pending" - expect(wouldRender).toBe(false) - }) - - it("banner MUST render when status is 'pending'", () => { - const status: string = "pending" - const wouldRender = status === "pending" - expect(wouldRender).toBe(true) - }) -}) - -describe("MigrationBanner — source structure assertions", () => { - it("component polls /api/backend/auth/me (not /users/me)", () => { - const fs = require("fs") - const path = require("path") - const source = fs.readFileSync( - path.join(__dirname, "../migration-banner.tsx"), - "utf8" - ) - expect(source).toContain("/api/backend/auth/me") - }) - - it("component uses a cleanup function (timer clearTimeout)", () => { - const fs = require("fs") - const path = require("path") - const source = fs.readFileSync( - path.join(__dirname, "../migration-banner.tsx"), - "utf8" - ) - expect(source).toContain("clearTimeout") - }) - - it("component uses cancelled flag to prevent state updates after unmount", () => { - const fs = require("fs") - const path = require("path") - const source = fs.readFileSync( - path.join(__dirname, "../migration-banner.tsx"), - "utf8" - ) - expect(source).toContain("cancelled") - }) - - it("component has a max elapsed time guard (60s)", () => { - const fs = require("fs") - const path = require("path") - const source = fs.readFileSync( - path.join(__dirname, "../migration-banner.tsx"), - "utf8" - ) - expect(source).toContain("MAX_ELAPSED_MS") - }) - - it("banner text says 'Setting up your workspace'", () => { - const fs = require("fs") - const path = require("path") - const source = fs.readFileSync( - path.join(__dirname, "../migration-banner.tsx"), - "utf8" - ) - expect(source).toContain("Setting up your workspace") - }) -}) - -describe("EnsureBackendSession — workspace hydration wiring", () => { - it("ensure-backend-session.tsx imports useWorkspaceStore", () => { - const fs = require("fs") - const path = require("path") - const source = fs.readFileSync( - path.join(__dirname, "../../components/ensure-backend-session.tsx"), - "utf8" - ) - expect(source).toContain("useWorkspaceStore") - }) - - it("ensure-backend-session.tsx calls loadFromBackend after session confirms", () => { - const fs = require("fs") - const path = require("path") - const source = fs.readFileSync( - path.join(__dirname, "../../components/ensure-backend-session.tsx"), - "utf8" - ) - expect(source).toContain("loadFromBackend") - }) - - it("workspace hydration is non-blocking (.catch not re-thrown)", () => { - const fs = require("fs") - const path = require("path") - const source = fs.readFileSync( - path.join(__dirname, "../../components/ensure-backend-session.tsx"), - "utf8" - ) - // loadFromBackend().catch(...) pattern confirms non-blocking - expect(source).toMatch(/loadFromBackend\(\)\.catch/) - }) -}) diff --git a/apps/desktop-ui/src/components/api-client/__tests__/environments-context.test.ts b/apps/desktop-ui/src/components/api-client/__tests__/environments-context.test.ts index cb5aa5cf..27b17d6a 100644 --- a/apps/desktop-ui/src/components/api-client/__tests__/environments-context.test.ts +++ b/apps/desktop-ui/src/components/api-client/__tests__/environments-context.test.ts @@ -1,8 +1,4 @@ -jest.mock("react-firebase-hooks/auth", () => ({ - useAuthState: () => [null, false], -})) -jest.mock("@/database/firebase", () => ({ auth: {} })) -jest.mock("@/lib/backend-auth", () => ({ backendFetch: jest.fn() })) +jest.mock("@/lib/desktop/api-fetch", () => ({ apiFetch: jest.fn() })) jest.mock("sonner", () => ({ toast: { success: jest.fn(), error: jest.fn() } })) /** diff --git a/apps/desktop-ui/src/components/api-client/__tests__/history-context.test.ts b/apps/desktop-ui/src/components/api-client/__tests__/history-context.test.ts index 63547317..ff200d2e 100644 --- a/apps/desktop-ui/src/components/api-client/__tests__/history-context.test.ts +++ b/apps/desktop-ui/src/components/api-client/__tests__/history-context.test.ts @@ -1,8 +1,4 @@ -jest.mock("react-firebase-hooks/auth", () => ({ - useAuthState: () => [null, false], -})) -jest.mock("@/database/firebase", () => ({ auth: {} })) -jest.mock("@/lib/backend-auth", () => ({ backendFetch: jest.fn() })) +jest.mock("@/lib/desktop/api-fetch", () => ({ apiFetch: jest.fn() })) /** * Tests for HistoryContext (history-context.tsx). diff --git a/apps/desktop-ui/src/components/api-client/collections/collections-sidebar.tsx b/apps/desktop-ui/src/components/api-client/collections/collections-sidebar.tsx index 686de5f2..1e8c41b6 100644 --- a/apps/desktop-ui/src/components/api-client/collections/collections-sidebar.tsx +++ b/apps/desktop-ui/src/components/api-client/collections/collections-sidebar.tsx @@ -8,7 +8,7 @@ import { Collection, CollectionRequest } from "../types" import { CollectionItem } from "./collection-item" import { FolderPlus, Trash2, Pencil, MoreHorizontal, Search, X, FileDown, Play, Server, Link2, Globe, PanelRightClose } from "lucide-react" import { buildShareUrl } from "@/lib/share-link" -import { backendFetch } from "@/lib/backend-auth" +import { apiFetch } from "@/lib/desktop/api-fetch" import { toast } from "sonner" import { downloadCollectionAsPostman } from "@/lib/export/postman" import { downloadCollectionAsOpenApi } from "@/lib/export/openapi" @@ -340,7 +340,7 @@ export function CollectionsSidebar({ const idx = Number(choice) const target = idx === 0 ? null : workspaces[idx - 1]?.id ?? null try { - const res = await backendFetch(`/api/backend/api-client/collections/${collection.id}`, { + const res = await apiFetch(`/api/backend/api-client/collections/${collection.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ workspace: target }), @@ -403,7 +403,7 @@ export function CollectionsSidebar({ onClick={async () => { if (typeof window === "undefined") return try { - const res = await backendFetch("/api/backend/api-client/public-mocks", { + const res = await apiFetch("/api/backend/api-client/public-mocks", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ diff --git a/apps/desktop-ui/src/components/api-client/collections/use-collections.ts b/apps/desktop-ui/src/components/api-client/collections/use-collections.ts index 8c3dc957..4a7d64ae 100644 --- a/apps/desktop-ui/src/components/api-client/collections/use-collections.ts +++ b/apps/desktop-ui/src/components/api-client/collections/use-collections.ts @@ -3,9 +3,8 @@ import * as React from "react" import { Collection, CollectionFolder, CollectionRequest } from "../types" import { toast } from "sonner" -import { auth } from "@/database/firebase" -import { useAuthState } from "react-firebase-hooks/auth" -import { backendFetch } from "@/lib/backend-auth" +import useAuth from "@/utils/useAuth" +import { apiFetch } from "@/lib/desktop/api-fetch" import { broadcastApiClientUpdate, useApiClientSyncListener } from "@/lib/api-client-sync" const STORAGE_KEY = "api-client-collections" @@ -15,7 +14,7 @@ function sortCollections(cols: Collection[]) { } export function useCollections() { - const [user, loading] = useAuthState(auth) + const { user, loading } = useAuth() const [collections, setCollections] = React.useState([]) const [isLoading, setIsLoading] = React.useState(true) const migrationRanRef = React.useRef(false) @@ -27,7 +26,7 @@ export function useCollections() { const authedFetch = React.useCallback( async (path: string, init?: RequestInit) => { if (!user) throw new Error("Not authenticated") - const res = await backendFetch(path, { + const res = await apiFetch(path, { ...init, headers: { "Content-Type": "application/json", diff --git a/apps/desktop-ui/src/components/api-client/comments-panel.tsx b/apps/desktop-ui/src/components/api-client/comments-panel.tsx index b8302217..ac5f5d77 100644 --- a/apps/desktop-ui/src/components/api-client/comments-panel.tsx +++ b/apps/desktop-ui/src/components/api-client/comments-panel.tsx @@ -6,8 +6,7 @@ import { Textarea } from "@/components/ui/textarea" import { ScrollArea } from "@/components/ui/scroll-area" import { Trash2, MessageSquare } from "lucide-react" import type { RequestComment } from "./types" -import { useAuthState } from "react-firebase-hooks/auth" -import { auth } from "@/database/firebase" +import { useAppUser } from "@/hooks/use-app-user" /** Pulled out so the lint rule for impure render-time calls doesn't flag the inline use. */ const nowMs = (): number => Date.now() @@ -18,11 +17,11 @@ interface CommentsPanelProps { } export function CommentsPanel({ comments, onChange }: CommentsPanelProps) { - const [user] = useAuthState(auth) + const user = useAppUser() const [draft, setDraft] = React.useState("") const list = comments ?? [] - const myName = user?.displayName ?? user?.email ?? "Anonymous" + const myName = user.name || "Anonymous" const handlePost = () => { const text = draft.trim() diff --git a/apps/desktop-ui/src/components/api-client/offline-indicator.tsx b/apps/desktop-ui/src/components/api-client/offline-indicator.tsx index cfaf7248..94a23c2b 100644 --- a/apps/desktop-ui/src/components/api-client/offline-indicator.tsx +++ b/apps/desktop-ui/src/components/api-client/offline-indicator.tsx @@ -4,7 +4,7 @@ import * as React from "react" import { CloudOff, Cloud, RefreshCw } from "lucide-react" import { cn } from "@/lib/utils" import { drainQueue, listQueue, subscribe, type QueuedMutation } from "@/lib/offline/queue" -import { backendFetch } from "@/lib/backend-auth" +import { apiFetch } from "@/lib/desktop/api-fetch" import { toast } from "sonner" export function OfflineIndicator() { @@ -31,7 +31,7 @@ export function OfflineIndicator() { if (listQueue().length === 0) return setDraining(true) try { - const result = await drainQueue((path, init) => backendFetch(path, init)) + const result = await drainQueue((path, init) => apiFetch(path, init)) if (result.succeeded > 0) { toast.success(`Replayed ${result.succeeded} pending change${result.succeeded === 1 ? "" : "s"}`) } diff --git a/apps/desktop-ui/src/components/api-client/use-environments.ts b/apps/desktop-ui/src/components/api-client/use-environments.ts index 7e6d7cf9..8150a9f9 100644 --- a/apps/desktop-ui/src/components/api-client/use-environments.ts +++ b/apps/desktop-ui/src/components/api-client/use-environments.ts @@ -2,9 +2,8 @@ import * as React from "react" import { toast } from "sonner" -import { auth } from "@/database/firebase" -import { useAuthState } from "react-firebase-hooks/auth" -import { backendFetch } from "@/lib/backend-auth" +import useAuth from "@/utils/useAuth" +import { apiFetch } from "@/lib/desktop/api-fetch" import { broadcastApiClientUpdate, useApiClientSyncListener } from "@/lib/api-client-sync" export interface EnvironmentVariable { @@ -29,7 +28,7 @@ function sortEnvs(envs: Environment[]) { } export function useEnvironments() { - const [user, loading] = useAuthState(auth) + const { user, loading } = useAuth() const [environments, setEnvironments] = React.useState([]) const [activeEnvId, setActiveEnvId] = React.useState(null) const [isLoading, setIsLoading] = React.useState(true) @@ -42,7 +41,7 @@ export function useEnvironments() { const authedFetch = React.useCallback( async (path: string, init?: RequestInit) => { if (!user) throw new Error("Not authenticated") - const res = await backendFetch(path, { + const res = await apiFetch(path, { ...init, headers: { "Content-Type": "application/json", diff --git a/apps/desktop-ui/src/components/api-client/use-history.ts b/apps/desktop-ui/src/components/api-client/use-history.ts index e26b00e7..209facec 100644 --- a/apps/desktop-ui/src/components/api-client/use-history.ts +++ b/apps/desktop-ui/src/components/api-client/use-history.ts @@ -2,9 +2,8 @@ import { useState, useCallback, useEffect, useRef } from "react" import { HistoryRequest, CollectionRequest } from "./types" -import { auth } from "@/database/firebase" -import { useAuthState } from "react-firebase-hooks/auth" -import { backendFetch } from "@/lib/backend-auth" +import useAuth from "@/utils/useAuth" +import { apiFetch } from "@/lib/desktop/api-fetch" import { broadcastApiClientUpdate, useApiClientSyncListener } from "@/lib/api-client-sync" const HISTORY_STORAGE_KEY = "api-client-history" @@ -71,7 +70,7 @@ function persistHistoryWithFallback(items: HistoryRequest[]): HistoryRequest[] { } export function useHistory() { - const [user, loading] = useAuthState(auth) + const { user, loading } = useAuth() const [history, setHistory] = useState([]) const [isHistoryLoading, setIsHistoryLoading] = useState(true) const migrationRanRef = useRef(false) @@ -79,7 +78,7 @@ export function useHistory() { const authedFetch = useCallback( async (path: string, init?: RequestInit) => { if (!user) throw new Error("Not authenticated") - const res = await backendFetch(path, { + const res = await apiFetch(path, { ...init, headers: { "Content-Type": "application/json", diff --git a/apps/desktop-ui/src/components/api-client/use-workspaces.ts b/apps/desktop-ui/src/components/api-client/use-workspaces.ts index 4d377650..07978ab8 100644 --- a/apps/desktop-ui/src/components/api-client/use-workspaces.ts +++ b/apps/desktop-ui/src/components/api-client/use-workspaces.ts @@ -1,9 +1,8 @@ "use client" import * as React from "react" -import { auth } from "@/database/firebase" -import { useAuthState } from "react-firebase-hooks/auth" -import { backendFetch } from "@/lib/backend-auth" +import useAuth from "@/utils/useAuth" +import { apiFetch } from "@/lib/desktop/api-fetch" import { toast } from "sonner" export interface Workspace { @@ -15,7 +14,7 @@ export interface Workspace { const ACTIVE_KEY = "api-client-active-workspace" export function useWorkspaces() { - const [user, loadingUser] = useAuthState(auth) + const { user, loading: loadingUser } = useAuth() const [workspaces, setWorkspaces] = React.useState([]) const [activeId, setActiveId] = React.useState(null) const [isLoading, setIsLoading] = React.useState(true) @@ -37,7 +36,7 @@ export function useWorkspaces() { const reload = React.useCallback(async () => { if (!user) return try { - const res = await backendFetch("/api/backend/api-client/workspaces") + const res = await apiFetch("/api/backend/api-client/workspaces") if (!res.ok) throw new Error(`HTTP ${res.status}`) setWorkspaces(await res.json()) } catch (e) { @@ -56,7 +55,7 @@ export function useWorkspaces() { const createWorkspace = async (name: string): Promise => { if (!user) return null try { - const res = await backendFetch("/api/backend/api-client/workspaces", { + const res = await apiFetch("/api/backend/api-client/workspaces", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }), @@ -75,7 +74,7 @@ export function useWorkspaces() { const renameWorkspace = async (id: string, name: string) => { if (!user) return try { - const res = await backendFetch(`/api/backend/api-client/workspaces/${id}`, { + const res = await apiFetch(`/api/backend/api-client/workspaces/${id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }), @@ -93,7 +92,7 @@ export function useWorkspaces() { const deleteWorkspace = async (id: string) => { if (!user) return try { - const res = await backendFetch(`/api/backend/api-client/workspaces/${id}`, { method: "DELETE" }) + const res = await apiFetch(`/api/backend/api-client/workspaces/${id}`, { method: "DELETE" }) if (!res.ok && res.status !== 204) throw new Error(`HTTP ${res.status}`) setWorkspaces((prev) => prev.filter((w) => w.id !== id)) if (activeId === id) setActiveId(null) diff --git a/apps/desktop-ui/src/components/api-key-vault/add-api-key-dialog.tsx b/apps/desktop-ui/src/components/api-key-vault/add-api-key-dialog.tsx index ec005965..6729921a 100644 --- a/apps/desktop-ui/src/components/api-key-vault/add-api-key-dialog.tsx +++ b/apps/desktop-ui/src/components/api-key-vault/add-api-key-dialog.tsx @@ -12,7 +12,6 @@ import { Plus, Eye, EyeOff, KeyRound } from "lucide-react" import { useIsMobile } from "@/components/hooks/use-mobile" import { useApiKeyVaultStore, type ApiKeyEntry, type ApiKeyEnv } from "@/store/api-key-vault-store" import { encryptData } from "@/lib/encryption" -import { auth } from "@/database/firebase" import { createApiKeyEntry, updateApiKeyEntry } from "@/lib/api-key-vault-api" import { cn } from "@/lib/utils" import { toast } from "sonner" @@ -270,10 +269,6 @@ export function AddApiKeyDialog({ children }: { children?: React.ReactNode }) { if (!canWrite) return null const handleSubmit = async (data: FormState) => { - if (!auth.currentUser) { - toast.error("Sign in to continue") - return - } if (!encryptionKey) { toast.error(cipherKeyErrorMessage()) return @@ -351,10 +346,6 @@ export function EditApiKeyDialog({ if (!canWrite) return null const handleSubmit = async (data: FormState) => { - if (!auth.currentUser) { - toast.error("Sign in to continue") - return - } if (!encryptionKey) { toast.error(cipherKeyErrorMessage()) return diff --git a/apps/desktop-ui/src/components/auth-logout-listener.tsx b/apps/desktop-ui/src/components/auth-logout-listener.tsx deleted file mode 100644 index dd4343e0..00000000 --- a/apps/desktop-ui/src/components/auth-logout-listener.tsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client" - -import * as React from "react" -import { useRouter } from "next/navigation" -import { FORCE_LOGOUT_EVENT, logoutUser, type LogoutReason } from "@/lib/logout-user" - -export function AuthLogoutListener() { - const router = useRouter() - - React.useEffect(() => { - const handler = (evt: Event) => { - const detail = (evt as CustomEvent<{ reason?: LogoutReason }>).detail - const reason = detail?.reason ?? "session-expired" - ;(async () => { - await logoutUser(reason) - router.replace("/login") - })() - } - - window.addEventListener(FORCE_LOGOUT_EVENT, handler) - return () => window.removeEventListener(FORCE_LOGOUT_EVENT, handler) - }, [router]) - - return null -} - diff --git a/apps/desktop-ui/src/components/clarity-analytics.tsx b/apps/desktop-ui/src/components/clarity-analytics.tsx deleted file mode 100644 index f223230e..00000000 --- a/apps/desktop-ui/src/components/clarity-analytics.tsx +++ /dev/null @@ -1,20 +0,0 @@ -"use client" - -import { useEffect } from "react" - -// Microsoft Clarity heatmaps + session replays. No-op unless -// NEXT_PUBLIC_CLARITY_PROJECT_ID is set (so it stays off in local dev), -// matching how Vercel Analytics only reports in production. -// Data lives in clarity.microsoft.com — nothing touches our backend. -let initialized = false // ponytail: module guard so StrictMode/remounts can't double-inject the script - -export function ClarityAnalytics() { - useEffect(() => { - const projectId = process.env.NEXT_PUBLIC_CLARITY_PROJECT_ID - if (!projectId || initialized) return - initialized = true - import("@microsoft/clarity").then((m) => m.default.init(projectId)) - }, []) - - return null -} diff --git a/apps/desktop-ui/src/components/client-shell.tsx b/apps/desktop-ui/src/components/client-shell.tsx index 849df1bf..b6ee4fa6 100644 --- a/apps/desktop-ui/src/components/client-shell.tsx +++ b/apps/desktop-ui/src/components/client-shell.tsx @@ -1,26 +1,23 @@ "use client" import dynamic from "next/dynamic" -import { Suspense, type ReactNode } from "react" +import { Suspense, useEffect, type ReactNode } from "react" import { UserPreferencesSync } from "@/components/user-preferences-sync" import { PinnedToolsPreferencesSync } from "@/components/pinned-tools-preferences-sync" import { AppUpdateNotifier } from "@/components/app-update-notifier" -import { AuthLogoutListener } from "@/components/auth-logout-listener" +import { initTelemetry } from "@/lib/telemetry" const GlobalCommandPalette = dynamic( () => import('@/components/global-command-palette').then((m) => m.GlobalCommandPalette), { ssr: false } ) -const ClientAnalytics = dynamic( - () => import('@vercel/analytics/next').then((m) => m.Analytics), - { ssr: false } -) -const ClientSpeedInsights = dynamic( - () => import('@vercel/speed-insights/next').then((m) => m.SpeedInsights), - { ssr: false } -) -const ClientClarity = dynamic( - () => import('@/components/clarity-analytics').then((m) => m.ClarityAnalytics), +// Vercel Analytics / Speed Insights / Microsoft Clarity used to live here, +// inherited from the marketing site. All three are wrong for this app: the +// Vercel ones post to a host that does not exist in Tauri, and Clarity pulls a +// remote script that session-records an offline, local-first app. Usage now +// goes through lib/telemetry.ts — opt-in, anonymous, two events. +const ClientTelemetryConsent = dynamic( + () => import('@/components/telemetry-consent-card').then((m) => m.TelemetryConsentCard), { ssr: false } ) const ClientToaster = dynamic( @@ -33,24 +30,21 @@ type Props = { } export function ClientShell({ children }: Props) { + useEffect(() => { + void initTelemetry() + }, []) + return ( <> - {children} - - - - - - - + diff --git a/apps/desktop-ui/src/components/dashboard/dashboard-tool-card.tsx b/apps/desktop-ui/src/components/dashboard/dashboard-tool-card.tsx index 6f286555..ce4d59d8 100644 --- a/apps/desktop-ui/src/components/dashboard/dashboard-tool-card.tsx +++ b/apps/desktop-ui/src/components/dashboard/dashboard-tool-card.tsx @@ -7,7 +7,6 @@ import { Sparkles, Pin } from 'lucide-react' import { useTranslations } from 'next-intl' import { Card, CardContent } from '@/components/ui/card' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { requiresAuth } from '@/lib/tool-config' import { TOOL_PATH_TO_MESSAGE_KEY } from '@/lib/tool-i18n' import { cn } from '@/lib/utils' import { type ToolCardProps, DEFAULT_ACCENT, formatRelativeTime } from './types' @@ -62,16 +61,8 @@ export const ToolCard = React.memo(function ToolCard({ })() : item.description - const itemRequiresAuth = item.url ? requiresAuth(item.url.toString()) : false const a = accent ?? DEFAULT_ACCENT - const handleClick = (e: React.MouseEvent) => { - if (itemRequiresAuth && !user) { - e.preventDefault() - window.location.href = '/login' - } - } - const pinned = item.url ? isPinned(item.url.toString()) : false const cardRef = React.useRef(null) @@ -88,7 +79,6 @@ export const ToolCard = React.memo(function ToolCard({ diff --git a/apps/desktop-ui/src/components/data-explorer/connection-service.ts b/apps/desktop-ui/src/components/data-explorer/connection-service.ts index 15b01030..4a1f095c 100644 --- a/apps/desktop-ui/src/components/data-explorer/connection-service.ts +++ b/apps/desktop-ui/src/components/data-explorer/connection-service.ts @@ -1,20 +1,15 @@ import { encryptData, decryptData } from "@/lib/encryption"; -import { proxyJsonAuthed } from "@/lib/backend-auth"; +import { apiRequestRaw } from "@/lib/backend-api"; import { toast } from "sonner"; import type { ConnectionFormValues, SourceId, UnifiedConnection } from "./types"; -const BACKEND_BASE_URL: string = - process.env.NEXT_PUBLIC_FASTAPI_BASE_URL || - process.env.NEXT_PUBLIC_BACKEND_BASE_URL || - "http://localhost:8000"; - const BASE_PATH = "/api/v1/data-explorer/connections"; /** Raw store row — the decrypted `config` is never present on the wire. */ export type UnifiedConnectionRaw = Omit; async function proxyRequest(method: string, path: string, body?: unknown): Promise { - const { status, data } = await proxyJsonAuthed(BACKEND_BASE_URL, method, path, body); + const { status, data } = await apiRequestRaw(method, path, body); if (status < 200 || status >= 300) { const err = data as Record | null; throw new Error( diff --git a/apps/desktop-ui/src/components/data-explorer/mongodb/connection-service.ts b/apps/desktop-ui/src/components/data-explorer/mongodb/connection-service.ts index 45fd8a66..2b6bfbaa 100644 --- a/apps/desktop-ui/src/components/data-explorer/mongodb/connection-service.ts +++ b/apps/desktop-ui/src/components/data-explorer/mongodb/connection-service.ts @@ -1,15 +1,9 @@ -import { auth } from "@/database/firebase"; import { encryptData, decryptData } from "@/lib/encryption"; -import { proxyJsonAuthed } from "@/lib/backend-auth"; +import { apiRequestRaw } from "@/lib/backend-api"; import { toast } from "sonner"; import { SavedConnection } from "./types"; import type { DbType } from "@/lib/nosql-dialects"; -const BACKEND_BASE_URL: string = - process.env.NEXT_PUBLIC_FASTAPI_BASE_URL || - process.env.NEXT_PUBLIC_BACKEND_BASE_URL || - "http://localhost:8000"; - // ── proxy helper (with automatic token refresh on 401) ─────────────────────── const proxyRequest = async ( @@ -17,9 +11,8 @@ const proxyRequest = async ( path: string, body?: unknown ): Promise => { - if (!auth.currentUser) throw new Error("Not authenticated."); - const { status, data } = await proxyJsonAuthed(BACKEND_BASE_URL, method, path, body); + const { status, data } = await apiRequestRaw(method, path, body); if (status < 200 || status >= 300) { const err = data as Record | null; diff --git a/apps/desktop-ui/src/components/data-explorer/mongodb/query-builder.tsx b/apps/desktop-ui/src/components/data-explorer/mongodb/query-builder.tsx index 4218e1b5..2dc7b291 100644 --- a/apps/desktop-ui/src/components/data-explorer/mongodb/query-builder.tsx +++ b/apps/desktop-ui/src/components/data-explorer/mongodb/query-builder.tsx @@ -8,8 +8,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover import { ScrollArea } from "@/components/ui/scroll-area"; import { cn } from "@/lib/utils"; import { toast } from "sonner"; -import { auth } from "@/database/firebase"; -import { useAuthState } from "react-firebase-hooks/auth"; +import useAuth from "@/utils/useAuth"; import { getNosqlQueryHistory, putNosqlQueryHistory, getNosqlSavedQueries, putNosqlSavedQueries, NosqlSavedQuery, @@ -65,7 +64,7 @@ export function QueryBuilder({ const [savedQueries, setSavedQueries] = useState([]); const [saveName, setSaveName] = useState(""); const [builderOpen, setBuilderOpen] = useState(false); - const [user] = useAuthState(auth); + const { user } = useAuth(); const { theme } = useTheme(); const [advancedOpen, setAdvancedOpen] = useState(false); const [advancedMode, setAdvancedMode] = useState<"json" | "stages">("json"); diff --git a/apps/desktop-ui/src/components/data-explorer/redis/connection-service.ts b/apps/desktop-ui/src/components/data-explorer/redis/connection-service.ts index 44be345c..3238c393 100644 --- a/apps/desktop-ui/src/components/data-explorer/redis/connection-service.ts +++ b/apps/desktop-ui/src/components/data-explorer/redis/connection-service.ts @@ -1,19 +1,10 @@ -import { auth } from "@/database/firebase"; import { encryptData, decryptData } from "@/lib/encryption"; import { toast } from "sonner"; -import { proxyJsonAuthed } from "@/lib/backend-auth"; +import { apiRequestRaw } from "@/lib/backend-api"; import { RedisConnectionConfig, SavedRedisConnection } from "./types"; -const BACKEND_BASE_URL: string = - process.env.NEXT_PUBLIC_FASTAPI_BASE_URL || - process.env.NEXT_PUBLIC_BACKEND_BASE_URL || - "http://localhost:8000"; - async function proxyRequest(method: string, path: string, body?: unknown): Promise { - const currentUser = auth.currentUser; - if (!currentUser) throw new Error("Not authenticated."); - - const { status, data } = await proxyJsonAuthed(BACKEND_BASE_URL, method, path, body); + const { status, data } = await apiRequestRaw(method, path, body); if (status < 200 || status >= 300) { throw new Error(`Request failed (${status})`); } diff --git a/apps/desktop-ui/src/components/data-explorer/sql/connection-service.ts b/apps/desktop-ui/src/components/data-explorer/sql/connection-service.ts index f728cf24..1465359f 100644 --- a/apps/desktop-ui/src/components/data-explorer/sql/connection-service.ts +++ b/apps/desktop-ui/src/components/data-explorer/sql/connection-service.ts @@ -1,19 +1,10 @@ -import { auth } from "@/database/firebase"; import { encryptData, decryptData } from "@/lib/encryption"; import { toast } from "sonner"; -import { proxyJsonAuthed } from "@/lib/backend-auth"; +import { apiRequestRaw } from "@/lib/backend-api"; import { SavedSqlConnection, SqlConnectionConfig } from "./types"; -const BACKEND_BASE_URL: string = - process.env.NEXT_PUBLIC_FASTAPI_BASE_URL || - process.env.NEXT_PUBLIC_BACKEND_BASE_URL || - "http://localhost:8000"; - async function proxyRequest(method: string, path: string, body?: unknown): Promise { - const currentUser = auth.currentUser; - if (!currentUser) throw new Error("Not authenticated."); - - const { status, data } = await proxyJsonAuthed(BACKEND_BASE_URL, method, path, body); + const { status, data } = await apiRequestRaw(method, path, body); if (status < 200 || status >= 300) { throw new Error(`Request failed (${status})`); } diff --git a/apps/desktop-ui/src/components/desktop/desktop-init.tsx b/apps/desktop-ui/src/components/desktop/desktop-init.tsx index 8451f37d..2f9c0389 100644 --- a/apps/desktop-ui/src/components/desktop/desktop-init.tsx +++ b/apps/desktop-ui/src/components/desktop/desktop-init.tsx @@ -1,48 +1,23 @@ "use client"; import { useEffect } from "react"; -import { usePathname, useRouter } from "next/navigation"; import { toast } from "sonner"; import { isDesktop } from "@/lib/desktop/is-desktop"; import { useWorkspaceStore } from "@/store/workspace-store"; /** - * Desktop-only bootstrap: mandatory one-time activation gate, deep-link - * sign-in listener, startup session probe, and update check. + * Desktop-only bootstrap: workspace hydration and the update check. * Renders nothing; a no-op on web (the isDesktop guard compiles to false). */ export function DesktopInit() { - const router = useRouter(); - const pathname = usePathname(); - - // Activation gate: without a local activation record every route funnels to - // /activate. Local check only — never blocks on the network. - useEffect(() => { - if (!isDesktop() || pathname === "/activate") return; - void (async () => { - const { getActivation } = await import("@/lib/desktop/activation"); - const activated = await getActivation().catch(() => null); - if (!activated) router.replace("/activate"); - })(); - }, [router, pathname]); - useEffect(() => { if (!isDesktop()) return; - void (async () => { - const [{ initDeepLinkListener }, { checkRemoteSession }] = await Promise.all([ - import("@/lib/desktop/cloud-signin"), - import("@/lib/desktop/remote"), - ]); - await initDeepLinkListener().catch(() => {}); - await checkRemoteSession().catch(() => {}); - // Hydrate the workspace store so encrypted tools (API keys, password - // manager, environment manager) resolve the always-present local personal - // workspace. Without this the store stays empty offline → activeWs null → - // cipher key null → "No active workspace." Runs after the session probe so - // a remote session (if any) merges in. - await useWorkspaceStore.getState().loadFromBackend().catch(() => {}); - })(); + // Hydrate the workspace store so encrypted tools (API keys, password + // manager, environment manager) resolve the always-present local personal + // workspace. Without this the store stays empty → activeWs null → cipher + // key null → "No active workspace." + void useWorkspaceStore.getState().loadFromBackend().catch(() => {}); }, []); // Auto-update notification: check on launch and every 6h so long-running diff --git a/apps/desktop-ui/src/components/desktop/desktop-login.tsx b/apps/desktop-ui/src/components/desktop/desktop-login.tsx deleted file mode 100644 index d074e63e..00000000 --- a/apps/desktop-ui/src/components/desktop/desktop-login.tsx +++ /dev/null @@ -1,84 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { Loader2, ExternalLink, WifiOff } from "lucide-react"; - -import { Button } from "@/components/ui/button"; - -/** - * Desktop sign-in panel. OAuth popups don't work in WKWebView, so cloud - * sign-in opens the system browser (`/login?desktop=1`), which hands a - * Firebase custom token back through the loopback callback. - */ -export function DesktopLogin() { - const [busy, setBusy] = useState(false); - const [online, setOnline] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - setOnline(navigator.onLine); - const on = () => setOnline(true); - const off = () => setOnline(false); - window.addEventListener("online", on); - window.addEventListener("offline", off); - // The deep-link handler navigates to /dashboard on success; nothing to do here. - return () => { - window.removeEventListener("online", on); - window.removeEventListener("offline", off); - }; - }, []); - - const signIn = async () => { - setBusy(true); - setError(null); - try { - const { startCloudSignIn } = await import("@/lib/desktop/cloud-signin"); - // Resolves once the browser hands the token back and the session is set; - // DesktopInit then routes to /dashboard. - await startCloudSignIn(); - } catch (e) { - setError(e instanceof Error ? e.message : "Sign-in failed. Please try again."); - } finally { - setBusy(false); - } - }; - - return ( -
- {/* Stays clickable while waiting so a stalled attempt can be retried - (each click opens a fresh browser handoff; old waits time out). */} - - - {busy && ( -

- Finish signing in in your browser, then return here. Click again to restart. -

- )} - - {!online && ( -

- - You're offline — sign-in needs a connection. -

- )} - - {error &&

{error}

} - -

- Your data is encrypted and stays on this Mac. -

-
- ); -} diff --git a/apps/desktop-ui/src/components/desktop/desktop-plan-settings.tsx b/apps/desktop-ui/src/components/desktop/desktop-plan-settings.tsx deleted file mode 100644 index 1188f173..00000000 --- a/apps/desktop-ui/src/components/desktop/desktop-plan-settings.tsx +++ /dev/null @@ -1,56 +0,0 @@ -'use client' - -import { useEffect, useState } from 'react' -import { useTranslations } from 'next-intl' -import { ExternalLink, UserRound } from 'lucide-react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { isDesktop } from '@/lib/desktop/is-desktop' -import type { ActivationRecord } from '@/lib/desktop/activation' -import { DesktopUpdateDialog } from './desktop-update-dialog' - -/** Settings card: locally-stored account summary + link to the web dashboard. */ -export function DesktopPlanSettings() { - const t = useTranslations('AccountCard') - const [record, setRecord] = useState(null) - - useEffect(() => { - if (!isDesktop()) return - void import('@/lib/desktop/activation').then(({ getActivation }) => - getActivation().then(setRecord).catch(() => {}) - ) - }, []) - - if (!isDesktop() || !record) return null - - const openAccountPage = async () => { - const [{ openUrl }, { desktopWebBase }] = await Promise.all([ - import('@tauri-apps/plugin-opener'), - import('@/lib/desktop/remote'), - ]) - await openUrl(`${desktopWebBase()}/dashboard`) - } - - return ( - - - - - - - {t('title')} - - - {record.display_name || record.email} - - - - - - - - ) -} diff --git a/apps/desktop-ui/src/components/ensure-backend-session.tsx b/apps/desktop-ui/src/components/ensure-backend-session.tsx deleted file mode 100644 index 1626174b..00000000 --- a/apps/desktop-ui/src/components/ensure-backend-session.tsx +++ /dev/null @@ -1,63 +0,0 @@ -"use client" - -import { useEffect, useState } from "react" -import type { User } from "firebase/auth" -import { ensureBackendSession } from "@/lib/backend-auth" -import { AppLoadingScreen } from "@/components/app-loading-screen" -import { useWorkspaceStore } from "@/store/workspace-store" - -type Props = { - user: User | null - children: React.ReactNode -} - -// Module-level cache: tracks which uid already has a valid backend session this page session. -// Cleared when the user changes (logout), preventing stale entries. -let confirmedSessionUid: string | null = null - -/** - * When Firebase has a user, ensures HttpOnly JWT cookies exist (new login or expired cookies). - * Caches the result per-uid so client-side re-navigation between routes never re-shows - * the full-screen spinner for an already-confirmed session. - */ -export function EnsureBackendSession({ user, children }: Props) { - const alreadyConfirmed = !!user && user.uid === confirmedSessionUid - const [ready, setReady] = useState(!user || alreadyConfirmed) - - useEffect(() => { - if (!user) { - confirmedSessionUid = null - setReady(true) - return - } - if (user.uid === confirmedSessionUid) { - setReady(true) - return - } - setReady(false) - let cancelled = false - ;(async () => { - try { - await ensureBackendSession(user) - confirmedSessionUid = user.uid - // Hydrate workspace store once per auth session, non-blocking - useWorkspaceStore.getState().loadFromBackend().catch((e) => { - console.warn("Workspace hydration failed:", e) - }) - } catch (e) { - console.error("Backend session sync failed:", e) - } finally { - if (!cancelled) setReady(true) - } - })() - return () => { - cancelled = true - } - }, [user]) - - if (user && !ready) { - return - } - - return <>{children} -} diff --git a/apps/desktop-ui/src/components/environment-manager/add-environment-set-dialog.tsx b/apps/desktop-ui/src/components/environment-manager/add-environment-set-dialog.tsx index 1d4abbbc..13c8c35c 100644 --- a/apps/desktop-ui/src/components/environment-manager/add-environment-set-dialog.tsx +++ b/apps/desktop-ui/src/components/environment-manager/add-environment-set-dialog.tsx @@ -18,7 +18,6 @@ import { useActiveToolPermissions } from "@/lib/workspace-rbac" import { Badge } from "@/components/ui/badge" import { EnvPasteCollapsible } from "@/components/environment-manager/env-paste-collapsible" import { encryptData } from "@/lib/encryption" -import { auth } from "@/database/firebase" import { createEnvSetEntry } from "@/lib/environment-manager-api" import { cn } from "@/lib/utils" import { toast } from "sonner" @@ -81,7 +80,7 @@ export function AddEnvironmentSetDialog({ children }: { children?: React.ReactNo const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() - if (!encryptionKey || !auth.currentUser) return + if (!encryptionKey) return const proj = project.trim() const env = environment.trim() if (!proj || !env) { diff --git a/apps/desktop-ui/src/components/environment-manager/edit-environment-set-dialog.tsx b/apps/desktop-ui/src/components/environment-manager/edit-environment-set-dialog.tsx index 29ec7755..7a4d9c8c 100644 --- a/apps/desktop-ui/src/components/environment-manager/edit-environment-set-dialog.tsx +++ b/apps/desktop-ui/src/components/environment-manager/edit-environment-set-dialog.tsx @@ -18,7 +18,6 @@ import { useCipherKey } from "@/lib/use-cipher-key" import { Badge } from "@/components/ui/badge" import { EnvPasteCollapsible } from "@/components/environment-manager/env-paste-collapsible" import { encryptData } from "@/lib/encryption" -import { auth } from "@/database/firebase" import { updateEnvSetEntry } from "@/lib/environment-manager-api" import { cn } from "@/lib/utils" import { toast } from "sonner" @@ -89,7 +88,7 @@ export function EditEnvironmentSetDialog({ entry, open, onOpenChange }: EditEnvi const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() - if (!encryptionKey || !auth.currentUser || !entry) return + if (!encryptionKey || !entry) return const proj = project.trim() const env = environment.trim() if (!proj || !env) { diff --git a/apps/desktop-ui/src/components/feedback-dialog.tsx b/apps/desktop-ui/src/components/feedback-dialog.tsx deleted file mode 100644 index 78373ea4..00000000 --- a/apps/desktop-ui/src/components/feedback-dialog.tsx +++ /dev/null @@ -1,277 +0,0 @@ -"use client"; - -import React, { useState } from "react"; -import { MessageSquarePlus, Loader2, CheckCircle2, Star } from "lucide-react"; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; -import { Button } from "@/components/ui/button"; -import { Textarea } from "@/components/ui/textarea"; -import { Label } from "@/components/ui/label"; -import { cn } from "@/lib/utils"; -import useAuth from "@/utils/useAuth"; -import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar"; -import { usePathname } from "next/navigation"; -import { backendFetch } from "@/lib/backend-auth"; - -type FeedbackType = "bug" | "feature" | "general"; - -const TYPES: { value: FeedbackType; label: string; emoji: string }[] = [ - { value: "bug", label: "Bug report", emoji: "🐛" }, - { value: "feature", label: "Feature request", emoji: "✨" }, - { value: "general", label: "General", emoji: "💬" }, -]; - -async function submitFeedback(payload: { - type: FeedbackType; - message: string; - rating: number | null; - email: string | null; - page: string; -}) { - const res = await backendFetch("/api/backend/feedback", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - if (!res.ok) { - const err = await res.json().catch(() => ({})); - throw new Error(err?.detail ?? "Failed to submit feedback"); - } - return res.json(); -} - -function StarRating({ - value, - onChange, -}: { - value: number | null; - onChange: (v: number | null) => void; -}) { - const [hovered, setHovered] = useState(null); - return ( -
- {[1, 2, 3, 4, 5].map((n) => { - const filled = (hovered ?? value ?? 0) >= n; - return ( - - ); - })} -
- ); -} - -export function FeedbackDialog({ variant }: { variant?: "sidebar" }) { - const { user } = useAuth(false); - const pathname = usePathname(); - - const [open, setOpen] = useState(false); - const [type, setType] = useState("general"); - const [message, setMessage] = useState(""); - const [rating, setRating] = useState(null); - const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle"); - const [errorMsg, setErrorMsg] = useState(""); - - function reset() { - setType("general"); - setMessage(""); - setRating(null); - setStatus("idle"); - setErrorMsg(""); - } - - function handleClose(v: boolean) { - if (!v) { - setOpen(false); - if (status === "success") reset(); - } else { - setOpen(true); - } - } - - async function handleSubmit(e: React.FormEvent) { - e.preventDefault(); - if (message.trim().length < 10) { - setErrorMsg("Message must be at least 10 characters."); - return; - } - setStatus("loading"); - setErrorMsg(""); - try { - await submitFeedback({ - type, - message: message.trim(), - rating, - email: user?.email ?? null, - page: pathname, - }); - setStatus("success"); - } catch (err) { - setStatus("error"); - setErrorMsg(err instanceof Error ? err.message : "Something went wrong."); - } - } - - const dialog = ( - - - - - - Share your feedback - - - - {status === "success" ? ( -
- -

Thanks for the feedback!

-

We read every submission.

- -
- ) : ( -
-
- -
- {TYPES.map((t) => ( - - ))} -
-
- -
- -