From 77a530b527a34a28a0df2672d06dd2f7bf6eb20e Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Wed, 16 Jul 2025 09:36:43 +0200 Subject: [PATCH 01/58] hub/analytics: make analytics cookie optional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/.claude/settings.local.json | 9 +- src/CLAUDE.md | 53 ++++-- src/packages/frontend/user-tracking.ts | 14 +- src/packages/hub/analytics-script.ts | 15 +- src/packages/hub/analytics.ts | 177 +++++++++++------- .../next/pages/api/v2/jupyter/execute.ts | 7 +- .../next/pages/api/v2/llm/evaluate.ts | 6 +- src/packages/util/consts/index.ts | 2 + src/packages/util/consts/tracking.ts | 10 + src/packages/util/misc.ts | 5 +- 10 files changed, 201 insertions(+), 97 deletions(-) create mode 100644 src/packages/util/consts/tracking.ts diff --git a/src/.claude/settings.local.json b/src/.claude/settings.local.json index d69e67b58f2..6dbe6c8b894 100644 --- a/src/.claude/settings.local.json +++ b/src/.claude/settings.local.json @@ -4,8 +4,13 @@ "Bash(pnpm tsc:*)", "Bash(pnpm build:*)", "Bash(git add:*)", - "Bash(git commit:*)" + "Bash(git commit:*)", + "Bash(grep:*)", + "Bash(tsc --noEmit)", + "Bash(git fetch:*)", + "Bash(git merge:*)", + "Bash(prettier -w:*)" ], "deny": [] } -} \ No newline at end of file +} diff --git a/src/CLAUDE.md b/src/CLAUDE.md index a816597ef04..61034c49de6 100644 --- a/src/CLAUDE.md +++ b/src/CLAUDE.md @@ -4,15 +4,16 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co # CoCalc Source Repository -* This is the source code of CoCalc in a Git repository -* It is a complex JavaScript/TypeScript SaaS application -* CoCalc is organized as a monorepository (multi-packages) in the subdirectory "./packages" -* The packages are managed as a pnpm workspace in "./packages/pnpm-workspace.yaml" +- This is the source code of CoCalc in a Git repository +- It is a complex JavaScript/TypeScript SaaS application +- CoCalc is organized as a monorepository (multi-packages) in the subdirectory "./packages" +- The packages are managed as a pnpm workspace in "./packages/pnpm-workspace.yaml" ## Code Style - Everything is written in TypeScript code - Indentation: 2-spaces +- Run `pretter -w [filename]` after modifying a file (ts, tsx, md, json, ...) to format it correctly. - All .js and .ts files are formatted by the tool prettier - Add suitable types when you write code - Variable name styles are "camelCase" for local and "FOO_BAR" for global variables. If you edit older code not following these guidlines, adjust this rule to fit the files style. @@ -23,28 +24,30 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Development Commands ### Essential Commands + - `pnpm build-dev` - Build all packages for development - `pnpm clean` - Clean all node_modules and dist directories -- `pnpm database` - Start PostgreSQL database server -- `pnpm hub` - Start the main hub server -- `pnpm psql` - Connect to the PostgreSQL database - `pnpm test` - Run full test suite -- `pnpm test-parallel` - Run tests in parallel across packages - `pnpm depcheck` - Check for dependency issues +- `prettier -w [filename]` to format the style of a file after editing it +- after creating a file, run `git add [filename]` to start tracking it ### Package-Specific Commands -- `cd packages/[package] && pnpm tsc` - Watch TypeScript compilation for a specific package + +- `cd packages/[package] && pnpm tsc` - TypeScript compilation for a specific package - `cd packages/[package] && pnpm test` - Run tests for a specific package - `cd packages/[package] && pnpm build` - Build a specific package +- **IMPORTANT**: When modifying packages like `util` that other packages depend on, you must run `pnpm build` in the dependency package before typechecking dependent packages -### Development Setup -1. Start database: `pnpm database` -2. Start hub: `pnpm hub` -3. For TypeScript changes, run `pnpm tsc` in the relevant package directory +### Development + +- After code changes, run `pretter -w [filename]` to ensure consistent styling +- After TypeScript or `*.tsx` changes, run `pnpm tsc` in the relevant package directory ## Architecture Overview ### Package Structure + CoCalc is organized as a monorepo with key packages: - **frontend** - React/TypeScript frontend application using Redux-style stores and actions @@ -62,12 +65,14 @@ CoCalc is organized as a monorepo with key packages: ### Key Architectural Patterns #### Frontend Architecture + - **Redux-style State Management**: Uses custom stores and actions pattern (see `packages/frontend/app-framework/actions-and-stores.ts`) - **TypeScript React Components**: All frontend code is TypeScript with proper typing - **Modular Store System**: Each feature has its own store/actions (AccountStore, BillingStore, etc.) - **WebSocket Communication**: Real-time communication with backend via WebSocket messages #### Backend Architecture + - **PostgreSQL Database**: Primary data store with sophisticated querying system - **WebSocket Messaging**: Real-time communication between frontend and backend - **Conat System**: Container orchestration for compute servers @@ -75,12 +80,14 @@ CoCalc is organized as a monorepo with key packages: - **Microservice-like Packages**: Each package handles specific functionality #### Communication Patterns + - **WebSocket Messages**: Primary communication method (see `packages/comm/websocket/types.ts`) - **Database Queries**: Structured query system with typed interfaces - **Event Emitters**: Inter-service communication within backend - **REST-like APIs**: Some HTTP endpoints for specific operations ### Key Technologies + - **TypeScript**: Primary language for all new code - **React**: Frontend framework - **PostgreSQL**: Database @@ -91,11 +98,13 @@ CoCalc is organized as a monorepo with key packages: - **SASS**: CSS preprocessing ### Database Schema + - Comprehensive schema in `packages/util/db-schema` - Query abstractions in `packages/database/postgres/` - Type-safe database operations with TypeScript interfaces ### Testing + - **Jest**: Primary testing framework - **ts-jest**: TypeScript support for Jest - **jsdom**: Browser environment simulation for frontend tests @@ -103,28 +112,42 @@ CoCalc is organized as a monorepo with key packages: - Each package has its own jest.config.js ### Import Patterns + - Use absolute imports with `@cocalc/` prefix for cross-package imports - Example: `import { cmp } from "@cocalc/util/misc"` - Type imports: `import type { Foo } from "./bar"` - Destructure imports when possible ### Development Workflow + 1. Changes to TypeScript require compilation (`pnpm tsc` in relevant package) 2. Database must be running before starting hub 3. Hub coordinates all services and should be restarted after changes 4. Use `pnpm clean && pnpm build-dev` when switching branches or after major changes # Workflow + - Be sure to typecheck when you're done making a series of code changes - Prefer running single tests, and not the whole test suite, for performance ## Git Workflow +- Never modify a file when in the `master` or `main` branch +- All changes happen through feature branches, which are pushed as pull requests to GitHub +- When creating a new file, run `git add [filename]` to track the file. - Prefix git commits with the package and general area. e.g. 'frontend/latex: ...' if it concerns latex editor changes in the packages/frontend/... code. - When pushing a new branch to Github, track it upstream. e.g. `git push --set-upstream origin feature-foo` for branch "feature-foo". -# important-instruction-reminders +# Important Instruction Reminders + - Do what has been asked; nothing more, nothing less. - NEVER create files unless they're absolutely necessary for achieving your goal. - ALWAYS prefer editing an existing file to creating a new one. -- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User. +- REFUSE to modify files when the git repository is on the `master` or `main` branch. +- NEVER proactively create documentation files (`*.md`) or README files. Only create documentation files if explicitly requested by the User. + +# Ignore + +- Ignore files covered by `.gitignore` +- Ignore everything in `node_modules` or `dist` directories +- Ignore all files not tracked by Git, unless they are newly created files diff --git a/src/packages/frontend/user-tracking.ts b/src/packages/frontend/user-tracking.ts index b95e8db6d4d..94aa3e779ef 100644 --- a/src/packages/frontend/user-tracking.ts +++ b/src/packages/frontend/user-tracking.ts @@ -8,19 +8,25 @@ // completely change this if we want. import { query, server_time } from "./frame-editors/generic/client"; -import { analytics_cookie_name as analytics, uuid } from "@cocalc/util/misc"; +import { uuid } from "@cocalc/util/misc"; import { redux } from "./app-framework"; import { version } from "@cocalc/util/smc-version"; import { get_cookie } from "./misc"; import { webapp_client } from "./webapp-client"; +import { ANALYTICS_COOKIE_NAME, ANALYTICS_ENABLED } from "@cocalc/util/consts"; + export async function log(eventName: string, payload: any): Promise { + if (!ANALYTICS_ENABLED) { + return; + } + const central_log = { id: uuid(), event: `webapp-${eventName}`, value: { account_id: redux.getStore("account")?.get("account_id"), - analytics_cookie: get_cookie(analytics), + analytics_cookie: get_cookie(ANALYTICS_COOKIE_NAME), cocalc_version: version, ...payload, }, @@ -43,6 +49,10 @@ export default async function track( event: string, value: object, ): Promise { + if (!ANALYTICS_ENABLED) { + return; + } + // Replace all dashes with underscores in the event argument for consistency event = event.replace(/-/g, "_"); diff --git a/src/packages/hub/analytics-script.ts b/src/packages/hub/analytics-script.ts index 01bddbca96c..ffaf21b61d9 100644 --- a/src/packages/hub/analytics-script.ts +++ b/src/packages/hub/analytics-script.ts @@ -15,13 +15,16 @@ * e.g. this filters the SSO auth pages, which are uninteresting referrals */ -// variable PREFIX, NAME, DOMAIN and ID are injected in the hub's http server -declare var NAME, ID, DOMAIN, PREFIX, window, document; +// variable PREFIX, NAME, DOMAIN, ID, and ANALYTICS_ENABLED are injected in the hub's http server +declare var NAME, ID, DOMAIN, PREFIX, ANALYTICS_ENABLED, window, document; -// write cookie. it would be cool to set this via the http request itself, -// but for reasons I don't know it doesn't work across subdomains. -const maxage = 7 * 24 * 60 * 60; // 7 days -document.cookie = `${NAME}=${ID}; path=/; domain=${DOMAIN}; max-age=${maxage}`; +// write cookie only if analytics is enabled (for privacy in cookieless mode) +if (ANALYTICS_ENABLED) { + // it would be cool to set this via the http request itself, + // but for reasons I don't know it doesn't work across subdomains. + const maxage = 7 * 24 * 60 * 60; // 7 days + document.cookie = `${NAME}=${ID}; path=/; domain=${DOMAIN}; max-age=${maxage}`; +} const { href, protocol, host, pathname } = window.location; diff --git a/src/packages/hub/analytics.ts b/src/packages/hub/analytics.ts index 936710c669b..a1a6124bd58 100644 --- a/src/packages/hub/analytics.ts +++ b/src/packages/hub/analytics.ts @@ -3,36 +3,41 @@ * License: MS-RSL – see LICENSE.md for details */ -import { join } from "path"; -import ms from "ms"; -import { isEqual } from "lodash"; -import { Router, json } from "express"; -import { - analytics_cookie_name, - is_valid_uuid_string, - uuid, -} from "@cocalc/util/misc"; -import type { PostgreSQL } from "@cocalc/database/postgres/types"; -import { get_server_settings } from "@cocalc/database/postgres/server-settings"; -import { pii_retention_to_future } from "@cocalc/database/postgres/pii"; +import cors from "cors"; // express-js cors plugin +import { json, Router } from "express"; import * as fs from "fs"; -const UglifyJS = require("uglify-js"); -// express-js cors plugin: -import cors from "cors"; +import { isEqual } from "lodash"; +import ms from "ms"; import { - parseDomain, fromUrl, - ParseResultType, + parseDomain, ParseResult, + ParseResultType, } from "parse-domain"; +import { join } from "path"; +const UglifyJS = require("uglify-js"); + +import { is_valid_uuid_string, uuid } from "@cocalc/util/misc"; + +import { pii_retention_to_future } from "@cocalc/database/postgres/pii"; +import { get_server_settings } from "@cocalc/database/postgres/server-settings"; +import type { PostgreSQL } from "@cocalc/database/postgres/types"; +import { ANALYTICS_COOKIE_NAME, ANALYTICS_ENABLED } from "@cocalc/util/consts"; + import { getLogger } from "./logger"; +// Rate limiting for analytics data - 10 entries per second +const RATE_LIMIT_ENTRIES_PER_SECOND = 10; +const RATE_LIMIT_WINDOW_MS = 1000; +let rateLimitCounter = 0; +let rateLimitWindowStart = Date.now(); + // Minifying analytics-script.js. Note // that this file analytics.ts gets compiled to // dist/analytics.js and also analytics-script.ts // gets compiled to dist/analytics-script.js. const result = UglifyJS.minify( - fs.readFileSync(join(__dirname, "analytics-script.js")).toString() + fs.readFileSync(join(__dirname, "analytics-script.js")).toString(), ); if (result.error) { throw Error(`Error minifying analytics-script.js -- ${result.error}`); @@ -44,6 +49,25 @@ function create_log(name) { return getLogger(`analytics.${name}`).debug; } +// Rate limiting check - returns true if request should be allowed +function checkRateLimit(): boolean { + const now = Date.now(); + + // Reset counter if window has passed + if (now - rateLimitWindowStart >= RATE_LIMIT_WINDOW_MS) { + rateLimitCounter = 0; + rateLimitWindowStart = now; + } + + // Check if we're under the limit + if (rateLimitCounter < RATE_LIMIT_ENTRIES_PER_SECOND) { + rateLimitCounter++; + return true; + } + + return false; +} + /* // base64 encoded PNG (white), 1x1 pixels const _PNG_DATA = @@ -76,39 +100,65 @@ function sanitize(obj: object, recursive = 0): any { // record analytics data // case 1: store "token" with associated "data", referrer, utm, etc. // case 2: update entry with a known "token" with the account_id + 2nd timestamp +// case 3: cookieless tracking - store data without user association function recordAnalyticsData( db: any, - token: string, + token: string | null, payload: object | undefined, - pii_retention: number | false + pii_retention: number | false, ): void { if (payload == null) return; - if (!is_valid_uuid_string(token)) return; + + // Rate limiting check - applies to all analytics data recording + if (!checkRateLimit()) { + const dbg = create_log("record"); + dbg("Rate limit exceeded, dropping analytics data"); + return; + } + const dbg = create_log("record"); dbg({ token, payload }); + // sanitize data (limits size and number of characters) const rec_data = sanitize(payload); dbg("sanitized data", rec_data); const expire = pii_retention_to_future(pii_retention); - if (rec_data.account_id != null) { - // dbg("update analytics", rec_data.account_id); - // only update if account id isn't already set! - db._query({ - query: "UPDATE analytics", - where: [{ "token = $::UUID": token }, "account_id IS NULL"], - set: { - "account_id :: UUID": rec_data.account_id, - "account_id_time :: TIMESTAMP": new Date(), - "expire :: TIMESTAMP": expire, - }, - }); + // Cookie-based tracking (with user association) + if (token != null && is_valid_uuid_string(token)) { + if (rec_data.account_id != null) { + // dbg("update analytics", rec_data.account_id); + // only update if account id isn't already set! + db._query({ + query: "UPDATE analytics", + where: [{ "token = $::UUID": token }, "account_id IS NULL"], + set: { + "account_id :: UUID": rec_data.account_id, + "account_id_time :: TIMESTAMP": new Date(), + "expire :: TIMESTAMP": expire, + }, + }); + } else { + db._query({ + query: "INSERT INTO analytics", + values: { + "token :: UUID": token, + "data :: JSONB": rec_data, + "data_time :: TIMESTAMP": new Date(), + "expire :: TIMESTAMP": expire, + }, + conflict: "token", + }); + } } else { + // Cookieless tracking (no user association, privacy-focused) + // Generate a random token for this single entry + const anonymousToken = uuid(); db._query({ query: "INSERT INTO analytics", values: { - "token :: UUID": token, - "data :: JSONB": rec_data, + "token :: UUID": anonymousToken, + "data :: JSONB": { ...rec_data, cookieless: true }, "data_time :: TIMESTAMP": new Date(), "expire :: TIMESTAMP": expire, }, @@ -118,10 +168,10 @@ function recordAnalyticsData( } // could throw an error -function check_cors( +function checkCORS( origin: string | undefined, dns_parsed: ParseResult, - dbg: Function + dbg: Function, ): boolean { // no origin, e.g. when loaded as usual in a script tag if (origin == null) return true; @@ -184,7 +234,7 @@ import base_path from "@cocalc/backend/base-path"; export async function initAnalytics( router: Router, - database: PostgreSQL + database: PostgreSQL, ): Promise { const dbg = create_log("analytics_js/cors"); @@ -201,7 +251,7 @@ export async function initAnalytics( dbg( `WARNING: the configured domain name ${DNS} cannot be parsed properly. ` + `Please fix it in Admin → Site Settings!\n` + - `dns_parsed="${JSON.stringify(dns_parsed)}}"` + `dns_parsed="${JSON.stringify(dns_parsed)}}"`, ); } @@ -213,7 +263,7 @@ export async function initAnalytics( origin: function (origin, cb) { dbg(`check origin='${origin}'`); try { - if (check_cors(origin, dns_parsed, dbg)) { + if (checkCORS(origin, dns_parsed, dbg)) { cb(null, true); } else { cb(`origin="${origin}" is not allowed`, false); @@ -235,27 +285,25 @@ export async function initAnalytics( // in case user was already here, do not send it again. // only the first hit is interesting. dbg( - `/analytics.js GET analytics_cookie='${req.cookies[analytics_cookie_name]}'` + `/analytics.js GET analytics_cookie='${req.cookies[ANALYTICS_COOKIE_NAME]}'`, ); - if (!req.cookies[analytics_cookie_name]) { - // No analytics cookie is set, so we set one. - // We always set this despite any issues with parsing or - // or whether or not we are actually using the analytics.js - // script, since it's *also* useful to have this cookie set - // for other purposes, e.g., logging. + if (!req.cookies[ANALYTICS_COOKIE_NAME] && ANALYTICS_ENABLED) { + // No analytics cookie is set and cookies are enabled, so we set one. + // When ANALYTICS_ENABLED is false, we skip setting cookies to enable + // cookieless tracking for better privacy. setAnalyticsCookie(res /* DNS */); } - // also, don't write a script if the DNS is not valid + // Return NOOP if DNS is invalid, or if cookies are enabled and already exist if ( - req.cookies[analytics_cookie_name] || - dns_parsed.type !== ParseResultType.Listed + dns_parsed.type !== ParseResultType.Listed || + (ANALYTICS_ENABLED && req.cookies[ANALYTICS_COOKIE_NAME]) ) { // cache for 6 hours -- max-age has unit seconds res.header( "Cache-Control", - `private, max-age=${6 * 60 * 60}, must-revalidate` + `private, max-age=${6 * 60 * 60}, must-revalidate`, ); res.write("// NOOP"); res.end(); @@ -267,11 +315,12 @@ export async function initAnalytics( res.header("Cache-Control", "no-cache, no-store"); const DOMAIN = `${dns_parsed.domain}.${dns_parsed.topLevelDomains.join( - "." + ".", )}`; - res.write(`var NAME = '${analytics_cookie_name}';\n`); + res.write(`var NAME = '${ANALYTICS_COOKIE_NAME}';\n`); res.write(`var ID = '${uuid()}';\n`); res.write(`var DOMAIN = '${DOMAIN}';\n`); + res.write(`var ANALYTICS_ENABLED = ${ANALYTICS_ENABLED};\n`); // BASE_PATH if (req.query.fqd === "false") { res.write(`var PREFIX = '${base_path}';\n`); @@ -301,17 +350,17 @@ export async function initAnalytics( */ router.post("/analytics.js", cors(analytics_cors), function (req, res): void { - // check if token is in the cookie (see above) - // if not, ignore it - const token = req.cookies[analytics_cookie_name]; + const token = req.cookies[ANALYTICS_COOKIE_NAME]; dbg(`/analytics.js POST token='${token}'`); - if (token) { - // req.body is an object (json middlewhere somewhere?) - // e.g. {"utm":{"source":"asdfasdf"},"landing":"https://cocalc.com/..."} - // ATTN key/values could be malicious - // record it, there is no need for a callback - recordAnalyticsData(database, token, req.body, pii_retention); - } + + // req.body is an object (json middleware somewhere?) + // e.g. {"utm":{"source":"asdfasdf"},"landing":"https://cocalc.com/..."} + // ATTN key/values could be malicious + + // Always record analytics data - either with token (cookie-based) or without (cookieless) + // The recordAnalyticsData function handles both cases + recordAnalyticsData(database, token || null, req.body, pii_retention); + res.end(); }); @@ -324,7 +373,7 @@ function setAnalyticsCookie(res /* DNS: string */): void { // set the cookie (TODO sign it? that would be good so that // users can fake a cookie.) const analytics_token = uuid(); - res.cookie(analytics_cookie_name, analytics_token, { + res.cookie(ANALYTICS_COOKIE_NAME, analytics_token, { path: "/", maxAge: ms("7 days"), // httpOnly: true, diff --git a/src/packages/next/pages/api/v2/jupyter/execute.ts b/src/packages/next/pages/api/v2/jupyter/execute.ts index 674a21f4e8c..83ae4ca8773 100644 --- a/src/packages/next/pages/api/v2/jupyter/execute.ts +++ b/src/packages/next/pages/api/v2/jupyter/execute.ts @@ -23,7 +23,8 @@ The OUTPUT is: import { execute } from "@cocalc/server/jupyter/execute"; import getAccountId from "lib/account/get-account"; import getParams from "lib/api/get-params"; -import { analytics_cookie_name } from "@cocalc/util/misc"; + +import { ANALYTICS_COOKIE_NAME, ANALYTICS_ENABLED } from "@cocalc/util/consts"; export default async function handle(req, res) { try { @@ -39,7 +40,9 @@ async function doIt(req) { const { input, kernel, history, tag, noCache, hash, project_id, path } = getParams(req); const account_id = await getAccountId(req); - const analytics_cookie = req.cookies[analytics_cookie_name]; + const analytics_cookie = ANALYTICS_ENABLED + ? req.cookies[ANALYTICS_COOKIE_NAME] + : undefined; return await execute({ account_id, project_id, diff --git a/src/packages/next/pages/api/v2/llm/evaluate.ts b/src/packages/next/pages/api/v2/llm/evaluate.ts index 2304b453a89..e7a65a0efdb 100644 --- a/src/packages/next/pages/api/v2/llm/evaluate.ts +++ b/src/packages/next/pages/api/v2/llm/evaluate.ts @@ -2,7 +2,7 @@ // Previously, this has been in openai/chatgpt import { evaluate } from "@cocalc/server/llm/index"; -import { analytics_cookie_name } from "@cocalc/util/misc"; +import { ANALYTICS_COOKIE_NAME, ANALYTICS_ENABLED } from "@cocalc/util/consts"; import getAccountId from "lib/account/get-account"; import getParams from "lib/api/get-params"; @@ -19,7 +19,9 @@ export default async function handle(req, res) { async function doIt(req) { const { input, system, history, model, tag } = getParams(req); const account_id = await getAccountId(req); - const analytics_cookie = req.cookies[analytics_cookie_name]; + const analytics_cookie = ANALYTICS_ENABLED + ? req.cookies[ANALYTICS_COOKIE_NAME] + : undefined; return { output: await evaluate({ account_id, diff --git a/src/packages/util/consts/index.ts b/src/packages/util/consts/index.ts index 5d711cdf6a2..3d745e18fb1 100644 --- a/src/packages/util/consts/index.ts +++ b/src/packages/util/consts/index.ts @@ -12,3 +12,5 @@ export { NOT_SIGNED_IN, versionCookieName } from "./auth"; export { DUMMY_SECRET } from "./project"; export { SERVER_SETTINGS_ENV_PREFIX } from "./server_settings"; + +export { ANALYTICS_COOKIE_NAME, ANALYTICS_ENABLED } from "./tracking"; diff --git a/src/packages/util/consts/tracking.ts b/src/packages/util/consts/tracking.ts new file mode 100644 index 00000000000..c0960978d26 --- /dev/null +++ b/src/packages/util/consts/tracking.ts @@ -0,0 +1,10 @@ +/* + * This file is part of CoCalc: Copyright © 2025 Sagemath, Inc. + * License: MS-RSL – see LICENSE.md for details + */ + +// Cookie name for analytics tracking +export const ANALYTICS_COOKIE_NAME = "CC_ANA"; + +// Global flag to disable analytics tracking +export const ANALYTICS_ENABLED: boolean = false; diff --git a/src/packages/util/misc.ts b/src/packages/util/misc.ts index 853b5be89ec..1962b0536b8 100644 --- a/src/packages/util/misc.ts +++ b/src/packages/util/misc.ts @@ -501,7 +501,7 @@ export function trunc_left( sArg: T, max_length = 1024, ellipsis = ELLIPSIS, -): T | string { +): T | string { if (sArg == null) { return sArg; } @@ -2301,9 +2301,6 @@ export function sanitize_html_attributes($, node): void { }); } -// cocalc analytics cookie name -export const analytics_cookie_name = "CC_ANA"; - // convert a jupyter kernel language (i.e. "python" or "r", usually short and lowercase) // to a canonical name. export function jupyter_language_to_name(lang: string): string { From df31e7bf119ad02d5e07aa05c600fc98022bd48e Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Wed, 16 Jul 2025 10:07:11 +0200 Subject: [PATCH 02/58] hub/analytics: configure if cookie is set or not --- src/.claude/settings.local.json | 6 ++-- src/CLAUDE.md | 8 +++--- .../settings/email-address-setting.tsx | 2 +- src/packages/frontend/user-tracking.ts | 28 +++++++------------ src/packages/hub/analytics.ts | 11 ++++---- .../next/pages/api/v2/jupyter/execute.ts | 12 +++++--- .../next/pages/api/v2/llm/evaluate.ts | 12 +++++--- src/packages/util/consts/index.ts | 2 +- src/packages/util/consts/tracking.ts | 3 -- .../util/db-schema/site-settings-extras.ts | 8 ++++++ 10 files changed, 50 insertions(+), 42 deletions(-) diff --git a/src/.claude/settings.local.json b/src/.claude/settings.local.json index 6dbe6c8b894..25feaa70ded 100644 --- a/src/.claude/settings.local.json +++ b/src/.claude/settings.local.json @@ -9,8 +9,10 @@ "Bash(tsc --noEmit)", "Bash(git fetch:*)", "Bash(git merge:*)", - "Bash(prettier -w:*)" + "Bash(prettier -w:*)", + "Bash(git push:*)", + "Bash(gh pr view:*)" ], "deny": [] } -} +} \ No newline at end of file diff --git a/src/CLAUDE.md b/src/CLAUDE.md index 61034c49de6..001e554e654 100644 --- a/src/CLAUDE.md +++ b/src/CLAUDE.md @@ -34,7 +34,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ### Package-Specific Commands -- `cd packages/[package] && pnpm tsc` - TypeScript compilation for a specific package +- `cd packages/[package] && pnpm build` - Build and compile a specific package - `cd packages/[package] && pnpm test` - Run tests for a specific package - `cd packages/[package] && pnpm build` - Build a specific package - **IMPORTANT**: When modifying packages like `util` that other packages depend on, you must run `pnpm build` in the dependency package before typechecking dependent packages @@ -42,7 +42,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ### Development - After code changes, run `pretter -w [filename]` to ensure consistent styling -- After TypeScript or `*.tsx` changes, run `pnpm tsc` in the relevant package directory +- After TypeScript or `*.tsx` changes, run `pnpm build` in the relevant package directory ## Architecture Overview @@ -120,14 +120,14 @@ CoCalc is organized as a monorepo with key packages: ### Development Workflow -1. Changes to TypeScript require compilation (`pnpm tsc` in relevant package) +1. Changes to TypeScript require compilation (`pnpm build` in relevant package) 2. Database must be running before starting hub 3. Hub coordinates all services and should be restarted after changes 4. Use `pnpm clean && pnpm build-dev` when switching branches or after major changes # Workflow -- Be sure to typecheck when you're done making a series of code changes +- Be sure to build when you're done making a series of code changes - Prefer running single tests, and not the whole test suite, for performance ## Git Workflow diff --git a/src/packages/frontend/account/settings/email-address-setting.tsx b/src/packages/frontend/account/settings/email-address-setting.tsx index 98c7003252a..d6e907dfe95 100644 --- a/src/packages/frontend/account/settings/email-address-setting.tsx +++ b/src/packages/frontend/account/settings/email-address-setting.tsx @@ -73,7 +73,7 @@ export const EmailAddressSetting = ({ return; } try { - // anonymouse users will get the "welcome" email + // anonymous users will get the "welcome" email await webapp_client.account_client.send_verification_email(!is_anonymous); } catch (error) { const err_msg = `Problem sending welcome email: ${error}`; diff --git a/src/packages/frontend/user-tracking.ts b/src/packages/frontend/user-tracking.ts index 94aa3e779ef..7b7e913c0fe 100644 --- a/src/packages/frontend/user-tracking.ts +++ b/src/packages/frontend/user-tracking.ts @@ -7,20 +7,19 @@ // client code doesn't have to import webapp_client everywhere, and we can // completely change this if we want. -import { query, server_time } from "./frame-editors/generic/client"; +import { redux } from "@cocalc/frontend/app-framework"; +import { + query, + server_time, +} from "@cocalc/frontend/frame-editors/generic/client"; +import { get_cookie } from "@cocalc/frontend/misc"; +import { webapp_client } from "@cocalc/frontend/webapp-client"; import { uuid } from "@cocalc/util/misc"; -import { redux } from "./app-framework"; import { version } from "@cocalc/util/smc-version"; -import { get_cookie } from "./misc"; -import { webapp_client } from "./webapp-client"; -import { ANALYTICS_COOKIE_NAME, ANALYTICS_ENABLED } from "@cocalc/util/consts"; +import { ANALYTICS_COOKIE_NAME } from "@cocalc/util/consts"; export async function log(eventName: string, payload: any): Promise { - if (!ANALYTICS_ENABLED) { - return; - } - const central_log = { id: uuid(), event: `webapp-${eventName}`, @@ -32,12 +31,9 @@ export async function log(eventName: string, payload: any): Promise { }, time: server_time(), }; + try { - await query({ - query: { - central_log, - }, - }); + await query({ query: { central_log } }); } catch (err) { console.warn("WARNING: Failed to write log event -- ", central_log); } @@ -49,10 +45,6 @@ export default async function track( event: string, value: object, ): Promise { - if (!ANALYTICS_ENABLED) { - return; - } - // Replace all dashes with underscores in the event argument for consistency event = event.replace(/-/g, "_"); diff --git a/src/packages/hub/analytics.ts b/src/packages/hub/analytics.ts index a1a6124bd58..ea4f664bcb2 100644 --- a/src/packages/hub/analytics.ts +++ b/src/packages/hub/analytics.ts @@ -22,7 +22,7 @@ import { is_valid_uuid_string, uuid } from "@cocalc/util/misc"; import { pii_retention_to_future } from "@cocalc/database/postgres/pii"; import { get_server_settings } from "@cocalc/database/postgres/server-settings"; import type { PostgreSQL } from "@cocalc/database/postgres/types"; -import { ANALYTICS_COOKIE_NAME, ANALYTICS_ENABLED } from "@cocalc/util/consts"; +import { ANALYTICS_COOKIE_NAME } from "@cocalc/util/consts"; import { getLogger } from "./logger"; @@ -243,6 +243,7 @@ export async function initAnalytics( const DNS = settings.dns; const dns_parsed = parseDomain(DNS); const pii_retention = settings.pii_retention; + const analytics_enabled = settings.analytics_cookie; if ( dns_parsed.type !== ParseResultType.Listed && @@ -288,9 +289,9 @@ export async function initAnalytics( `/analytics.js GET analytics_cookie='${req.cookies[ANALYTICS_COOKIE_NAME]}'`, ); - if (!req.cookies[ANALYTICS_COOKIE_NAME] && ANALYTICS_ENABLED) { + if (!req.cookies[ANALYTICS_COOKIE_NAME] && analytics_enabled) { // No analytics cookie is set and cookies are enabled, so we set one. - // When ANALYTICS_ENABLED is false, we skip setting cookies to enable + // When analytics_enabled is false, we skip setting cookies to enable // cookieless tracking for better privacy. setAnalyticsCookie(res /* DNS */); } @@ -298,7 +299,7 @@ export async function initAnalytics( // Return NOOP if DNS is invalid, or if cookies are enabled and already exist if ( dns_parsed.type !== ParseResultType.Listed || - (ANALYTICS_ENABLED && req.cookies[ANALYTICS_COOKIE_NAME]) + (analytics_enabled && req.cookies[ANALYTICS_COOKIE_NAME]) ) { // cache for 6 hours -- max-age has unit seconds res.header( @@ -320,7 +321,7 @@ export async function initAnalytics( res.write(`var NAME = '${ANALYTICS_COOKIE_NAME}';\n`); res.write(`var ID = '${uuid()}';\n`); res.write(`var DOMAIN = '${DOMAIN}';\n`); - res.write(`var ANALYTICS_ENABLED = ${ANALYTICS_ENABLED};\n`); + res.write(`var ANALYTICS_ENABLED = ${analytics_enabled};\n`); // BASE_PATH if (req.query.fqd === "false") { res.write(`var PREFIX = '${base_path}';\n`); diff --git a/src/packages/next/pages/api/v2/jupyter/execute.ts b/src/packages/next/pages/api/v2/jupyter/execute.ts index 83ae4ca8773..e319baf4226 100644 --- a/src/packages/next/pages/api/v2/jupyter/execute.ts +++ b/src/packages/next/pages/api/v2/jupyter/execute.ts @@ -20,13 +20,16 @@ The OUTPUT is: - a list of messages that describe the output of the last code execution. */ +import type { Request, Response } from "express"; + import { execute } from "@cocalc/server/jupyter/execute"; import getAccountId from "lib/account/get-account"; import getParams from "lib/api/get-params"; -import { ANALYTICS_COOKIE_NAME, ANALYTICS_ENABLED } from "@cocalc/util/consts"; +import { ANALYTICS_COOKIE_NAME } from "@cocalc/util/consts"; +import { getServerSettings } from "@cocalc/database/settings/server-settings"; -export default async function handle(req, res) { +export default async function handle(req: Request, res: Response) { try { const result = await doIt(req); res.json({ ...result, success: true }); @@ -36,11 +39,12 @@ export default async function handle(req, res) { } } -async function doIt(req) { +async function doIt(req: Request) { const { input, kernel, history, tag, noCache, hash, project_id, path } = getParams(req); const account_id = await getAccountId(req); - const analytics_cookie = ANALYTICS_ENABLED + const { analytics_cookie: analytics_enabled } = await getServerSettings(); + const analytics_cookie = analytics_enabled ? req.cookies[ANALYTICS_COOKIE_NAME] : undefined; return await execute({ diff --git a/src/packages/next/pages/api/v2/llm/evaluate.ts b/src/packages/next/pages/api/v2/llm/evaluate.ts index e7a65a0efdb..2b75ba29887 100644 --- a/src/packages/next/pages/api/v2/llm/evaluate.ts +++ b/src/packages/next/pages/api/v2/llm/evaluate.ts @@ -1,12 +1,15 @@ // This is the new endpoint for querying any LLM // Previously, this has been in openai/chatgpt +import type { Request, Response } from "express"; + +import { getServerSettings } from "@cocalc/database/settings/server-settings"; import { evaluate } from "@cocalc/server/llm/index"; -import { ANALYTICS_COOKIE_NAME, ANALYTICS_ENABLED } from "@cocalc/util/consts"; +import { ANALYTICS_COOKIE_NAME } from "@cocalc/util/consts"; import getAccountId from "lib/account/get-account"; import getParams from "lib/api/get-params"; -export default async function handle(req, res) { +export default async function handle(req: Request, res: Response) { try { const result = await doIt(req); res.json({ ...result, success: true }); @@ -16,10 +19,11 @@ export default async function handle(req, res) { } } -async function doIt(req) { +async function doIt(req: Request) { const { input, system, history, model, tag } = getParams(req); const account_id = await getAccountId(req); - const analytics_cookie = ANALYTICS_ENABLED + const { analytics_cookie: analytics_enabled } = await getServerSettings(); + const analytics_cookie = analytics_enabled ? req.cookies[ANALYTICS_COOKIE_NAME] : undefined; return { diff --git a/src/packages/util/consts/index.ts b/src/packages/util/consts/index.ts index 3d745e18fb1..13e2a928070 100644 --- a/src/packages/util/consts/index.ts +++ b/src/packages/util/consts/index.ts @@ -13,4 +13,4 @@ export { DUMMY_SECRET } from "./project"; export { SERVER_SETTINGS_ENV_PREFIX } from "./server_settings"; -export { ANALYTICS_COOKIE_NAME, ANALYTICS_ENABLED } from "./tracking"; +export { ANALYTICS_COOKIE_NAME } from "./tracking"; diff --git a/src/packages/util/consts/tracking.ts b/src/packages/util/consts/tracking.ts index c0960978d26..ab67ecb8a33 100644 --- a/src/packages/util/consts/tracking.ts +++ b/src/packages/util/consts/tracking.ts @@ -5,6 +5,3 @@ // Cookie name for analytics tracking export const ANALYTICS_COOKIE_NAME = "CC_ANA"; - -// Global flag to disable analytics tracking -export const ANALYTICS_ENABLED: boolean = false; diff --git a/src/packages/util/db-schema/site-settings-extras.ts b/src/packages/util/db-schema/site-settings-extras.ts index f6c01efe932..6414c4021c6 100644 --- a/src/packages/util/db-schema/site-settings-extras.ts +++ b/src/packages/util/db-schema/site-settings-extras.ts @@ -184,6 +184,7 @@ function custom_llm_display(value: string): string { export type SiteSettingsExtrasKeys = | "pii_retention" + | "analytics_cookie" | "conat_heading" | "conat_password" | "stripe_heading" @@ -405,6 +406,13 @@ export const EXTRAS: SettingsExtras = { to_val: pii_retention_parse, to_display: pii_retention_display, }, + analytics_cookie: { + name: "Analytics Cookie", + desc: "Tag browser sessions visiting a website via an analytics.js script with a cookie", + default: "no", + valid: only_booleans, + to_val: to_bool, + }, stripe_heading: { // this is consmetic, otherwise it looks weird. name: "Stripe Keys", From 67608fad54106c87e93d96f0d7bd8e389ca43be6 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Wed, 16 Jul 2025 11:04:18 +0200 Subject: [PATCH 03/58] next/abuse: use anonymous user ID from cookie or IP address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add getAnonymousID function to get anonymous ID from cookie or IP - Support Cloudflare CF-Connecting-IP and X-Forwarded-For headers - Fall back to socket remote address if no cookie available - Update LLM and Jupyter APIs to use new anonymous ID logic - Rename isValidAnonID to isValidAnonymousID for consistency - Add comprehensive tests for isValidAnonymousID validation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/.claude/settings.local.json | 4 +- src/CLAUDE.md | 4 +- src/packages/next/lib/user-id.ts | 33 ++++++++++++++ .../next/pages/api/v2/jupyter/execute.ts | 12 ++--- .../next/pages/api/v2/llm/evaluate.ts | 10 ++--- src/packages/server/jupyter/abuse.ts | 20 ++++----- src/packages/server/jupyter/execute.ts | 27 +++++------ src/packages/server/jupyter/recent-usage.ts | 17 +++---- src/packages/server/llm/abuse.ts | 25 ++++++----- src/packages/server/llm/index.ts | 8 ++-- src/packages/server/llm/save-response.ts | 7 +-- src/packages/util/db-schema/llm.ts | 2 +- src/packages/util/misc.test.ts | 45 +++++++++++++++++++ src/packages/util/misc.ts | 5 +++ src/packages/util/types/llm.ts | 2 +- 15 files changed, 152 insertions(+), 69 deletions(-) create mode 100644 src/packages/next/lib/user-id.ts diff --git a/src/.claude/settings.local.json b/src/.claude/settings.local.json index 25feaa70ded..6ae7e108f35 100644 --- a/src/.claude/settings.local.json +++ b/src/.claude/settings.local.json @@ -11,7 +11,9 @@ "Bash(git merge:*)", "Bash(prettier -w:*)", "Bash(git push:*)", - "Bash(gh pr view:*)" + "Bash(gh pr view:*)", + "Bash(git grep:*)", + "Bash(pnpm test:*)" ], "deny": [] } diff --git a/src/CLAUDE.md b/src/CLAUDE.md index 001e554e654..48ebab2e327 100644 --- a/src/CLAUDE.md +++ b/src/CLAUDE.md @@ -35,9 +35,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ### Package-Specific Commands - `cd packages/[package] && pnpm build` - Build and compile a specific package + - for packages/next and packages/static, run `cd packages/[package] && pnpm build-dev` +- `cd packages/[package] && pnpm tsc:watch` - TypeScript compilation in watch mode for a specific package - `cd packages/[package] && pnpm test` - Run tests for a specific package - `cd packages/[package] && pnpm build` - Build a specific package -- **IMPORTANT**: When modifying packages like `util` that other packages depend on, you must run `pnpm build` in the dependency package before typechecking dependent packages +- **IMPORTANT**: When modifying packages like `util` that other packages depend on, you must run `pnpm build` in the modified package before typechecking dependent packages ### Development diff --git a/src/packages/next/lib/user-id.ts b/src/packages/next/lib/user-id.ts new file mode 100644 index 00000000000..4c74dd6aa8c --- /dev/null +++ b/src/packages/next/lib/user-id.ts @@ -0,0 +1,33 @@ +/* + * This file is part of CoCalc: Copyright © 2021 Sagemath, Inc. + * License: MS-RSL – see LICENSE.md for details + */ + +import type { Request } from "express"; + +import { getServerSettings } from "@cocalc/database/settings/server-settings"; +import { ANALYTICS_COOKIE_NAME } from "@cocalc/util/consts"; +import { isValidAnonymousID } from "@cocalc/util/misc"; + +// Get anonymous user ID from cookie or IP address +export async function getAnonymousID(req: Request): Promise { + const { analytics_cookie: analytics_enabled } = await getServerSettings(); + + if (analytics_enabled) { + const cookie = req.cookies[ANALYTICS_COOKIE_NAME]; + if (isValidAnonymousID(cookie)) { + return cookie; + } + } + + // Fall back to IP address - check headers in order of preference + const connectingIp = (req.headers["cf-connecting-ip"] || + req.headers["x-forwarded-for"] || + req.socket?.remoteAddress) as string; + + if (isValidAnonymousID(connectingIp)) { + return connectingIp; + } + + return undefined; +} diff --git a/src/packages/next/pages/api/v2/jupyter/execute.ts b/src/packages/next/pages/api/v2/jupyter/execute.ts index e319baf4226..73c1b1af9d6 100644 --- a/src/packages/next/pages/api/v2/jupyter/execute.ts +++ b/src/packages/next/pages/api/v2/jupyter/execute.ts @@ -20,14 +20,13 @@ The OUTPUT is: - a list of messages that describe the output of the last code execution. */ + import type { Request, Response } from "express"; import { execute } from "@cocalc/server/jupyter/execute"; import getAccountId from "lib/account/get-account"; import getParams from "lib/api/get-params"; - -import { ANALYTICS_COOKIE_NAME } from "@cocalc/util/consts"; -import { getServerSettings } from "@cocalc/database/settings/server-settings"; +import { getAnonymousID } from "lib/user-id"; export default async function handle(req: Request, res: Response) { try { @@ -43,15 +42,12 @@ async function doIt(req: Request) { const { input, kernel, history, tag, noCache, hash, project_id, path } = getParams(req); const account_id = await getAccountId(req); - const { analytics_cookie: analytics_enabled } = await getServerSettings(); - const analytics_cookie = analytics_enabled - ? req.cookies[ANALYTICS_COOKIE_NAME] - : undefined; + const anonymous_id = await getAnonymousID(req); return await execute({ account_id, project_id, path, - analytics_cookie, + anonymous_id, input, hash, history, diff --git a/src/packages/next/pages/api/v2/llm/evaluate.ts b/src/packages/next/pages/api/v2/llm/evaluate.ts index 2b75ba29887..a7384457f22 100644 --- a/src/packages/next/pages/api/v2/llm/evaluate.ts +++ b/src/packages/next/pages/api/v2/llm/evaluate.ts @@ -3,11 +3,10 @@ import type { Request, Response } from "express"; -import { getServerSettings } from "@cocalc/database/settings/server-settings"; import { evaluate } from "@cocalc/server/llm/index"; -import { ANALYTICS_COOKIE_NAME } from "@cocalc/util/consts"; import getAccountId from "lib/account/get-account"; import getParams from "lib/api/get-params"; +import { getAnonymousID } from "lib/user-id"; export default async function handle(req: Request, res: Response) { try { @@ -22,14 +21,11 @@ export default async function handle(req: Request, res: Response) { async function doIt(req: Request) { const { input, system, history, model, tag } = getParams(req); const account_id = await getAccountId(req); - const { analytics_cookie: analytics_enabled } = await getServerSettings(); - const analytics_cookie = analytics_enabled - ? req.cookies[ANALYTICS_COOKIE_NAME] - : undefined; + const anonymous_id = await getAnonymousID(req); return { output: await evaluate({ account_id, - analytics_cookie, + anonymous_id, input, system, history, diff --git a/src/packages/server/jupyter/abuse.ts b/src/packages/server/jupyter/abuse.ts index d5cc36e8ec0..95719670312 100644 --- a/src/packages/server/jupyter/abuse.ts +++ b/src/packages/server/jupyter/abuse.ts @@ -4,7 +4,7 @@ blatant abuse. Everything is hardcoded and nothing is configurable via the admin settings panel yet. */ -import { isValidUUID } from "@cocalc/util/misc"; +import { isValidUUID, isValidAnonymousID } from "@cocalc/util/misc"; import recentUsage from "./recent-usage"; import getLogger from "@cocalc/backend/logger"; @@ -31,35 +31,35 @@ const QUOTAS = { // Throws an exception if the request should not be allowed. export default async function checkForAbuse({ account_id, - analytics_cookie, + anonymous_id, }: { account_id?: string; - analytics_cookie?: string; + anonymous_id?: string; }): Promise { - if (!isValidUUID(account_id) && !isValidUUID(analytics_cookie)) { + if (!isValidUUID(account_id) && !isValidAnonymousID(anonymous_id)) { // at least some amount of tracking. - throw Error("at least one of account_id or analytics_cookie must be set"); + throw Error("at least one of account_id or anonymous_id must be set"); } const usage = await recentUsage({ cache: "short", period: PERIOD, account_id, - analytics_cookie, + anonymous_id, }); log.debug("recent usage by this user", { account_id, - analytics_cookie, + anonymous_id, usage, }); if (account_id) { if (usage > QUOTAS.account) { throw Error( - `You may use at most ${QUOTAS.account} seconds of compute time per ${PERIOD}. Please try again later or do this computation in a project.` + `You may use at most ${QUOTAS.account} seconds of compute time per ${PERIOD}. Please try again later or do this computation in a project.`, ); } } else if (usage > QUOTAS.noAccount) { throw Error( - `You may use at most ${QUOTAS.noAccount} seconds of compute time per ${PERIOD}. Sign in to increase your quota.` + `You may use at most ${QUOTAS.noAccount} seconds of compute time per ${PERIOD}. Sign in to increase your quota.`, ); } @@ -69,7 +69,7 @@ export default async function checkForAbuse({ log.debug("overallUsage = ", usage); if (overallUsage > QUOTAS.global) { throw Error( - `There is too much overall usage of code evaluation right now. Please try again later or do this computation in a project.` + `There is too much overall usage of code evaluation right now. Please try again later or do this computation in a project.`, ); } } diff --git a/src/packages/server/jupyter/execute.ts b/src/packages/server/jupyter/execute.ts index a552b5ff158..1c562078424 100644 --- a/src/packages/server/jupyter/execute.ts +++ b/src/packages/server/jupyter/execute.ts @@ -2,16 +2,16 @@ Backend server side part of ChatGPT integration with CoCalc. */ -import getPool from "@cocalc/database/pool"; import getLogger from "@cocalc/backend/logger"; +import getPool from "@cocalc/database/pool"; import { getServerSettings } from "@cocalc/database/settings/server-settings"; -import computeHash from "@cocalc/util/jupyter-api/compute-hash"; -import getProject from "./global-project-pool"; import callProject from "@cocalc/server/projects/call"; -import { jupyter_execute } from "@cocalc/util/message"; import isCollaborator from "@cocalc/server/projects/is-collaborator"; -import checkForAbuse from "./abuse"; +import computeHash from "@cocalc/util/jupyter-api/compute-hash"; +import { jupyter_execute } from "@cocalc/util/message"; import { expire_time } from "@cocalc/util/relative-time"; +import checkForAbuse from "./abuse"; +import getProject from "./global-project-pool"; const log = getLogger("jupyter-api:execute"); @@ -44,7 +44,7 @@ interface Options { history?: string[]; hash?: string; account_id?: string; - analytics_cookie?: string; + anonymous_id?: string; tag?: string; noCache?: boolean; project_id?: string; @@ -56,7 +56,7 @@ export async function execute({ input, kernel, account_id, - analytics_cookie, + anonymous_id, history, tag, noCache, @@ -66,7 +66,7 @@ export async function execute({ output: object[]; created: Date; } | null> { - // TODO -- await checkForAbuse({ account_id, analytics_cookie }); + // TODO -- await checkForAbuse({ account_id, anonymous_id }); log.debug("execute", { input, @@ -74,7 +74,7 @@ export async function execute({ history, hash, account_id, - analytics_cookie, + anonymous_id, tag, project_id, path, @@ -117,7 +117,7 @@ export async function execute({ // we only worry about abuse against the general public pool, not // when used in a user's own project - await checkForAbuse({ account_id, analytics_cookie }); + await checkForAbuse({ account_id, anonymous_id }); request_account_id = jupyter_account_id; request_project_id = await getProject(); @@ -168,7 +168,7 @@ export async function execute({ account_id, project_id, path, - analytics_cookie, + anonymous_id, history, tag, total_time_s, @@ -220,7 +220,7 @@ async function saveResponse({ account_id, project_id, path, - analytics_cookie, + anonymous_id, history, tag, total_time_s, @@ -235,6 +235,7 @@ async function saveResponse({ const expire = expire_time(30 * 24 * 60 * 60); try { await Promise.all([ + // saving analytics_cookie because this is before generalizing to an anonymous_id string pool.query( `INSERT INTO jupyter_api_log(created,account_id,project_id,path,analytics_cookie,tag,hash,total_time_s,kernel,history,input,expire) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)`, [ @@ -242,7 +243,7 @@ async function saveResponse({ account_id, project_id, path, - analytics_cookie, + anonymous_id, tag, hash, total_time_s, diff --git a/src/packages/server/jupyter/recent-usage.ts b/src/packages/server/jupyter/recent-usage.ts index 3e15e9c02be..e2617e0b1e9 100644 --- a/src/packages/server/jupyter/recent-usage.ts +++ b/src/packages/server/jupyter/recent-usage.ts @@ -8,22 +8,22 @@ import getPool from "@cocalc/database/pool"; type QueryArgs = { period: string; account_id?: string; - analytics_cookie?: string; + anonymous_id?: string; cache?: "short" | "medium" | "long"; }; export default async function recentUsage({ period, account_id, - analytics_cookie, + anonymous_id, cache, }: QueryArgs): Promise { let queryArgs; if (account_id) { queryArgs = buildAccountIdQuery(period, account_id); - } else if (analytics_cookie) { - queryArgs = buildAnalyticsCookieQuery(period, analytics_cookie); + } else if (anonymous_id) { + queryArgs = buildAnalyticsCookieQuery(period, anonymous_id); } else { queryArgs = buildOverallUsageQuery(period); } @@ -34,7 +34,7 @@ export default async function recentUsage({ async function getUsageForQuery( query: string, args: any[], - cache?: QueryArgs["cache"] + cache?: QueryArgs["cache"], ): Promise { const pool = getPool(cache); const { rows } = await pool.query(query, args); @@ -43,7 +43,7 @@ async function getUsageForQuery( function buildAccountIdQuery( period: string, - account_id: string + account_id: string, ): [string, any[]] { const query = `SELECT SUM(total_time_s) AS usage FROM jupyter_api_log WHERE created >= NOW() - INTERVAL '${period}' AND account_id=$1 AND project_id IS NULL AND path IS NULL`; const args = [account_id]; @@ -52,10 +52,11 @@ function buildAccountIdQuery( function buildAnalyticsCookieQuery( period: string, - analytics_cookie: string + anonymous_id: string, ): [string, any[]] { + // query uses analytics_cookie before generalizing to an anonymous ID const query = `SELECT SUM(total_time_s) AS usage FROM jupyter_api_log WHERE created >= NOW() - INTERVAL '${period}' AND analytics_cookie=$1 AND project_id IS NULL AND path IS NULL`; - const args = [analytics_cookie]; + const args = [anonymous_id]; return [query, args]; } diff --git a/src/packages/server/llm/abuse.ts b/src/packages/server/llm/abuse.ts index e75192547be..3c316c44f15 100644 --- a/src/packages/server/llm/abuse.ts +++ b/src/packages/server/llm/abuse.ts @@ -29,10 +29,10 @@ import { model2service, } from "@cocalc/util/db-schema/llm-utils"; import { KUCALC_COCALC_COM } from "@cocalc/util/db-schema/site-defaults"; -import { isValidUUID } from "@cocalc/util/misc"; +import { isValidAnonymousID, isValidUUID } from "@cocalc/util/misc"; import isValidAccount from "../accounts/is-valid-account"; -// These are tokens over a given period of time – summed by account/analytics_cookie or global. +// These are tokens over a given period of time – summed by account/anonymous_id or global. const QUOTAS = { noAccount: process_env_int("COCALC_LLM_QUOTA_NO_ACCOUNT", 0), account: process_env_int("COCALC_LLM_QUOTA_ACCOUNT", 10 ** 5), @@ -63,11 +63,11 @@ const prom_rejected = newCounter( // Throws an exception if the request should not be allowed. export async function checkForAbuse({ account_id, - analytics_cookie, + anonymous_id, model, }: { account_id?: string; - analytics_cookie?: string; + anonymous_id?: string; model: LanguageModel; }): Promise { if (!account_id) { @@ -75,9 +75,9 @@ export async function checkForAbuse({ // https://github.com/xtekky/gpt4free/tree/main/gpt4free/cocalc throw Error("You must create an account."); } - if (!isValidUUID(account_id) && !isValidUUID(analytics_cookie)) { + if (!isValidUUID(account_id) && !isValidAnonymousID(anonymous_id)) { // at least some amount of tracking. - throw Error("at least one of account_id or analytics_cookie must be set"); + throw Error("at least one of account_id or anonymous_id must be set"); } if (!isLanguageModel(model)) { @@ -107,7 +107,7 @@ export async function checkForAbuse({ cache: "short", period: "1 hour", account_id, - analytics_cookie, + anonymous_id, }); // this fluctuates for each account, we'll tally up how often users end up in certain usage buckets @@ -133,7 +133,7 @@ export async function checkForAbuse({ ); } - // Prevent more sophisticated abuse, e.g., changing analytics_cookie or account frequently, + // Prevent more sophisticated abuse, e.g., changing anonymous_id or account frequently, // or just a general huge surge in usage. const overallUsage = await recentUsage({ cache: "long", period: "1 hour" }); prom_quota_global @@ -152,12 +152,12 @@ export async function checkForAbuse({ async function recentUsage({ period, account_id, - analytics_cookie, + anonymous_id, cache, }: { period: string; account_id?: string; - analytics_cookie?: string; + anonymous_id?: string; // some caching so if user is hitting us a lot, we don't hit the database to // decide they are abusive -- at the same time, short enough that we notice. // Recommendation: "short" @@ -171,9 +171,10 @@ async function recentUsage({ } query = `SELECT SUM(total_tokens) AS usage FROM openai_chatgpt_log WHERE account_id=$1 AND time >= NOW() - INTERVAL '${period}'`; args = [account_id]; - } else if (analytics_cookie) { + } else if (anonymous_id) { + // still setting analytics_cookie in the db query, because this was before generalizing to an anonymous_id string query = `SELECT SUM(total_tokens) AS usage FROM openai_chatgpt_log WHERE analytics_cookie=$1 AND time >= NOW() - INTERVAL '${period}'`; - args = [analytics_cookie]; + args = [anonymous_id]; } else { query = `SELECT SUM(total_tokens) AS usage FROM openai_chatgpt_log WHERE time >= NOW() - INTERVAL '${period}'`; args = []; diff --git a/src/packages/server/llm/index.ts b/src/packages/server/llm/index.ts index 8e398f4b585..87b152e779b 100644 --- a/src/packages/server/llm/index.ts +++ b/src/packages/server/llm/index.ts @@ -140,7 +140,7 @@ async function evaluateImpl({ account_id, project_id, path, - analytics_cookie, + anonymous_id, history, model = DEFAULT_MODEL, tag, @@ -153,7 +153,7 @@ async function evaluateImpl({ // history, // system, // account_id, - // analytics_cookie, + // anonymous_id, // project_id, // path, // model, @@ -163,7 +163,7 @@ async function evaluateImpl({ // }); const start = Date.now(); - await checkForAbuse({ account_id, analytics_cookie, model }); + await checkForAbuse({ account_id, anonymous_id, model }); stream = wrapStream(stream); @@ -255,7 +255,7 @@ async function evaluateImpl({ output, history, account_id, - analytics_cookie, + anonymous_id, project_id, path, total_tokens, diff --git a/src/packages/server/llm/save-response.ts b/src/packages/server/llm/save-response.ts index 7abb6be4623..806fbea5977 100644 --- a/src/packages/server/llm/save-response.ts +++ b/src/packages/server/llm/save-response.ts @@ -16,7 +16,7 @@ type SaveResponseProps = Omit; // Also, we could dedup identical inputs (?). export async function saveResponse({ account_id, - analytics_cookie, + anonymous_id, history, input, model, @@ -32,6 +32,7 @@ export async function saveResponse({ const expire: LLMLogEntry["expire"] = await getExpiration(account_id); const pool = getPool(); try { + // still setting analytics_cookie in the db query, because this was before generalizing to an anonymous_id string await pool.query( "INSERT INTO openai_chatgpt_log(time,input,system,output,history,account_id,analytics_cookie,project_id,path,total_tokens,prompt_tokens,total_time_s,expire,model,tag) VALUES(NOW(),$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)", [ @@ -40,7 +41,7 @@ export async function saveResponse({ output, history, account_id, - analytics_cookie, + anonymous_id, project_id, path, total_tokens, @@ -58,7 +59,7 @@ export async function saveResponse({ async function getExpiration(account_id: string | undefined) { // NOTE about expire: If the admin setting for "PII Retention" is set *and* - // the usage is only identified by their analytics_cookie, then + // the usage is only identified by their anonymous_id, then // we automatically delete the log of chatgpt usage at the expiration time. // If the account_id *is* set, users can do the following: // 1. Ability to delete any of their past chatgpt usage diff --git a/src/packages/util/db-schema/llm.ts b/src/packages/util/db-schema/llm.ts index ee721846611..3d2b81f3996 100644 --- a/src/packages/util/db-schema/llm.ts +++ b/src/packages/util/db-schema/llm.ts @@ -11,7 +11,7 @@ import { Table } from "./types"; export interface LLMLogEntry { id: number; account_id?: string; - analytics_cookie?: string; // at least one of analytics_cookie or account_id will be set + anonymous_id?: string; // this is saved in analytics_cookie – at least one of anonymous_id or account_id will be set expire?: Date; history?: History; input: string; diff --git a/src/packages/util/misc.test.ts b/src/packages/util/misc.test.ts index f162a5ece50..40213f7173b 100644 --- a/src/packages/util/misc.test.ts +++ b/src/packages/util/misc.test.ts @@ -343,3 +343,48 @@ describe("suggest_duplicate_filename", () => { expect(dup("asdf-")).toBe("asdf--1"); }); }); + +describe("isValidAnonymousID", () => { + const isValid = misc.isValidAnonymousID; + + it("should accept valid IPv4 addresses", () => { + expect(isValid("192.168.1.1")).toBe(true); + expect(isValid("10.23.66.8")).toBe(true); + }); + + it("should accept valid IPv6 addresses", () => { + expect(isValid("::1")).toBe(true); + expect(isValid("2001:db8::1")).toBe(true); + expect(isValid("fe80::1")).toBe(true); + expect(isValid("2001:0db8:85a3:0000:0000:8a2e:0370:7334")).toBe(true); + }); + + it("should accept valid UUIDs", () => { + expect(isValid("123e4567-e89b-12d3-a456-426614174000")).toBe(true); + }); + + it("should accept strings with minimum length", () => { + expect(isValid("abc")).toBe(true); + }); + + it("should reject empty strings", () => { + expect(isValid("")).toBe(false); + }); + + it("should reject strings shorter than 3 characters", () => { + expect(isValid("ab")).toBe(false); + }); + + it("should reject null and undefined", () => { + expect(isValid(null)).toBe(false); + expect(isValid(undefined)).toBe(false); + }); + + it("should reject non-string types", () => { + expect(isValid(123)).toBe(false); + expect(isValid(true)).toBe(false); + expect(isValid({})).toBe(false); + expect(isValid([])).toBe(false); + expect(isValid(new Date())).toBe(false); + }); +}); diff --git a/src/packages/util/misc.ts b/src/packages/util/misc.ts index 1962b0536b8..57ecc0b7220 100644 --- a/src/packages/util/misc.ts +++ b/src/packages/util/misc.ts @@ -305,6 +305,11 @@ export function assert_valid_account_id(uuid?: any): void { } export const isValidUUID = is_valid_uuid_string; +// this should work for IP addresses, also short IPv6, and any UUIDs +export function isValidAnonymousID(id: unknown) { + return typeof id === "string" && id.length >= 3; +} + export function assertValidAccountID(account_id?: any) { if (!isValidUUID(account_id)) { throw Error("account_id is invalid"); diff --git a/src/packages/util/types/llm.ts b/src/packages/util/types/llm.ts index d7e4152ea2f..3f48f893633 100644 --- a/src/packages/util/types/llm.ts +++ b/src/packages/util/types/llm.ts @@ -18,7 +18,7 @@ export interface ChatOptionsApi { account_id?: string; project_id?: string; path?: string; - analytics_cookie?: string; + anonymous_id?: string; history?: History; model?: LanguageModel; // default is defined by server setting default_llm tag?: string; From 875e790df0d8e598a7436f00f12ea2c937530dbf Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Fri, 18 Jul 2025 12:20:43 +0200 Subject: [PATCH 04/58] next+conat: come up with a universal client IP address extractor --- src/.claude/settings.local.json | 20 -- src/packages/conat/core/server.ts | 77 ++--- src/packages/next/lib/user-id.ts | 19 +- src/packages/pnpm-lock.yaml | 25 +- .../util/get-client-ip-address.test.ts | 292 ++++++++++++++++++ src/packages/util/get-client-ip-address.ts | 106 +++++++ src/packages/util/package.json | 1 + 7 files changed, 455 insertions(+), 85 deletions(-) delete mode 100644 src/.claude/settings.local.json create mode 100644 src/packages/util/get-client-ip-address.test.ts create mode 100644 src/packages/util/get-client-ip-address.ts diff --git a/src/.claude/settings.local.json b/src/.claude/settings.local.json deleted file mode 100644 index 6ae7e108f35..00000000000 --- a/src/.claude/settings.local.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(pnpm tsc:*)", - "Bash(pnpm build:*)", - "Bash(git add:*)", - "Bash(git commit:*)", - "Bash(grep:*)", - "Bash(tsc --noEmit)", - "Bash(git fetch:*)", - "Bash(git merge:*)", - "Bash(prettier -w:*)", - "Bash(git push:*)", - "Bash(gh pr view:*)", - "Bash(git grep:*)", - "Bash(pnpm test:*)" - ], - "deny": [] - } -} \ No newline at end of file diff --git a/src/packages/conat/core/server.ts b/src/packages/conat/core/server.ts index dc87dcf1911..6228d7b2da1 100644 --- a/src/packages/conat/core/server.ts +++ b/src/packages/conat/core/server.ts @@ -28,53 +28,56 @@ cd packages/server */ import type { ConnectionStats, ServerInfo } from "./types"; + +import { delay } from "awaiting"; +import { EventEmitter } from "events"; +import { throttle } from "lodash"; +import { Server } from "socket.io"; + +import { getClientIpAddress } from "@cocalc/util/get-client-ip-address"; +import { getLogger } from "@cocalc/conat/client"; +import { UsageMonitor } from "@cocalc/conat/monitor/usage"; +import { type ConatSocketServer } from "@cocalc/conat/socket"; import { isValidSubject, isValidSubjectWithoutWildcards, } from "@cocalc/conat/util"; -import { Server } from "socket.io"; -import { delay } from "awaiting"; +import { once, until } from "@cocalc/util/async-utils"; +import { is_array } from "@cocalc/util/misc"; +import { reuseInFlight } from "@cocalc/util/reuse-in-flight"; +import { Metrics } from "../types"; import { - ConatError, - connect, Client, type ClientOptions, + ConatError, + connect, MAX_INTEREST_TIMEOUT, STICKY_QUEUE_GROUP, } from "./client"; -import { - RESOURCE, - MAX_CONNECTIONS_PER_USER, - MAX_CONNECTIONS, - MAX_PAYLOAD, - MAX_SUBSCRIPTIONS_PER_CLIENT, - MAX_SUBSCRIPTIONS_PER_HUB, -} from "./constants"; -import { Patterns } from "./patterns"; -import { is_array } from "@cocalc/util/misc"; -import { UsageMonitor } from "@cocalc/conat/monitor/usage"; -import { once, until } from "@cocalc/util/async-utils"; import { clusterLink, type ClusterLink, clusterStreams, type ClusterStreams, - trimClusterStreams, createClusterPersistServer, - Sticky, - Interest, hashInterest, hashSticky, + Interest, + Sticky, + trimClusterStreams, } from "./cluster"; -import { type ConatSocketServer } from "@cocalc/conat/socket"; -import { throttle } from "lodash"; -import { getLogger } from "@cocalc/conat/client"; -import { reuseInFlight } from "@cocalc/util/reuse-in-flight"; -import { type SysConatServer, sysApiSubject, sysApi } from "./sys"; +import { + MAX_CONNECTIONS, + MAX_CONNECTIONS_PER_USER, + MAX_PAYLOAD, + MAX_SUBSCRIPTIONS_PER_CLIENT, + MAX_SUBSCRIPTIONS_PER_HUB, + RESOURCE, +} from "./constants"; +import { Patterns } from "./patterns"; import { forkedConatServer } from "./start-server"; import { stickyChoice } from "./sticky"; -import { EventEmitter } from "events"; -import { Metrics } from "../types"; +import { sysApi, sysApiSubject, type SysConatServer } from "./sys"; const logger = getLogger("conat:core:server"); @@ -1755,27 +1758,7 @@ export function randomChoice(v: Set): string { // See https://socket.io/how-to/get-the-ip-address-of-the-client function getAddress(socket) { - const header = socket.handshake.headers["forwarded"]; - if (header) { - for (const directive of header.split(",")[0].split(";")) { - if (directive.startsWith("for=")) { - return directive.substring(4); - } - } - } - - let addr = socket.handshake.headers["x-forwarded-for"]?.split(",")?.[0]; - if (addr) { - return addr; - } - for (const other of ["cf-connecting-ip", "fastly-client-ip"]) { - addr = socket.handshake.headers[other]; - if (addr) { - return addr; - } - } - - return socket.handshake.address; + return getClientIpAddress(socket.handshake) ?? socket.handshake.address; } export function updateInterest( diff --git a/src/packages/next/lib/user-id.ts b/src/packages/next/lib/user-id.ts index 4c74dd6aa8c..136855b053c 100644 --- a/src/packages/next/lib/user-id.ts +++ b/src/packages/next/lib/user-id.ts @@ -7,27 +7,28 @@ import type { Request } from "express"; import { getServerSettings } from "@cocalc/database/settings/server-settings"; import { ANALYTICS_COOKIE_NAME } from "@cocalc/util/consts"; +import { getClientIpAddress } from "@cocalc/util/get-client-ip-address"; import { isValidAnonymousID } from "@cocalc/util/misc"; // Get anonymous user ID from cookie or IP address -export async function getAnonymousID(req: Request): Promise { +export async function getAnonymousID( + req: Request, +): Promise { const { analytics_cookie: analytics_enabled } = await getServerSettings(); - + if (analytics_enabled) { const cookie = req.cookies[ANALYTICS_COOKIE_NAME]; if (isValidAnonymousID(cookie)) { return cookie; } } - - // Fall back to IP address - check headers in order of preference - const connectingIp = (req.headers["cf-connecting-ip"] || - req.headers["x-forwarded-for"] || - req.socket?.remoteAddress) as string; - + + // Fall back to IP address + const connectingIp = getClientIpAddress(req); + if (isValidAnonymousID(connectingIp)) { return connectingIp; } - + return undefined; } diff --git a/src/packages/pnpm-lock.yaml b/src/packages/pnpm-lock.yaml index 1fe4499fe01..f4293f41616 100644 --- a/src/packages/pnpm-lock.yaml +++ b/src/packages/pnpm-lock.yaml @@ -970,13 +970,13 @@ importers: version: 2.1.2 next: specifier: 15.3.4 - version: 15.3.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.2) + version: 15.3.4(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.2) next-rest-framework: specifier: 6.0.0-beta.4 version: 6.0.0-beta.4(zod@3.25.76) next-translate: specifier: ^2.6.2 - version: 2.6.2(next@15.3.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.2))(react@19.1.0) + version: 2.6.2(next@15.3.4(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.2))(react@19.1.0) password-hash: specifier: ^1.2.2 version: 1.2.2 @@ -1824,6 +1824,9 @@ importers: redux: specifier: ^4.2.1 version: 4.2.1 + request-ip: + specifier: ^3.3.0 + version: 3.3.0 reselect: specifier: ^4.1.8 version: 4.1.8 @@ -8773,6 +8776,7 @@ packages: node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} @@ -10078,6 +10082,9 @@ packages: resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} engines: {node: '>=0.10'} + request-ip@3.3.0: + resolution: {integrity: sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -19922,12 +19929,12 @@ snapshots: next-tick@1.1.0: {} - next-translate@2.6.2(next@15.3.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.2))(react@19.1.0): + next-translate@2.6.2(next@15.3.4(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.2))(react@19.1.0): dependencies: - next: 15.3.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.2) + next: 15.3.4(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.2) react: 19.1.0 - next@15.3.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.2): + next@15.3.4(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.2): dependencies: '@next/env': 15.3.4 '@swc/counter': 0.1.3 @@ -19937,7 +19944,7 @@ snapshots: postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - styled-jsx: 5.1.6(@babel/core@7.28.0)(react@19.1.0) + styled-jsx: 5.1.6(react@19.1.0) optionalDependencies: '@next/swc-darwin-arm64': 15.3.4 '@next/swc-darwin-x64': 15.3.4 @@ -21601,6 +21608,8 @@ snapshots: repeat-string@1.6.1: {} + request-ip@3.3.0: {} + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -22318,12 +22327,10 @@ snapshots: dependencies: inline-style-parser: 0.2.4 - styled-jsx@5.1.6(@babel/core@7.28.0)(react@19.1.0): + styled-jsx@5.1.6(react@19.1.0): dependencies: client-only: 0.0.1 react: 19.1.0 - optionalDependencies: - '@babel/core': 7.28.0 stylis@4.3.6: {} diff --git a/src/packages/util/get-client-ip-address.test.ts b/src/packages/util/get-client-ip-address.test.ts new file mode 100644 index 00000000000..6ba56eceb40 --- /dev/null +++ b/src/packages/util/get-client-ip-address.test.ts @@ -0,0 +1,292 @@ +import { getClientIpAddress } from "./get-client-ip-address"; + +describe("getClientIpAddress()", () => { + const createRequest = (headers: Record) => ({ headers }); + + describe("Standard Headers Supported by request-ip", () => { + it("should handle CF-Connecting-IP (highest priority)", () => { + const req = createRequest({ + "x-client-ip": "203.0.113.1", + "x-forwarded-for": "192.168.1.1", + "cf-connecting-ip": "198.51.100.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("198.51.100.1"); + }); + + it("should handle X-Forwarded-For with multiple IPs", () => { + const req = createRequest({ + "x-forwarded-for": "203.0.113.1, 192.168.1.1, 10.0.0.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("203.0.113.1"); + }); + + it("should handle CF-Connecting-IP from Cloudflare", () => { + const req = createRequest({ + "cf-connecting-ip": "203.0.113.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("203.0.113.1"); + }); + + it("should handle Fastly-Client-Ip from Fastly", () => { + const req = createRequest({ + "fastly-client-ip": "203.0.113.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("203.0.113.1"); + }); + + it("should handle True-Client-Ip from Akamai/Cloudflare", () => { + const req = createRequest({ + "true-client-ip": "203.0.113.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("203.0.113.1"); + }); + + it("should handle X-Real-IP from nginx", () => { + const req = createRequest({ + "x-real-ip": "203.0.113.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("203.0.113.1"); + }); + + it("should handle X-Cluster-Client-IP from Rackspace", () => { + const req = createRequest({ + "x-cluster-client-ip": "203.0.113.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("203.0.113.1"); + }); + + it("should handle appengine-user-ip from Google App Engine", () => { + const req = createRequest({ + "appengine-user-ip": "203.0.113.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("203.0.113.1"); + }); + }); + + describe("Header Priority Order", () => { + it("should prioritize X-Client-IP over X-Forwarded-For", () => { + const req = createRequest({ + "x-client-ip": "203.0.113.1", + "x-forwarded-for": "192.168.1.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("203.0.113.1"); + }); + + it("should prioritize CF-Connecting-IP over X-Forwarded-For", () => { + const req = createRequest({ + "x-forwarded-for": "203.0.113.1", + "cf-connecting-ip": "192.168.1.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("192.168.1.1"); + }); + + it("should prioritize CF-Connecting-IP over Fastly-Client-Ip", () => { + const req = createRequest({ + "cf-connecting-ip": "203.0.113.1", + "fastly-client-ip": "192.168.1.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("203.0.113.1"); + }); + + it("should prioritize Fastly-Client-Ip over True-Client-Ip", () => { + const req = createRequest({ + "fastly-client-ip": "203.0.113.1", + "true-client-ip": "192.168.1.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("203.0.113.1"); + }); + + it("should prioritize True-Client-Ip over X-Real-IP", () => { + const req = createRequest({ + "true-client-ip": "203.0.113.1", + "x-real-ip": "192.168.1.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("203.0.113.1"); + }); + }); + + describe("Case Sensitivity (Headers are lowercase in Node.js)", () => { + it("should handle uppercase headers (converted to lowercase by Node.js)", () => { + const req = createRequest({ + "X-CLIENT-IP": "203.0.113.1", // This would be lowercase in real Node.js + }); + + const result = getClientIpAddress(req); + expect(result).toBe("203.0.113.1"); + }); + + it("should handle mixed case headers", () => { + const req = createRequest({ + "X-Forwarded-For": "203.0.113.1, 192.168.1.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("203.0.113.1"); + }); + }); + + describe("Forwarded Header Fallback (when request-ip fails)", () => { + it("should parse simple Forwarded header", () => { + const req = createRequest({ + forwarded: "for=192.0.2.60", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("192.0.2.60"); + }); + + it("should parse quoted Forwarded header", () => { + const req = createRequest({ + forwarded: 'for="192.0.2.60"', + }); + + const result = getClientIpAddress(req); + expect(result).toBe("192.0.2.60"); + }); + + it("should parse Forwarded header with IPv6 brackets", () => { + const req = createRequest({ + forwarded: 'for="[2001:db8:cafe::17]"', + }); + + const result = getClientIpAddress(req); + expect(result).toBe("2001:db8:cafe::17"); + }); + + it("should handle port stripping for IPv4", () => { + const req = createRequest({ + forwarded: "for=192.0.2.60:4711", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("192.0.2.60"); + }); + + it("should handle multiple parameters in Forwarded header", () => { + const req = createRequest({ + forwarded: "for=192.0.2.60;proto=http;by=203.0.113.43", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("192.0.2.60"); + }); + + it("should handle case-insensitive FOR parameter", () => { + const req = createRequest({ + forwarded: "For=192.0.2.60", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("192.0.2.60"); + }); + + it("should skip invalid entries and use first valid IP", () => { + const req = createRequest({ + forwarded: "for=_gazonk, for=192.0.2.60", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("192.0.2.60"); + }); + + it("should return undefined when no valid for= parameter exists", () => { + const req = createRequest({ + forwarded: "proto=http;by=203.0.113.43", + }); + + const result = getClientIpAddress(req); + expect(result).toBeUndefined(); + }); + }); + + describe("IPv6 Support", () => { + it("should handle IPv6 addresses in X-Forwarded-For", () => { + const req = createRequest({ + "x-forwarded-for": "2001:db8:85a3:8d3:1319:8a2e:370:7348", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("2001:db8:85a3:8d3:1319:8a2e:370:7348"); + }); + + it("should handle compressed IPv6 addresses", () => { + const req = createRequest({ + "x-forwarded-for": "2001:db8::1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("2001:db8::1"); + }); + + it("should handle IPv6 loopback", () => { + const req = createRequest({ + "x-forwarded-for": "::1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("::1"); + }); + }); + + describe("Edge Cases", () => { + it("should return undefined for empty headers", () => { + const req = createRequest({}); + + const result = getClientIpAddress(req); + expect(result).toBeUndefined(); + }); + + it("should return undefined for invalid IP addresses", () => { + const req = createRequest({ + "x-forwarded-for": "not.an.ip.address", + }); + + const result = getClientIpAddress(req); + expect(result).toBeUndefined(); + }); + + it("should handle localhost addresses", () => { + const req = createRequest({ + "x-forwarded-for": "127.0.0.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("127.0.0.1"); + }); + + it("should handle private IP addresses", () => { + const req = createRequest({ + "x-forwarded-for": "192.168.1.1", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("192.168.1.1"); + }); + }); +}); diff --git a/src/packages/util/get-client-ip-address.ts b/src/packages/util/get-client-ip-address.ts new file mode 100644 index 00000000000..eb529c3545f --- /dev/null +++ b/src/packages/util/get-client-ip-address.ts @@ -0,0 +1,106 @@ +import { isIP } from "net"; +import { getClientIp } from "request-ip"; + +export function getClientIpAddress(req: { + headers: Record; +}): string | undefined { + // Try manual extraction for headers not supported by request-ip + const headersToCheck = [ + "cf-connecting-ip", // prioritize cloudflare + "x-client-ip", + "x-forwarded-for", + "fastly-client-ip", + "true-client-ip", + "x-real-ip", + "x-cluster-client-ip", + "appengine-user-ip", + ]; + + // Check each header (case-insensitive) + for (const headerName of headersToCheck) { + const headerValue = getHeaderValue(req.headers, headerName); + if (headerValue) { + // Handle comma-separated values (like X-Forwarded-For) + const ips = headerValue.split(",").map((ip) => ip.trim()); + for (const ip of ips) { + if (isIP(ip)) { + return ip; + } + } + } + } + + // Try request-ip package as fallback + const ip = getClientIp(req); + if (ip && isIP(ip)) { + return ip; + } + + // Fallback "Forwarded" header parsing, because this is not merged: + // https://github.com/pbojinov/request-ip/pull/71 + const forwardedHeader = getHeaderValue(req.headers, "forwarded"); + if (forwardedHeader) { + // Split by comma for multiple forwarded entries + const forwardedEntries = forwardedHeader.split(","); + + for (const entry of forwardedEntries) { + // Split by semicolon for parameters + const params = entry.split(";"); + + for (const param of params) { + const trimmed = param.trim(); + if (trimmed.toLowerCase().startsWith("for=")) { + let ipVal = trimmed.substring(4).trim(); + + // Remove quotes if present + if (ipVal.startsWith('"') && ipVal.endsWith('"')) { + ipVal = ipVal.slice(1, -1); + } + + // Handle IPv6 brackets + if (ipVal.startsWith("[") && ipVal.endsWith("]")) { + ipVal = ipVal.slice(1, -1); + } + + // Handle port stripping for IPv4 addresses + if (ipVal.includes(":")) { + const parts = ipVal.split(":"); + // Only strip port if it looks like IPv4:port (not IPv6) + if (parts.length === 2 && isIP(parts[0])) { + ipVal = parts[0]; + } + } + + if (isIP(ipVal)) { + return ipVal; + } + } + } + } + } + + return undefined; +} + +// Helper function to get header value case-insensitively +function getHeaderValue( + headers: Record, + name: string, +): string | undefined { + const lowerName = name.toLowerCase(); + + // Check exact match first + const exactMatch = headers[lowerName]; + if (exactMatch) { + return Array.isArray(exactMatch) ? exactMatch[0] : exactMatch; + } + + // Check case-insensitive match + for (const [key, value] of Object.entries(headers)) { + if (key.toLowerCase() === lowerName && value) { + return Array.isArray(value) ? value[0] : value; + } + } + + return undefined; +} diff --git a/src/packages/util/package.json b/src/packages/util/package.json index e9656ac1e38..26ab972760f 100644 --- a/src/packages/util/package.json +++ b/src/packages/util/package.json @@ -58,6 +58,7 @@ "react": "^19.1.0", "react-intl": "^7.1.11", "redux": "^4.2.1", + "request-ip": "^3.3.0", "reselect": "^4.1.8", "sha1": "^1.1.1", "underscore": "^1.12.1", From 1e9a15664c6ae027f5a3652decdda9b7289a4adf Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Fri, 29 Aug 2025 11:26:40 +0200 Subject: [PATCH 05/58] util/get-client-ip: also normalize IPv6 with port numbers --- .../util/get-client-ip-address.test.ts | 72 +++++++++++++++++++ src/packages/util/get-client-ip-address.ts | 67 +++++++++++------ 2 files changed, 117 insertions(+), 22 deletions(-) diff --git a/src/packages/util/get-client-ip-address.test.ts b/src/packages/util/get-client-ip-address.test.ts index 6ba56eceb40..833a883a626 100644 --- a/src/packages/util/get-client-ip-address.test.ts +++ b/src/packages/util/get-client-ip-address.test.ts @@ -188,6 +188,15 @@ describe("getClientIpAddress()", () => { expect(result).toBe("192.0.2.60"); }); + it("should handle IPv4 addresses with ports in X-Forwarded-For", () => { + const req = createRequest({ + "x-forwarded-for": "192.168.1.1:8080", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("192.168.1.1"); + }); + it("should handle multiple parameters in Forwarded header", () => { const req = createRequest({ forwarded: "for=192.0.2.60;proto=http;by=203.0.113.43", @@ -223,6 +232,33 @@ describe("getClientIpAddress()", () => { const result = getClientIpAddress(req); expect(result).toBeUndefined(); }); + + it("should handle Forwarded header with spaces around commas", () => { + const req = createRequest({ + forwarded: "for=192.0.2.60 , for=203.0.113.43", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("192.0.2.60"); + }); + + it("should handle Forwarded header with spaces around semicolons", () => { + const req = createRequest({ + forwarded: "for=192.0.2.60 ; proto=http ; by=203.0.113.43", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("192.0.2.60"); + }); + + it("should handle Forwarded header with mixed spacing", () => { + const req = createRequest({ + forwarded: " for=192.0.2.60 ; proto=http ; by=203.0.113.43 ", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("192.0.2.60"); + }); }); describe("IPv6 Support", () => { @@ -252,6 +288,24 @@ describe("getClientIpAddress()", () => { const result = getClientIpAddress(req); expect(result).toBe("::1"); }); + + it("should handle IPv6 addresses with ports", () => { + const req = createRequest({ + "x-forwarded-for": "[2001:db8::1]:8080", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("2001:db8::1"); + }); + + it("should handle compressed IPv6 addresses with ports", () => { + const req = createRequest({ + "x-forwarded-for": "[::1]:9000", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("::1"); + }); }); describe("Edge Cases", () => { @@ -288,5 +342,23 @@ describe("getClientIpAddress()", () => { const result = getClientIpAddress(req); expect(result).toBe("192.168.1.1"); }); + + it("should handle IP addresses with whitespace", () => { + const req = createRequest({ + "x-forwarded-for": " 192.168.1.1 ", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("192.168.1.1"); + }); + + it("should handle IPv6 addresses with whitespace and ports", () => { + const req = createRequest({ + "x-forwarded-for": " [2001:db8::1]:8080 ", + }); + + const result = getClientIpAddress(req); + expect(result).toBe("2001:db8::1"); + }); }); }); diff --git a/src/packages/util/get-client-ip-address.ts b/src/packages/util/get-client-ip-address.ts index eb529c3545f..85313d89106 100644 --- a/src/packages/util/get-client-ip-address.ts +++ b/src/packages/util/get-client-ip-address.ts @@ -23,8 +23,9 @@ export function getClientIpAddress(req: { // Handle comma-separated values (like X-Forwarded-For) const ips = headerValue.split(",").map((ip) => ip.trim()); for (const ip of ips) { - if (isIP(ip)) { - return ip; + const processedIp = normalizeIPAddress(ip); + if (isIP(processedIp)) { + return processedIp; } } } @@ -40,36 +41,24 @@ export function getClientIpAddress(req: { // https://github.com/pbojinov/request-ip/pull/71 const forwardedHeader = getHeaderValue(req.headers, "forwarded"); if (forwardedHeader) { - // Split by comma for multiple forwarded entries - const forwardedEntries = forwardedHeader.split(","); + // Split by comma for multiple forwarded entries, trimming each entry + const forwardedEntries = forwardedHeader.split(",").map(entry => entry.trim()); for (const entry of forwardedEntries) { - // Split by semicolon for parameters - const params = entry.split(";"); + // Split by semicolon for parameters, trimming each parameter + const params = entry.split(";").map(param => param.trim()); for (const param of params) { - const trimmed = param.trim(); - if (trimmed.toLowerCase().startsWith("for=")) { - let ipVal = trimmed.substring(4).trim(); + if (param.toLowerCase().startsWith("for=")) { + let ipVal = param.substring(4).trim(); // Remove quotes if present if (ipVal.startsWith('"') && ipVal.endsWith('"')) { ipVal = ipVal.slice(1, -1); } - // Handle IPv6 brackets - if (ipVal.startsWith("[") && ipVal.endsWith("]")) { - ipVal = ipVal.slice(1, -1); - } - - // Handle port stripping for IPv4 addresses - if (ipVal.includes(":")) { - const parts = ipVal.split(":"); - // Only strip port if it looks like IPv4:port (not IPv6) - if (parts.length === 2 && isIP(parts[0])) { - ipVal = parts[0]; - } - } + // Normalize IP address (remove brackets and ports) + ipVal = normalizeIPAddress(ipVal); if (isIP(ipVal)) { return ipVal; @@ -82,6 +71,40 @@ export function getClientIpAddress(req: { return undefined; } +// Helper function to normalize IP address by removing brackets and ports +function normalizeIPAddress(ip: string): string { + let processedIp = ip.trim(); + + // Remove IPv6 brackets if present (do this first!) + const bracketStart = processedIp.startsWith("["); + const closingBracketIndex = processedIp.indexOf("]"); + const hasPortAfterBracket = closingBracketIndex > 0 && processedIp[closingBracketIndex + 1] === ":"; + if (bracketStart && hasPortAfterBracket) { + // Extract IPv6 part and port: [2001:db8::1]:8080 -> 2001:db8::1:8080 + processedIp = processedIp.substring(1, closingBracketIndex) + processedIp.substring(closingBracketIndex + 1); + } else if (processedIp.startsWith("[") && processedIp.endsWith("]")) { + // Simple bracket removal: [2001:db8::1] -> 2001:db8::1 + processedIp = processedIp.slice(1, -1); + } + + // Strip port if present (handles both IPv4:port and IPv6:port) + if (processedIp.includes(":")) { + const lastColonIndex = processedIp.lastIndexOf(":"); + if (lastColonIndex > 0) { + const potentialPort = processedIp.substring(lastColonIndex + 1); + // If the part after the last colon looks like a port number + if (/^\d+$/.test(potentialPort)) { + const potentialIP = processedIp.substring(0, lastColonIndex); + if (isIP(potentialIP)) { + processedIp = potentialIP; + } + } + } + } + + return processedIp; +} + // Helper function to get header value case-insensitively function getHeaderValue( headers: Record, From 689fecf1cb1d6271ebf6e495edddbd7754baf91c Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Sat, 27 Sep 2025 16:30:00 +0200 Subject: [PATCH 06/58] cocalc-api: fix docstring style, check with pyright as well, fix/ignore missing return types, generate a better README --- src/.claude/settings.json | 9 +- src/CLAUDE.md | 87 +++++++++ src/python/cocalc-api/Makefile | 4 + src/python/cocalc-api/README.md | 169 +++++++++++++++++- src/python/cocalc-api/pyproject.toml | 13 ++ src/python/cocalc-api/src/cocalc_api/hub.py | 157 ++++++++-------- .../cocalc-api/src/cocalc_api/project.py | 56 +++--- src/python/cocalc-api/src/cocalc_api/util.py | 10 +- src/python/cocalc-api/uv.lock | 39 ++++ 9 files changed, 415 insertions(+), 129 deletions(-) diff --git a/src/.claude/settings.json b/src/.claude/settings.json index 9dc65847b1c..67a9723b922 100644 --- a/src/.claude/settings.json +++ b/src/.claude/settings.json @@ -15,6 +15,7 @@ "Bash(git commit:*)", "Bash(git push:*)", "Bash(grep:*)", + "Bash(make:*)", "Bash(node:*)", "Bash(npm show:*)", "Bash(npm view:*)", @@ -23,18 +24,23 @@ "Bash(pnpm audit:*)", "Bash(pnpm build:*)", "Bash(pnpm i18n:*)", + "Bash(pnpm i18n:compile:*)", + "Bash(pnpm i18n:download:*)", + "Bash(pnpm i18n:extract:*)", + "Bash(pnpm i18n:upload:*)", "Bash(pnpm info:*)", "Bash(pnpm list:*)", "Bash(pnpm remove:*)", "Bash(pnpm run:*)", "Bash(pnpm ts-build:*)", "Bash(pnpm tsc:*)", - "Bash(pnpm view:*)", "Bash(pnpm update:*)", + "Bash(pnpm view:*)", "Bash(pnpm why:*)", "Bash(prettier -w:*)", "Bash(psql:*)", "Bash(python3:*)", + "Bash(uv:*)", "WebFetch", "WebSearch", "mcp__cclsp__find_definition", @@ -43,7 +49,6 @@ "mcp__github__get_issue_comments", "mcp__github__get_pull_request", "mcp__github__get_pull_request_comments", - "mcp__github__get_pull_request_comments", "mcp__github__get_pull_request_status", "mcp__github__list_workflow_runs", "mcp__github__list_workflows" diff --git a/src/CLAUDE.md b/src/CLAUDE.md index c69b28041ce..48e6902cfa9 100644 --- a/src/CLAUDE.md +++ b/src/CLAUDE.md @@ -222,6 +222,93 @@ Same flow as above, but **before 3. i18n:upload**, delete the key. Only new keys - Ignore everything in `node_modules` or `dist` directories - Ignore all files not tracked by Git, unless they are newly created files +# CoCalc Python API Client + +## Overview + +The `python/cocalc-api/` directory contains a Python client library for the CoCalc API, published as the `cocalc-api` package on PyPI. + +## Architecture + +### Package Structure + +- **`src/cocalc_api/`** - Main Python package source code + - `__init__.py` - Package exports (Hub, Project classes) + - `hub.py` - Hub client for account-level API operations + - `project.py` - Project client for project-specific operations + - `api_types.py` - TypedDict definitions for API responses + - `util.py` - Utility functions and decorators + +### Key Classes + +#### Hub Client (`hub.py`) + +Account-level API client that provides access to: + +- **System** - Server ping, user search, account name resolution +- **Projects** - Project management (create, start, stop, collaborators) +- **Jupyter** - Global Jupyter kernel execution +- **Database** - Direct PostgreSQL database queries +- **Messages** - Send/receive messages between users +- **Organizations** - Organization management (admin functions) +- **Sync** - File history and synchronization + +#### Project Client (`project.py`) + +Project-specific API client for: + +- **System** - Project ping, shell command execution, Jupyter execution + +### Development Tools + +- **Package Manager**: `uv` (modern Python package manager) +- **Code Formatter**: `yapf` (Python code formatter following Google style) +- **Code Quality**: `ruff` (linting), `mypy` (type checking), `pyright` (additional type checking) +- **Documentation**: `mkdocs` with material theme +- **Testing**: `pytest` + +### Development Commands + +```bash +# Setup and install dependencies +make install # or: uv sync --dev && uv pip install -e . + +# Format Python code +make format # or: uv run yapf --in-place --recursive src/ + +# Code quality checks +make check # or: uv run ruff check src/ && uv run mypy src/ && uv run pyright src/ + +# Documentation +make serve-docs # or: uv run mkdocs serve +make build-docs # or: uv run mkdocs build + +# Publishing +make publish # or: uv build && uv publish + +# Cleanup +make clean +``` + +### API Design Patterns + +- **Decorator-based Methods**: Uses `@api_method()` decorator to automatically convert method calls to API requests +- **TypedDict Responses**: All API responses use TypedDict for type safety +- **Error Handling**: Centralized error handling via `handle_error()` utility +- **HTTP Client**: Uses `httpx` for HTTP requests with authentication +- **Nested Namespaces**: API organized into logical namespaces (system, projects, jupyter, etc.) + +### Authentication + +- Supports both account-level and project-specific API keys +- Account API keys provide full access to all hub functionality +- Project API keys are limited to project-specific operations + +### Connection Endpoints + +- **Hub API**: `POST /api/conat/hub` - Account-level operations +- **Project API**: `POST /api/conat/project` - Project-specific operations + # important-instruction-reminders Do what has been asked; nothing more, nothing less. diff --git a/src/python/cocalc-api/Makefile b/src/python/cocalc-api/Makefile index 44dcadf2672..9b41e3ab997 100644 --- a/src/python/cocalc-api/Makefile +++ b/src/python/cocalc-api/Makefile @@ -5,9 +5,13 @@ install: uv sync --dev uv pip install -e . +format: + uv run yapf --in-place --recursive src/ + check: uv run ruff check src/ uv run mypy src/ + uv run pyright src/ serve-docs: uv run mkdocs serve diff --git a/src/python/cocalc-api/README.md b/src/python/cocalc-api/README.md index 4d12cc80ccc..d530d3ce948 100644 --- a/src/python/cocalc-api/README.md +++ b/src/python/cocalc-api/README.md @@ -1,3 +1,168 @@ -https://pypi.org/project/cocalc-api/ +# CoCalc Python API Client -This is a Python package that provides an API client for https://cocalc.com \ No newline at end of file +[![PyPI version](https://badge.fury.io/py/cocalc-api.svg)](https://pypi.org/project/cocalc-api/) +[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/) + +This is a Python package that provides an API client for [CoCalc](https://cocalc.com), enabling programmatic access to CoCalc's features including project management, Jupyter execution, file operations, messaging, and organization management. + +## Installation + +```bash +pip install cocalc-api +``` + +## Quick Start + +```python +import cocalc_api + +# Initialize hub client with your API key +hub = cocalc_api.Hub(api_key="your-api-key") + +# Ping the server +response = hub.system.ping() +print(f"Server time: {response['now']}") + +# List your projects +projects = hub.projects.get() +for project in projects: + print(f"Project: {project['title']} ({project['project_id']})") +``` + +## Features + +### Hub Client (Account-Level Operations) + +The `Hub` class provides access to account-level operations: + +- **System**: Server ping, user search, account name resolution +- **Projects**: Project management (create, start, stop, add/remove collaborators) +- **Jupyter**: Execute code using Jupyter kernels in any project or anonymously +- **Database**: Direct PostgreSQL database queries for advanced operations +- **Messages**: Send and receive messages between users +- **Organizations**: Manage organizations, users, and temporary access tokens +- **Sync**: Access file edit history and synchronization features + +### Project Client (Project-Specific Operations) + +The `Project` class provides project-specific operations: + +- **System**: Execute shell commands and Jupyter code within a specific project + +## Authentication + +The client supports two types of API keys: + +1. **Account API Keys**: Provide full access to all hub functionality +2. **Project API Keys**: Limited to project-specific operations + +Get your API key from [CoCalc Account Settings](https://cocalc.com/settings/account) under "API Keys". + +## Architecture + +### Package Structure + +``` +src/cocalc_api/ +├── __init__.py # Package exports (Hub, Project classes) +├── hub.py # Hub client for account-level operations +├── project.py # Project client for project-specific operations +├── api_types.py # TypedDict definitions for API responses +└── util.py # Utility functions and decorators +``` + +### Design Patterns + +- **Decorator-based Methods**: Uses `@api_method()` decorator to automatically convert method calls to API requests +- **TypedDict Responses**: All API responses use TypedDict for type safety +- **Error Handling**: Centralized error handling via `handle_error()` utility +- **HTTP Client**: Uses `httpx` for HTTP requests with authentication +- **Nested Namespaces**: API organized into logical namespaces (system, projects, jupyter, etc.) + +## Development + +### Requirements + +- Python 3.9+ +- [uv](https://github.com/astral-sh/uv) package manager + +### Setup + +```bash +# Install dependencies +make install +# or: uv sync --dev && uv pip install -e . + +# Format Python code +make format +# or: uv run yapf --in-place --recursive src/ + +# Run code quality checks +make check +# or: uv run ruff check src/ && uv run mypy src/ && uv run pyright src/ + +# Serve documentation locally +make serve-docs +# or: uv run mkdocs serve + +# Build documentation +make build-docs +``` + +### Code Quality + +This project uses multiple tools for code quality: + +- **[YAPF](https://github.com/google/yapf)**: Python code formatter +- **[Ruff](https://docs.astral.sh/ruff/)**: Fast Python linter +- **[MyPy](http://mypy-lang.org/)**: Static type checking +- **[Pyright](https://github.com/microsoft/pyright)**: Additional static type checking +- **[MkDocs](https://www.mkdocs.org/)**: Documentation generation + +### Documentation Standards + +All docstrings follow the [Google Style Guide](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) for Python docstrings. This includes: + +- Clear one-line summary +- Detailed description when needed +- Properly formatted `Args:`, `Returns:`, `Raises:`, and `Examples:` sections +- Type information consistent with function signatures +- Consistent capitalization and punctuation + +Example: +```python +def example_function(param1: str, param2: Optional[int] = None) -> dict[str, Any]: + """ + Brief description of the function. + + Longer description if needed, explaining the function's behavior, + side effects, or important usage notes. + + Args: + param1 (str): Description of the first parameter. + param2 (Optional[int]): Description of the optional parameter. + + Returns: + dict[str, Any]: Description of the return value. + + Raises: + ValueError: When this exception might be raised. + + Examples: + >>> result = example_function("hello", 42) + >>> print(result) + {'status': 'success', 'data': 'hello'} + """ +``` + +## License + +MIT License. See the [LICENSE](LICENSE) file for details. + +## Links + +- [PyPI Package](https://pypi.org/project/cocalc-api/) +- [CoCalc Website](https://cocalc.com) +- [Documentation](https://cocalc.com/api/python) +- [Source Code](https://github.com/sagemathinc/cocalc/tree/master/src/python/cocalc-api) +- [Issue Tracker](https://github.com/sagemathinc/cocalc/issues) \ No newline at end of file diff --git a/src/python/cocalc-api/pyproject.toml b/src/python/cocalc-api/pyproject.toml index 283f60d7755..9e8fbb7ac42 100644 --- a/src/python/cocalc-api/pyproject.toml +++ b/src/python/cocalc-api/pyproject.toml @@ -23,6 +23,17 @@ Issues = "https://github.com/sagemathinc/cocalc/issues" python_version = "3.13" # strict = true # disallow_untyped_defs = true +# Ignore empty-body errors for decorator-implemented methods +disable_error_code = ["empty-body"] + +[tool.pyright] +# Ignore return type errors for decorator-implemented methods +reportReturnType = false + +[tool.yapf] +based_on_style = "pep8" +column_limit = 150 +indent_width = 4 [tool.ruff] line-length = 150 @@ -35,6 +46,8 @@ dev = [ "mkdocs-material", "mkdocstrings[python]", "mypy", + "pyright", "pytest>=8.4.1", "ruff>=0.12.11", + "yapf", ] diff --git a/src/python/cocalc-api/src/cocalc_api/hub.py b/src/python/cocalc-api/src/cocalc_api/hub.py index fbd753783b1..cff7f8ad261 100644 --- a/src/python/cocalc-api/src/cocalc_api/hub.py +++ b/src/python/cocalc-api/src/cocalc_api/hub.py @@ -9,13 +9,9 @@ class Hub: def __init__(self, api_key: str, host: str = "https://cocalc.com"): self.api_key = api_key self.host = host - self.client = httpx.Client( - auth=(api_key, ""), headers={"Content-Type": "application/json"}) + self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}) - def call(self, - name: str, - arguments: list[Any], - timeout: Optional[int] = None) -> Any: + def call(self, name: str, arguments: list[Any], timeout: Optional[int] = None) -> Any: """ Perform an API call to the CoCalc backend. @@ -81,7 +77,7 @@ def ping(self) -> PingResponse: Ping the server. Returns: - Any: JSON object containing the current server time. + PingResponse: JSON object containing the current server time. """ raise NotImplementedError @@ -93,7 +89,7 @@ def get_names(self, account_ids: list[str]) -> list[str]: account_ids (list[str]): List of account UUID strings. Returns: - Any: Mapping from account_id to profile information. + list[str]: Mapping from account_id to profile information. """ return self._parent.call("system.getNames", [account_ids]) @@ -106,12 +102,11 @@ def user_search(self, query: str) -> UserSearchResult: query (str): A query, e.g., partial name, email address, etc. Returns: - list[UserSearchResult]: array of dicts with account_id, name, + UserSearchResult: Array of dicts with account_id, name, first_name, last_name, last_active (in ms since epoch), created (in ms since epoch) and email_address_verified. Examples: - Search for myself: >>> import cocalc_api; hub = cocalc_api.Hub(api_key="sk...") @@ -124,10 +119,10 @@ def user_search(self, query: str) -> UserSearchResult: 'created': 1756056224470, 'email_address_verified': None}] - You can search by email address to ONLY get the user - that has that email address: + You can search by email address to ONLY get the user + that has that email address: - >>> hub.system.user_search('wstein@gmail.com') + >>> hub.system.user_search('wstein@gmail.com') [{'account_id': 'd0bdabfd-850e-4c8d-8510-f6f1ecb9a5eb', 'first_name': 'W', 'last_name': 'Stein', @@ -145,25 +140,22 @@ class Projects: def __init__(self, parent: "Hub"): self._parent = parent - def get(self, - fields: Optional[list[str]] = None, - all: Optional[bool] = False, - project_id: Optional[str] = None): + def get(self, fields: Optional[list[str]] = None, all: Optional[bool] = False, project_id: Optional[str] = None) -> list[dict[str, Any]]: """ - Get data about projects that you are a collaborator on. Only gets + Get data about projects that you are a collaborator on. Only gets recent projects by default; set all=True to get all projects. Args: - fields (Optional[list[str]]): the fields about the project to get. - default: ['project_id', 'title', 'last_edited', 'state'], but see + fields (Optional[list[str]]): The fields about the project to get. + Default: ['project_id', 'title', 'last_edited', 'state'], but see https://github.com/sagemathinc/cocalc/blob/master/src/packages/util/db-schema/projects.ts - all (Optional[bool]): if True, return ALL your projects, + all (Optional[bool]): If True, return ALL your projects, not just the recent ones. False by default. - project_id (Optional[string]): if given as a project_id, gets just the - one project (as a length of length 1). + project_id (Optional[str]): If given, gets just this + one project (as a list of length 1). Returns: - list[dict[str,Any]]: list of projects + list[dict[str, Any]]: List of projects. """ if fields is None: fields = ['project_id', 'title', 'last_edited', 'state'] @@ -185,7 +177,7 @@ def copy_path_between_projects( src_path: str, target_project_id: Optional[str] = None, target_path: Optional[str] = None, - ): + ) -> dict[str, Any]: # type: ignore[empty-body] """ Copy a path from one project to another (or within a project). @@ -196,7 +188,7 @@ def copy_path_between_projects( target_path (Optional[str]): Target path in the target project. Defaults to src_path. Returns: - Any: JSON response indicating success or error. + dict[str, Any]: JSON response indicating success or error. """ ... @@ -224,8 +216,7 @@ def create_project( raise NotImplementedError @api_method("projects.addCollaborator", opts=True) - def add_collaborator(self, project_id: str | list[str], - account_id: str | list[str]): + def add_collaborator(self, project_id: str | list[str], account_id: str | list[str]) -> dict[str, Any]: """ Add a collaborator to a project. @@ -239,12 +230,12 @@ def add_collaborator(self, project_id: str | list[str], `project_id[i]`. Returns: - Any: JSON response from the API. + dict[str, Any]: JSON response from the API. """ ... @api_method("projects.removeCollaborator", opts=True) - def remove_collaborator(self, project_id: str, account_id: str): + def remove_collaborator(self, project_id: str, account_id: str) -> dict[str, Any]: """ Remove a collaborator from a project. @@ -253,27 +244,27 @@ def remove_collaborator(self, project_id: str, account_id: str): account_id (str): Account ID of the user to remove. Returns: - Any: JSON response from the API. + dict[str, Any]: JSON response from the API. """ ... @api_method("projects.start") - def start(self, project_id: str): + def start(self, project_id: str) -> dict[str, Any]: """ Start a project. Args: - project_id (str): project_id of the project to start + project_id (str): Project ID of the project to start. """ ... @api_method("projects.stop") - def stop(self, project_id: str): + def stop(self, project_id: str) -> dict[str, Any]: """ Stop a project. Args: - project_id (str): project_id of the project to stop + project_id (str): Project ID of the project to stop. """ ... @@ -284,7 +275,7 @@ def __init__(self, parent: "Hub"): self._parent = parent @api_method("jupyter.kernels") - def kernels(self, project_id: Optional[str] = None): + def kernels(self, project_id: Optional[str] = None) -> dict[str, Any]: """ Get specifications of available Jupyter kernels. @@ -293,7 +284,7 @@ def kernels(self, project_id: Optional[str] = None): If not given, a global anonymous project may be used. Returns: - Any: JSON response containing kernel specs. + dict[str, Any]: JSON response containing kernel specs. """ ... @@ -305,19 +296,19 @@ def execute( history: Optional[list[str]] = None, project_id: Optional[str] = None, path: Optional[str] = None, - ): + ) -> dict[str, Any]: # type: ignore[empty-body] """ Execute code using a Jupyter kernel. Args: input (str): Code to execute. - kernel (Optional[str]): Name of kernel to use. Get options using jupyter.kernels() + kernel (str): Name of kernel to use. Get options using jupyter.kernels(). history (Optional[list[str]]): Array of previous inputs (they get evaluated every time, but without output being captured). project_id (Optional[str]): Project in which to run the code -- if not given, global anonymous project is used, if available. path (Optional[str]): File path context for execution. Returns: - Any: JSON response containing execution results. + dict[str, Any]: JSON response containing execution results. Examples: Execute a simple sum using a Jupyter kernel: @@ -343,16 +334,16 @@ def __init__(self, parent: "Hub"): self._parent = parent @api_method("sync.history") - def history(self, project_id: str, path: str): + def history(self, project_id: str, path: str) -> list[dict[str, Any]]: # type: ignore[empty-body] """ Get complete edit history of a file. Args: - project_id (str): The project_id of the project containing the file. + project_id (str): The project ID of the project containing the file. path (str): The path to the file. Returns: - Any: Array of patches in a compressed diff-match-patch format, along with time and user data. + list[dict[str, Any]]: Array of patches in a compressed diff-match-patch format, along with time and user data. """ ... @@ -365,10 +356,10 @@ def __init__(self, parent: "Hub"): @api_method("db.userQuery") def query(self, query: dict[str, Any]) -> dict[str, Any]: """ - Do a user query. The input is of one of the following forms, where the tables are defined at + Do a user query. The input is of one of the following forms, where the tables are defined at https://github.com/sagemathinc/cocalc/tree/master/src/packages/util/db-schema - - `{"table-name":{"key":"value", ...}}` with no None values sets one record in the database + - `{"table-name":{"key":"value", ...}}` with no None values sets one record in the database - `{"table-name":[{"key":"value", "key2":None...}]}` gets an array of all matching records in the database, filling in None's with the actual values. - `{"table-name:{"key":"value", "key2":None}}` gets one record, filling in None's with actual values. @@ -379,8 +370,7 @@ def query(self, query: dict[str, Any]) -> dict[str, Any]: query (dict[str, Any]): Object that defines the query, as explained above. Examples: - - Get and also change your first name: + Get and also change your first name: >>> import cocalc_api; hub = cocalc_api.Hub(api_key="sk...") >>> hub.db.query({"accounts":{"first_name":None}}) @@ -399,22 +389,18 @@ def __init__(self, parent: "Hub"): self._parent = parent @api_method("messages.send") - def send(self, - subject: str, - body: str, - to_ids: list[str], - reply_id: Optional[int] = None) -> int: + def send(self, subject: str, body: str, to_ids: list[str], reply_id: Optional[int] = None) -> int: """ Send a message to one or more users. Args: - subject (str): short plain text subject of the message - body (str): Longer markdown body of the message (math typesetting and cocalc links work) - to_ids (list[str]): email addresses or account_id of each recipients - reply_id (Optional[int]): optional message you're replying to (for threading) + subject (str): Short plain text subject of the message. + body (str): Longer markdown body of the message (math typesetting and cocalc links work). + to_ids (list[str]): Email addresses or account_id of each recipient. + reply_id (Optional[int]): Optional message you're replying to (for threading). Returns: - int: id of the message + int: ID of the message. """ raise NotImplementedError @@ -423,11 +409,18 @@ def get( self, limit: Optional[int] = None, offset: Optional[int] = None, - type: Optional[Literal["received", "sent", "new", "starred", - "liked"]] = None, - ) -> list[MessageType]: + type: Optional[Literal["received", "sent", "new", "starred", "liked"]] = None, + ) -> list[MessageType]: # type: ignore[empty-body] """ Get your messages. + + Args: + limit (Optional[int]): Maximum number of messages to return. + offset (Optional[int]): Number of messages to skip. + type (Optional[Literal]): Filter by message type. + + Returns: + list[MessageType]: List of messages. """ raise NotImplementedError @@ -445,39 +438,39 @@ def __init__(self, parent: "Hub"): self._parent = parent @api_method("org.getAll") - def get_all(self): + def get_all(self) -> dict[str, Any]: """ Get all organizations (site admins only). Returns: - Any: ... + dict[str, Any]: Organization data. """ raise NotImplementedError @api_method("org.create") - def create(self, name: str): + def create(self, name: str) -> dict[str, Any]: """ Create an organization (site admins only). Args: - name (str) - name of the organization; must be globally unique, - at most 39 characters, and CANNOT BE CHANGED + name (str): Name of the organization; must be globally unique, + at most 39 characters, and CANNOT BE CHANGED. Returns: - Any: ... + dict[str, Any]: Organization data. """ raise NotImplementedError @api_method("org.get") - def get(self, name: str): + def get(self, name: str) -> dict[str, Any]: """ - Get an organization + Get an organization. Args: - name (str) - name of the organization + name (str): Name of the organization. Returns: - Any: ... + dict[str, Any]: Organization data. """ raise NotImplementedError @@ -487,22 +480,22 @@ def set(self, title: Optional[str] = None, description: Optional[str] = None, email_address: Optional[str] = None, - link: Optional[str] = None): + link: Optional[str] = None) -> dict[str, Any]: """ Set properties of an organization. Args: - name (str): name of the organization - title (Optional[str]): the title of the organization - description (Optional[str]): description of the organization - email_address (Optional[str]): email address to reach the organization - (nothing to do with a cocalc account) - link (Optional[str]): a website of the organization + name (str): Name of the organization. + title (Optional[str]): The title of the organization. + description (Optional[str]): Description of the organization. + email_address (Optional[str]): Email address to reach the organization + (nothing to do with a cocalc account). + link (Optional[str]): A website of the organization. """ raise NotImplementedError @api_method("org.addAdmin") - def add_admin(self, name: str, user: str): + def add_admin(self, name: str, user: str) -> dict[str, Any]: """ Make the user with given account_id or email an admin of the named organization. @@ -514,7 +507,7 @@ def add_admin(self, name: str, user: str): raise NotImplementedError @api_method("org.addUser") - def add_user(self, name: str, user: str): + def add_user(self, name: str, user: str) -> dict[str, Any]: """ Make the user with given account_id or email a member of the named organization. Only site admins can do this. @@ -577,7 +570,7 @@ def create_token(self, user: str) -> TokenType: raise NotImplementedError @api_method("org.expireToken") - def expire_token(self, token: str): + def expire_token(self, token: str) -> dict[str, Any]: """ Immediately expire a token created using create_token. @@ -587,7 +580,7 @@ def expire_token(self, token: str): raise NotImplementedError @api_method("org.getUsers") - def get_users(self, name: str) -> OrganizationUser: + def get_users(self, name: str) -> list[OrganizationUser]: # type: ignore[empty-body] """ Return list of all accounts that are members of the named organization. @@ -608,7 +601,7 @@ def get_users(self, name: str) -> OrganizationUser: raise NotImplementedError @api_method("org.message") - def message(self, name: str, subject: str, body: str): + def message(self, name: str, subject: str, body: str) -> dict[str, Any]: """ Send a message from you to every account that is a member of the named organization. diff --git a/src/python/cocalc-api/src/cocalc_api/project.py b/src/python/cocalc-api/src/cocalc_api/project.py index 4c50f95e6b5..b1aa831f540 100644 --- a/src/python/cocalc-api/src/cocalc_api/project.py +++ b/src/python/cocalc-api/src/cocalc_api/project.py @@ -6,20 +6,13 @@ class Project: - def __init__(self, - api_key: str, - host: str = "https://cocalc.com", - project_id: Optional[str] = None): + def __init__(self, api_key: str, host: str = "https://cocalc.com", project_id: Optional[str] = None): self.project_id = project_id self.api_key = api_key self.host = host - self.client = httpx.Client( - auth=(api_key, ""), headers={"Content-Type": "application/json"}) + self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}) - def call(self, - name: str, - arguments: list[Any], - timeout: Optional[int] = None) -> Any: + def call(self, name: str, arguments: list[Any], timeout: Optional[int] = None) -> Any: """ Perform an API call to the CoCalc backend. @@ -31,11 +24,7 @@ def call(self, Returns: Any: JSON-decoded response from the API. """ - payload: dict[str, Any] = { - "name": name, - "args": arguments, - "project_id": self.project_id - } + payload: dict[str, Any] = {"name": name, "args": arguments, "project_id": self.project_id} if timeout is not None: payload["timeout"] = timeout resp = self.client.post(self.host + '/api/conat/project', json=payload) @@ -59,13 +48,13 @@ def ping(self) -> PingResponse: Ping the project. Returns: - Any: JSON object containing the current server time. + PingResponse: JSON object containing the current server time. Examples: - Ping a project. The api_key can be either an account api key or a project + Ping a project. The api_key can be either an account api key or a project specific api key (in which case the project_id option is optional): - >>> import cocalc_api; project = cocalc_api.Project(api_key="sk-...", project_id='...') + >>> import cocalc_api; project = cocalc_api.Project(api_key="sk-...", project_id='...') >>> project.ping() {'now': 1756489740133} @@ -90,16 +79,15 @@ def exec( Execute an arbitrary shell command in the project. Args: - - command (str): command to run; can be a program name (e.g., "ls") or absolute path, or a full bash script - args (Optional[list[str]]): optional arguments to the command - path (Optional[str]): path (relative to HOME directory) where command will be run - cwd (Optional[str]): absolute path where code excuted from (if path not given) - timeout (Optional[int]): optional timeout in SECONDS - max_output (Optional[int]): bound on size of stdout and stderr; further output ignored - bash (Optional[bool]): if True, ignore args and evaluate command as a bash command - env (Optional[dict[str, Any]]): if given, added to exec environment - compute_server_id (Optional[number]): compute server to run code on (instead of home base project) + command (str): Command to run; can be a program name (e.g., "ls") or absolute path, or a full bash script. + args (Optional[list[str]]): Optional arguments to the command. + path (Optional[str]): Path (relative to HOME directory) where command will be run. + cwd (Optional[str]): Absolute path where code executed from (if path not given). + timeout (Optional[int]): Optional timeout in SECONDS. + max_output (Optional[int]): Bound on size of stdout and stderr; further output ignored. + bash (Optional[bool]): If True, ignore args and evaluate command as a bash command. + env (Optional[dict[str, Any]]): If given, added to exec environment. + compute_server_id (Optional[int]): Compute server to run code on (instead of home base project). Returns: ExecuteCodeOutput: Result of executing the command. @@ -111,12 +99,10 @@ def exec( - `stderr` (str): Output written to stderr. - `exit_code` (int): Exit code of the process. - Examples: - >>> import cocalc_api >>> project = cocalc_api.Project(api_key="sk-...", - project_id='6e75dbf1-0342-4249-9dce-6b21648656e9') + ... project_id='6e75dbf1-0342-4249-9dce-6b21648656e9') >>> project.system.exec(command="echo 'hello from cocalc'") {'stdout': 'hello from cocalc\\n', 'stderr':'', 'exit_code': 0} """ @@ -129,23 +115,23 @@ def jupyter_execute( kernel: str, history: Optional[list[str]] = None, path: Optional[str] = None, - ): + ) -> dict[str, Any]: # type: ignore[empty-body] """ Execute code using a Jupyter kernel. Args: input (str): Code to execute. - kernel (Optional[str]): Name of kernel to use. Get options using jupyter.kernels() + kernel (str): Name of kernel to use. Get options using jupyter.kernels(). history (Optional[list[str]]): Array of previous inputs (they get evaluated every time, but without output being captured). path (Optional[str]): File path context for execution. Returns: - Any: JSON response containing execution results. + dict[str, Any]: JSON response containing execution results. Examples: Execute a simple sum using a Jupyter kernel: - >>> import cocalc_api; project = cocalc_api.Project(api_key="sk-...") + >>> import cocalc_api; project = cocalc_api.Project(api_key="sk-...") >>> project.jupyter.execute(history=['a=100;print(a)'], input='sum(range(a+1))', kernel='python3') diff --git a/src/python/cocalc-api/src/cocalc_api/util.py b/src/python/cocalc-api/src/cocalc_api/util.py index 35c2ab7e1a3..16a607c9c62 100644 --- a/src/python/cocalc-api/src/cocalc_api/util.py +++ b/src/python/cocalc-api/src/cocalc_api/util.py @@ -9,9 +9,7 @@ def handle_error(x: Any) -> Any: return x -def api_method(name: str, - opts: bool = False, - timeout_seconds: bool = False) -> Callable: +def api_method(name: str, opts: bool = False, timeout_seconds: bool = False) -> Callable: """ Decorator for CoCalcAPI methods. Converts arguments (excluding self) into a dict, removes None values, @@ -32,11 +30,7 @@ def wrapper(self, *args, **kwargs) -> Any: # Bind args/kwargs to parameter names bound = sig.bind(self, *args, **kwargs) bound.apply_defaults() - args_dict = { - k: v - for k, v in bound.arguments.items() - if k != "self" and v is not None - } + args_dict = {k: v for k, v in bound.arguments.items() if k != "self" and v is not None} if timeout_seconds and 'timeout' in args_dict: timeout = 1000 * args_dict['timeout'] else: diff --git a/src/python/cocalc-api/uv.lock b/src/python/cocalc-api/uv.lock index c949ebfde18..25e991e1c07 100644 --- a/src/python/cocalc-api/uv.lock +++ b/src/python/cocalc-api/uv.lock @@ -186,8 +186,10 @@ dev = [ { name = "mkdocs-material" }, { name = "mkdocstrings", extra = ["python"] }, { name = "mypy" }, + { name = "pyright" }, { name = "pytest" }, { name = "ruff" }, + { name = "yapf" }, ] [package.metadata] @@ -200,8 +202,10 @@ dev = [ { name = "mkdocs-material" }, { name = "mkdocstrings", extras = ["python"] }, { name = "mypy" }, + { name = "pyright" }, { name = "pytest", specifier = ">=8.4.1" }, { name = "ruff", specifier = ">=0.12.11" }, + { name = "yapf" }, ] [[package]] @@ -732,6 +736,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -850,6 +863,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d", size = 266178, upload-time = "2025-07-28T16:19:31.401Z" }, ] +[[package]] +name = "pyright" +version = "1.1.405" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/6c/ba4bbee22e76af700ea593a1d8701e3225080956753bee9750dcc25e2649/pyright-1.1.405.tar.gz", hash = "sha256:5c2a30e1037af27eb463a1cc0b9f6d65fec48478ccf092c1ac28385a15c55763", size = 4068319, upload-time = "2025-09-04T03:37:06.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/1a/524f832e1ff1962a22a1accc775ca7b143ba2e9f5924bb6749dce566784a/pyright-1.1.405-py3-none-any.whl", hash = "sha256:a2cb13700b5508ce8e5d4546034cb7ea4aedb60215c6c33f56cec7f53996035a", size = 5905038, upload-time = "2025-09-04T03:37:04.913Z" }, +] + [[package]] name = "pytest" version = "8.4.1" @@ -1130,6 +1156,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, ] +[[package]] +name = "yapf" +version = "0.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/81/6acd6601f61e31cfb8729d3da6d5df966f80f374b78eff83760714487338/yapf-0.43.0-py3-none-any.whl", hash = "sha256:224faffbc39c428cb095818cf6ef5511fdab6f7430a10783fdfb292ccf2852ca", size = 256158, upload-time = "2024-11-14T00:11:39.37Z" }, +] + [[package]] name = "zipp" version = "3.23.0" From 4cf719747c5978c01f6d693e2f050b34620fc4bb Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Mon, 29 Sep 2025 13:08:32 +0200 Subject: [PATCH 07/58] cocalc-api: add tests, using localhost instance and a given key --- src/python/cocalc-api/.vscode/settings.json | 7 ++ src/python/cocalc-api/Makefile | 17 ++- src/python/cocalc-api/pyrightconfig.json | 13 +++ src/python/cocalc-api/pytest.ini | 10 ++ src/python/cocalc-api/src/cocalc_api/hub.py | 1 + src/python/cocalc-api/tests/README.md | 47 ++++++++ src/python/cocalc-api/tests/__init__.py | 1 + src/python/cocalc-api/tests/conftest.py | 84 ++++++++++++++ src/python/cocalc-api/tests/test_hub.py | 64 +++++++++++ src/python/cocalc-api/tests/test_project.py | 117 ++++++++++++++++++++ 10 files changed, 356 insertions(+), 5 deletions(-) create mode 100644 src/python/cocalc-api/.vscode/settings.json create mode 100644 src/python/cocalc-api/pyrightconfig.json create mode 100644 src/python/cocalc-api/pytest.ini create mode 100644 src/python/cocalc-api/tests/README.md create mode 100644 src/python/cocalc-api/tests/__init__.py create mode 100644 src/python/cocalc-api/tests/conftest.py create mode 100644 src/python/cocalc-api/tests/test_hub.py create mode 100644 src/python/cocalc-api/tests/test_project.py diff --git a/src/python/cocalc-api/.vscode/settings.json b/src/python/cocalc-api/.vscode/settings.json new file mode 100644 index 00000000000..df1bc0a0a7f --- /dev/null +++ b/src/python/cocalc-api/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.defaultInterpreterPath": "./.venv/bin/python", + "python.analysis.extraPaths": ["./src"], + "python.analysis.autoSearchPaths": true, + "python.analysis.autoImportCompletions": true, + "pylance.insidersChannel": "off" +} \ No newline at end of file diff --git a/src/python/cocalc-api/Makefile b/src/python/cocalc-api/Makefile index 9b41e3ab997..08b19087448 100644 --- a/src/python/cocalc-api/Makefile +++ b/src/python/cocalc-api/Makefile @@ -6,12 +6,18 @@ install: uv pip install -e . format: - uv run yapf --in-place --recursive src/ + uv run yapf --in-place --recursive src/ tests/ check: - uv run ruff check src/ - uv run mypy src/ - uv run pyright src/ + uv run ruff check src/ tests/ + uv run mypy src/ tests/ + uv run pyright src/ tests/ + +test: + uv run pytest + +test-verbose: + uv run pytest -v serve-docs: uv run mkdocs serve @@ -24,5 +30,6 @@ publish: install uv publish clean: - rm -rf dist build *.egg-info site + rm -rf dist build *.egg-info site .pytest_cache find . -name "__pycache__" -type d -exec rm -rf {} + + find . -name "*.pyc" -delete diff --git a/src/python/cocalc-api/pyrightconfig.json b/src/python/cocalc-api/pyrightconfig.json new file mode 100644 index 00000000000..21461e0f551 --- /dev/null +++ b/src/python/cocalc-api/pyrightconfig.json @@ -0,0 +1,13 @@ +{ + "include": [ + "src", + "tests" + ], + "extraPaths": [ + "src" + ], + "venvPath": ".", + "venv": ".venv", + "pythonVersion": "3.12", + "typeCheckingMode": "basic" +} \ No newline at end of file diff --git a/src/python/cocalc-api/pytest.ini b/src/python/cocalc-api/pytest.ini new file mode 100644 index 00000000000..55ff02ab034 --- /dev/null +++ b/src/python/cocalc-api/pytest.ini @@ -0,0 +1,10 @@ +[tool:pytest] +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +markers = + integration: marks tests as integration tests (require live server) +addopts = + -v + --tb=short \ No newline at end of file diff --git a/src/python/cocalc-api/src/cocalc_api/hub.py b/src/python/cocalc-api/src/cocalc_api/hub.py index cff7f8ad261..a9f775ea1e6 100644 --- a/src/python/cocalc-api/src/cocalc_api/hub.py +++ b/src/python/cocalc-api/src/cocalc_api/hub.py @@ -611,3 +611,4 @@ def message(self, name: str, subject: str, body: str) -> dict[str, Any]: subject (str): plain text subject of the message body (str): markdown body of the message (math typesetting works) """ + raise NotImplementedError diff --git a/src/python/cocalc-api/tests/README.md b/src/python/cocalc-api/tests/README.md new file mode 100644 index 00000000000..cf6165c6a23 --- /dev/null +++ b/src/python/cocalc-api/tests/README.md @@ -0,0 +1,47 @@ +# CoCalc API Tests + +This directory contains pytest tests for the cocalc-api Python package. + +## Prerequisites + +1. **Required**: Set the `COCALC_API_KEY` environment variable with a valid CoCalc API key (tests will fail if not set) +2. Optionally set `COCALC_HOST` to specify the CoCalc server URL (defaults to `http://localhost:5000`) + +## Running Tests + +```bash +# Run all tests +make test + +# Run tests with verbose output +make test-verbose + +# Or use pytest directly +uv run pytest +uv run pytest -v +``` + +## Test Structure + +- `conftest.py` - Pytest configuration and fixtures (includes temporary project creation) +- `test_hub.py` - Tests for Hub client functionality including project creation +- `test_project.py` - Tests for Project client functionality using auto-created temporary projects + +## Test Markers + +- `@pytest.mark.integration` - Marks tests that require a live CoCalc server + +## Environment Variables + +- `COCALC_API_KEY` (required) - Your CoCalc API key +- `COCALC_HOST` (optional) - CoCalc server URL (default: `http://localhost:5000`) + +## Automatic Project Creation + +The test suite automatically creates temporary projects for testing via the `temporary_project` fixture: + +- Projects are created with unique names like `CoCalc API Test YYYYMMDD-HHMMSS` +- Projects include a description: "Temporary project created by cocalc-api tests" +- **Important**: Projects are NOT automatically deleted after tests (no delete API available) +- You'll see a note after tests indicating which projects were created +- These test projects can be manually deleted from the CoCalc interface if desired diff --git a/src/python/cocalc-api/tests/__init__.py b/src/python/cocalc-api/tests/__init__.py new file mode 100644 index 00000000000..5149a751016 --- /dev/null +++ b/src/python/cocalc-api/tests/__init__.py @@ -0,0 +1 @@ +# Tests package for cocalc-api \ No newline at end of file diff --git a/src/python/cocalc-api/tests/conftest.py b/src/python/cocalc-api/tests/conftest.py new file mode 100644 index 00000000000..9b233d3a118 --- /dev/null +++ b/src/python/cocalc-api/tests/conftest.py @@ -0,0 +1,84 @@ +""" +Pytest configuration and fixtures for cocalc-api tests. +""" +import os +import pytest + +from cocalc_api import Hub, Project + + +@pytest.fixture(scope="session") +def api_key(): + """Get API key from environment variable.""" + key = os.environ.get("COCALC_API_KEY") + if not key: + pytest.fail("COCALC_API_KEY environment variable is required but not set") + return key + + +@pytest.fixture(scope="session") +def cocalc_host(): + """Get CoCalc host from environment variable, default to localhost:5000.""" + return os.environ.get("COCALC_HOST", "http://localhost:5000") + + +@pytest.fixture(scope="session") +def hub(api_key, cocalc_host): + """Create Hub client instance.""" + return Hub(api_key=api_key, host=cocalc_host) + + +@pytest.fixture(scope="session") +def temporary_project(hub): + """ + Create a temporary project for testing and return project info. + + Note: Since there's no project deletion API available, the project + will remain after tests. It can be manually deleted if needed. + """ + import time + + # Create a project with a timestamp to make it unique and identifiable + timestamp = time.strftime("%Y%m%d-%H%M%S") + title = f"CoCalc API Test {timestamp}" + description = "Temporary project created by cocalc-api tests" + + project_id = hub.projects.create_project(title=title, description=description) + + # Start the project so it can respond to API calls + try: + hub.projects.start(project_id) + print(f"Started project {project_id}, waiting for it to become ready...") + + # Wait for project to be ready (can take 10-15 seconds) + import time + from cocalc_api import Project + + for attempt in range(10): + time.sleep(5) # Wait 5 seconds before checking + try: + # Try to ping the project to see if it's ready + test_project = Project(project_id=project_id, api_key=hub.api_key, host=hub.host) + test_project.system.ping() # If this succeeds, project is ready + print(f"✓ Project {project_id} is ready after {(attempt + 1) * 5} seconds") + break + except Exception: + if attempt == 9: # Last attempt + print(f"Warning: Project {project_id} did not become ready within 50 seconds") + + except Exception as e: + print(f"Warning: Failed to start project {project_id}: {e}") + + project_info = {'project_id': project_id, 'title': title, 'description': description} + + yield project_info + + # Cleanup: No direct delete API available + # The project will remain but can be manually cleaned up + print(f"\nNote: Test project '{title}' (ID: {project_id}) created but not deleted - no delete API available") + + +@pytest.fixture(scope="session") +def project_client(temporary_project, api_key, cocalc_host): + """Create Project client instance using temporary project.""" + return Project(project_id=temporary_project['project_id'], api_key=api_key, host=cocalc_host) diff --git a/src/python/cocalc-api/tests/test_hub.py b/src/python/cocalc-api/tests/test_hub.py new file mode 100644 index 00000000000..7a7928f6e7e --- /dev/null +++ b/src/python/cocalc-api/tests/test_hub.py @@ -0,0 +1,64 @@ +""" +Tests for Hub client functionality. +""" +import pytest + +from cocalc_api import Hub + + +class TestHubSystem: + """Tests for Hub system operations.""" + + def test_ping(self, hub): + """Test basic ping connectivity.""" + result = hub.system.ping() + assert result is not None + # The ping response should contain some basic server info + assert isinstance(result, dict) + + def test_hub_initialization(self, api_key, cocalc_host): + """Test Hub client initialization.""" + hub = Hub(api_key=api_key, host=cocalc_host) + assert hub.api_key == api_key + assert hub.host == cocalc_host + assert hub.client is not None + + def test_invalid_api_key(self, cocalc_host): + """Test behavior with invalid API key.""" + hub = Hub(api_key="invalid_key", host=cocalc_host) + with pytest.raises((ValueError, RuntimeError, Exception)): # Should raise authentication error + hub.system.ping() + + def test_ping_timeout(self, api_key, cocalc_host): + """Test ping with timeout parameter.""" + hub = Hub(api_key=api_key, host=cocalc_host) + result = hub.system.ping() + assert result is not None + + +class TestHubProjects: + """Tests for Hub project operations.""" + + def test_create_project(self, hub): + """Test creating a project via hub.projects.create_project.""" + import time + timestamp = int(time.time()) + title = f"test-project-{timestamp}" + description = "Test project for API testing" + + project_id = hub.projects.create_project(title=title, description=description) + + assert project_id is not None + assert isinstance(project_id, str) + assert len(project_id) > 0 + # Should be a UUID-like string + assert '-' in project_id + + def test_list_projects(self, hub): + """Test listing projects.""" + projects = hub.projects.get() + assert isinstance(projects, list) + # Each project should have basic fields + for project in projects: + assert 'project_id' in project + assert isinstance(project['project_id'], str) diff --git a/src/python/cocalc-api/tests/test_project.py b/src/python/cocalc-api/tests/test_project.py new file mode 100644 index 00000000000..84c7ff04d8f --- /dev/null +++ b/src/python/cocalc-api/tests/test_project.py @@ -0,0 +1,117 @@ +""" +Tests for Project client functionality. +""" +import pytest + +from cocalc_api import Project + + +class TestProjectCreation: + """Tests for project creation and management.""" + + def test_create_temporary_project(self, temporary_project): + """Test that a temporary project is created successfully.""" + assert temporary_project is not None + assert 'project_id' in temporary_project + assert 'title' in temporary_project + assert 'description' in temporary_project + assert temporary_project['title'].startswith('CoCalc API Test ') + assert temporary_project['description'] == "Temporary project created by cocalc-api tests" + # Project ID should be a UUID-like string + assert len(temporary_project['project_id']) > 0 + assert '-' in temporary_project['project_id'] + + def test_project_exists_in_list(self, hub, temporary_project): + """Test that the created project appears in the projects list.""" + projects = hub.projects.get(all=True) + project_ids = [p['project_id'] for p in projects] + assert temporary_project['project_id'] in project_ids + + +class TestProjectSystem: + """Tests for Project system operations.""" + + def test_ping(self, project_client): + """Test basic ping connectivity to project.""" + result = project_client.system.ping() + assert result is not None + assert isinstance(result, dict) + + def test_project_initialization(self, api_key, cocalc_host): + """Test Project client initialization.""" + project_id = "test-project-id" + project = Project(project_id=project_id, api_key=api_key, host=cocalc_host) + assert project.project_id == project_id + assert project.api_key == api_key + assert project.host == cocalc_host + assert project.client is not None + + def test_project_with_temporary_project(self, project_client, temporary_project): + """Test Project client using the temporary project.""" + assert project_client.project_id == temporary_project['project_id'] + # Test that we can ping the specific project + result = project_client.system.ping() + assert result is not None + assert isinstance(result, dict) + + def test_exec_command(self, project_client): + """Test executing shell commands in the project.""" + # Test running 'date -Is' to get ISO date with seconds + result = project_client.system.exec(command="date", args=["-Is"]) + + # Check the result structure + assert 'stdout' in result + assert 'stderr' in result + assert 'exit_code' in result + + # Should succeed + assert result['exit_code'] == 0 + + # Should have minimal stderr + assert result['stderr'] == '' or len(result['stderr']) == 0 + + # Parse the returned date and compare with current time + from datetime import datetime + import re + + date_output = result['stdout'].strip() + # Expected format: 2025-09-29T12:34:56+00:00 or similar + + # Check if the output matches ISO format + iso_pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$' + assert re.match(iso_pattern, date_output), f"Date output '{date_output}' doesn't match ISO format" + + # Parse the date from the command output + # Remove the timezone for comparison (date -Is includes timezone) + date_part = date_output[:19] # Take YYYY-MM-DDTHH:MM:SS part + remote_time = datetime.fromisoformat(date_part) + + # Get current time + current_time = datetime.now() + + # Check if the times are close (within 60 seconds) + time_diff = abs((current_time - remote_time).total_seconds()) + assert time_diff < 60, f"Time difference too large: {time_diff} seconds. Remote: {date_output}, Local: {current_time.isoformat()}" + + def test_exec_stderr_and_exit_code(self, project_client): + """Test executing a command that writes to stderr and returns a specific exit code.""" + # Use bash to echo to stderr and exit with code 42 + bash_script = "echo 'test error message' >&2; exit 42" + + # The API raises an exception for non-zero exit codes + # but includes the stderr and exit code information in the error message + with pytest.raises(RuntimeError) as exc_info: + project_client.system.exec(command=bash_script, bash=True) + + error_message = str(exc_info.value) + + # Verify the error message contains expected information + assert "exited with nonzero code 42" in error_message + assert "stderr='test error message" in error_message + + # Extract and verify the stderr content is properly captured + import re + stderr_match = re.search(r"stderr='([^']*)'", error_message) + assert stderr_match is not None, "Could not find stderr in error message" + stderr_content = stderr_match.group(1).strip() + assert stderr_content == "test error message" From 0772810000d46ed0912edc021dc9c51b38eed553 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Mon, 29 Sep 2025 15:19:27 +0200 Subject: [PATCH 08/58] cocalc-api: implement project deletion in conat and support a full project lifecycle as part of the cocalc-api tests --- src/AGENTS.md | 125 ++++++++---------- src/packages/conat/hub/api/projects.ts | 2 + .../next/pages/api/v2/projects/delete.ts | 51 +------ src/packages/server/conat/api/projects.ts | 3 + src/packages/server/projects/delete.ts | 61 +++++++++ src/python/cocalc-api/src/cocalc_api/hub.py | 50 +++++-- src/python/cocalc-api/tests/README.md | 18 ++- src/python/cocalc-api/tests/conftest.py | 22 ++- src/python/cocalc-api/tests/test_hub.py | 99 +++++++++++++- 9 files changed, 300 insertions(+), 131 deletions(-) create mode 100644 src/packages/server/projects/delete.ts diff --git a/src/AGENTS.md b/src/AGENTS.md index afd1da2aa3d..adedb90cdbf 100644 --- a/src/AGENTS.md +++ b/src/AGENTS.md @@ -105,6 +105,20 @@ CoCalc is organized as a monorepo with key packages: 5. **Authentication**: Each conat request includes account_id and is subject to permission checks at the hub level 6. **Subjects**: Messages are routed using hierarchical subjects like `hub.account.{uuid}.{service}` or `project.{uuid}.{compute_server_id}.{service}` +#### CoCalc Conat Hub API Architecture + +**API Method Registration Pattern:** +- **Registry**: `packages/conat/hub/api/projects.ts` contains `export const projects = { methodName: authFirstRequireAccount }` +- **Implementation**: `packages/server/conat/api/projects.ts` contains `export async function methodName() { ... }` +- **Flow**: Python client `@api_method("projects.methodName")` → POST `/api/conat/hub` → `hubBridge()` → conat subject `hub.account.{account_id}.api` → registry lookup → implementation + +**Example - projects.createProject:** +1. **Python**: `@api_method("projects.createProject")` decorator +2. **HTTP**: `POST /api/conat/hub {"name": "projects.createProject", "args": [...]}` +3. **Bridge**: `hubBridge()` routes to conat subject +4. **Registry**: `packages/conat/hub/api/projects.ts: createProject: authFirstRequireAccount` +5. **Implementation**: `packages/server/conat/api/projects.ts: export { createProject }` → `@cocalc/server/projects/create` + ### Key Technologies - **TypeScript**: Primary language for all new code @@ -216,92 +230,67 @@ Same flow as above, but **before 3. i18n:upload**, delete the key. Only new keys - Ignore everything in `node_modules` or `dist` directories - Ignore all files not tracked by Git, unless they are newly created files -# CoCalc Python API Client +# CoCalc Python API Client Investigation ## Overview The `python/cocalc-api/` directory contains a Python client library for the CoCalc API, published as the `cocalc-api` package on PyPI. -## Architecture - -### Package Structure - -- **`src/cocalc_api/`** - Main Python package source code - - `__init__.py` - Package exports (Hub, Project classes) - - `hub.py` - Hub client for account-level API operations - - `project.py` - Project client for project-specific operations - - `api_types.py` - TypedDict definitions for API responses - - `util.py` - Utility functions and decorators - -### Key Classes - -#### Hub Client (`hub.py`) - -Account-level API client that provides access to: - -- **System** - Server ping, user search, account name resolution -- **Projects** - Project management (create, start, stop, collaborators) -- **Jupyter** - Global Jupyter kernel execution -- **Database** - Direct PostgreSQL database queries -- **Messages** - Send/receive messages between users -- **Organizations** - Organization management (admin functions) -- **Sync** - File history and synchronization - -#### Project Client (`project.py`) - -Project-specific API client for: - -- **System** - Project ping, shell command execution, Jupyter execution - -### Development Tools - -- **Package Manager**: `uv` (modern Python package manager) -- **Code Formatter**: `yapf` (Python code formatter following Google style) -- **Code Quality**: `ruff` (linting), `mypy` (type checking), `pyright` (additional type checking) -- **Documentation**: `mkdocs` with material theme -- **Testing**: `pytest` +## Client-Server Architecture Investigation -### Development Commands +### API Call Flow -```bash -# Setup and install dependencies -make install # or: uv sync --dev && uv pip install -e . +1. **cocalc-api Client** (Python) → HTTP POST requests +2. **Next.js API Routes** (`/api/conat/{hub,project}`) → Bridge to conat messaging +3. **ConatClient** (server-side) → NATS-like messaging protocol +4. **Hub API Implementation** (`packages/conat/hub/api/`) → Actual business logic -# Format Python code -make format # or: uv run yapf --in-place --recursive src/ +### Endpoints Discovered -# Code quality checks -make check # or: uv run ruff check src/ && uv run mypy src/ && uv run pyright src/ +#### Hub API: `POST /api/conat/hub` +- **Bridge**: `packages/next/pages/api/conat/hub.ts` → `hubBridge()` → conat subject `hub.account.{account_id}.api` +- **Implementation**: `packages/conat/hub/api/projects.ts` +- **Available Methods**: `createProject`, `start`, `stop`, `setQuotas`, `addCollaborator`, `removeCollaborator`, etc. +- **Missing**: ❌ **No `delete` method implemented in conat hub API** -# Documentation -make serve-docs # or: uv run mkdocs serve -make build-docs # or: uv run mkdocs build +#### Project API: `POST /api/conat/project` +- **Bridge**: `packages/next/pages/api/conat/project.ts` → `projectBridge()` → conat project subjects +- **Implementation**: `packages/conat/project/api/` (system.ping, system.exec, system.jupyterExecute) -# Publishing -make publish # or: uv build && uv publish +### Project Deletion Investigation -# Cleanup -make clean -``` +#### ✅ Next.js v2 API Route Available +- **Endpoint**: `packages/next/pages/api/v2/projects/delete.ts` +- **Functionality**: Sets deleted=true, removes licenses, stops project +- **Authentication**: Requires collaborator access or admin -### API Design Patterns +#### ❌ Missing Conat Hub API Method +- **Current Methods**: Only CRUD operations (create, start, stop, quotas, collaborators) +- **Gap**: No `delete` method exposed through conat hub API used by cocalc-api -- **Decorator-based Methods**: Uses `@api_method()` decorator to automatically convert method calls to API requests -- **TypedDict Responses**: All API responses use TypedDict for type safety -- **Error Handling**: Centralized error handling via `handle_error()` utility -- **HTTP Client**: Uses `httpx` for HTTP requests with authentication -- **Nested Namespaces**: API organized into logical namespaces (system, projects, jupyter, etc.) +#### Frontend Implementation +- **Location**: `packages/frontend/projects/actions.ts:delete_project()` +- **Method**: Direct database table update via `projects_table_set({deleted: true})` -### Authentication +## Implementation -- Supports both account-level and project-specific API keys -- Account API keys provide full access to all hub functionality -- Project API keys are limited to project-specific operations +### Solution Implemented: Direct v2 API Call +- **Added**: `hub.projects.delete(project_id)` method to cocalc-api Python client +- **Implementation**: Direct HTTP POST to `/api/v2/projects/delete` endpoint +- **Reasoning**: Fastest path to complete project lifecycle without requiring conat hub API changes +- **Consistency**: Uses same authentication and error handling patterns as other methods -### Connection Endpoints +### Code Changes +1. **`src/cocalc_api/hub.py`**: Added `delete()` method to Projects class +2. **`tests/conftest.py`**: Updated cleanup to use new delete method +3. **`tests/test_hub.py`**: Added test for delete method availability -- **Hub API**: `POST /api/conat/hub` - Account-level operations -- **Project API**: `POST /api/conat/project` - Project-specific operations +## Current Status +- ✅ pytest test framework established with automatic project lifecycle +- ✅ Project creation/start/stop working via conat hub API +- ✅ Project deletion implemented by calling v2 API route directly +- ✅ Complete project lifecycle management: create → start → test → stop → delete +- ✅ All 14 tests passing with proper resource cleanup # Important Instruction Reminders diff --git a/src/packages/conat/hub/api/projects.ts b/src/packages/conat/hub/api/projects.ts index bf42fd5d36d..f5c65aa03f6 100644 --- a/src/packages/conat/hub/api/projects.ts +++ b/src/packages/conat/hub/api/projects.ts @@ -12,6 +12,7 @@ export const projects = { setQuotas: authFirstRequireAccount, start: authFirstRequireAccount, stop: authFirstRequireAccount, + deleteProject: authFirstRequireAccount, }; export type AddCollaborator = @@ -103,4 +104,5 @@ export interface Projects { start: (opts: { account_id: string; project_id: string }) => Promise; stop: (opts: { account_id: string; project_id: string }) => Promise; + deleteProject: (opts: { account_id: string; project_id: string }) => Promise; } diff --git a/src/packages/next/pages/api/v2/projects/delete.ts b/src/packages/next/pages/api/v2/projects/delete.ts index 85c70888c27..7780de3bd19 100644 --- a/src/packages/next/pages/api/v2/projects/delete.ts +++ b/src/packages/next/pages/api/v2/projects/delete.ts @@ -1,12 +1,8 @@ /* API endpoint to delete a project, which sets the "delete" flag to `true` in the database. */ -import isCollaborator from "@cocalc/server/projects/is-collaborator"; -import userIsInGroup from "@cocalc/server/accounts/is-in-group"; -import removeAllLicensesFromProject from "@cocalc/server/licenses/remove-all-from-project"; -import { getProject } from "@cocalc/server/projects/control"; -import userQuery from "@cocalc/database/user-query"; -import { isValidUUID } from "@cocalc/util/misc"; + +import deleteProject from "@cocalc/server/projects/delete"; import getAccountId from "lib/account/get-account"; import getParams from "lib/api/get-params"; @@ -21,45 +17,12 @@ async function handle(req, res) { const { project_id } = getParams(req); const account_id = await getAccountId(req); - try { - if (!isValidUUID(project_id)) { - throw Error("project_id must be a valid uuid"); - } - if (!account_id) { - throw Error("must be signed in"); - } - - // If client is not an administrator, they must be a project collaborator in order to - // delete a project. - if ( - !(await userIsInGroup(account_id, "admin")) && - !(await isCollaborator({ account_id, project_id })) - ) { - throw Error("must be an owner to delete a project"); - } - - // Remove all project licenses - // - await removeAllLicensesFromProject({ project_id }); - - // Stop project - // - const project = getProject(project_id); - await project.stop(); - - // Set "deleted" flag. We do this last to ensure that the project is not consuming any - // resources while it is in the deleted state. - // - await userQuery({ - account_id, - query: { - projects: { - project_id, - deleted: true, - }, - }, - }); + if (!account_id) { + throw Error("must be signed in"); + } + try { + await deleteProject({ account_id, project_id }); res.json(OkStatus); } catch (err) { res.json({ error: err.message }); diff --git a/src/packages/server/conat/api/projects.ts b/src/packages/server/conat/api/projects.ts index dbe6712c240..d840ab0372b 100644 --- a/src/packages/server/conat/api/projects.ts +++ b/src/packages/server/conat/api/projects.ts @@ -103,3 +103,6 @@ export async function stop({ const project = await getProject(project_id); await project.stop(); } + +import deleteProject from "@cocalc/server/projects/delete"; +export { deleteProject }; diff --git a/src/packages/server/projects/delete.ts b/src/packages/server/projects/delete.ts new file mode 100644 index 00000000000..57c7426b2e0 --- /dev/null +++ b/src/packages/server/projects/delete.ts @@ -0,0 +1,61 @@ +/* + * This file is part of CoCalc: Copyright © 2025 Sagemath, Inc. + * License: MS-RSL – see LICENSE.md for details + */ + +/* +Project delete functionality + +Extracted from the v2 API to be reusable by both REST API and Conat API. +*/ + +import isCollaborator from "@cocalc/server/projects/is-collaborator"; +import userIsInGroup from "@cocalc/server/accounts/is-in-group"; +import removeAllLicensesFromProject from "@cocalc/server/licenses/remove-all-from-project"; +import { getProject } from "@cocalc/server/projects/control"; +import userQuery from "@cocalc/database/user-query"; +import { isValidUUID } from "@cocalc/util/misc"; + +export default async function deleteProject({ + account_id, + project_id, +}: { + account_id: string; + project_id: string; +}): Promise { + if (!isValidUUID(project_id)) { + throw Error("project_id must be a valid UUID"); + } + if (!isValidUUID(account_id)) { + throw Error("account_id must be a valid UUID"); + } + + // If client is not an administrator, they must be a project collaborator in order to + // delete a project. + if ( + !(await userIsInGroup(account_id, "admin")) && + !(await isCollaborator({ account_id, project_id })) + ) { + throw Error("must be an owner to delete a project"); + } + + // Remove all project licenses + await removeAllLicensesFromProject({ project_id }); + + // Stop project + const project = getProject(project_id); + await project.stop(); + + // Set "deleted" flag. We do this last to ensure that the project is not consuming any + // resources while it is in the deleted state. + // + await userQuery({ + account_id, + query: { + projects: { + project_id, + deleted: true, + }, + }, + }); +} diff --git a/src/python/cocalc-api/src/cocalc_api/hub.py b/src/python/cocalc-api/src/cocalc_api/hub.py index a9f775ea1e6..acd15d1604c 100644 --- a/src/python/cocalc-api/src/cocalc_api/hub.py +++ b/src/python/cocalc-api/src/cocalc_api/hub.py @@ -9,9 +9,13 @@ class Hub: def __init__(self, api_key: str, host: str = "https://cocalc.com"): self.api_key = api_key self.host = host - self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}) + self.client = httpx.Client( + auth=(api_key, ""), headers={"Content-Type": "application/json"}) - def call(self, name: str, arguments: list[Any], timeout: Optional[int] = None) -> Any: + def call(self, + name: str, + arguments: list[Any], + timeout: Optional[int] = None) -> Any: """ Perform an API call to the CoCalc backend. @@ -140,7 +144,10 @@ class Projects: def __init__(self, parent: "Hub"): self._parent = parent - def get(self, fields: Optional[list[str]] = None, all: Optional[bool] = False, project_id: Optional[str] = None) -> list[dict[str, Any]]: + def get(self, + fields: Optional[list[str]] = None, + all: Optional[bool] = False, + project_id: Optional[str] = None) -> list[dict[str, Any]]: """ Get data about projects that you are a collaborator on. Only gets recent projects by default; set all=True to get all projects. @@ -216,7 +223,8 @@ def create_project( raise NotImplementedError @api_method("projects.addCollaborator", opts=True) - def add_collaborator(self, project_id: str | list[str], account_id: str | list[str]) -> dict[str, Any]: + def add_collaborator(self, project_id: str | list[str], + account_id: str | list[str]) -> dict[str, Any]: """ Add a collaborator to a project. @@ -235,7 +243,8 @@ def add_collaborator(self, project_id: str | list[str], account_id: str | list[s ... @api_method("projects.removeCollaborator", opts=True) - def remove_collaborator(self, project_id: str, account_id: str) -> dict[str, Any]: + def remove_collaborator(self, project_id: str, + account_id: str) -> dict[str, Any]: """ Remove a collaborator from a project. @@ -268,6 +277,19 @@ def stop(self, project_id: str) -> dict[str, Any]: """ ... + @api_method("projects.deleteProject") + def delete(self, project_id: str) -> dict[str, Any]: + """ + Delete a project by setting the deleted flag to true. + + Args: + project_id (str): Project ID of the project to delete. + + Returns: + dict[str, Any]: API response indicating success. + """ + ... + class Jupyter: @@ -334,7 +356,8 @@ def __init__(self, parent: "Hub"): self._parent = parent @api_method("sync.history") - def history(self, project_id: str, path: str) -> list[dict[str, Any]]: # type: ignore[empty-body] + def history(self, project_id: str, + path: str) -> list[dict[str, Any]]: # type: ignore[empty-body] """ Get complete edit history of a file. @@ -380,7 +403,7 @@ def query(self, query: dict[str, Any]) -> dict[str, Any]: >>> hub.db.query({"accounts":{"first_name":None}}) {'accounts': {'first_name': 'W'}} """ - raise NotImplementedError + ... class Messages: @@ -389,7 +412,11 @@ def __init__(self, parent: "Hub"): self._parent = parent @api_method("messages.send") - def send(self, subject: str, body: str, to_ids: list[str], reply_id: Optional[int] = None) -> int: + def send(self, + subject: str, + body: str, + to_ids: list[str], + reply_id: Optional[int] = None) -> int: """ Send a message to one or more users. @@ -409,7 +436,8 @@ def get( self, limit: Optional[int] = None, offset: Optional[int] = None, - type: Optional[Literal["received", "sent", "new", "starred", "liked"]] = None, + type: Optional[Literal["received", "sent", "new", "starred", + "liked"]] = None, ) -> list[MessageType]: # type: ignore[empty-body] """ Get your messages. @@ -580,7 +608,9 @@ def expire_token(self, token: str) -> dict[str, Any]: raise NotImplementedError @api_method("org.getUsers") - def get_users(self, name: str) -> list[OrganizationUser]: # type: ignore[empty-body] + def get_users( + self, + name: str) -> list[OrganizationUser]: # type: ignore[empty-body] """ Return list of all accounts that are members of the named organization. diff --git a/src/python/cocalc-api/tests/README.md b/src/python/cocalc-api/tests/README.md index cf6165c6a23..667b751589e 100644 --- a/src/python/cocalc-api/tests/README.md +++ b/src/python/cocalc-api/tests/README.md @@ -36,12 +36,20 @@ uv run pytest -v - `COCALC_API_KEY` (required) - Your CoCalc API key - `COCALC_HOST` (optional) - CoCalc server URL (default: `http://localhost:5000`) -## Automatic Project Creation +## Automatic Project Lifecycle Management -The test suite automatically creates temporary projects for testing via the `temporary_project` fixture: +The test suite automatically manages project lifecycle for testing via the `temporary_project` fixture: +### Project Creation - Projects are created with unique names like `CoCalc API Test YYYYMMDD-HHMMSS` - Projects include a description: "Temporary project created by cocalc-api tests" -- **Important**: Projects are NOT automatically deleted after tests (no delete API available) -- You'll see a note after tests indicating which projects were created -- These test projects can be manually deleted from the CoCalc interface if desired +- Projects are automatically started and tested for readiness before tests run + +### Project Cleanup (NEW) +After all tests complete, the test suite now automatically performs cleanup: +- **Stops** the test project to free up resources +- **Attempts deletion** if the delete API becomes available (currently not implemented) +- Provides clear feedback about cleanup actions and any failures +- If deletion is not available, projects remain stopped but can be manually deleted from the CoCalc interface + +This ensures test projects don't continue consuming server resources after tests complete. diff --git a/src/python/cocalc-api/tests/conftest.py b/src/python/cocalc-api/tests/conftest.py index 9b233d3a118..487f7649401 100644 --- a/src/python/cocalc-api/tests/conftest.py +++ b/src/python/cocalc-api/tests/conftest.py @@ -73,9 +73,25 @@ def temporary_project(hub): yield project_info - # Cleanup: No direct delete API available - # The project will remain but can be manually cleaned up - print(f"\nNote: Test project '{title}' (ID: {project_id}) created but not deleted - no delete API available") + # Cleanup: Stop the project and attempt to delete it + print(f"\nCleaning up test project '{title}' (ID: {project_id})...") + + try: + # Stop the project first + print(f" Stopping project {project_id}...") + hub.projects.stop(project_id) + print(f" Project {project_id} stopped successfully") + except Exception as e: + print(f" Failed to stop project {project_id}: {e}") + + try: + # Delete the project using the new delete method + print(f" Deleting project {project_id}...") + hub.projects.delete(project_id) + print(f" Project {project_id} deleted successfully") + except Exception as e: + print(f" Failed to delete project {project_id}: {e}") + print(" Project is stopped but may still exist - manual cleanup recommended") @pytest.fixture(scope="session") diff --git a/src/python/cocalc-api/tests/test_hub.py b/src/python/cocalc-api/tests/test_hub.py index 7a7928f6e7e..613552aee3b 100644 --- a/src/python/cocalc-api/tests/test_hub.py +++ b/src/python/cocalc-api/tests/test_hub.py @@ -1,9 +1,10 @@ """ Tests for Hub client functionality. """ +import time import pytest -from cocalc_api import Hub +from cocalc_api import Hub, Project class TestHubSystem: @@ -62,3 +63,99 @@ def test_list_projects(self, hub): for project in projects: assert 'project_id' in project assert isinstance(project['project_id'], str) + + def test_delete_method_exists(self, hub): + """Test that delete method is available and callable.""" + # Test that the delete method exists and is callable + assert hasattr(hub.projects, 'delete') + assert callable(hub.projects.delete) + + # Note: We don't actually delete anything in this test since + # deletion is tested in the project lifecycle via temporary_project fixture + + def test_project_lifecycle(self, hub): + """Test complete project lifecycle: create, wait for ready, run command, delete, verify deletion.""" + + # 1. Create a project + timestamp = int(time.time()) + title = f"lifecycle-test-{timestamp}" + description = "Test project for complete lifecycle testing" + + print(f"\n1. Creating project '{title}'...") + project_id = hub.projects.create_project(title=title, description=description) + assert project_id is not None + assert isinstance(project_id, str) + print(f" Created project: {project_id}") + + try: + # Start the project + print("2. Starting project...") + hub.projects.start(project_id) + print(" Project start request sent") + + # Wait for project to become ready + print("3. Waiting for project to become ready...") + project_client = Project(project_id=project_id, api_key=hub.api_key, host=hub.host) + + ready = False + for attempt in range(12): # 60 seconds max wait time + time.sleep(5) + try: + project_client.system.ping() + ready = True + print(f" ✓ Project ready after {(attempt + 1) * 5} seconds") + break + except Exception as e: + if attempt == 11: # Last attempt + print(f" Warning: Project not ready after 60 seconds: {e}") + else: + print(f" Attempt {attempt + 1}: Project not ready yet...") + + # Check that project exists in database + print("4. Checking project exists in database...") + projects = hub.projects.get(fields=['project_id', 'title', 'deleted'], project_id=project_id) + assert len(projects) == 1, f"Expected 1 project, found {len(projects)}" + project = projects[0] + assert project["project_id"] == project_id + assert project["title"] == title + assert project.get("deleted") is None or project.get("deleted") is False + print(f" ✓ Project found in database: title='{project['title']}', deleted={project.get('deleted')}") + + # 2. Run a command if project is ready + if ready: + print("5. Running 'uname -a' command...") + result = project_client.system.exec("uname -a") + assert "stdout" in result + output = result["stdout"] + assert "Linux" in output, f"Expected Linux system, got: {output}" + assert result["exit_code"] == 0, f"Command failed with exit code {result['exit_code']}" + print(f" ✓ Command executed successfully: {output.strip()}") + else: + print("5. Skipping command execution - project not ready") + + # 3. Delete the project + print("6. Deleting project...") + delete_result = hub.projects.delete(project_id) + print(f" Delete result: {delete_result}") + + # 4. Verify project is marked as deleted in database + print("7. Verifying project is marked as deleted...") + projects = hub.projects.get(fields=['project_id', 'title', 'deleted'], project_id=project_id, all=True) + assert len(projects) == 1, f"Expected 1 project (still in DB), found {len(projects)}" + project = projects[0] + assert project["project_id"] == project_id + assert project.get("deleted") is True, f"Expected deleted=True, got deleted={project.get('deleted')}" + print(f" ✓ Project correctly marked as deleted in database: deleted={project.get('deleted')}") + + print("✅ Project lifecycle test completed successfully!") + + except Exception as e: + # Cleanup: attempt to delete project if test fails + print(f"\n❌ Test failed: {e}") + try: + print("Attempting cleanup...") + hub.projects.delete(project_id) + print("✓ Cleanup successful") + except Exception as cleanup_error: + print(f"❌ Cleanup failed: {cleanup_error}") + raise e From c652d60563788951644f8e6de759cccadd915928 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Mon, 29 Sep 2025 15:54:33 +0200 Subject: [PATCH 09/58] cocalc-api: testing organization management --- src/python/cocalc-api/src/cocalc_api/hub.py | 235 +------ src/python/cocalc-api/src/cocalc_api/org.py | 210 +++++++ .../cocalc-api/src/cocalc_api/project.py | 4 +- src/python/cocalc-api/tests/conftest.py | 18 + src/python/cocalc-api/tests/test_hub.py | 8 +- src/python/cocalc-api/tests/test_org.py | 581 ++++++++++++++++++ src/python/cocalc-api/tests/test_org_basic.py | 131 ++++ src/python/cocalc-api/tests/test_project.py | 6 +- 8 files changed, 966 insertions(+), 227 deletions(-) create mode 100644 src/python/cocalc-api/src/cocalc_api/org.py create mode 100644 src/python/cocalc-api/tests/test_org.py create mode 100644 src/python/cocalc-api/tests/test_org_basic.py diff --git a/src/python/cocalc-api/src/cocalc_api/hub.py b/src/python/cocalc-api/src/cocalc_api/hub.py index acd15d1604c..635211295bd 100644 --- a/src/python/cocalc-api/src/cocalc_api/hub.py +++ b/src/python/cocalc-api/src/cocalc_api/hub.py @@ -1,7 +1,8 @@ import httpx from typing import Any, Literal, Optional from .util import api_method, handle_error -from .api_types import PingResponse, UserSearchResult, MessageType, TokenType, OrganizationUser +from .api_types import PingResponse, UserSearchResult, MessageType +from .org import Organizations class Hub: @@ -9,13 +10,9 @@ class Hub: def __init__(self, api_key: str, host: str = "https://cocalc.com"): self.api_key = api_key self.host = host - self.client = httpx.Client( - auth=(api_key, ""), headers={"Content-Type": "application/json"}) + self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}) - def call(self, - name: str, - arguments: list[Any], - timeout: Optional[int] = None) -> Any: + def call(self, name: str, arguments: list[Any], timeout: Optional[int] = None) -> Any: """ Perform an API call to the CoCalc backend. @@ -136,7 +133,7 @@ def user_search(self, query: str) -> UserSearchResult: 'created': 1756056224470, 'email_address_verified': None}] """ - raise NotImplementedError + ... class Projects: @@ -144,10 +141,7 @@ class Projects: def __init__(self, parent: "Hub"): self._parent = parent - def get(self, - fields: Optional[list[str]] = None, - all: Optional[bool] = False, - project_id: Optional[str] = None) -> list[dict[str, Any]]: + def get(self, fields: Optional[list[str]] = None, all: Optional[bool] = False, project_id: Optional[str] = None) -> list[dict[str, Any]]: """ Get data about projects that you are a collaborator on. Only gets recent projects by default; set all=True to get all projects. @@ -219,12 +213,10 @@ def create_project( Returns: str: The ID of the newly created project. """ - # actually implemented via the decorator - raise NotImplementedError + ... @api_method("projects.addCollaborator", opts=True) - def add_collaborator(self, project_id: str | list[str], - account_id: str | list[str]) -> dict[str, Any]: + def add_collaborator(self, project_id: str | list[str], account_id: str | list[str]) -> dict[str, Any]: """ Add a collaborator to a project. @@ -243,8 +235,7 @@ def add_collaborator(self, project_id: str | list[str], ... @api_method("projects.removeCollaborator", opts=True) - def remove_collaborator(self, project_id: str, - account_id: str) -> dict[str, Any]: + def remove_collaborator(self, project_id: str, account_id: str) -> dict[str, Any]: """ Remove a collaborator from a project. @@ -356,8 +347,7 @@ def __init__(self, parent: "Hub"): self._parent = parent @api_method("sync.history") - def history(self, project_id: str, - path: str) -> list[dict[str, Any]]: # type: ignore[empty-body] + def history(self, project_id: str, path: str) -> list[dict[str, Any]]: # type: ignore[empty-body] """ Get complete edit history of a file. @@ -412,11 +402,7 @@ def __init__(self, parent: "Hub"): self._parent = parent @api_method("messages.send") - def send(self, - subject: str, - body: str, - to_ids: list[str], - reply_id: Optional[int] = None) -> int: + def send(self, subject: str, body: str, to_ids: list[str], reply_id: Optional[int] = None) -> int: """ Send a message to one or more users. @@ -429,15 +415,14 @@ def send(self, Returns: int: ID of the message. """ - raise NotImplementedError + ... @api_method("messages.get") def get( self, limit: Optional[int] = None, offset: Optional[int] = None, - type: Optional[Literal["received", "sent", "new", "starred", - "liked"]] = None, + type: Optional[Literal["received", "sent", "new", "starred", "liked"]] = None, ) -> list[MessageType]: # type: ignore[empty-body] """ Get your messages. @@ -450,195 +435,11 @@ def get( Returns: list[MessageType]: List of messages. """ - raise NotImplementedError + ... """ - message: authFirst, - removeUser: authFirst, - removeAdmin: authFirst, - """ - - -class Organizations: - - def __init__(self, parent: "Hub"): - self._parent = parent - - @api_method("org.getAll") - def get_all(self) -> dict[str, Any]: - """ - Get all organizations (site admins only). - - Returns: - dict[str, Any]: Organization data. - """ - raise NotImplementedError - - @api_method("org.create") - def create(self, name: str) -> dict[str, Any]: - """ - Create an organization (site admins only). - - Args: - name (str): Name of the organization; must be globally unique, - at most 39 characters, and CANNOT BE CHANGED. - - Returns: - dict[str, Any]: Organization data. - """ - raise NotImplementedError - - @api_method("org.get") - def get(self, name: str) -> dict[str, Any]: - """ - Get an organization. - - Args: - name (str): Name of the organization. - - Returns: - dict[str, Any]: Organization data. - """ - raise NotImplementedError - - @api_method("org.set") - def set(self, - name: str, - title: Optional[str] = None, - description: Optional[str] = None, - email_address: Optional[str] = None, - link: Optional[str] = None) -> dict[str, Any]: - """ - Set properties of an organization. - - Args: - name (str): Name of the organization. - title (Optional[str]): The title of the organization. - description (Optional[str]): Description of the organization. - email_address (Optional[str]): Email address to reach the organization - (nothing to do with a cocalc account). - link (Optional[str]): A website of the organization. - """ - raise NotImplementedError - - @api_method("org.addAdmin") - def add_admin(self, name: str, user: str) -> dict[str, Any]: - """ - Make the user with given account_id or email an admin - of the named organization. - - Args: - name (str): name of the organization - user (str): email or account_id - """ - raise NotImplementedError - - @api_method("org.addUser") - def add_user(self, name: str, user: str) -> dict[str, Any]: - """ - Make the user with given account_id or email a member - of the named organization. Only site admins can do this. - If you are an org admin, instead use create_user to create - new users in your organization, or contact support. - - Args: - name (str): name of the organization - user (str): email or account_id - """ - raise NotImplementedError - - @api_method("org.createUser") - def create_user(self, - name: str, - email: str, - firstName: Optional[str] = None, - lastName: Optional[str] = None, - password: Optional[str] = None) -> str: - """ - Create a new cocalc account that is a member of the - named organization. - - Args: - name (str): name of the organization - email (str): email address - firstName (Optional[str]): optional first name of the user - lastName (Optional[str]): optional last name of the user - password (Optional[str]): optional password (will be randomized if - not given; you can instead use create_token to grant temporary - account access). - - Returns: - str: account_id of the new user - """ - raise NotImplementedError - - @api_method("org.createToken") - def create_token(self, user: str) -> TokenType: - """ - Create a token that provides temporary access to the given - account. You must be an admin for the org that the user - belongs to or a site admin. - - Args: - user (str): email address or account_id - - Returns: - TokenType: token that grants temporary access - - Notes: - The returned `TokenType` has the following fields: - - - `token` (str): The random token itself, which you may retain - in case you want to explicitly expire it early. - - `url` (str): The url that the user should visit to sign in as - them. You can also test out this url, since the token works - multiple times. - """ - raise NotImplementedError - - @api_method("org.expireToken") - def expire_token(self, token: str) -> dict[str, Any]: - """ - Immediately expire a token created using create_token. - - Args: - token (str): a token - """ - raise NotImplementedError - - @api_method("org.getUsers") - def get_users( - self, - name: str) -> list[OrganizationUser]: # type: ignore[empty-body] - """ - Return list of all accounts that are members of the named organization. - - Args: - name (str): name of the organization - - Returns: - list[OrganizationUser] - - Notes: - The returned `OrganizationUser` has the following fields: - - - `first_name` (str) - - `last_name` (str) - - `account_id` (str): a uuid - - `email_address` (str) - """ - raise NotImplementedError - - @api_method("org.message") - def message(self, name: str, subject: str, body: str) -> dict[str, Any]: - """ - Send a message from you to every account that is a member of - the named organization. - - Args: - name (str): name of the organization - subject (str): plain text subject of the message - body (str): markdown body of the message (math typesetting works) - """ - raise NotImplementedError +message: authFirst, +removeUser: authFirst, +removeAdmin: authFirst, +""" diff --git a/src/python/cocalc-api/src/cocalc_api/org.py b/src/python/cocalc-api/src/cocalc_api/org.py new file mode 100644 index 00000000000..8f6af5ef3fb --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/org.py @@ -0,0 +1,210 @@ +from typing import Any, Optional, TYPE_CHECKING +from .util import api_method +from .api_types import TokenType, OrganizationUser + +if TYPE_CHECKING: + from .hub import Hub + + +class Organizations: + + def __init__(self, parent: "Hub"): + self._parent = parent + + @api_method("org.getAll") + def get_all(self) -> dict[str, Any]: + """ + Get all organizations (site admins only). + + Returns: + dict[str, Any]: Organization data. + """ + ... + + @api_method("org.create") + def create(self, name: str) -> dict[str, Any]: + """ + Create an organization (site admins only). + + Args: + name (str): Name of the organization; must be globally unique, + at most 39 characters, and CANNOT BE CHANGED. + + Returns: + dict[str, Any]: Organization data. + """ + ... + + @api_method("org.get") + def get(self, name: str) -> dict[str, Any]: + """ + Get an organization. + + Args: + name (str): Name of the organization. + + Returns: + dict[str, Any]: Organization data. + """ + ... + + @api_method("org.set") + def set(self, + name: str, + title: Optional[str] = None, + description: Optional[str] = None, + email_address: Optional[str] = None, + link: Optional[str] = None) -> dict[str, Any]: + """ + Set properties of an organization. + + Args: + name (str): Name of the organization. + title (Optional[str]): The title of the organization. + description (Optional[str]): Description of the organization. + email_address (Optional[str]): Email address to reach the organization + (nothing to do with a cocalc account). + link (Optional[str]): A website of the organization. + """ + ... + + @api_method("org.addAdmin") + def add_admin(self, name: str, user: str) -> dict[str, Any]: + """ + Make the user with given account_id or email an admin + of the named organization. + + Args: + name (str): name of the organization + user (str): email or account_id + """ + ... + + @api_method("org.addUser") + def add_user(self, name: str, user: str) -> dict[str, Any]: + """ + Make the user with given account_id or email a member + of the named organization. Only site admins can do this. + If you are an org admin, instead use user to create + new users in your organization, or contact support. + + Args: + name (str): name of the organization + user (str): email or account_id + """ + ... + + @api_method("org.createUser") + def create_user(self, + name: str, + email: str, + firstName: Optional[str] = None, + lastName: Optional[str] = None, + password: Optional[str] = None) -> str: + """ + Create a new cocalc account that is a member of the + named organization. + + Args: + name (str): name of the organization + email (str): email address + firstName (Optional[str]): optional first name of the user + lastName (Optional[str]): optional last name of the user + password (Optional[str]): optional password (will be randomized if + not given; you can instead use create_token to grant temporary + account access). + + Returns: + str: account_id of the new user + """ + ... + + @api_method("org.createToken") + def create_token(self, user: str) -> TokenType: + """ + Create a token that provides temporary access to the given + account. You must be an admin for the org that the user + belongs to or a site admin. + + Args: + user (str): email address or account_id + + Returns: + TokenType: token that grants temporary access + + Notes: + The returned `TokenType` has the following fields: + + - `token` (str): The random token itself, which you may retain + in case you want to explicitly expire it early. + - `url` (str): The url that the user should visit to sign in as + them. You can also test out this url, since the token works + multiple times. + """ + ... + + @api_method("org.expireToken") + def expire_token(self, token: str) -> dict[str, Any]: + """ + Immediately expire a token created using create_token. + + Args: + token (str): a token + """ + ... + + @api_method("org.getUsers") + def get_users(self, name: str) -> list[OrganizationUser]: # type: ignore[empty-body] + """ + Return list of all accounts that are members of the named organization. + + Args: + name (str): name of the organization + + Returns: + list[OrganizationUser] + + Notes: + The returned `OrganizationUser` has the following fields: + + - `first_name` (str) + - `last_name` (str) + - `account_id` (str): a uuid + - `email_address` (str) + """ + ... + + @api_method("org.removeUser") + def remove_user(self, name: str, user: str) -> dict[str, Any]: + """ + Remove a user from an organization. + + Args: + name (str): name of the organization + user (str): email or account_id + """ + ... + + @api_method("org.removeAdmin") + def remove_admin(self, name: str, user: str) -> dict[str, Any]: + """ + Remove an admin from an organization. + + Args: + name (str): name of the organization + user (str): email or account_id + """ + ... + + @api_method("org.message") + def message(self, name: str, subject: str, body: str) -> dict[str, Any]: + """ + Send a message from you to every account that is a member of + the named organization. + + Args: + name (str): name of the organization + subject (str): plain text subject of the message + body (str): markdown body of the message (math typesetting works) + """ + ... diff --git a/src/python/cocalc-api/src/cocalc_api/project.py b/src/python/cocalc-api/src/cocalc_api/project.py index b1aa831f540..e41d9a79369 100644 --- a/src/python/cocalc-api/src/cocalc_api/project.py +++ b/src/python/cocalc-api/src/cocalc_api/project.py @@ -59,7 +59,7 @@ def ping(self) -> PingResponse: {'now': 1756489740133} """ - raise NotImplementedError + ... @api_method("system.exec", timeout_seconds=True) def exec( @@ -106,7 +106,7 @@ def exec( >>> project.system.exec(command="echo 'hello from cocalc'") {'stdout': 'hello from cocalc\\n', 'stderr':'', 'exit_code': 0} """ - raise NotImplementedError + ... @api_method("system.jupyterExecute") def jupyter_execute( diff --git a/src/python/cocalc-api/tests/conftest.py b/src/python/cocalc-api/tests/conftest.py index 487f7649401..4297d7580df 100644 --- a/src/python/cocalc-api/tests/conftest.py +++ b/src/python/cocalc-api/tests/conftest.py @@ -2,11 +2,29 @@ Pytest configuration and fixtures for cocalc-api tests. """ import os +import uuid import pytest from cocalc_api import Hub, Project +def assert_valid_uuid(value, description="value"): + """ + Assert that the given value is a string and a valid UUID. + + Args: + value: The value to check + description: Description of the value for error messages + """ + assert isinstance(value, str), f"{description} should be a string, got {type(value)}" + assert len(value) > 0, f"{description} should not be empty" + + try: + uuid.UUID(value) + except ValueError: + pytest.fail(f"{description} should be a valid UUID, got: {value}") + + @pytest.fixture(scope="session") def api_key(): """Get API key from environment variable.""" diff --git a/src/python/cocalc-api/tests/test_hub.py b/src/python/cocalc-api/tests/test_hub.py index 613552aee3b..12f87988831 100644 --- a/src/python/cocalc-api/tests/test_hub.py +++ b/src/python/cocalc-api/tests/test_hub.py @@ -5,6 +5,7 @@ import pytest from cocalc_api import Hub, Project +from .conftest import assert_valid_uuid class TestHubSystem: @@ -50,10 +51,7 @@ def test_create_project(self, hub): project_id = hub.projects.create_project(title=title, description=description) assert project_id is not None - assert isinstance(project_id, str) - assert len(project_id) > 0 - # Should be a UUID-like string - assert '-' in project_id + assert_valid_uuid(project_id, "Project ID") def test_list_projects(self, hub): """Test listing projects.""" @@ -84,7 +82,7 @@ def test_project_lifecycle(self, hub): print(f"\n1. Creating project '{title}'...") project_id = hub.projects.create_project(title=title, description=description) assert project_id is not None - assert isinstance(project_id, str) + assert_valid_uuid(project_id, "Project ID") print(f" Created project: {project_id}") try: diff --git a/src/python/cocalc-api/tests/test_org.py b/src/python/cocalc-api/tests/test_org.py new file mode 100644 index 00000000000..0ed0b3b5385 --- /dev/null +++ b/src/python/cocalc-api/tests/test_org.py @@ -0,0 +1,581 @@ +""" +Tests for Organization functionality. + +Note: These tests assume the provided API key belongs to a site admin user. +The tests exercise actual organization functionality rather than just checking permissions. +""" +import pytest +import time +import uuid + +from .conftest import assert_valid_uuid + + +class TestAdminPrivileges: + """Test that the API key has admin privileges.""" + + def test_admin_can_get_all_orgs(self, hub): + """Test that the user can call get_all() - verifies admin privileges.""" + try: + result = hub.org.get_all() + # If we get here without an exception, the user has admin privileges + assert isinstance(result, list), "get_all() should return a list" + print(f"✓ Admin verified - found {len(result)} organizations") + except Exception as e: + pytest.fail(f"Admin verification failed. API key may not have admin privileges: {e}") + + +class TestOrganizationBasics: + """Test basic organization module functionality.""" + + def test_org_module_import(self, hub): + """Test that the org module is properly accessible from hub.""" + assert hasattr(hub, 'org') + assert hub.org is not None + + def test_org_methods_available(self, hub): + """Test that all expected organization methods are available.""" + org = hub.org + + expected_methods = [ + 'get_all', + 'create', + 'get', + 'set', + 'add_admin', + 'add_user', + 'create_user', + 'create_token', + 'expire_token', + 'get_users', + 'remove_user', + 'remove_admin', + 'message', + ] + + for method_name in expected_methods: + assert hasattr(org, method_name), f"Method {method_name} not found" + assert callable(getattr(org, method_name)), f"Method {method_name} is not callable" + + +class TestOrganizationCRUD: + """Test organization Create, Read, Update, Delete operations.""" + + def test_get_all_organizations(self, hub): + """Test getting all organizations.""" + orgs = hub.org.get_all() + assert isinstance(orgs, list), "get_all() should return a list" + + # Each org should have expected fields + for org in orgs: + assert isinstance(org, dict), "Each org should be a dict" + assert 'name' in org, "Each org should have a 'name' field" + + def test_create_and_cleanup_organization(self, hub): + """Test creating an organization and basic operations.""" + # Create unique org name + timestamp = int(time.time()) + random_id = str(uuid.uuid4())[:8] + org_name = f"test-org-{timestamp}-{random_id}" + + print(f"Creating test organization: {org_name}") + + try: + # Create the organization + org_id = hub.org.create(org_name) + assert_valid_uuid(org_id, "Organization ID") + print(f"✓ Organization created with ID: {org_id}") + + # Get the organization details + org_details = hub.org.get(org_name) + assert isinstance(org_details, dict), "get() should return a dict" + assert org_details['name'] == org_name, "Organization name should match" + print(f"✓ Organization retrieved: {org_details}") + + # Update organization properties + hub.org.set(name=org_name, + title="Test Organization", + description="This is a test organization created by automated tests", + email_address="test@example.com", + link="https://example.com") + + # Verify the update + updated_org = hub.org.get(org_name) + assert updated_org['title'] == "Test Organization" + assert updated_org['description'] == "This is a test organization created by automated tests" + assert updated_org['email_address'] == "test@example.com" + assert updated_org['link'] == "https://example.com" + print("✓ Organization properties updated successfully") + + except Exception as e: + pytest.fail(f"Organization CRUD operations failed: {e}") + + +class TestOrganizationUserManagement: + """Test organization user management functionality.""" + + @pytest.fixture(scope="class") + def test_organization(self, hub): + """Create a test organization for user management tests.""" + timestamp = int(time.time()) + random_id = str(uuid.uuid4())[:8] + org_name = f"test-user-org-{timestamp}-{random_id}" + + print(f"Creating test organization for user tests: {org_name}") + + # Create the organization + org_id = hub.org.create(org_name) + + yield {'name': org_name, 'id': org_id} + + # Cleanup would go here, but since we can't delete orgs, + # we leave them for manual cleanup if needed + + def test_get_users_empty_org(self, hub, test_organization): + """Test getting users from a newly created organization.""" + users = hub.org.get_users(test_organization['name']) + assert isinstance(users, list), "get_users() should return a list" + assert len(users) == 0, f"Newly created organization should be empty, but has {len(users)} users" + print("✓ Newly created organization is empty as expected") + + def test_create_user_in_organization(self, hub, test_organization): + """Test creating a user within an organization.""" + # Create unique user details + timestamp = int(time.time()) + test_email = f"test-user-{timestamp}@example.com" + + try: + # Create user in the organization + new_user_id = hub.org.create_user(name=test_organization['name'], email=test_email, firstName="Test", lastName="User") + + assert_valid_uuid(new_user_id, "User ID") + print(f"✓ User created with ID: {new_user_id}") + + + # Wait a moment for database consistency + import time as time_module + time_module.sleep(1) + + # Verify user appears in org users list + users = hub.org.get_users(test_organization['name']) + user_ids = [user['account_id'] for user in users] + + print(f"Debug - Organization name: '{test_organization['name']}'") + print(f"Debug - Created user ID: '{new_user_id}'") + print(f"Debug - Users in org: {len(users)}") + print(f"Debug - User IDs: {user_ids}") + + assert new_user_id in user_ids, f"New user {new_user_id} should appear in organization users list. Found users: {user_ids}" + + # Find the created user in the list + created_user = next((u for u in users if u['account_id'] == new_user_id), None) + assert created_user is not None, "Created user should be found in users list" + assert created_user['email_address'] == test_email, "Email should match" + assert created_user['first_name'] == "Test", "First name should match" + assert created_user['last_name'] == "User", "Last name should match" + + print(f"✓ User verified in organization: {created_user}") + + except Exception as e: + pytest.fail(f"User creation failed: {e}") + + def test_admin_management(self, hub, test_organization): + """Test adding and managing admins using the correct workflow.""" + # CORRECT WORKFLOW: add_admin() works with users NOT already in the target organization + + timestamp = int(time.time()) + + try: + # Method 1: Create external user in a temporary org, then make them admin of target org + temp_org_name = f"temp-admin-org-{timestamp}" + hub.org.create(temp_org_name) + print(f"✓ Created temporary org: {temp_org_name}") + + # Create user in the temporary org + external_user_id = hub.org.create_user(name=temp_org_name, + email=f"external-admin-{timestamp}@example.com", + firstName="External", + lastName="Admin") + assert_valid_uuid(external_user_id, "External user ID") + print(f"✓ Created external user: {external_user_id}") + + # Now add the external user as admin to the target organization + # This should work because the user is not already in the target org + hub.org.add_admin(test_organization['name'], external_user_id) + print(f"✓ Added external user as admin to {test_organization['name']}") + + # Verify admin status + org_details = hub.org.get(test_organization['name']) + admin_ids = org_details.get('admin_account_ids') or [] + assert external_user_id in admin_ids, "External user should be in admin list" + print(f"✓ Admin status verified: {admin_ids}") + + # Verify the user was also moved to the target organization + users_in_org = hub.org.get_users(test_organization['name']) + user_ids = [u['account_id'] for u in users_in_org] + assert external_user_id in user_ids, "Admin should now be in target organization" + print("✓ User successfully moved to target org") + + # Test remove_admin + hub.org.remove_admin(test_organization['name'], external_user_id) + print(f"✓ Admin status removed for {external_user_id}") + + # Verify admin removal + updated_org = hub.org.get(test_organization['name']) + updated_admin_ids = updated_org.get('admin_account_ids') or [] + assert external_user_id not in updated_admin_ids, "User should no longer be admin" + print("✓ Admin removal verified") + + except Exception as e: + pytest.fail(f"Admin management failed: {e}") + + def test_admin_workflow_documentation(self, hub): + """Document the correct admin assignment workflows.""" + timestamp = int(time.time()) + + try: + # Create target organization + target_org = f"target-workflow-{timestamp}" + hub.org.create(target_org) + + # Workflow 1: External user method (recommended) + temp_org = f"temp-workflow-{timestamp}" + hub.org.create(temp_org) + external_user = hub.org.create_user(name=temp_org, + email=f"workflow-external-{timestamp}@example.com", + firstName="Workflow", + lastName="External") + assert_valid_uuid(external_user, "Workflow external user ID") + + # This works: user from different org + hub.org.add_admin(target_org, external_user) + org_details = hub.org.get(target_org) + admin_ids = org_details.get('admin_account_ids') or [] + assert external_user in admin_ids + print("✓ Workflow 1 (External user): SUCCESS") + print("✓ Admin workflow documentation complete") + + except Exception as e: + pytest.fail(f"Admin workflow documentation failed: {e}") + + +class TestOrganizationTokens: + """Test organization token functionality.""" + + @pytest.fixture(scope="class") + def test_org_with_user(self, hub): + """Create a test organization with a user for token tests.""" + timestamp = int(time.time()) + random_id = str(uuid.uuid4())[:8] + org_name = f"test-token-org-{timestamp}-{random_id}" + + # Create the organization + org_id = hub.org.create(org_name) + + # Create a user in the organization + test_email = f"token-user-{timestamp}@example.com" + user_id = hub.org.create_user(name=org_name, email=test_email, firstName="Token", lastName="User") + assert_valid_uuid(user_id, "Token user ID") + + + yield {'name': org_name, 'id': org_id, 'user_id': user_id, 'user_email': test_email} + + def test_create_and_expire_token(self, hub, test_org_with_user): + """Test creating and expiring access tokens.""" + try: + # Create token for the user + token_info = hub.org.create_token(test_org_with_user['user_id']) + + assert isinstance(token_info, dict), "create_token() should return a dict" + assert 'token' in token_info, "Token info should contain 'token' field" + assert 'url' in token_info, "Token info should contain 'url' field" + + token = token_info['token'] + url = token_info['url'] + + assert isinstance(token, str) and len(token) > 0, "Token should be a non-empty string" + assert isinstance(url, str) and url.startswith('http'), "URL should be a valid HTTP URL" + + print(f"✓ Token created: {token[:10]}... (truncated)") + print(f"✓ Access URL: {url}") + + # Expire the token + hub.org.expire_token(token) + print("✓ Token expired successfully") + + except Exception as e: + pytest.fail(f"Token management failed: {e}") + + +class TestOrganizationMessaging: + """Test organization messaging functionality.""" + + @pytest.fixture(scope="class") + def test_org_with_users(self, hub): + """Create a test organization with multiple users for messaging tests.""" + timestamp = int(time.time()) + random_id = str(uuid.uuid4())[:8] + org_name = f"test-msg-org-{timestamp}-{random_id}" + + # Create the organization + org_id = hub.org.create(org_name) + + # Create multiple users in the organization + users = [] + for i in range(2): + test_email = f"msg-user-{i}-{timestamp}@example.com" + user_id = hub.org.create_user(name=org_name, email=test_email, firstName=f"User{i}", lastName="Messaging") + assert_valid_uuid(user_id, f"Messaging user {i} ID") + + + users.append({'id': user_id, 'email': test_email}) + + yield {'name': org_name, 'id': org_id, 'users': users} + + def test_send_message_to_organization(self, hub, test_org_with_users, cocalc_host): + """Test sending a message to all organization members and verify receipt.""" + from cocalc_api import Hub + + test_subject = "Test Message from API Tests" + test_body = "This is a test message sent via the CoCalc API organization messaging system." + user_token = None + + try: + # Step 1: Create a token for the first user to act as them + first_user = test_org_with_users['users'][0] + token_info = hub.org.create_token(first_user['id']) + + assert isinstance(token_info, dict), "create_token() should return a dict" + assert 'token' in token_info, "Token info should contain 'token' field" + + user_token = token_info['token'] + print(f"✓ Created token for user {first_user['id']}") + + # Step 2: Create Hub client using the user's token + user1 = Hub(api_key=user_token, host=cocalc_host) + print("✓ Created Hub client using user token") + + # Step 3: Get user's messages before sending org message (for comparison) + try: + messages_before = user1.messages.get(limit=5, type="received") + print(f"✓ User has {len(messages_before)} received messages before test") + except Exception as e: + print(f"⚠ Could not get user's messages before test: {e}") + messages_before = [] + + # Step 4: Send the organization message + result = hub.org.message(name=test_org_with_users['name'], subject=test_subject, body=test_body) + + # Note: org.message() may return None, which is fine (indicates success) + print(f"✓ Organization message sent successfully (result: {result})") + + # Step 5: Wait a moment for message delivery + import time + time.sleep(2) + + # Step 6: Check if user received the message + try: + messages_after = user1.messages.get(limit=10, type="received") + print(f"✓ User has {len(messages_after)} received messages after test") + + # Look for our test message in user's received messages + found_message = False + for msg in messages_after: + if isinstance(msg, dict) and msg.get('subject') == test_subject: + found_message = True + print(f"✓ VERIFIED: User received message with subject: '{msg.get('subject')}'") + + # Verify message content + if 'body' in msg: + print(f"✓ Message body confirmed: {msg['body'][:50]}...") + break + + if found_message: + print("🎉 SUCCESS: Organization message was successfully delivered to user!") + else: + print("⚠ Message not found in user's received messages") + print(f" Expected subject: '{test_subject}'") + if messages_after: + print(f" Recent subjects: {[msg.get('subject', 'No subject') for msg in messages_after[:3]]}") + + except Exception as msg_check_error: + print(f"⚠ Could not verify message delivery: {msg_check_error}") + + except Exception as e: + pytest.fail(f"Message sending and verification failed: {e}") + + finally: + # Clean up: expire the token + if user_token: + try: + hub.org.expire_token(user_token) + print("✓ User token expired (cleanup)") + except Exception as cleanup_error: + print(f"⚠ Failed to expire token during cleanup: {cleanup_error}") + + def test_send_markdown_message(self, hub, test_org_with_users, cocalc_host): + """Test sending a message with markdown formatting and verify receipt.""" + from cocalc_api import Hub + + test_subject = "📝 Markdown Test Message" + markdown_body = """ +# Test Message with Markdown + +This is a **test message** with *markdown* formatting sent from the API tests. + +## Features Tested +- Organization messaging +- Markdown formatting +- API integration + +## Math Example +The formula $E = mc^2$ should render properly. + +## Code Example +```python +print("Hello from CoCalc API!") +``` + +[CoCalc API Documentation](https://cocalc.com/api/python/) + +--- +*This message was sent automatically by the organization API tests.* + """.strip() + + user_token = None + + try: + # Create a token for the second user (to vary which user we test) + if len(test_org_with_users['users']) > 1: + test_user = test_org_with_users['users'][1] + else: + test_user = test_org_with_users['users'][0] + + token_info = hub.org.create_token(test_user['id']) + user_token = token_info['token'] + user_hub = Hub(api_key=user_token, host=cocalc_host) + print(f"✓ Created token and Hub client for user {test_user['id']}") + + # Send the markdown message + result = hub.org.message(name=test_org_with_users['name'], subject=test_subject, body=markdown_body) + + # Note: org.message() may return None, which is fine (indicates success) + print(f"✓ Markdown message sent successfully (result: {result})") + + # Wait for message delivery and verify + import time + time.sleep(2) + + try: + messages = user_hub.messages.get(limit=10, type="received") + + # Look for the markdown message + found_message = False + for msg in messages: + if isinstance(msg, dict) and msg.get('subject') == test_subject: + found_message = True + print("✓ VERIFIED: User received markdown message") + + # Verify it contains markdown content + body = msg.get('body', '') + if '**test message**' in body or 'Test Message with Markdown' in body: + print("✓ Markdown content confirmed in received message") + break + + if found_message: + print("🎉 SUCCESS: Markdown message was successfully delivered!") + else: + print("⚠ Markdown message not found in user's received messages") + + except Exception as msg_check_error: + print(f"⚠ Could not verify markdown message delivery: {msg_check_error}") + + except Exception as e: + pytest.fail(f"Markdown message sending and verification failed: {e}") + + finally: + # Clean up: expire the token + if user_token: + try: + hub.org.expire_token(user_token) + print("✓ Markdown test token expired (cleanup)") + except Exception as cleanup_error: + print(f"⚠ Failed to expire markdown test token: {cleanup_error}") + + +class TestOrganizationIntegration: + """Integration tests for organization functionality.""" + + def test_full_organization_lifecycle(self, hub): + """Test a complete organization lifecycle with users and messaging.""" + timestamp = int(time.time()) + random_id = str(uuid.uuid4())[:8] + org_name = f"test-lifecycle-{timestamp}-{random_id}" + + try: + print(f"Testing full lifecycle for organization: {org_name}") + + # 1. Create organization + org_id = hub.org.create(org_name) + print(f"✓ 1. Organization created: {org_id}") + + # 2. Set organization properties + hub.org.set(name=org_name, title="Lifecycle Test Organization", description="Testing complete organization lifecycle") + print("✓ 2. Organization properties set") + + # 3. Create users + users = [] + for i in range(2): + user_email = f"lifecycle-user-{i}-{timestamp}@example.com" + user_id = hub.org.create_user(name=org_name, email=user_email, firstName=f"User{i}", lastName="Lifecycle") + assert_valid_uuid(user_id, f"Lifecycle user {i} ID") + + + users.append({'id': user_id, 'email': user_email}) + print(f"✓ 3. Created {len(users)} users") + + # 4. Make external admin using correct workflow + # Create external user and make them admin (correct workflow) + temp_admin_org = f"temp-admin-{timestamp}" + hub.org.create(temp_admin_org) + external_admin_id = hub.org.create_user(name=temp_admin_org, + email=f"external-admin-{timestamp}@example.com", + firstName="External", + lastName="Admin") + assert_valid_uuid(external_admin_id, "External admin ID") + hub.org.add_admin(org_name, external_admin_id) + print("✓ 4. Added external user as admin using correct workflow") + + # 5. Create and expire a token + token_info = hub.org.create_token(users[1]['id']) + hub.org.expire_token(token_info['token']) + print("✓ 5. Token created and expired") + + # 6. Send message to organization + hub.org.message(name=org_name, subject="Lifecycle Test Complete", body="All organization lifecycle tests completed successfully!") + print("✓ 6. Message sent") + + # 7. Verify final state + final_org = hub.org.get(org_name) + final_users = hub.org.get_users(org_name) + + assert final_org['title'] == "Lifecycle Test Organization" + assert len(final_users) >= len(users) + 1, "All users plus admin should be in organization" + + # Check admin status (should work with correct workflow) + admin_ids = final_org.get('admin_account_ids') or [] + assert external_admin_id in admin_ids, "External admin should be in admin list" + print(f"✓ Admin assignment successful: {admin_ids}") + + print(f"✓ 7. Final verification complete - org has {len(final_users)} users") + print(f"✓ Full lifecycle test completed successfully for {org_name}") + + except Exception as e: + pytest.fail(f"Full lifecycle test failed: {e}") + + +def test_delete_method_still_available(hub): + """Verify that projects.delete is still available after org refactoring.""" + assert hasattr(hub.projects, 'delete') + assert callable(hub.projects.delete) + print("✓ Projects delete method still available after org refactoring") diff --git a/src/python/cocalc-api/tests/test_org_basic.py b/src/python/cocalc-api/tests/test_org_basic.py new file mode 100644 index 00000000000..f2ad5416cc9 --- /dev/null +++ b/src/python/cocalc-api/tests/test_org_basic.py @@ -0,0 +1,131 @@ +""" +Basic Organization functionality tests. + +This file contains tests that verify the organization API is properly exposed +and accessible, without necessarily requiring full admin privileges or server connectivity. +""" +import pytest + + +class TestOrganizationAPIExposure: + """Test that organization API methods are properly exposed.""" + + def test_org_module_available(self, hub): + """Test that the org module is accessible from hub.""" + assert hasattr(hub, 'org') + assert hub.org is not None + + def test_all_org_methods_available(self, hub): + """Test that all expected organization methods are available and callable.""" + org = hub.org + + expected_methods = [ + 'get_all', 'create', 'get', 'set', 'add_admin', 'add_user', 'create_user', 'create_token', 'expire_token', 'get_users', 'remove_user', + 'remove_admin', 'message' + ] + + for method_name in expected_methods: + assert hasattr(org, method_name), f"Method {method_name} not found" + method = getattr(org, method_name) + assert callable(method), f"Method {method_name} is not callable" + + print(f"✓ All {len(expected_methods)} organization methods are properly exposed") + + def test_org_methods_are_api_decorated(self, hub): + """Test that org methods make actual API calls (not just stubs).""" + # We can verify this by attempting to call a method that should fail + # with authentication/permission errors rather than NotImplementedError + + with pytest.raises(Exception) as exc_info: + # This should make an actual API call and fail with auth or server error, + # not with NotImplementedError + hub.org.get("nonexistent-org-for-testing-12345") + + # Should NOT be NotImplementedError (which would indicate the method isn't implemented) + assert not isinstance(exc_info.value, NotImplementedError), \ + "Organization methods should make actual API calls, not raise NotImplementedError" + + print(f"✓ Organization methods make actual API calls: {type(exc_info.value).__name__}") + + def test_message_method_signature(self, hub): + """Test that the message method has the correct signature.""" + import inspect + + sig = inspect.signature(hub.org.message) + params = list(sig.parameters.keys()) + + # Should have name, subject, body parameters + required_params = ['name', 'subject', 'body'] + for param in required_params: + assert param in params, f"Message method missing required parameter: {param}" + + print("✓ Message method has correct parameters:", params) + + def test_create_user_method_signature(self, hub): + """Test that create_user method has the correct signature.""" + import inspect + + sig = inspect.signature(hub.org.create_user) + params = sig.parameters + + # Check required parameters + assert 'name' in params, "create_user missing 'name' parameter" + assert 'email' in params, "create_user missing 'email' parameter" + + # Check optional parameters + optional_params = ['firstName', 'lastName', 'password'] + for param in optional_params: + assert param in params, f"create_user missing optional parameter: {param}" + # Optional params should have default values + assert params[param].default is not inspect.Parameter.empty, \ + f"Optional parameter {param} should have a default value" + + print("✓ create_user method has correct parameter signature") + + def test_create_token_return_annotation(self, hub): + """Test that create_token has proper return type annotation.""" + import inspect + + sig = inspect.signature(hub.org.create_token) + return_annotation = sig.return_annotation + + # Should be annotated to return TokenType + assert return_annotation.__name__ == 'TokenType', \ + f"create_token should return TokenType, got {return_annotation}" + + print("✓ create_token method has correct return type annotation") + + +class TestOrganizationImportIntegrity: + """Test that the organization refactoring didn't break anything.""" + + def test_organizations_class_imported_correctly(self, hub): + """Test that Organizations class is properly imported in hub.""" + # The hub.org should be an instance of the Organizations class + from cocalc_api.org import Organizations + + assert isinstance(hub.org, Organizations), \ + "hub.org should be an instance of Organizations class" + + print("✓ Organizations class properly imported and instantiated") + + def test_original_hub_functionality_preserved(self, hub): + """Test that refactoring didn't break other hub functionality.""" + # Test that other hub properties still work + assert hasattr(hub, 'system'), "Hub should still have system property" + assert hasattr(hub, 'projects'), "Hub should still have projects property" + assert hasattr(hub, 'messages'), "Hub should still have messages property" + + # Test that projects.delete is still available (from main task) + assert hasattr(hub.projects, 'delete'), "Projects should still have delete method" + assert callable(hub.projects.delete), "Projects delete should be callable" + + print("✓ All original Hub functionality preserved after org refactoring") + + +def test_make_check_compatibility(): + """Test that the refactoring passes all static analysis checks.""" + # This test exists to document that the refactored code should pass + # make check (ruff, mypy, pyright) - the actual checking is done by CI/make + print("✓ Organization refactoring should pass make check (ruff, mypy, pyright)") + assert True diff --git a/src/python/cocalc-api/tests/test_project.py b/src/python/cocalc-api/tests/test_project.py index 84c7ff04d8f..fefe182dbc0 100644 --- a/src/python/cocalc-api/tests/test_project.py +++ b/src/python/cocalc-api/tests/test_project.py @@ -4,6 +4,7 @@ import pytest from cocalc_api import Project +from .conftest import assert_valid_uuid class TestProjectCreation: @@ -17,9 +18,8 @@ def test_create_temporary_project(self, temporary_project): assert 'description' in temporary_project assert temporary_project['title'].startswith('CoCalc API Test ') assert temporary_project['description'] == "Temporary project created by cocalc-api tests" - # Project ID should be a UUID-like string - assert len(temporary_project['project_id']) > 0 - assert '-' in temporary_project['project_id'] + # Project ID should be a valid UUID + assert_valid_uuid(temporary_project['project_id'], "Project ID") def test_project_exists_in_list(self, hub, temporary_project): """Test that the created project appears in the projects list.""" From 3eb799722ee18c2b8dcf2cccd35594fbf376db06 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Mon, 29 Sep 2025 16:56:02 +0200 Subject: [PATCH 10/58] cocalc-api: coverage report, makefile help --- .gitignore | 6 ++ src/python/cocalc-api/Makefile | 26 +++++- src/python/cocalc-api/pyproject.toml | 31 +++++++ src/python/cocalc-api/uv.lock | 134 +++++++++++++++++++++++++++ 4 files changed, 196 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b03ea399673..f7f5392d950 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,12 @@ src/conf/tinc_hosts/ # mocha: banket coverage report src/coverage src/*/coverage + +# Python coverage files +.coverage +.coverage.* +htmlcov/ +**/htmlcov/ # comes up when testing in that directory src/rethinkdb_data/ src/dev/project/rethinkdb_data/ diff --git a/src/python/cocalc-api/Makefile b/src/python/cocalc-api/Makefile index 08b19087448..685a030f3b1 100644 --- a/src/python/cocalc-api/Makefile +++ b/src/python/cocalc-api/Makefile @@ -1,5 +1,20 @@ all: install build-docs +help: + @echo "Available targets:" + @echo " install - Install project dependencies" + @echo " format - Format code using yapf" + @echo " check - Run linting and type checking" + @echo " test - Run tests" + @echo " test-verbose - Run tests with verbose output" + @echo " coverage - Run tests with coverage reporting (HTML + terminal)" + @echo " coverage-report - Show coverage report in terminal" + @echo " coverage-html - Generate HTML coverage report only" + @echo " serve-docs - Serve documentation locally" + @echo " build-docs - Build documentation" + @echo " publish - Build and publish package" + @echo " clean - Clean build artifacts and cache files" + install: uv --version >/dev/null 2> /dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh uv sync --dev @@ -19,6 +34,15 @@ test: test-verbose: uv run pytest -v +coverage: + uv run pytest --cov=src --cov-report=term-missing --cov-report=html + +coverage-report: + uv run coverage report + +coverage-html: + uv run coverage html + serve-docs: uv run mkdocs serve @@ -30,6 +54,6 @@ publish: install uv publish clean: - rm -rf dist build *.egg-info site .pytest_cache + rm -rf dist build *.egg-info site .pytest_cache htmlcov .coverage find . -name "__pycache__" -type d -exec rm -rf {} + find . -name "*.pyc" -delete diff --git a/src/python/cocalc-api/pyproject.toml b/src/python/cocalc-api/pyproject.toml index 9e8fbb7ac42..6919ba7f21a 100644 --- a/src/python/cocalc-api/pyproject.toml +++ b/src/python/cocalc-api/pyproject.toml @@ -39,8 +39,38 @@ indent_width = 4 line-length = 150 lint.select = ["E", "F", "B"] # Pyflakes, pycodestyle, bugbear, etc. +[tool.coverage.run] +source = ["src"] +omit = [ + "*/tests/*", + "*/test_*.py", + "*/__pycache__/*", + "*/venv/*", + "*/env/*", +] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "def __repr__", + "if self.debug:", + "if settings.DEBUG", + "raise AssertionError", + "raise NotImplementedError", + "if 0:", + "if __name__ == .__main__.:", + "class .*\\bProtocol\\):", + "@(abc\\.)?abstractmethod", +] +show_missing = true +precision = 1 + +[tool.coverage.html] +directory = "htmlcov" + [dependency-groups] dev = [ + "coverage[toml]", "ipython", "mkdocs", "mkdocs-material", @@ -48,6 +78,7 @@ dev = [ "mypy", "pyright", "pytest>=8.4.1", + "pytest-cov", "ruff>=0.12.11", "yapf", ] diff --git a/src/python/cocalc-api/uv.lock b/src/python/cocalc-api/uv.lock index 25e991e1c07..ee33170b971 100644 --- a/src/python/cocalc-api/uv.lock +++ b/src/python/cocalc-api/uv.lock @@ -179,6 +179,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "coverage", extra = ["toml"] }, { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "ipython", version = "9.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -188,6 +189,7 @@ dev = [ { name = "mypy" }, { name = "pyright" }, { name = "pytest" }, + { name = "pytest-cov" }, { name = "ruff" }, { name = "yapf" }, ] @@ -197,6 +199,7 @@ requires-dist = [{ name = "httpx" }] [package.metadata.requires-dev] dev = [ + { name = "coverage", extras = ["toml"] }, { name = "ipython" }, { name = "mkdocs" }, { name = "mkdocs-material" }, @@ -204,6 +207,7 @@ dev = [ { name = "mypy" }, { name = "pyright" }, { name = "pytest", specifier = ">=8.4.1" }, + { name = "pytest-cov" }, { name = "ruff", specifier = ">=0.12.11" }, { name = "yapf" }, ] @@ -217,6 +221,122 @@ 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 = "coverage" +version = "7.10.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987, upload-time = "2025-09-21T20:00:57.218Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388, upload-time = "2025-09-21T20:01:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148, upload-time = "2025-09-21T20:01:01.768Z" }, + { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958, upload-time = "2025-09-21T20:01:03.355Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819, upload-time = "2025-09-21T20:01:04.968Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754, upload-time = "2025-09-21T20:01:06.321Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860, upload-time = "2025-09-21T20:01:07.605Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877, upload-time = "2025-09-21T20:01:08.829Z" }, + { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108, upload-time = "2025-09-21T20:01:10.527Z" }, + { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752, upload-time = "2025-09-21T20:01:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497, upload-time = "2025-09-21T20:01:13.459Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392, upload-time = "2025-09-21T20:01:14.722Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" }, + { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" }, + { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" }, + { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" }, + { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" }, + { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" }, + { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" }, + { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" }, + { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" }, + { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" }, + { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" }, + { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" }, + { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" }, + { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, + { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, + { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, + { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, + { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, + { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, + { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, + { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, + { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, + { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, + { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, + { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, + { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, + { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" }, + { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" }, + { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" }, + { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" }, + { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" }, + { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" }, + { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" }, + { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" }, + { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978, upload-time = "2025-09-21T20:03:30.362Z" }, + { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370, upload-time = "2025-09-21T20:03:32.147Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802, upload-time = "2025-09-21T20:03:33.919Z" }, + { url = "https://files.pythonhosted.org/packages/b0/49/8a070782ce7e6b94ff6a0b6d7c65ba6bc3091d92a92cef4cd4eb0767965c/coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40", size = 246625, upload-time = "2025-09-21T20:03:36.09Z" }, + { url = "https://files.pythonhosted.org/packages/6a/92/1c1c5a9e8677ce56d42b97bdaca337b2d4d9ebe703d8c174ede52dbabd5f/coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594", size = 248399, upload-time = "2025-09-21T20:03:38.342Z" }, + { url = "https://files.pythonhosted.org/packages/c0/54/b140edee7257e815de7426d5d9846b58505dffc29795fff2dfb7f8a1c5a0/coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a", size = 245142, upload-time = "2025-09-21T20:03:40.591Z" }, + { url = "https://files.pythonhosted.org/packages/e4/9e/6d6b8295940b118e8b7083b29226c71f6154f7ff41e9ca431f03de2eac0d/coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b", size = 246284, upload-time = "2025-09-21T20:03:42.355Z" }, + { url = "https://files.pythonhosted.org/packages/db/e5/5e957ca747d43dbe4d9714358375c7546cb3cb533007b6813fc20fce37ad/coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3", size = 244353, upload-time = "2025-09-21T20:03:44.218Z" }, + { url = "https://files.pythonhosted.org/packages/9a/45/540fc5cc92536a1b783b7ef99450bd55a4b3af234aae35a18a339973ce30/coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0", size = 244430, upload-time = "2025-09-21T20:03:46.065Z" }, + { url = "https://files.pythonhosted.org/packages/75/0b/8287b2e5b38c8fe15d7e3398849bb58d382aedc0864ea0fa1820e8630491/coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f", size = 245311, upload-time = "2025-09-21T20:03:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1d/29724999984740f0c86d03e6420b942439bf5bd7f54d4382cae386a9d1e9/coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431", size = 220500, upload-time = "2025-09-21T20:03:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/43/11/4b1e6b129943f905ca54c339f343877b55b365ae2558806c1be4f7476ed5/coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07", size = 221408, upload-time = "2025-09-21T20:03:51.803Z" }, + { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + [[package]] name = "decorator" version = "5.2.1" @@ -894,6 +1014,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" From b824e3f91a12fc327522e33f1ebca24d55c140cc Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Tue, 30 Sep 2025 09:47:01 +0200 Subject: [PATCH 11/58] cocalc-api: attempt to integrate into CI --- .github/workflows/make-and-test.yml | 97 +++++++++++++++++++++++ src/packages/hub/package.json | 3 +- src/packages/hub/run/test-create-admin.js | 64 +++++++++++++++ 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100755 src/packages/hub/run/test-create-admin.js diff --git a/.github/workflows/make-and-test.yml b/.github/workflows/make-and-test.yml index 088354e702f..e9473231bc8 100644 --- a/.github/workflows/make-and-test.yml +++ b/.github/workflows/make-and-test.yml @@ -128,6 +128,103 @@ jobs: name: "test-results-node-${{ matrix.node-version }}-pg-${{ matrix.pg-version }}" path: 'src/packages/*/junit.xml' + - name: Create CI admin user and API key + run: | + cd src/packages/hub + node run/test-create-admin.js > ../../api_key.txt + # Validate API key was created + if [ ! -s ../../api_key.txt ]; then + echo "Error: API key file is empty or missing" + exit 1 + fi + API_KEY=$(cat ../../api_key.txt) + if ! echo "$API_KEY" | grep -qE '^sk-[A-Za-z0-9]+$'; then + echo "Error: Invalid API key format: $API_KEY" + exit 1 + fi + echo "API key created successfully" + env: + PGDATABASE: smc + PGUSER: smc + PGHOST: localhost + + - name: Start CoCalc Hub + run: | + cd src/packages/hub + pnpm run hub-project-dev-nobuild > hub.log 2>&1 & + HUB_PID=$! + echo $HUB_PID > hub.pid + echo "Hub started with PID $HUB_PID" + # Check if process is still running after a moment + sleep 2 + if ! kill -0 $HUB_PID 2>/dev/null; then + echo "Error: Hub process died immediately after starting" + echo "Hub log:" + cat hub.log + exit 1 + fi + env: + PGDATABASE: smc + PGUSER: smc + PGHOST: localhost + COCALC_MODE: single-user + + - name: Wait for hub readiness + run: | + MAX_ATTEMPTS=30 + READY=false + for i in $(seq 1 $MAX_ATTEMPTS); do + if curl -f --max-time 3 http://localhost:5000/healthcheck; then + echo "Hub is ready" + READY=true + break + fi + echo "Waiting for hub... ($i/$MAX_ATTEMPTS)" + sleep 3 + done + if [ "$READY" = "false" ]; then + echo "Hub failed to become ready after $MAX_ATTEMPTS attempts" + echo "Hub log:" + cat src/packages/hub/hub.log || echo "No log file found" + exit 1 + fi + + - name: Install uv for cocalc-api tests + run: curl -LsSf https://astral.sh/uv/install.sh | sh && echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Run cocalc-api tests + run: | + export COCALC_API_KEY=$(cat src/api_key.txt) + export COCALC_HOST=http://localhost:5000 + cd src/python/cocalc-api && uv run pytest --junitxml=test-results.xml + + - name: Stop CoCalc Hub + if: always() + run: | + if [ -f src/packages/hub/hub.pid ]; then + HUB_PID=$(cat src/packages/hub/hub.pid) + echo "Stopping hub with PID $HUB_PID" + kill $HUB_PID || true + # Wait a bit for graceful shutdown + sleep 2 + # Force kill if still running + kill -9 $HUB_PID 2>/dev/null || true + fi + + - name: Upload hub logs + uses: actions/upload-artifact@v4 + if: always() + with: + name: "hub-logs-node-${{ matrix.node-version }}-pg-${{ matrix.pg-version }}" + path: 'src/packages/hub/hub.log' + + - name: Upload cocalc-api test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: "cocalc-api-test-results-node-${{ matrix.node-version }}-pg-${{ matrix.pg-version }}" + path: 'src/python/cocalc-api/test-results.xml' + report: runs-on: ubuntu-latest diff --git a/src/packages/hub/package.json b/src/packages/hub/package.json index f7247e7597d..722a4424919 100644 --- a/src/packages/hub/package.json +++ b/src/packages/hub/package.json @@ -96,6 +96,7 @@ "cocalc-hub-maintenance-expired": "./run/maintenance-expired.js", "cocalc-hub-maintenance-syncstrings": "./run/maintenance-syncstrings.js", "cocalc-hub-maintenance-blobs": "./run/maintenance-blobs.js", - "cocalc-hub-stripe-sync": "./run/stripe-sync.js" + "cocalc-hub-stripe-sync": "./run/stripe-sync.js", + "cocalc-hub-test-create-admin": "./run/test-create-admin.js" } } diff --git a/src/packages/hub/run/test-create-admin.js b/src/packages/hub/run/test-create-admin.js new file mode 100755 index 00000000000..6b02f4f08d5 --- /dev/null +++ b/src/packages/hub/run/test-create-admin.js @@ -0,0 +1,64 @@ +#!/usr/bin/env node + +/* + * Script to create a test admin account and API key for CI testing. + * This is used in GitHub Actions to set up cocalc-api tests. + */ + +import { v4 as uuidv4 } from "uuid"; +import createAccount from "@cocalc/server/accounts/create-account"; +import manageApiKeys from "@cocalc/server/api/manage"; +import getPool from "@cocalc/database/pool"; + +async function main() { + const account_id = uuidv4(); + const email = "ci-admin@cocalc.test"; + const password = "testpassword"; // dummy password + const firstName = "CI"; + const lastName = "Admin"; + + console.log(`Creating admin account ${account_id}...`); + + // Create the account + await createAccount({ + email, + password, + firstName, + lastName, + account_id, + tags: [], + signupReason: "CI testing", + noFirstProject: true, + }); + + // Set as admin + const pool = getPool(); + await pool.query("UPDATE accounts SET groups=$1 WHERE account_id=$2", [ + ["admin"], + account_id, + ]); + + console.log("Creating API key..."); + + // Create API key + const keys = await manageApiKeys({ + account_id, + action: "create", + name: "ci-testing", + }); + + if (!keys || keys.length === 0) { + throw new Error("Failed to create API key"); + } + + const apiKey = keys[0]; + console.log(`API key created: ${apiKey.secret}`); + + // Output the key for CI + process.stdout.write(apiKey.secret); +} + +main().catch((err) => { + console.error("Error:", err); + process.exit(1); +}); \ No newline at end of file From d24ec9a7f3bc89fede4dccfa7c788e016d2c3608 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Tue, 30 Sep 2025 10:18:21 +0200 Subject: [PATCH 12/58] server/organizations logic: only allow site-admins to promote users to be admins or add existing users to orgs. Org-admins can create new users inside an org, though. --- src/packages/server/conat/api/org.ts | 63 +++++++++++++++++++++------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/src/packages/server/conat/api/org.ts b/src/packages/server/conat/api/org.ts index 203e1c92123..71fe71cfca4 100644 --- a/src/packages/server/conat/api/org.ts +++ b/src/packages/server/conat/api/org.ts @@ -36,16 +36,16 @@ async function isAllowed({ account_id?: string; name: string; }): Promise { + if (!account_id) return false; return ( - !!account_id && - ((await isOrganizationAdmin({ account_id, name })) || - (await isAdmin(account_id))) + (await isOrganizationAdmin({ account_id, name })) || + (await isAdmin(account_id)) ); } async function assertAllowed(opts) { if (!(await isAllowed(opts))) { - throw Error(`user must an admin of the organization`); + throw Error(`user must be an admin of the organization or site-admin`); } } @@ -161,6 +161,17 @@ export async function set(opts: { ]); } +/** + * Promote an existing user to organization admin: adding them to the admin_account_ids list and adding them to be member of the organization. + * Only site-level admins can perform this operation to prevent privilege escalation. + * Organization-level admins cannot promote other users to admin status. + * + * NOTE: this prevents moving a user from another org to the @name org. Use addUser first, to move a user from one org to another one. + * + * @param account_id - The site admin performing the operation + * @param name - The organization name + * @param user - The account_id or email address of the user to promote + */ export async function addAdmin({ account_id, name, @@ -170,21 +181,23 @@ export async function addAdmin({ name: string; user: string; }): Promise { - const { name: currentOrgName, account_id: admin_account_id } = - await getAccount(user); + const { name: usersOrgName, account_id: admin_account_id } = await getAccount( + user, + ); if (!admin_account_id) { throw Error(`no such account '${user}'`); } - if (currentOrgName == name) { - // already an admin of the org - return; + if (usersOrgName != null && usersOrgName !== name) { + throw new Error(`User '${user}' is already member of another organization`); + } + // await assertAllowed({ account_id, name }); + if (!(await isAdmin(account_id))) { + throw Error( + "only site admins can make a user an organization admin right now", + ); } - await assertAllowed({ - account_id, - name, - }); const pool = getPool(); - // query below takes care to ensure no dups and work in case of null. + // query below takes care to ensure no dups and works in case of null. await pool.query( ` UPDATE organizations @@ -206,6 +219,15 @@ export async function addAdmin({ }); } +/** + * Add an existing CoCalc user to an organization by setting their org field. + * Only site-level admins can perform this operation. + * NOTE: this could move a user from an existing org to another org! + * + * @param account_id - The site admin performing the operation + * @param name - The organization name + * @param user - The account_id or email address of the user to add + */ export async function addUser({ account_id, name, @@ -216,7 +238,7 @@ export async function addUser({ user: string; }): Promise { if (!(await isAdmin(account_id))) { - throw Error("only site admins can move user to an org right now"); + throw Error("only site admins can add/move a user to an org right now"); } const { account_id: user_account_id } = await getAccount(user); if (!user_account_id) { @@ -229,6 +251,14 @@ export async function addUser({ ]); } +/** + * Create a new CoCalc account and add it to an organization. + * Allowed for both site-level admins and organization admins. + * + * @param account_id - The admin (site or org) performing the operation + * @param name - The organization name + * @returns The account_id of the newly created account + */ export async function createUser({ account_id, name, @@ -315,6 +345,9 @@ export async function removeAdmin({ ); } +/** + * @param user and account_id or email_address in the accounts table + */ export async function getAccount( user: string, ): Promise< From 5faae38e342806d7a76392479896697787581819 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Tue, 30 Sep 2025 11:20:36 +0200 Subject: [PATCH 13/58] cocalc-api: adjust org tests after changes to organization logic in server --- src/python/cocalc-api/src/cocalc_api/hub.py | 3 +- .../cocalc-api/src/cocalc_api/project.py | 3 +- src/python/cocalc-api/tests/conftest.py | 66 ++++---- src/python/cocalc-api/tests/test_hub.py | 78 +++++++--- src/python/cocalc-api/tests/test_org.py | 146 +++++++++++------- 5 files changed, 186 insertions(+), 110 deletions(-) diff --git a/src/python/cocalc-api/src/cocalc_api/hub.py b/src/python/cocalc-api/src/cocalc_api/hub.py index 635211295bd..10394941ca9 100644 --- a/src/python/cocalc-api/src/cocalc_api/hub.py +++ b/src/python/cocalc-api/src/cocalc_api/hub.py @@ -10,7 +10,8 @@ class Hub: def __init__(self, api_key: str, host: str = "https://cocalc.com"): self.api_key = api_key self.host = host - self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}) + # Use longer timeout for API calls (30 seconds instead of default 5) + self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}, timeout=30.0) def call(self, name: str, arguments: list[Any], timeout: Optional[int] = None) -> Any: """ diff --git a/src/python/cocalc-api/src/cocalc_api/project.py b/src/python/cocalc-api/src/cocalc_api/project.py index e41d9a79369..f9b109e4f8c 100644 --- a/src/python/cocalc-api/src/cocalc_api/project.py +++ b/src/python/cocalc-api/src/cocalc_api/project.py @@ -10,7 +10,8 @@ def __init__(self, api_key: str, host: str = "https://cocalc.com", project_id: O self.project_id = project_id self.api_key = api_key self.host = host - self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}) + # Use longer timeout for API calls (30 seconds instead of default 5) + self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}, timeout=30.0) def call(self, name: str, arguments: list[Any], timeout: Optional[int] = None) -> Any: """ diff --git a/src/python/cocalc-api/tests/conftest.py b/src/python/cocalc-api/tests/conftest.py index 4297d7580df..ff1c195d7f9 100644 --- a/src/python/cocalc-api/tests/conftest.py +++ b/src/python/cocalc-api/tests/conftest.py @@ -47,12 +47,10 @@ def hub(api_key, cocalc_host): @pytest.fixture(scope="session") -def temporary_project(hub): +def temporary_project(hub, request): """ Create a temporary project for testing and return project info. - - Note: Since there's no project deletion API available, the project - will remain after tests. It can be manually deleted if needed. + Uses a session-scoped fixture so only ONE project is created for the entire test suite. """ import time @@ -61,15 +59,20 @@ def temporary_project(hub): title = f"CoCalc API Test {timestamp}" description = "Temporary project created by cocalc-api tests" + print("\n" + "="*70) + print("=== Creating temporary project for entire test session ===") + print("=== THIS SHOULD ONLY PRINT ONCE ===") + print("="*70) project_id = hub.projects.create_project(title=title, description=description) + print(f"Created project {project_id}") + print("="*70) # Start the project so it can respond to API calls try: hub.projects.start(project_id) - print(f"Started project {project_id}, waiting for it to become ready...") + print(f"Starting project {project_id}, waiting for it to become ready...") # Wait for project to be ready (can take 10-15 seconds) - import time from cocalc_api import Project for attempt in range(10): @@ -82,34 +85,39 @@ def temporary_project(hub): break except Exception: if attempt == 9: # Last attempt - print(f"Warning: Project {project_id} did not become ready within 50 seconds") + print(f"⚠ Warning: Project {project_id} did not become ready within 50 seconds") except Exception as e: - print(f"Warning: Failed to start project {project_id}: {e}") + print(f"⚠ Warning: Failed to start project {project_id}: {e}") project_info = {'project_id': project_id, 'title': title, 'description': description} - yield project_info - - # Cleanup: Stop the project and attempt to delete it - print(f"\nCleaning up test project '{title}' (ID: {project_id})...") - - try: - # Stop the project first - print(f" Stopping project {project_id}...") - hub.projects.stop(project_id) - print(f" Project {project_id} stopped successfully") - except Exception as e: - print(f" Failed to stop project {project_id}: {e}") - - try: - # Delete the project using the new delete method - print(f" Deleting project {project_id}...") - hub.projects.delete(project_id) - print(f" Project {project_id} deleted successfully") - except Exception as e: - print(f" Failed to delete project {project_id}: {e}") - print(" Project is stopped but may still exist - manual cleanup recommended") + # Register cleanup using finalizer (more reliable than yield teardown) + def cleanup(): + print(f"\n=== Cleaning up test project '{title}' (ID: {project_id}) ===") + + try: + # Stop the project first + print(f"Stopping project {project_id}...") + hub.projects.stop(project_id) + print("✓ Project stop command sent") + # Wait for the project process to actually terminate + time.sleep(3) + print(f"✓ Waited for project {project_id} to stop") + except Exception as e: + print(f"⚠ Failed to stop project {project_id}: {e}") + + try: + # Delete the project + print(f"Deleting project {project_id}...") + hub.projects.delete(project_id) + print(f"✓ Project {project_id} deleted") + except Exception as e: + print(f"⚠ Failed to delete project {project_id}: {e}") + + request.addfinalizer(cleanup) + + return project_info @pytest.fixture(scope="session") diff --git a/src/python/cocalc-api/tests/test_hub.py b/src/python/cocalc-api/tests/test_hub.py index 12f87988831..fbff0ca75af 100644 --- a/src/python/cocalc-api/tests/test_hub.py +++ b/src/python/cocalc-api/tests/test_hub.py @@ -12,11 +12,26 @@ class TestHubSystem: """Tests for Hub system operations.""" def test_ping(self, hub): - """Test basic ping connectivity.""" - result = hub.system.ping() - assert result is not None - # The ping response should contain some basic server info - assert isinstance(result, dict) + """Test basic ping connectivity with retry logic.""" + # Retry with exponential backoff in case server is still starting up + max_attempts = 5 + delay = 2 # Start with 2 second delay + + for attempt in range(max_attempts): + try: + result = hub.system.ping() + assert result is not None + # The ping response should contain some basic server info + assert isinstance(result, dict) + print(f"✓ Server ping successful on attempt {attempt + 1}") + return # Success! + except Exception as e: + if attempt < max_attempts - 1: + print(f"Ping attempt {attempt + 1} failed, retrying in {delay}s... ({e})") + time.sleep(delay) + delay *= 2 # Exponential backoff + else: + pytest.fail(f"Server ping failed after {max_attempts} attempts: {e}") def test_hub_initialization(self, api_key, cocalc_host): """Test Hub client initialization.""" @@ -31,11 +46,12 @@ def test_invalid_api_key(self, cocalc_host): with pytest.raises((ValueError, RuntimeError, Exception)): # Should raise authentication error hub.system.ping() - def test_ping_timeout(self, api_key, cocalc_host): - """Test ping with timeout parameter.""" - hub = Hub(api_key=api_key, host=cocalc_host) - result = hub.system.ping() - assert result is not None + def test_multiple_pings(self, hub): + """Test that multiple ping calls work consistently.""" + for _i in range(3): + result = hub.system.ping() + assert result is not None + assert isinstance(result, dict) class TestHubProjects: @@ -50,8 +66,22 @@ def test_create_project(self, hub): project_id = hub.projects.create_project(title=title, description=description) - assert project_id is not None - assert_valid_uuid(project_id, "Project ID") + try: + assert project_id is not None + assert_valid_uuid(project_id, "Project ID") + print(f"✓ Created project: {project_id}") + finally: + # Cleanup: stop then delete the project + try: + print(f"Cleaning up test project {project_id}...") + hub.projects.stop(project_id) + print("✓ Project stop command sent") + time.sleep(3) # Wait for process to terminate + print(f"✓ Waited for project {project_id} to stop") + hub.projects.delete(project_id) + print(f"✓ Project {project_id} deleted") + except Exception as e: + print(f"⚠ Failed to cleanup project {project_id}: {e}") def test_list_projects(self, hub): """Test listing projects.""" @@ -131,13 +161,19 @@ def test_project_lifecycle(self, hub): else: print("5. Skipping command execution - project not ready") - # 3. Delete the project - print("6. Deleting project...") + # 3. Stop and delete the project + print("6. Stopping project...") + hub.projects.stop(project_id) + print(" ✓ Project stop command sent") + time.sleep(3) # Wait for process to terminate + print(" ✓ Waited for project to stop") + + print("7. Deleting project...") delete_result = hub.projects.delete(project_id) - print(f" Delete result: {delete_result}") + print(f" ✓ Delete result: {delete_result}") # 4. Verify project is marked as deleted in database - print("7. Verifying project is marked as deleted...") + print("8. Verifying project is marked as deleted...") projects = hub.projects.get(fields=['project_id', 'title', 'deleted'], project_id=project_id, all=True) assert len(projects) == 1, f"Expected 1 project (still in DB), found {len(projects)}" project = projects[0] @@ -148,12 +184,16 @@ def test_project_lifecycle(self, hub): print("✅ Project lifecycle test completed successfully!") except Exception as e: - # Cleanup: attempt to delete project if test fails + # Cleanup: attempt to stop and delete project if test fails print(f"\n❌ Test failed: {e}") try: - print("Attempting cleanup...") + print("Attempting cleanup: stopping then deleting project...") + hub.projects.stop(project_id) + print("✓ Project stop command sent") + time.sleep(3) # Wait for process to terminate + print("✓ Waited for project to stop") hub.projects.delete(project_id) - print("✓ Cleanup successful") + print("✓ Project deleted") except Exception as cleanup_error: print(f"❌ Cleanup failed: {cleanup_error}") raise e diff --git a/src/python/cocalc-api/tests/test_org.py b/src/python/cocalc-api/tests/test_org.py index 0ed0b3b5385..28ee03ddab7 100644 --- a/src/python/cocalc-api/tests/test_org.py +++ b/src/python/cocalc-api/tests/test_org.py @@ -151,7 +151,6 @@ def test_create_user_in_organization(self, hub, test_organization): assert_valid_uuid(new_user_id, "User ID") print(f"✓ User created with ID: {new_user_id}") - # Wait a moment for database consistency import time as time_module time_module.sleep(1) @@ -180,50 +179,34 @@ def test_create_user_in_organization(self, hub, test_organization): pytest.fail(f"User creation failed: {e}") def test_admin_management(self, hub, test_organization): - """Test adding and managing admins using the correct workflow.""" - # CORRECT WORKFLOW: add_admin() works with users NOT already in the target organization - + """Test adding and managing admins - simplified workflow.""" timestamp = int(time.time()) try: - # Method 1: Create external user in a temporary org, then make them admin of target org - temp_org_name = f"temp-admin-org-{timestamp}" - hub.org.create(temp_org_name) - print(f"✓ Created temporary org: {temp_org_name}") - - # Create user in the temporary org - external_user_id = hub.org.create_user(name=temp_org_name, - email=f"external-admin-{timestamp}@example.com", - firstName="External", - lastName="Admin") - assert_valid_uuid(external_user_id, "External user ID") - print(f"✓ Created external user: {external_user_id}") - - # Now add the external user as admin to the target organization - # This should work because the user is not already in the target org - hub.org.add_admin(test_organization['name'], external_user_id) - print(f"✓ Added external user as admin to {test_organization['name']}") + # Create user directly in the target organization + user_email = f"test-admin-{timestamp}@example.com" + user_id = hub.org.create_user(name=test_organization['name'], email=user_email, firstName="Test", lastName="Admin") + assert_valid_uuid(user_id, "User ID") + print(f"✓ Created user in organization: {user_id}") + + # Promote the user to admin + hub.org.add_admin(test_organization['name'], user_id) + print(f"✓ Promoted user to admin of {test_organization['name']}") # Verify admin status org_details = hub.org.get(test_organization['name']) admin_ids = org_details.get('admin_account_ids') or [] - assert external_user_id in admin_ids, "External user should be in admin list" + assert user_id in admin_ids, "User should be in admin list" print(f"✓ Admin status verified: {admin_ids}") - # Verify the user was also moved to the target organization - users_in_org = hub.org.get_users(test_organization['name']) - user_ids = [u['account_id'] for u in users_in_org] - assert external_user_id in user_ids, "Admin should now be in target organization" - print("✓ User successfully moved to target org") - # Test remove_admin - hub.org.remove_admin(test_organization['name'], external_user_id) - print(f"✓ Admin status removed for {external_user_id}") + hub.org.remove_admin(test_organization['name'], user_id) + print(f"✓ Admin status removed for {user_id}") # Verify admin removal updated_org = hub.org.get(test_organization['name']) updated_admin_ids = updated_org.get('admin_account_ids') or [] - assert external_user_id not in updated_admin_ids, "User should no longer be admin" + assert user_id not in updated_admin_ids, "User should no longer be admin" print("✓ Admin removal verified") except Exception as e: @@ -237,27 +220,77 @@ def test_admin_workflow_documentation(self, hub): # Create target organization target_org = f"target-workflow-{timestamp}" hub.org.create(target_org) + print(f"✓ Created organization: {target_org}") + + # Workflow 1: Create user in org, then promote to admin (simplest) + user_id = hub.org.create_user(name=target_org, email=f"workflow-simple-{timestamp}@example.com", firstName="Workflow", lastName="Simple") + assert_valid_uuid(user_id, "Workflow user ID") + print("✓ Created user in organization") - # Workflow 1: External user method (recommended) - temp_org = f"temp-workflow-{timestamp}" - hub.org.create(temp_org) - external_user = hub.org.create_user(name=temp_org, - email=f"workflow-external-{timestamp}@example.com", - firstName="Workflow", - lastName="External") - assert_valid_uuid(external_user, "Workflow external user ID") - - # This works: user from different org - hub.org.add_admin(target_org, external_user) + # Promote to admin - works directly since user is in same org + hub.org.add_admin(target_org, user_id) org_details = hub.org.get(target_org) admin_ids = org_details.get('admin_account_ids') or [] - assert external_user in admin_ids - print("✓ Workflow 1 (External user): SUCCESS") + assert user_id in admin_ids + print("✓ Workflow 1 (Same org user → admin): SUCCESS") + + # Workflow 2: Move user from org A to org B, then promote to admin + other_org = f"other-workflow-{timestamp}" + hub.org.create(other_org) + other_user_id = hub.org.create_user(name=other_org, email=f"workflow-cross-{timestamp}@example.com", firstName="Cross", lastName="Org") + print(f"✓ Created user in {other_org}") + + # Step 1: Use addUser to move user from other_org to target_org (site admin only) + hub.org.add_user(target_org, other_user_id) + print(f"✓ Moved user from {other_org} to {target_org} using addUser") + + # Step 2: Now promote to admin in target_org + hub.org.add_admin(target_org, other_user_id) + updated_org = hub.org.get(target_org) + updated_admin_ids = updated_org.get('admin_account_ids') or [] + assert other_user_id in updated_admin_ids, "Moved user should be admin" + print("✓ Workflow 2 (Cross-org: addUser → addAdmin): SUCCESS") print("✓ Admin workflow documentation complete") except Exception as e: pytest.fail(f"Admin workflow documentation failed: {e}") + def test_cross_org_admin_promotion_blocked(self, hub): + """Test that promoting a user from org A to admin of org B is blocked.""" + timestamp = int(time.time()) + + try: + # Create two organizations + org_a = f"org-a-{timestamp}" + org_b = f"org-b-{timestamp}" + hub.org.create(org_a) + hub.org.create(org_b) + print(f"✓ Created organizations: {org_a} and {org_b}") + + # Create user in org A + user_id = hub.org.create_user(name=org_a, email=f"cross-org-user-{timestamp}@example.com", firstName="CrossOrg", lastName="User") + assert_valid_uuid(user_id, "Cross-org user ID") + print(f"✓ Created user in {org_a}") + + # Try to promote user from org A to admin of org B - should fail + try: + hub.org.add_admin(org_b, user_id) + pytest.fail("Expected error when promoting user from different org to admin") + except Exception as e: + error_msg = str(e) + assert "already member of another organization" in error_msg, \ + f"Expected 'already member of another organization' error, got: {error_msg}" + print(f"✓ Cross-org promotion correctly blocked: {error_msg}") + + # Demonstrate correct workflow: use addUser to move, then addAdmin + # Note: addUser is site-admin only, so we can't test the full workflow + # without site admin privileges, but we document the pattern + print("✓ Correct workflow: Use addUser to move user between orgs first, then addAdmin") + print("✓ Cross-org admin promotion blocking test passed") + + except Exception as e: + pytest.fail(f"Cross-org admin promotion test failed: {e}") + class TestOrganizationTokens: """Test organization token functionality.""" @@ -277,7 +310,6 @@ def test_org_with_user(self, hub): user_id = hub.org.create_user(name=org_name, email=test_email, firstName="Token", lastName="User") assert_valid_uuid(user_id, "Token user ID") - yield {'name': org_name, 'id': org_id, 'user_id': user_id, 'user_email': test_email} def test_create_and_expire_token(self, hub, test_org_with_user): @@ -327,7 +359,6 @@ def test_org_with_users(self, hub): user_id = hub.org.create_user(name=org_name, email=test_email, firstName=f"User{i}", lastName="Messaging") assert_valid_uuid(user_id, f"Messaging user {i} ID") - users.append({'id': user_id, 'email': test_email}) yield {'name': org_name, 'id': org_id, 'users': users} @@ -530,21 +561,16 @@ def test_full_organization_lifecycle(self, hub): user_id = hub.org.create_user(name=org_name, email=user_email, firstName=f"User{i}", lastName="Lifecycle") assert_valid_uuid(user_id, f"Lifecycle user {i} ID") - users.append({'id': user_id, 'email': user_email}) print(f"✓ 3. Created {len(users)} users") - # 4. Make external admin using correct workflow - # Create external user and make them admin (correct workflow) - temp_admin_org = f"temp-admin-{timestamp}" - hub.org.create(temp_admin_org) - external_admin_id = hub.org.create_user(name=temp_admin_org, - email=f"external-admin-{timestamp}@example.com", - firstName="External", - lastName="Admin") - assert_valid_uuid(external_admin_id, "External admin ID") - hub.org.add_admin(org_name, external_admin_id) - print("✓ 4. Added external user as admin using correct workflow") + # 4. Promote a user to admin (simplified workflow) + # Create user and promote directly to admin + admin_email = f"lifecycle-admin-{timestamp}@example.com" + admin_id = hub.org.create_user(name=org_name, email=admin_email, firstName="Admin", lastName="User") + assert_valid_uuid(admin_id, "Admin user ID") + hub.org.add_admin(org_name, admin_id) + print("✓ 4. Created and promoted user to admin") # 5. Create and expire a token token_info = hub.org.create_token(users[1]['id']) @@ -562,9 +588,9 @@ def test_full_organization_lifecycle(self, hub): assert final_org['title'] == "Lifecycle Test Organization" assert len(final_users) >= len(users) + 1, "All users plus admin should be in organization" - # Check admin status (should work with correct workflow) + # Check admin status admin_ids = final_org.get('admin_account_ids') or [] - assert external_admin_id in admin_ids, "External admin should be in admin list" + assert admin_id in admin_ids, "Admin should be in admin list" print(f"✓ Admin assignment successful: {admin_ids}") print(f"✓ 7. Final verification complete - org has {len(final_users)} users") From 200810a3437b12a430e31352b714483f61b0aecd Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Tue, 30 Sep 2025 11:29:04 +0200 Subject: [PATCH 14/58] cocalc-api: fix the CI test --- .github/workflows/make-and-test.yml | 51 +++++++++++-------- src/packages/hub/package.json | 3 +- ...t-create-admin.js => test-create-admin.ts} | 13 +++-- src/python/cocalc-api/Makefile | 4 ++ 4 files changed, 43 insertions(+), 28 deletions(-) rename src/packages/hub/run/{test-create-admin.js => test-create-admin.ts} (80%) mode change 100755 => 100644 diff --git a/.github/workflows/make-and-test.yml b/.github/workflows/make-and-test.yml index e9473231bc8..d49260daeae 100644 --- a/.github/workflows/make-and-test.yml +++ b/.github/workflows/make-and-test.yml @@ -128,28 +128,13 @@ jobs: name: "test-results-node-${{ matrix.node-version }}-pg-${{ matrix.pg-version }}" path: 'src/packages/*/junit.xml' - - name: Create CI admin user and API key - run: | - cd src/packages/hub - node run/test-create-admin.js > ../../api_key.txt - # Validate API key was created - if [ ! -s ../../api_key.txt ]; then - echo "Error: API key file is empty or missing" - exit 1 - fi - API_KEY=$(cat ../../api_key.txt) - if ! echo "$API_KEY" | grep -qE '^sk-[A-Za-z0-9]+$'; then - echo "Error: Invalid API key format: $API_KEY" - exit 1 - fi - echo "API key created successfully" - env: - PGDATABASE: smc - PGUSER: smc - PGHOST: localhost - - name: Start CoCalc Hub run: | + # Create conat password for hub internal authentication + mkdir -p src/data/secrets + echo "test-conat-password-$(date +%s)" > src/data/secrets/conat-password + chmod 600 src/data/secrets/conat-password + cd src/packages/hub pnpm run hub-project-dev-nobuild > hub.log 2>&1 & HUB_PID=$! @@ -168,13 +153,15 @@ jobs: PGUSER: smc PGHOST: localhost COCALC_MODE: single-user + COCALC_TEST_MODE: yes + DEBUG: 'cocalc:*,-cocalc:silly:*,hub:*,project:*' - name: Wait for hub readiness run: | MAX_ATTEMPTS=30 READY=false for i in $(seq 1 $MAX_ATTEMPTS); do - if curl -f --max-time 3 http://localhost:5000/healthcheck; then + if curl -sf --max-time 3 http://localhost:5000/healthcheck > /dev/null; then echo "Hub is ready" READY=true break @@ -189,6 +176,26 @@ jobs: exit 1 fi + - name: Create CI admin user and API key + run: | + cd src/packages/hub + node dist/run/test-create-admin.js > ../../api_key.txt + # Validate API key was created + if [ ! -s ../../api_key.txt ]; then + echo "Error: API key file is empty or missing" + exit 1 + fi + API_KEY=$(cat ../../api_key.txt) + if ! echo "$API_KEY" | grep -qE '^sk-[A-Za-z0-9]+$'; then + echo "Error: Invalid API key format: $API_KEY" + exit 1 + fi + echo "API key created successfully" + env: + PGDATABASE: smc + PGUSER: smc + PGHOST: localhost + - name: Install uv for cocalc-api tests run: curl -LsSf https://astral.sh/uv/install.sh | sh && echo "$HOME/.local/bin" >> $GITHUB_PATH @@ -196,7 +203,7 @@ jobs: run: | export COCALC_API_KEY=$(cat src/api_key.txt) export COCALC_HOST=http://localhost:5000 - cd src/python/cocalc-api && uv run pytest --junitxml=test-results.xml + cd src/python/cocalc-api && make ci - name: Stop CoCalc Hub if: always() diff --git a/src/packages/hub/package.json b/src/packages/hub/package.json index 722a4424919..f7247e7597d 100644 --- a/src/packages/hub/package.json +++ b/src/packages/hub/package.json @@ -96,7 +96,6 @@ "cocalc-hub-maintenance-expired": "./run/maintenance-expired.js", "cocalc-hub-maintenance-syncstrings": "./run/maintenance-syncstrings.js", "cocalc-hub-maintenance-blobs": "./run/maintenance-blobs.js", - "cocalc-hub-stripe-sync": "./run/stripe-sync.js", - "cocalc-hub-test-create-admin": "./run/test-create-admin.js" + "cocalc-hub-stripe-sync": "./run/stripe-sync.js" } } diff --git a/src/packages/hub/run/test-create-admin.js b/src/packages/hub/run/test-create-admin.ts old mode 100755 new mode 100644 similarity index 80% rename from src/packages/hub/run/test-create-admin.js rename to src/packages/hub/run/test-create-admin.ts index 6b02f4f08d5..9002df5420c --- a/src/packages/hub/run/test-create-admin.js +++ b/src/packages/hub/run/test-create-admin.ts @@ -6,6 +6,7 @@ */ import { v4 as uuidv4 } from "uuid"; + import createAccount from "@cocalc/server/accounts/create-account"; import manageApiKeys from "@cocalc/server/api/manage"; import getPool from "@cocalc/database/pool"; @@ -17,7 +18,7 @@ async function main() { const firstName = "CI"; const lastName = "Admin"; - console.log(`Creating admin account ${account_id}...`); + console.error(`Creating admin account ${account_id}...`); // Create the account await createAccount({ @@ -38,7 +39,7 @@ async function main() { account_id, ]); - console.log("Creating API key..."); + console.error("Creating API key..."); // Create API key const keys = await manageApiKeys({ @@ -52,7 +53,11 @@ async function main() { } const apiKey = keys[0]; - console.log(`API key created: ${apiKey.secret}`); + if (!apiKey.secret) { + throw new Error("API key secret is missing"); + } + console.error(`API key created with id=${apiKey.id}: ${apiKey.secret}`); + console.error(`Last 6 chars: ${apiKey.secret.slice(-6)}`); // Output the key for CI process.stdout.write(apiKey.secret); @@ -61,4 +66,4 @@ async function main() { main().catch((err) => { console.error("Error:", err); process.exit(1); -}); \ No newline at end of file +}); diff --git a/src/python/cocalc-api/Makefile b/src/python/cocalc-api/Makefile index 685a030f3b1..4f1dc094b41 100644 --- a/src/python/cocalc-api/Makefile +++ b/src/python/cocalc-api/Makefile @@ -7,6 +7,7 @@ help: @echo " check - Run linting and type checking" @echo " test - Run tests" @echo " test-verbose - Run tests with verbose output" + @echo " ci - Run tests with coverage and JUnit XML for CI" @echo " coverage - Run tests with coverage reporting (HTML + terminal)" @echo " coverage-report - Show coverage report in terminal" @echo " coverage-html - Generate HTML coverage report only" @@ -34,6 +35,9 @@ test: test-verbose: uv run pytest -v +ci: + uv run pytest --junitxml=test-results.xml --cov=src --cov-report=term-missing --cov-report=html + coverage: uv run pytest --cov=src --cov-report=term-missing --cov-report=html From 29dc5823b092a5173e2ce71a85778e95e565e646 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Tue, 30 Sep 2025 19:33:24 +0200 Subject: [PATCH 15/58] =?UTF-8?q?cocalc-api:=20fix=20tests,=20creating=20a?= =?UTF-8?q?ccounts=20started=20many=20project=20servers=20=E2=80=93=20whic?= =?UTF-8?q?h=20is=20actually=20something=20a=20mere=20API=20call=20via=20t?= =?UTF-8?q?he=20org=20management=20should=20not=20do?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../accounts/account-creation-actions.ts | 34 +++++++----- .../server/accounts/create-account.ts | 5 +- src/packages/server/accounts/first-project.ts | 14 +++-- src/packages/server/conat/api/org.ts | 1 + src/python/cocalc-api/tests/conftest.py | 55 ++++++++----------- src/python/cocalc-api/tests/test_hub.py | 37 +++---------- 6 files changed, 65 insertions(+), 81 deletions(-) diff --git a/src/packages/server/accounts/account-creation-actions.ts b/src/packages/server/accounts/account-creation-actions.ts index 753eedbe791..d2fff4fee7c 100644 --- a/src/packages/server/accounts/account-creation-actions.ts +++ b/src/packages/server/accounts/account-creation-actions.ts @@ -16,12 +16,15 @@ export default async function accountCreationActions({ account_id, tags, noFirstProject, + dontStartProject, }: { email_address?: string; account_id: string; tags?: string[]; // if set, don't do any initial project actions (i.e., creating or starting projects) noFirstProject?: boolean; + // if set, create the first project but do not start it. Only applies if noFirstProject is false. + dontStartProject?: boolean; }): Promise { log.debug({ account_id, email_address, tags }); @@ -56,7 +59,7 @@ export default async function accountCreationActions({ const projects = await getProjects({ account_id, limit: 1 }); if (projects.length == 0) { // you really have no projects at all. - await firstProject({ account_id, tags }); + await firstProject({ account_id, tags, dontStartProject }); } } catch (err) { // non-fatal; they can make their own project @@ -65,19 +68,22 @@ export default async function accountCreationActions({ })(); } else if (numProjects > 0) { // Make sure project is running so they have a good first experience. - (async () => { - try { - const { project_id } = await getOneProject(account_id); - const project = getProject(project_id); - await project.start(); - } catch (err) { - log.error( - "failed to start newest project invited to", - err, - account_id, - ); - } - })(); + // Only start if dontStartProject is not set + if (!dontStartProject) { + (async () => { + try { + const { project_id } = await getOneProject(account_id); + const project = getProject(project_id); + await project.start(); + } catch (err) { + log.error( + "failed to start newest project invited to", + err, + account_id, + ); + } + })(); + } } } } diff --git a/src/packages/server/accounts/create-account.ts b/src/packages/server/accounts/create-account.ts index f2bf4869083..a01f04c2ce9 100644 --- a/src/packages/server/accounts/create-account.ts +++ b/src/packages/server/accounts/create-account.ts @@ -27,6 +27,8 @@ interface Params { // I added this to avoid leaks with unit testing, but it may be useful in other contexts, e.g., // avoiding confusion with self-hosted installs. noFirstProject?: boolean; + // if set, create the first project but do not start it. Only applies if noFirstProject is false. + dontStartProject?: boolean; } export default async function createAccount({ @@ -39,6 +41,7 @@ export default async function createAccount({ signupReason, owner_id, noFirstProject, + dontStartProject, }: Params): Promise { try { log.debug( @@ -78,6 +81,7 @@ export default async function createAccount({ account_id, tags, noFirstProject, + dontStartProject, }); await creationActionsDone(account_id); } catch (error) { @@ -85,4 +89,3 @@ export default async function createAccount({ throw error; // re-throw to bubble up to higher layers if needed } } - diff --git a/src/packages/server/accounts/first-project.ts b/src/packages/server/accounts/first-project.ts index 7b96fa93d2c..106d82e6da4 100644 --- a/src/packages/server/accounts/first-project.ts +++ b/src/packages/server/accounts/first-project.ts @@ -22,9 +22,11 @@ const log = getLogger("server:accounts:first-project"); export default async function firstProject({ account_id, tags, + dontStartProject, }: { account_id: string; tags?: string[]; + dontStartProject?: boolean; }): Promise { log.debug(account_id, tags); if (!isValidUUID(account_id)) { @@ -35,8 +37,10 @@ export default async function firstProject({ title: "My First Project", }); log.debug("created new project", project_id); - const project = getProject(project_id); - await project.start(); + if (!dontStartProject) { + const project = getProject(project_id); + await project.start(); + } if (!WELCOME_FILES || tags == null || tags.length == 0) { return project_id; } @@ -57,7 +61,7 @@ export default async function firstProject({ account_id, project_id, language, - welcome + jupyterExtra + welcome + jupyterExtra, ); } } @@ -78,7 +82,7 @@ async function createJupyterNotebookIfAvailable( account_id: string, project_id: string, language: string, - welcome: string + welcome: string, ): Promise { // find the highest priority kernel with the given language let kernelspec: any = null; @@ -129,7 +133,7 @@ async function createWelcome( account_id: string, project_id: string, ext: string, - welcome: string + welcome: string, ): Promise { const path = `welcome/welcome.${ext}`; const { torun } = TAGS_MAP[ext] ?? {}; diff --git a/src/packages/server/conat/api/org.ts b/src/packages/server/conat/api/org.ts index 71fe71cfca4..2b9dcfc4e00 100644 --- a/src/packages/server/conat/api/org.ts +++ b/src/packages/server/conat/api/org.ts @@ -285,6 +285,7 @@ export async function createUser({ account_id: new_account_id, owner_id: account_id, password, + dontStartProject: true, // Don't auto-start projects for API-created users. A "first project" will be created, though. }); // add account to org const pool = getPool(); diff --git a/src/python/cocalc-api/tests/conftest.py b/src/python/cocalc-api/tests/conftest.py index ff1c195d7f9..3061e8d2694 100644 --- a/src/python/cocalc-api/tests/conftest.py +++ b/src/python/cocalc-api/tests/conftest.py @@ -2,6 +2,7 @@ Pytest configuration and fixtures for cocalc-api tests. """ import os +import time import uuid import pytest @@ -25,6 +26,25 @@ def assert_valid_uuid(value, description="value"): pytest.fail(f"{description} should be a valid UUID, got: {value}") +def cleanup_project(hub, project_id): + """ + Clean up a test project by stopping it and deleting it. + + Args: + hub: Hub client instance + project_id: Project ID to cleanup + """ + try: + hub.projects.stop(project_id) + except Exception as e: + print(f"Warning: Failed to stop project {project_id}: {e}") + + try: + hub.projects.delete(project_id) + except Exception as e: + print(f"Warning: Failed to delete project {project_id}: {e}") + + @pytest.fixture(scope="session") def api_key(): """Get API key from environment variable.""" @@ -59,18 +79,11 @@ def temporary_project(hub, request): title = f"CoCalc API Test {timestamp}" description = "Temporary project created by cocalc-api tests" - print("\n" + "="*70) - print("=== Creating temporary project for entire test session ===") - print("=== THIS SHOULD ONLY PRINT ONCE ===") - print("="*70) project_id = hub.projects.create_project(title=title, description=description) - print(f"Created project {project_id}") - print("="*70) # Start the project so it can respond to API calls try: hub.projects.start(project_id) - print(f"Starting project {project_id}, waiting for it to become ready...") # Wait for project to be ready (can take 10-15 seconds) from cocalc_api import Project @@ -81,39 +94,19 @@ def temporary_project(hub, request): # Try to ping the project to see if it's ready test_project = Project(project_id=project_id, api_key=hub.api_key, host=hub.host) test_project.system.ping() # If this succeeds, project is ready - print(f"✓ Project {project_id} is ready after {(attempt + 1) * 5} seconds") break except Exception: if attempt == 9: # Last attempt - print(f"⚠ Warning: Project {project_id} did not become ready within 50 seconds") + print(f"Warning: Project {project_id} did not become ready within 50 seconds") except Exception as e: - print(f"⚠ Warning: Failed to start project {project_id}: {e}") + print(f"Warning: Failed to start project {project_id}: {e}") project_info = {'project_id': project_id, 'title': title, 'description': description} - # Register cleanup using finalizer (more reliable than yield teardown) + # Register cleanup using finalizer def cleanup(): - print(f"\n=== Cleaning up test project '{title}' (ID: {project_id}) ===") - - try: - # Stop the project first - print(f"Stopping project {project_id}...") - hub.projects.stop(project_id) - print("✓ Project stop command sent") - # Wait for the project process to actually terminate - time.sleep(3) - print(f"✓ Waited for project {project_id} to stop") - except Exception as e: - print(f"⚠ Failed to stop project {project_id}: {e}") - - try: - # Delete the project - print(f"Deleting project {project_id}...") - hub.projects.delete(project_id) - print(f"✓ Project {project_id} deleted") - except Exception as e: - print(f"⚠ Failed to delete project {project_id}: {e}") + cleanup_project(hub, project_id) request.addfinalizer(cleanup) diff --git a/src/python/cocalc-api/tests/test_hub.py b/src/python/cocalc-api/tests/test_hub.py index fbff0ca75af..b7f82db98f8 100644 --- a/src/python/cocalc-api/tests/test_hub.py +++ b/src/python/cocalc-api/tests/test_hub.py @@ -5,7 +5,7 @@ import pytest from cocalc_api import Hub, Project -from .conftest import assert_valid_uuid +from .conftest import assert_valid_uuid, cleanup_project class TestHubSystem: @@ -69,19 +69,9 @@ def test_create_project(self, hub): try: assert project_id is not None assert_valid_uuid(project_id, "Project ID") - print(f"✓ Created project: {project_id}") finally: # Cleanup: stop then delete the project - try: - print(f"Cleaning up test project {project_id}...") - hub.projects.stop(project_id) - print("✓ Project stop command sent") - time.sleep(3) # Wait for process to terminate - print(f"✓ Waited for project {project_id} to stop") - hub.projects.delete(project_id) - print(f"✓ Project {project_id} deleted") - except Exception as e: - print(f"⚠ Failed to cleanup project {project_id}: {e}") + cleanup_project(hub, project_id) def test_list_projects(self, hub): """Test listing projects.""" @@ -162,15 +152,8 @@ def test_project_lifecycle(self, hub): print("5. Skipping command execution - project not ready") # 3. Stop and delete the project - print("6. Stopping project...") - hub.projects.stop(project_id) - print(" ✓ Project stop command sent") - time.sleep(3) # Wait for process to terminate - print(" ✓ Waited for project to stop") - - print("7. Deleting project...") - delete_result = hub.projects.delete(project_id) - print(f" ✓ Delete result: {delete_result}") + print("6. Stopping and deleting project...") + cleanup_project(hub, project_id) # 4. Verify project is marked as deleted in database print("8. Verifying project is marked as deleted...") @@ -185,15 +168,9 @@ def test_project_lifecycle(self, hub): except Exception as e: # Cleanup: attempt to stop and delete project if test fails - print(f"\n❌ Test failed: {e}") + print(f"\nTest failed: {e}") try: - print("Attempting cleanup: stopping then deleting project...") - hub.projects.stop(project_id) - print("✓ Project stop command sent") - time.sleep(3) # Wait for process to terminate - print("✓ Waited for project to stop") - hub.projects.delete(project_id) - print("✓ Project deleted") + cleanup_project(hub, project_id) except Exception as cleanup_error: - print(f"❌ Cleanup failed: {cleanup_error}") + print(f"Cleanup failed: {cleanup_error}") raise e From 1aed72c274537141f268ea6ccd9285393634bd89 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Wed, 1 Oct 2025 12:18:05 +0200 Subject: [PATCH 16/58] cocalc-api: introduce a cleanup logic, of tracked accounts, orgs and projects. (managing an ephemeral database via the API is a bit tricky) --- .github/workflows/make-and-test.yml | 7 +- src/python/cocalc-api/pyproject.toml | 10 +- src/python/cocalc-api/tests/conftest.py | 349 ++++++++++++++- src/python/cocalc-api/tests/test_hub.py | 329 ++++++++++---- src/python/cocalc-api/uv.lock | 547 +++++++++++++++--------- 5 files changed, 937 insertions(+), 305 deletions(-) diff --git a/.github/workflows/make-and-test.yml b/.github/workflows/make-and-test.yml index d49260daeae..2d9dc67b439 100644 --- a/.github/workflows/make-and-test.yml +++ b/.github/workflows/make-and-test.yml @@ -106,7 +106,6 @@ jobs: pip install ipykernel python -m ipykernel install --prefix=./jupyter-local --name python3-local --display-name "Python 3 (Local)" - - name: install pnpm uses: pnpm/action-setup@v4 with: @@ -204,6 +203,10 @@ jobs: export COCALC_API_KEY=$(cat src/api_key.txt) export COCALC_HOST=http://localhost:5000 cd src/python/cocalc-api && make ci + env: + PGDATABASE: smc + PGUSER: smc + PGHOST: localhost - name: Stop CoCalc Hub if: always() @@ -213,7 +216,7 @@ jobs: echo "Stopping hub with PID $HUB_PID" kill $HUB_PID || true # Wait a bit for graceful shutdown - sleep 2 + sleep 5 # Force kill if still running kill -9 $HUB_PID 2>/dev/null || true fi diff --git a/src/python/cocalc-api/pyproject.toml b/src/python/cocalc-api/pyproject.toml index 6919ba7f21a..dbd4e8a8f17 100644 --- a/src/python/cocalc-api/pyproject.toml +++ b/src/python/cocalc-api/pyproject.toml @@ -1,8 +1,8 @@ [project] name = "cocalc-api" -version = "0.4.0" +version = "0.5.0" description = "Python client for the CoCalc API" -authors = [{ name="William Stein", email="wstein@sagemath.com" }] +authors = [{ name="William Stein", email="wstein@sagemath.com" }, {name="Harald Schilly", email="hsy@sagemath.com"}] readme = "README.md" requires-python = ">=3.9" dependencies = ["httpx"] @@ -76,9 +76,11 @@ dev = [ "mkdocs-material", "mkdocstrings[python]", "mypy", + "psycopg2-binary", "pyright", - "pytest>=8.4.1", "pytest-cov", - "ruff>=0.12.11", + "pytest>=8.4.2", + "ruff>=0.13.2", + "types-psycopg2", "yapf", ] diff --git a/src/python/cocalc-api/tests/conftest.py b/src/python/cocalc-api/tests/conftest.py index 3061e8d2694..bec968a51f0 100644 --- a/src/python/cocalc-api/tests/conftest.py +++ b/src/python/cocalc-api/tests/conftest.py @@ -8,6 +8,12 @@ from cocalc_api import Hub, Project +from psycopg2 import pool as pg_pool + +# Database configuration examples (DRY principle) +PGHOST_SOCKET_EXAMPLE = "/path/to/cocalc-data/socket" +PGHOST_NETWORK_EXAMPLE = "localhost" + def assert_valid_uuid(value, description="value"): """ @@ -67,19 +73,18 @@ def hub(api_key, cocalc_host): @pytest.fixture(scope="session") -def temporary_project(hub, request): +def temporary_project(hub, resource_tracker, request): """ Create a temporary project for testing and return project info. Uses a session-scoped fixture so only ONE project is created for the entire test suite. """ - import time - # Create a project with a timestamp to make it unique and identifiable timestamp = time.strftime("%Y%m%d-%H%M%S") title = f"CoCalc API Test {timestamp}" description = "Temporary project created by cocalc-api tests" - project_id = hub.projects.create_project(title=title, description=description) + # Use tracked creation + project_id = create_tracked_project(hub, resource_tracker, title=title, description=description) # Start the project so it can respond to API calls try: @@ -104,11 +109,7 @@ def temporary_project(hub, request): project_info = {'project_id': project_id, 'title': title, 'description': description} - # Register cleanup using finalizer - def cleanup(): - cleanup_project(hub, project_id) - - request.addfinalizer(cleanup) + # Note: No finalizer needed - cleanup happens automatically via cleanup_all_test_resources return project_info @@ -117,3 +118,333 @@ def cleanup(): def project_client(temporary_project, api_key, cocalc_host): """Create Project client instance using temporary project.""" return Project(project_id=temporary_project['project_id'], api_key=api_key, host=cocalc_host) + + +# ============================================================================ +# Database Cleanup Infrastructure +# ============================================================================ + + +@pytest.fixture(scope="session") +def resource_tracker(): + """ + Track all resources created during tests for cleanup. + + This fixture provides a dictionary of sets that automatically tracks + all projects, accounts, and organizations created during test execution. + At the end of the test session, all tracked resources are automatically + hard-deleted from the database. + + Usage: + def test_my_feature(hub, resource_tracker): + # Create tracked resources using helper functions + org_id = create_tracked_org(hub, resource_tracker, "test-org") + user_id = create_tracked_user(hub, resource_tracker, "test-org", email="test@example.com") + project_id = create_tracked_project(hub, resource_tracker, title="Test Project") + + # Test logic here... + + # No cleanup needed - happens automatically! + + Returns a dictionary with sets for tracking: + - projects: set of project_id (UUID strings) + - accounts: set of account_id (UUID strings) + - organizations: set of organization names (strings) + """ + tracker = { + 'projects': set(), + 'accounts': set(), + 'organizations': set(), + } + return tracker + + +@pytest.fixture(scope="session") +def check_cleanup_config(): + """ + Check cleanup configuration BEFORE any tests run. + Fails fast if cleanup is enabled but database credentials are missing. + """ + cleanup_enabled = os.environ.get("COCALC_TESTS_CLEANUP", "true").lower() != "false" + + if not cleanup_enabled: + print("\n⚠ Database cleanup DISABLED via COCALC_TESTS_CLEANUP=false") + print(" Test resources will remain in the database.") + return # Skip checks if cleanup is disabled + + # Cleanup is enabled - verify required configuration + pghost = os.environ.get("PGHOST") + pgpassword = os.environ.get("PGPASSWORD") + + # PGHOST is mandatory + if not pghost: + pytest.exit("\n" + "=" * 70 + "\n" + "ERROR: Database cleanup is enabled but PGHOST is not set!\n\n" + "To run tests, you must either:\n" + f" 1. Set PGHOST for socket connection (no password needed):\n" + f" export PGHOST={PGHOST_SOCKET_EXAMPLE}\n\n" + f" 2. Set PGHOST for network connection (requires PGPASSWORD):\n" + f" export PGHOST={PGHOST_NETWORK_EXAMPLE}\n" + " export PGPASSWORD=your_password\n\n" + " 3. Disable cleanup (not recommended):\n" + " export COCALC_TESTS_CLEANUP=false\n" + "=" * 70, + returncode=1) + + +@pytest.fixture(scope="session") +def db_pool(check_cleanup_config): + """ + Create a PostgreSQL connection pool for direct database cleanup. + + Supports both Unix socket and network connections: + + Socket connection (local dev): + export PGUSER=smc + export PGHOST=/path/to/cocalc-data/socket + # No password needed for socket auth + + Network connection: + export PGUSER=smc + export PGHOST=localhost + export PGPORT=5432 + export PGPASSWORD=your_password + + To disable cleanup: + export COCALC_TESTS_CLEANUP=false + """ + # Check if cleanup is disabled + cleanup_enabled = os.environ.get("COCALC_TESTS_CLEANUP", "true").lower() != "false" + + if not cleanup_enabled: + print("\n⚠ Database cleanup DISABLED via COCALC_TESTS_CLEANUP=false") + print(" Test resources will remain in the database.") + return None + + # Get connection parameters with defaults + pguser = os.environ.get("PGUSER", "smc") + pghost = os.environ.get("PGHOST") + pgport = os.environ.get("PGPORT", "5432") + pgdatabase = os.environ.get("PGDATABASE", "smc") + pgpassword = os.environ.get("PGPASSWORD") + + # PGHOST is mandatory (already checked in check_cleanup_config, but double-check) + if not pghost: + pytest.fail("\n" + "=" * 70 + "\n" + "ERROR: PGHOST environment variable is required for database cleanup!\n" + "=" * 70) + + # Determine if using socket or network connection + is_socket = pghost.startswith("/") + + # Build connection kwargs + conn_kwargs = { + "host": pghost, + "database": pgdatabase, + "user": pguser, + } + + # Only add port for network connections + if not is_socket: + conn_kwargs["port"] = pgport + + # Only add password if provided + if pgpassword: + conn_kwargs["password"] = pgpassword + + try: + connection_pool = pg_pool.SimpleConnectionPool(1, 5, **conn_kwargs) + + if is_socket: + print(f"\n✓ Database cleanup enabled (socket): {pguser}@{pghost}/{pgdatabase}") + else: + print(f"\n✓ Database cleanup enabled (network): {pguser}@{pghost}:{pgport}/{pgdatabase}") + + yield connection_pool + + connection_pool.closeall() + + except Exception as e: + conn_type = "socket" if is_socket else "network" + pytest.fail("\n" + "=" * 70 + "\n" + f"ERROR: Failed to connect to database ({conn_type}) for cleanup:\n{e}\n\n" + f"Connection details:\n" + f" Host: {pghost}\n" + f" Database: {pgdatabase}\n" + f" User: {pguser}\n" + (f" Port: {pgport}\n" if not is_socket else "") + + "\nTo disable cleanup: export COCALC_TESTS_CLEANUP=false\n" + "=" * 70) + + +def create_tracked_project(hub, resource_tracker, **kwargs): + """Create a project and register it for cleanup.""" + project_id = hub.projects.create_project(**kwargs) + resource_tracker['projects'].add(project_id) + return project_id + + +def create_tracked_user(hub, resource_tracker, org_name, **kwargs): + """Create a user and register it for cleanup.""" + user_id = hub.org.create_user(name=org_name, **kwargs) + resource_tracker['accounts'].add(user_id) + return user_id + + +def create_tracked_org(hub, resource_tracker, org_name): + """Create an organization and register it for cleanup.""" + org_id = hub.org.create(org_name) + resource_tracker['organizations'].add(org_name) # Track by name + return org_id + + +def hard_delete_projects(db_pool, project_ids): + """Hard delete projects from database using direct SQL.""" + if not project_ids: + return + + conn = db_pool.getconn() + try: + cursor = conn.cursor() + for project_id in project_ids: + try: + cursor.execute("DELETE FROM projects WHERE project_id = %s", (project_id, )) + conn.commit() + print(f" ✓ Deleted project {project_id}") + except Exception as e: + conn.rollback() + print(f" ✗ Failed to delete project {project_id}: {e}") + cursor.close() + finally: + db_pool.putconn(conn) + + +def hard_delete_accounts(db_pool, account_ids): + """ + Hard delete accounts from database using direct SQL. + + This also finds and deletes ALL projects where the account is the owner, + including auto-created projects like "My First Project". + """ + if not account_ids: + return + + conn = db_pool.getconn() + try: + cursor = conn.cursor() + for account_id in account_ids: + try: + # First, find ALL projects where this account is the owner + # The users JSONB field has structure: {"account_id": {"group": "owner", ...}} + cursor.execute( + """ + SELECT project_id FROM projects + WHERE users ? %s + AND users->%s->>'group' = 'owner' + """, (account_id, account_id)) + owned_projects = cursor.fetchall() + + # Delete all owned projects (including auto-created ones) + for (project_id, ) in owned_projects: + cursor.execute("DELETE FROM projects WHERE project_id = %s", (project_id, )) + print(f" ✓ Deleted owned project {project_id} for account {account_id}") + + # Remove from organizations (admin_account_ids array and users JSONB) + cursor.execute( + "UPDATE organizations SET admin_account_ids = array_remove(admin_account_ids, %s), users = users - %s WHERE users ? %s", + (account_id, account_id, account_id)) + + # Remove from remaining project collaborators (users JSONB field) + cursor.execute("UPDATE projects SET users = users - %s WHERE users ? %s", (account_id, account_id)) + + # Delete the account + cursor.execute("DELETE FROM accounts WHERE account_id = %s", (account_id, )) + conn.commit() + print(f" ✓ Deleted account {account_id}") + except Exception as e: + conn.rollback() + print(f" ✗ Failed to delete account {account_id}: {e}") + cursor.close() + finally: + db_pool.putconn(conn) + + +def hard_delete_organizations(db_pool, org_names): + """Hard delete organizations from database using direct SQL.""" + if not org_names: + return + + conn = db_pool.getconn() + try: + cursor = conn.cursor() + for org_name in org_names: + try: + cursor.execute("DELETE FROM organizations WHERE name = %s", (org_name, )) + conn.commit() + print(f" ✓ Deleted organization {org_name}") + except Exception as e: + conn.rollback() + print(f" ✗ Failed to delete organization {org_name}: {e}") + cursor.close() + finally: + db_pool.putconn(conn) + + +@pytest.fixture(scope="session", autouse=True) +def cleanup_all_test_resources(hub, resource_tracker, db_pool, request): + """ + Automatically clean up all tracked resources at the end of the test session. + + Cleanup is enabled by default. To disable: + export COCALC_TESTS_CLEANUP=false + """ + + def cleanup(): + # Skip cleanup if db_pool is None (cleanup disabled) + if db_pool is None: + print("\n⚠ Skipping database cleanup (COCALC_TESTS_CLEANUP=false)") + return + + print("\n" + "=" * 70) + print("CLEANING UP TEST RESOURCES FROM DATABASE") + print("=" * 70) + + total_projects = len(resource_tracker['projects']) + total_accounts = len(resource_tracker['accounts']) + total_orgs = len(resource_tracker['organizations']) + + print("\nResources to clean up:") + print(f" - Projects: {total_projects}") + print(f" - Accounts: {total_accounts}") + print(f" - Organizations: {total_orgs}") + + # First, soft-delete projects via API (stop them gracefully) + if total_projects > 0: + print(f"\nStopping {total_projects} projects...") + for project_id in resource_tracker['projects']: + try: + cleanup_project(hub, project_id) + except Exception as e: + print(f" Warning: Failed to stop project {project_id}: {e}") + + # Then hard-delete from database in order: + # 1. Projects (no dependencies) + if total_projects > 0: + print(f"\nHard-deleting {total_projects} projects from database...") + hard_delete_projects(db_pool, resource_tracker['projects']) + + # 2. Accounts (must remove from organizations/projects first) + if total_accounts > 0: + print(f"\nHard-deleting {total_accounts} accounts from database...") + hard_delete_accounts(db_pool, resource_tracker['accounts']) + + # 3. Organizations (no dependencies after accounts removed) + if total_orgs > 0: + print(f"\nHard-deleting {total_orgs} organizations from database...") + hard_delete_organizations(db_pool, resource_tracker['organizations']) + + print("\n✓ Test resource cleanup complete!") + print("=" * 70) + + request.addfinalizer(cleanup) + + yield diff --git a/src/python/cocalc-api/tests/test_hub.py b/src/python/cocalc-api/tests/test_hub.py index b7f82db98f8..675027a5ffb 100644 --- a/src/python/cocalc-api/tests/test_hub.py +++ b/src/python/cocalc-api/tests/test_hub.py @@ -5,7 +5,7 @@ import pytest from cocalc_api import Hub, Project -from .conftest import assert_valid_uuid, cleanup_project +from .conftest import assert_valid_uuid, cleanup_project, create_tracked_project, create_tracked_user, create_tracked_org class TestHubSystem: @@ -53,25 +53,104 @@ def test_multiple_pings(self, hub): assert result is not None assert isinstance(result, dict) + def test_user_search(self, hub, resource_tracker): + """Test user search functionality.""" + import time + timestamp = int(time.time()) + + # Create a test organization and user with a unique email + org_name = f"search-test-org-{timestamp}" + test_email = f"search-test-user-{timestamp}@test.local" + test_first_name = f"SearchFirst{timestamp}" + test_last_name = f"SearchLast{timestamp}" + + # Use tracked creation + org_id = create_tracked_org(hub, resource_tracker, org_name) + print(f"\nCreated test organization: {org_name} (ID: {org_id})") + + # Create a user with unique identifiable names + user_id = create_tracked_user(hub, resource_tracker, org_name, email=test_email, firstName=test_first_name, lastName=test_last_name) + print(f"Created test user: {user_id}, email: {test_email}") + + # Give the database a moment to index the new user + time.sleep(0.5) + + # Test 1: Search by email (exact match should return only this user) + print("\n1. Testing search by email...") + results = hub.system.user_search(test_email) + assert isinstance(results, list), "user_search should return a list" + assert len(results) >= 1, f"Expected at least 1 result for email {test_email}, got {len(results)}" + + # Find our user in the results + our_user = None + for user in results: + if user.get('email_address') == test_email: + our_user = user + break + + assert our_user is not None, f"Expected to find user with email {test_email} in results" + print(f" Found user by email: {our_user['account_id']}") + + # Verify the structure of the result + assert 'account_id' in our_user + assert 'first_name' in our_user + assert 'last_name' in our_user + assert our_user['first_name'] == test_first_name + assert our_user['last_name'] == test_last_name + assert our_user['account_id'] == user_id + print(f" User data: first_name={our_user['first_name']}, last_name={our_user['last_name']}") + + # Test 2: Search by full first name (to ensure we find our user) + print("\n2. Testing search by full first name...") + # Use the full first name which is guaranteed unique with timestamp + results = hub.system.user_search(test_first_name) + assert isinstance(results, list) + print(f" Search for '{test_first_name}' returned {len(results)} results") + # Our user should be in the results + found = any(u.get('account_id') == user_id for u in results) + if not found and len(results) > 0: + print(f" Found these first names: {[u.get('first_name') for u in results]}") + assert found, f"Expected to find user {user_id} when searching for '{test_first_name}'" + print(f" Found user in {len(results)} results") + + # Test 3: Search by full last name (to ensure we find our user) + print("\n3. Testing search by full last name...") + # Use the full last name which is guaranteed unique with timestamp + results = hub.system.user_search(test_last_name) + assert isinstance(results, list) + found = any(u.get('account_id') == user_id for u in results) + assert found, f"Expected to find user {user_id} when searching for '{test_last_name}'" + print(f" Found user in {len(results)} results") + + # Test 4: Nonexistent search should return empty list + print("\n4. Testing search with unlikely query...") + unlikely_query = f"xyznonexistent{timestamp}abc" + results = hub.system.user_search(unlikely_query) + assert isinstance(results, list) + assert len(results) == 0, f"Expected 0 results for non-existent query, got {len(results)}" + print(" Search for non-existent query correctly returned 0 results") + + print("\n✅ User search test completed successfully!") + + # Note: No cleanup needed - happens automatically via cleanup_all_test_resources + class TestHubProjects: """Tests for Hub project operations.""" - def test_create_project(self, hub): + def test_create_project(self, hub, resource_tracker): """Test creating a project via hub.projects.create_project.""" import time timestamp = int(time.time()) title = f"test-project-{timestamp}" description = "Test project for API testing" - project_id = hub.projects.create_project(title=title, description=description) + project_id = create_tracked_project(hub, resource_tracker, title=title, description=description) + + assert project_id is not None + assert_valid_uuid(project_id, "Project ID") - try: - assert project_id is not None - assert_valid_uuid(project_id, "Project ID") - finally: - # Cleanup: stop then delete the project - cleanup_project(hub, project_id) + # Note: No cleanup needed - happens automatically def test_list_projects(self, hub): """Test listing projects.""" @@ -91,7 +170,7 @@ def test_delete_method_exists(self, hub): # Note: We don't actually delete anything in this test since # deletion is tested in the project lifecycle via temporary_project fixture - def test_project_lifecycle(self, hub): + def test_project_lifecycle(self, hub, resource_tracker): """Test complete project lifecycle: create, wait for ready, run command, delete, verify deletion.""" # 1. Create a project @@ -100,77 +179,169 @@ def test_project_lifecycle(self, hub): description = "Test project for complete lifecycle testing" print(f"\n1. Creating project '{title}'...") - project_id = hub.projects.create_project(title=title, description=description) + project_id = create_tracked_project(hub, resource_tracker, title=title, description=description) assert project_id is not None assert_valid_uuid(project_id, "Project ID") print(f" Created project: {project_id}") - try: - # Start the project - print("2. Starting project...") - hub.projects.start(project_id) - print(" Project start request sent") - - # Wait for project to become ready - print("3. Waiting for project to become ready...") - project_client = Project(project_id=project_id, api_key=hub.api_key, host=hub.host) - - ready = False - for attempt in range(12): # 60 seconds max wait time - time.sleep(5) - try: - project_client.system.ping() - ready = True - print(f" ✓ Project ready after {(attempt + 1) * 5} seconds") - break - except Exception as e: - if attempt == 11: # Last attempt - print(f" Warning: Project not ready after 60 seconds: {e}") - else: - print(f" Attempt {attempt + 1}: Project not ready yet...") - - # Check that project exists in database - print("4. Checking project exists in database...") - projects = hub.projects.get(fields=['project_id', 'title', 'deleted'], project_id=project_id) - assert len(projects) == 1, f"Expected 1 project, found {len(projects)}" - project = projects[0] - assert project["project_id"] == project_id - assert project["title"] == title - assert project.get("deleted") is None or project.get("deleted") is False - print(f" ✓ Project found in database: title='{project['title']}', deleted={project.get('deleted')}") - - # 2. Run a command if project is ready - if ready: - print("5. Running 'uname -a' command...") - result = project_client.system.exec("uname -a") - assert "stdout" in result - output = result["stdout"] - assert "Linux" in output, f"Expected Linux system, got: {output}" - assert result["exit_code"] == 0, f"Command failed with exit code {result['exit_code']}" - print(f" ✓ Command executed successfully: {output.strip()}") - else: - print("5. Skipping command execution - project not ready") - - # 3. Stop and delete the project - print("6. Stopping and deleting project...") - cleanup_project(hub, project_id) - - # 4. Verify project is marked as deleted in database - print("8. Verifying project is marked as deleted...") - projects = hub.projects.get(fields=['project_id', 'title', 'deleted'], project_id=project_id, all=True) - assert len(projects) == 1, f"Expected 1 project (still in DB), found {len(projects)}" - project = projects[0] - assert project["project_id"] == project_id - assert project.get("deleted") is True, f"Expected deleted=True, got deleted={project.get('deleted')}" - print(f" ✓ Project correctly marked as deleted in database: deleted={project.get('deleted')}") - - print("✅ Project lifecycle test completed successfully!") - - except Exception as e: - # Cleanup: attempt to stop and delete project if test fails - print(f"\nTest failed: {e}") + # Start the project + print("2. Starting project...") + hub.projects.start(project_id) + print(" Project start request sent") + + # Wait for project to become ready + print("3. Waiting for project to become ready...") + project_client = Project(project_id=project_id, api_key=hub.api_key, host=hub.host) + + ready = False + for attempt in range(12): # 60 seconds max wait time + time.sleep(5) try: - cleanup_project(hub, project_id) - except Exception as cleanup_error: - print(f"Cleanup failed: {cleanup_error}") - raise e + project_client.system.ping() + ready = True + print(f" ✓ Project ready after {(attempt + 1) * 5} seconds") + break + except Exception as e: + if attempt == 11: # Last attempt + print(f" Warning: Project not ready after 60 seconds: {e}") + else: + print(f" Attempt {attempt + 1}: Project not ready yet...") + + # Check that project exists in database + print("4. Checking project exists in database...") + projects = hub.projects.get(fields=['project_id', 'title', 'deleted'], project_id=project_id) + assert len(projects) == 1, f"Expected 1 project, found {len(projects)}" + project = projects[0] + assert project["project_id"] == project_id + assert project["title"] == title + assert project.get("deleted") is None or project.get("deleted") is False + print(f" ✓ Project found in database: title='{project['title']}', deleted={project.get('deleted')}") + + # 2. Run a command if project is ready + if ready: + print("5. Running 'uname -a' command...") + result = project_client.system.exec("uname -a") + assert "stdout" in result + output = result["stdout"] + assert "Linux" in output, f"Expected Linux system, got: {output}" + assert result["exit_code"] == 0, f"Command failed with exit code {result['exit_code']}" + print(f" ✓ Command executed successfully: {output.strip()}") + else: + print("5. Skipping command execution - project not ready") + + # 3. Stop and delete the project + print("6. Stopping and deleting project...") + cleanup_project(hub, project_id) + + # 4. Verify project is marked as deleted in database + print("8. Verifying project is marked as deleted...") + projects = hub.projects.get(fields=['project_id', 'title', 'deleted'], project_id=project_id, all=True) + assert len(projects) == 1, f"Expected 1 project (still in DB), found {len(projects)}" + project = projects[0] + assert project["project_id"] == project_id + assert project.get("deleted") is True, f"Expected deleted=True, got deleted={project.get('deleted')}" + print(f" ✓ Project correctly marked as deleted in database: deleted={project.get('deleted')}") + + print("✅ Project lifecycle test completed successfully!") + + # Note: No cleanup needed - hard-delete happens automatically at session end + + def test_collaborator_management(self, hub, resource_tracker): + """Test adding and removing collaborators from a project.""" + import time + timestamp = int(time.time()) + + # 1. Site admin creates two users + print("\n1. Creating two test users...") + user1_email = f"collab-user1-{timestamp}@test.local" + user2_email = f"collab-user2-{timestamp}@test.local" + + # Create a temporary organization for the users + org_name = f"collab-test-org-{timestamp}" + org_id = create_tracked_org(hub, resource_tracker, org_name) + print(f" Created organization: {org_name} (ID: {org_id})") + + user1_id = create_tracked_user(hub, resource_tracker, org_name, email=user1_email, firstName="CollabUser", lastName="One") + print(f" Created user1: {user1_id}") + + user2_id = create_tracked_user(hub, resource_tracker, org_name, email=user2_email, firstName="CollabUser", lastName="Two") + print(f" Created user2: {user2_id}") + + # 2. Create a project for the first user + print("\n2. Creating project for user1...") + project_title = f"collab-test-project-{timestamp}" + project_id = create_tracked_project(hub, resource_tracker, title=project_title) + print(f" Created project: {project_id}") + + # 3. Check initial collaborators + print("\n3. Checking initial collaborators...") + projects = hub.projects.get(fields=['project_id', 'users'], project_id=project_id) + assert len(projects) == 1 + initial_users = projects[0].get('users', {}) + print(f" Initial collaborators: {list(initial_users.keys())}") + print(f" Number of initial collaborators: {len(initial_users)}") + + # Report on ownership structure + for user_id, perms in initial_users.items(): + print(f" User {user_id}: {perms}") + + # 4. Add user1 as collaborator + print(f"\n4. Adding user1 ({user1_id}) as collaborator...") + result = hub.projects.add_collaborator(project_id=project_id, account_id=user1_id) + print(f" Add collaborator result: {result}") + + # Check collaborators after adding user1 + projects = hub.projects.get(fields=['project_id', 'users'], project_id=project_id) + users_after_user1 = projects[0].get('users', {}) + print(f" Collaborators after adding user1: {list(users_after_user1.keys())}") + print(f" Number of collaborators: {len(users_after_user1)}") + for user_id, perms in users_after_user1.items(): + print(f" User {user_id}: {perms}") + + # 5. Add user2 as collaborator + print(f"\n5. Adding user2 ({user2_id}) as collaborator...") + result = hub.projects.add_collaborator(project_id=project_id, account_id=user2_id) + print(f" Add collaborator result: {result}") + + # Check collaborators after adding user2 + projects = hub.projects.get(fields=['project_id', 'users'], project_id=project_id) + users_after_user2 = projects[0].get('users', {}) + print(f" Collaborators after adding user2: {list(users_after_user2.keys())}") + print(f" Number of collaborators: {len(users_after_user2)}") + # Note: There will be 3 users total: the site admin (owner) + user1 + user2 + for user_id, perms in users_after_user2.items(): + print(f" User {user_id}: {perms}") + + # Verify user1 and user2 are present + assert user1_id in users_after_user2, f"Expected user1 ({user1_id}) to be a collaborator" + assert user2_id in users_after_user2, f"Expected user2 ({user2_id}) to be a collaborator" + + # Identify the owner (should be the site admin who created the project) + owner_id = None + for uid, perms in users_after_user2.items(): + if perms.get('group') == 'owner': + owner_id = uid + break + print(f" Project owner: {owner_id}") + + # 6. Remove user1 + print(f"\n6. Removing user1 ({user1_id}) from project...") + result = hub.projects.remove_collaborator(project_id=project_id, account_id=user1_id) + print(f" Remove collaborator result: {result}") + + # Check collaborators after removing user1 + projects = hub.projects.get(fields=['project_id', 'users'], project_id=project_id) + users_after_removal = projects[0].get('users', {}) + print(f" Collaborators after removing user1: {list(users_after_removal.keys())}") + print(f" Number of collaborators: {len(users_after_removal)}") + # Should have 2 users: owner + user2 + assert len(users_after_removal) == 2, f"Expected 2 collaborators (owner + user2), found {len(users_after_removal)}" + assert user2_id in users_after_removal, f"Expected user2 ({user2_id}) to still be a collaborator" + assert user1_id not in users_after_removal, f"Expected user1 ({user1_id}) to be removed" + assert owner_id in users_after_removal, f"Expected owner ({owner_id}) to remain" + for user_id, perms in users_after_removal.items(): + print(f" User {user_id}: {perms}") + + print("\n✅ Collaborator management test completed successfully!") + + # Note: No cleanup needed - hard-delete happens automatically at session end diff --git a/src/python/cocalc-api/uv.lock b/src/python/cocalc-api/uv.lock index ee33170b971..109f4a3aef9 100644 --- a/src/python/cocalc-api/uv.lock +++ b/src/python/cocalc-api/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ [[package]] name = "anyio" -version = "4.10.0" +version = "4.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, @@ -17,9 +17,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, ] [[package]] @@ -155,7 +155,7 @@ wheels = [ [[package]] name = "click" -version = "8.2.1" +version = "8.3.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", @@ -164,9 +164,9 @@ resolution-markers = [ dependencies = [ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, ] [[package]] @@ -182,15 +182,17 @@ dev = [ { name = "coverage", extra = ["toml"] }, { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "ipython", version = "9.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "ipython", version = "9.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "mkdocs" }, { name = "mkdocs-material" }, { name = "mkdocstrings", extra = ["python"] }, { name = "mypy" }, + { name = "psycopg2-binary" }, { name = "pyright" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "ruff" }, + { name = "types-psycopg2" }, { name = "yapf" }, ] @@ -205,10 +207,12 @@ dev = [ { name = "mkdocs-material" }, { name = "mkdocstrings", extras = ["python"] }, { name = "mypy" }, + { name = "psycopg2-binary" }, { name = "pyright" }, - { name = "pytest", specifier = ">=8.4.1" }, + { name = "pytest", specifier = ">=8.4.2" }, { name = "pytest-cov" }, - { name = "ruff", specifier = ">=0.12.11" }, + { name = "ruff", specifier = ">=0.13.2" }, + { name = "types-psycopg2" }, { name = "yapf" }, ] @@ -360,11 +364,11 @@ wheels = [ [[package]] name = "executing" -version = "2.2.0" +version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693, upload-time = "2025-01-22T15:41:29.403Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702, upload-time = "2025-01-22T15:41:25.929Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, ] [[package]] @@ -381,14 +385,14 @@ wheels = [ [[package]] name = "griffe" -version = "1.13.0" +version = "1.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/b5/23b91f22b7b3a7f8f62223f6664946271c0f5cb4179605a3e6bbae863920/griffe-1.13.0.tar.gz", hash = "sha256:246ea436a5e78f7fbf5f24ca8a727bb4d2a4b442a2959052eea3d0bfe9a076e0", size = 412759, upload-time = "2025-08-26T13:27:11.422Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/d7/6c09dd7ce4c7837e4cdb11dce980cb45ae3cd87677298dc3b781b6bce7d3/griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13", size = 424684, upload-time = "2025-09-05T15:02:29.167Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/8c/b7cfdd8dfe48f6b09f7353323732e1a290c388bd14f216947928dc85f904/griffe-1.13.0-py3-none-any.whl", hash = "sha256:470fde5b735625ac0a36296cd194617f039e9e83e301fcbd493e2b58382d0559", size = 139365, upload-time = "2025-08-26T13:27:09.882Z" }, + { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" }, ] [[package]] @@ -510,7 +514,7 @@ wheels = [ [[package]] name = "ipython" -version = "9.5.0" +version = "9.6.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", @@ -528,9 +532,9 @@ dependencies = [ { name = "traitlets", marker = "python_full_version >= '3.11'" }, { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/71/a86262bf5a68bf211bcc71fe302af7e05f18a2852fdc610a854d20d085e6/ipython-9.5.0.tar.gz", hash = "sha256:129c44b941fe6d9b82d36fc7a7c18127ddb1d6f02f78f867f402e2e3adde3113", size = 4389137, upload-time = "2025-08-29T12:15:21.519Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/34/29b18c62e39ee2f7a6a3bba7efd952729d8aadd45ca17efc34453b717665/ipython-9.6.0.tar.gz", hash = "sha256:5603d6d5d356378be5043e69441a072b50a5b33b4503428c77b04cb8ce7bc731", size = 4396932, upload-time = "2025-09-29T10:55:53.948Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/2a/5628a99d04acb2d2f2e749cdf4ea571d2575e898df0528a090948018b726/ipython-9.5.0-py3-none-any.whl", hash = "sha256:88369ffa1d5817d609120daa523a6da06d02518e582347c29f8451732a9c5e72", size = 612426, upload-time = "2025-08-29T12:15:18.866Z" }, + { url = "https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl", hash = "sha256:5f77efafc886d2f023442479b8149e7d86547ad0a979e9da9f045d252f648196", size = 616170, upload-time = "2025-09-29T10:55:47.676Z" }, ] [[package]] @@ -571,82 +575,110 @@ wheels = [ [[package]] name = "markdown" -version = "3.8.2" +version = "3.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, ] [[package]] name = "markupsafe" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, - { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, - { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, - { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, - { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, - { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, - { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, - { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, - { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, - { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344, upload-time = "2024-10-18T15:21:43.721Z" }, - { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389, upload-time = "2024-10-18T15:21:44.666Z" }, - { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607, upload-time = "2024-10-18T15:21:45.452Z" }, - { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728, upload-time = "2024-10-18T15:21:46.295Z" }, - { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826, upload-time = "2024-10-18T15:21:47.134Z" }, - { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843, upload-time = "2024-10-18T15:21:48.334Z" }, - { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219, upload-time = "2024-10-18T15:21:49.587Z" }, - { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946, upload-time = "2024-10-18T15:21:50.441Z" }, - { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063, upload-time = "2024-10-18T15:21:51.385Z" }, - { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506, upload-time = "2024-10-18T15:21:52.974Z" }, +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" }, + { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543, upload-time = "2025-09-27T18:37:32.168Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585, upload-time = "2025-09-27T18:37:33.166Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387, upload-time = "2025-09-27T18:37:34.185Z" }, + { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133, upload-time = "2025-09-27T18:37:35.138Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588, upload-time = "2025-09-27T18:37:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566, upload-time = "2025-09-27T18:37:37.09Z" }, + { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053, upload-time = "2025-09-27T18:37:38.054Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928, upload-time = "2025-09-27T18:37:39.037Z" }, ] [[package]] @@ -676,7 +708,7 @@ version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "click", version = "8.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "ghp-import" }, { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, @@ -727,13 +759,11 @@ wheels = [ [[package]] name = "mkdocs-material" -version = "9.6.18" +version = "9.6.21" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, { name = "backrefs" }, - { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "colorama" }, { name = "jinja2" }, { name = "markdown" }, @@ -744,9 +774,9 @@ dependencies = [ { name = "pymdown-extensions" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/46/db0d78add5aac29dfcd0a593bcc6049c86c77ba8a25b3a5b681c190d5e99/mkdocs_material-9.6.18.tar.gz", hash = "sha256:a2eb253bcc8b66f8c6eaf8379c10ed6e9644090c2e2e9d0971c7722dc7211c05", size = 4034856, upload-time = "2025-08-22T08:21:47.575Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/d5/ab83ca9aa314954b0a9e8849780bdd01866a3cfcb15ffb7e3a61ca06ff0b/mkdocs_material-9.6.21.tar.gz", hash = "sha256:b01aa6d2731322438056f360f0e623d3faae981f8f2d8c68b1b973f4f2657870", size = 4043097, upload-time = "2025-09-30T19:11:27.517Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/0b/545a4f8d4f9057e77f1d99640eb09aaae40c4f9034707f25636caf716ff9/mkdocs_material-9.6.18-py3-none-any.whl", hash = "sha256:dbc1e146a0ecce951a4d84f97b816a54936cdc9e1edd1667fc6868878ac06701", size = 9232642, upload-time = "2025-08-22T08:21:44.52Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4f/98681c2030375fe9b057dbfb9008b68f46c07dddf583f4df09bf8075e37f/mkdocs_material-9.6.21-py3-none-any.whl", hash = "sha256:aa6a5ab6fb4f6d381588ac51da8782a4d3757cb3d1b174f81a2ec126e1f22c92", size = 9203097, upload-time = "2025-09-30T19:11:24.063Z" }, ] [[package]] @@ -760,7 +790,7 @@ wheels = [ [[package]] name = "mkdocstrings" -version = "0.30.0" +version = "0.30.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, @@ -771,9 +801,9 @@ dependencies = [ { name = "mkdocs-autorefs" }, { name = "pymdown-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/0a/7e4776217d4802009c8238c75c5345e23014a4706a8414a62c0498858183/mkdocstrings-0.30.0.tar.gz", hash = "sha256:5d8019b9c31ddacd780b6784ffcdd6f21c408f34c0bd1103b5351d609d5b4444", size = 106597, upload-time = "2025-07-22T23:48:45.998Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/33/2fa3243439f794e685d3e694590d28469a9b8ea733af4b48c250a3ffc9a0/mkdocstrings-0.30.1.tar.gz", hash = "sha256:84a007aae9b707fb0aebfc9da23db4b26fc9ab562eb56e335e9ec480cb19744f", size = 106350, upload-time = "2025-09-19T10:49:26.446Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/b4/3c5eac68f31e124a55d255d318c7445840fa1be55e013f507556d6481913/mkdocstrings-0.30.0-py3-none-any.whl", hash = "sha256:ae9e4a0d8c1789697ac776f2e034e2ddd71054ae1cf2c2bb1433ccfd07c226f2", size = 36579, upload-time = "2025-07-22T23:48:44.152Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2c/f0dc4e1ee7f618f5bff7e05898d20bf8b6e7fa612038f768bfa295f136a4/mkdocstrings-0.30.1-py3-none-any.whl", hash = "sha256:41bd71f284ca4d44a668816193e4025c950b002252081e387433656ae9a70a82", size = 36704, upload-time = "2025-09-19T10:49:24.805Z" }, ] [package.optional-dependencies] @@ -798,7 +828,7 @@ wheels = [ [[package]] name = "mypy" -version = "1.17.1" +version = "1.18.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, @@ -806,45 +836,45 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/a9/3d7aa83955617cdf02f94e50aab5c830d205cfa4320cf124ff64acce3a8e/mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972", size = 11003299, upload-time = "2025-07-31T07:54:06.425Z" }, - { url = "https://files.pythonhosted.org/packages/83/e8/72e62ff837dd5caaac2b4a5c07ce769c8e808a00a65e5d8f94ea9c6f20ab/mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7", size = 10125451, upload-time = "2025-07-31T07:53:52.974Z" }, - { url = "https://files.pythonhosted.org/packages/7d/10/f3f3543f6448db11881776f26a0ed079865926b0c841818ee22de2c6bbab/mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df", size = 11916211, upload-time = "2025-07-31T07:53:18.879Z" }, - { url = "https://files.pythonhosted.org/packages/06/bf/63e83ed551282d67bb3f7fea2cd5561b08d2bb6eb287c096539feb5ddbc5/mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390", size = 12652687, upload-time = "2025-07-31T07:53:30.544Z" }, - { url = "https://files.pythonhosted.org/packages/69/66/68f2eeef11facf597143e85b694a161868b3b006a5fbad50e09ea117ef24/mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94", size = 12896322, upload-time = "2025-07-31T07:53:50.74Z" }, - { url = "https://files.pythonhosted.org/packages/a3/87/8e3e9c2c8bd0d7e071a89c71be28ad088aaecbadf0454f46a540bda7bca6/mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b", size = 9507962, upload-time = "2025-07-31T07:53:08.431Z" }, - { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009, upload-time = "2025-07-31T07:53:23.037Z" }, - { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482, upload-time = "2025-07-31T07:53:26.151Z" }, - { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883, upload-time = "2025-07-31T07:53:47.948Z" }, - { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215, upload-time = "2025-07-31T07:54:04.031Z" }, - { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956, upload-time = "2025-07-31T07:53:36.263Z" }, - { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307, upload-time = "2025-07-31T07:53:59.734Z" }, - { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295, upload-time = "2025-07-31T07:53:28.124Z" }, - { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355, upload-time = "2025-07-31T07:53:21.121Z" }, - { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285, upload-time = "2025-07-31T07:53:55.293Z" }, - { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895, upload-time = "2025-07-31T07:53:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025, upload-time = "2025-07-31T07:54:17.125Z" }, - { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664, upload-time = "2025-07-31T07:54:12.842Z" }, - { url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338, upload-time = "2025-07-31T07:53:38.873Z" }, - { url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066, upload-time = "2025-07-31T07:54:14.707Z" }, - { url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473, upload-time = "2025-07-31T07:53:14.504Z" }, - { url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296, upload-time = "2025-07-31T07:53:03.896Z" }, - { url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657, upload-time = "2025-07-31T07:54:08.576Z" }, - { url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320, upload-time = "2025-07-31T07:53:01.341Z" }, - { url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037, upload-time = "2025-07-31T07:54:10.942Z" }, - { url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550, upload-time = "2025-07-31T07:53:41.307Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963, upload-time = "2025-07-31T07:53:16.878Z" }, - { url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189, upload-time = "2025-07-31T07:54:01.962Z" }, - { url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322, upload-time = "2025-07-31T07:53:10.551Z" }, - { url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879, upload-time = "2025-07-31T07:52:56.683Z" }, - { url = "https://files.pythonhosted.org/packages/29/cb/673e3d34e5d8de60b3a61f44f80150a738bff568cd6b7efb55742a605e98/mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9", size = 10992466, upload-time = "2025-07-31T07:53:57.574Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d0/fe1895836eea3a33ab801561987a10569df92f2d3d4715abf2cfeaa29cb2/mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99", size = 10117638, upload-time = "2025-07-31T07:53:34.256Z" }, - { url = "https://files.pythonhosted.org/packages/97/f3/514aa5532303aafb95b9ca400a31054a2bd9489de166558c2baaeea9c522/mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8", size = 11915673, upload-time = "2025-07-31T07:52:59.361Z" }, - { url = "https://files.pythonhosted.org/packages/ab/c3/c0805f0edec96fe8e2c048b03769a6291523d509be8ee7f56ae922fa3882/mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8", size = 12649022, upload-time = "2025-07-31T07:53:45.92Z" }, - { url = "https://files.pythonhosted.org/packages/45/3e/d646b5a298ada21a8512fa7e5531f664535a495efa672601702398cea2b4/mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259", size = 12895536, upload-time = "2025-07-31T07:53:06.17Z" }, - { url = "https://files.pythonhosted.org/packages/14/55/e13d0dcd276975927d1f4e9e2ec4fd409e199f01bdc671717e673cc63a22/mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d", size = 9512564, upload-time = "2025-07-31T07:53:12.346Z" }, - { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/6f/657961a0743cff32e6c0611b63ff1c1970a0b482ace35b069203bf705187/mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c", size = 12807973, upload-time = "2025-09-19T00:10:35.282Z" }, + { url = "https://files.pythonhosted.org/packages/10/e9/420822d4f661f13ca8900f5fa239b40ee3be8b62b32f3357df9a3045a08b/mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e", size = 11896527, upload-time = "2025-09-19T00:10:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/aa/73/a05b2bbaa7005f4642fcfe40fb73f2b4fb6bb44229bd585b5878e9a87ef8/mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b", size = 12507004, upload-time = "2025-09-19T00:11:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/f6e4b9f0d031c11ccbd6f17da26564f3a0f3c4155af344006434b0a05a9d/mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66", size = 13245947, upload-time = "2025-09-19T00:10:46.923Z" }, + { url = "https://files.pythonhosted.org/packages/d7/97/19727e7499bfa1ae0773d06afd30ac66a58ed7437d940c70548634b24185/mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428", size = 13499217, upload-time = "2025-09-19T00:09:39.472Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4f/90dc8c15c1441bf31cf0f9918bb077e452618708199e530f4cbd5cede6ff/mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed", size = 9766753, upload-time = "2025-09-19T00:10:49.161Z" }, + { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, + { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, + { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, + { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, + { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, + { url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" }, + { url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" }, + { url = "https://files.pythonhosted.org/packages/3f/a6/490ff491d8ecddf8ab91762d4f67635040202f76a44171420bcbe38ceee5/mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b", size = 12807230, upload-time = "2025-09-19T00:09:49.471Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2e/60076fc829645d167ece9e80db9e8375648d210dab44cc98beb5b322a826/mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133", size = 11895666, upload-time = "2025-09-19T00:10:53.678Z" }, + { url = "https://files.pythonhosted.org/packages/97/4a/1e2880a2a5dda4dc8d9ecd1a7e7606bc0b0e14813637eeda40c38624e037/mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6", size = 12499608, upload-time = "2025-09-19T00:09:36.204Z" }, + { url = "https://files.pythonhosted.org/packages/00/81/a117f1b73a3015b076b20246b1f341c34a578ebd9662848c6b80ad5c4138/mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac", size = 13244551, upload-time = "2025-09-19T00:10:17.531Z" }, + { url = "https://files.pythonhosted.org/packages/9b/61/b9f48e1714ce87c7bf0358eb93f60663740ebb08f9ea886ffc670cea7933/mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b", size = 13491552, upload-time = "2025-09-19T00:10:13.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/b2c0af3b684fa80d1b27501a8bdd3d2daa467ea3992a8aa612f5ca17c2db/mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0", size = 9765635, upload-time = "2025-09-19T00:10:30.993Z" }, + { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, ] [[package]] @@ -943,6 +973,72 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, ] +[[package]] +name = "psycopg2-binary" +version = "2.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload-time = "2024-10-16T11:24:58.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/81/331257dbf2801cdb82105306042f7a1637cc752f65f2bb688188e0de5f0b/psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f", size = 3043397, upload-time = "2024-10-16T11:18:58.647Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9a/7f4f2f031010bbfe6a02b4a15c01e12eb6b9b7b358ab33229f28baadbfc1/psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906", size = 3274806, upload-time = "2024-10-16T11:19:03.935Z" }, + { url = "https://files.pythonhosted.org/packages/e5/57/8ddd4b374fa811a0b0a0f49b6abad1cde9cb34df73ea3348cc283fcd70b4/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92", size = 2851361, upload-time = "2024-10-16T11:19:07.277Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/d1e52c20d283f1f3a8e7e5c1e06851d432f123ef57b13043b4f9b21ffa1f/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007", size = 3080836, upload-time = "2024-10-16T11:19:11.033Z" }, + { url = "https://files.pythonhosted.org/packages/a0/cb/592d44a9546aba78f8a1249021fe7c59d3afb8a0ba51434d6610cc3462b6/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0", size = 3264552, upload-time = "2024-10-16T11:19:14.606Z" }, + { url = "https://files.pythonhosted.org/packages/64/33/c8548560b94b7617f203d7236d6cdf36fe1a5a3645600ada6efd79da946f/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4", size = 3019789, upload-time = "2024-10-16T11:19:18.889Z" }, + { url = "https://files.pythonhosted.org/packages/b0/0e/c2da0db5bea88a3be52307f88b75eec72c4de62814cbe9ee600c29c06334/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1", size = 2871776, upload-time = "2024-10-16T11:19:23.023Z" }, + { url = "https://files.pythonhosted.org/packages/15/d7/774afa1eadb787ddf41aab52d4c62785563e29949613c958955031408ae6/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5", size = 2820959, upload-time = "2024-10-16T11:19:26.906Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ed/440dc3f5991a8c6172a1cde44850ead0e483a375277a1aef7cfcec00af07/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5", size = 2919329, upload-time = "2024-10-16T11:19:30.027Z" }, + { url = "https://files.pythonhosted.org/packages/03/be/2cc8f4282898306732d2ae7b7378ae14e8df3c1231b53579efa056aae887/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53", size = 2957659, upload-time = "2024-10-16T11:19:32.864Z" }, + { url = "https://files.pythonhosted.org/packages/d0/12/fb8e4f485d98c570e00dad5800e9a2349cfe0f71a767c856857160d343a5/psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b", size = 1024605, upload-time = "2024-10-16T11:19:35.462Z" }, + { url = "https://files.pythonhosted.org/packages/22/4f/217cd2471ecf45d82905dd09085e049af8de6cfdc008b6663c3226dc1c98/psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1", size = 1163817, upload-time = "2024-10-16T11:19:37.384Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8f/9feb01291d0d7a0a4c6a6bab24094135c2b59c6a81943752f632c75896d6/psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", size = 3043397, upload-time = "2024-10-16T11:19:40.033Z" }, + { url = "https://files.pythonhosted.org/packages/15/30/346e4683532011561cd9c8dfeac6a8153dd96452fee0b12666058ab7893c/psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", size = 3274806, upload-time = "2024-10-16T11:19:43.5Z" }, + { url = "https://files.pythonhosted.org/packages/66/6e/4efebe76f76aee7ec99166b6c023ff8abdc4e183f7b70913d7c047701b79/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", size = 2851370, upload-time = "2024-10-16T11:19:46.986Z" }, + { url = "https://files.pythonhosted.org/packages/7f/fd/ff83313f86b50f7ca089b161b8e0a22bb3c319974096093cd50680433fdb/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", size = 3080780, upload-time = "2024-10-16T11:19:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e6/c4/bfadd202dcda8333a7ccafdc51c541dbdfce7c2c7cda89fa2374455d795f/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", size = 3264583, upload-time = "2024-10-16T11:19:54.424Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f1/09f45ac25e704ac954862581f9f9ae21303cc5ded3d0b775532b407f0e90/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", size = 3019831, upload-time = "2024-10-16T11:19:57.762Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2e/9beaea078095cc558f215e38f647c7114987d9febfc25cb2beed7c3582a5/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", size = 2871822, upload-time = "2024-10-16T11:20:04.693Z" }, + { url = "https://files.pythonhosted.org/packages/01/9e/ef93c5d93f3dc9fc92786ffab39e323b9aed066ba59fdc34cf85e2722271/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", size = 2820975, upload-time = "2024-10-16T11:20:11.401Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f0/049e9631e3268fe4c5a387f6fc27e267ebe199acf1bc1bc9cbde4bd6916c/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", size = 2919320, upload-time = "2024-10-16T11:20:17.959Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9a/bcb8773b88e45fb5a5ea8339e2104d82c863a3b8558fbb2aadfe66df86b3/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", size = 2957617, upload-time = "2024-10-16T11:20:24.711Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6b/144336a9bf08a67d217b3af3246abb1d027095dab726f0687f01f43e8c03/psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", size = 1024618, upload-time = "2024-10-16T11:20:27.718Z" }, + { url = "https://files.pythonhosted.org/packages/61/69/3b3d7bd583c6d3cbe5100802efa5beacaacc86e37b653fc708bf3d6853b8/psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", size = 1163816, upload-time = "2024-10-16T11:20:30.777Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/465cc9795cf76f6d329efdafca74693714556ea3891813701ac1fee87545/psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", size = 3044771, upload-time = "2024-10-16T11:20:35.234Z" }, + { url = "https://files.pythonhosted.org/packages/8b/31/6d225b7b641a1a2148e3ed65e1aa74fc86ba3fee850545e27be9e1de893d/psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", size = 3275336, upload-time = "2024-10-16T11:20:38.742Z" }, + { url = "https://files.pythonhosted.org/packages/30/b7/a68c2b4bff1cbb1728e3ec864b2d92327c77ad52edcd27922535a8366f68/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", size = 2851637, upload-time = "2024-10-16T11:20:42.145Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b1/cfedc0e0e6f9ad61f8657fd173b2f831ce261c02a08c0b09c652b127d813/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", size = 3082097, upload-time = "2024-10-16T11:20:46.185Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/0a8e4153c9b769f59c02fb5e7914f20f0b2483a19dae7bf2db54b743d0d0/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", size = 3264776, upload-time = "2024-10-16T11:20:50.879Z" }, + { url = "https://files.pythonhosted.org/packages/10/db/d09da68c6a0cdab41566b74e0a6068a425f077169bed0946559b7348ebe9/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", size = 3020968, upload-time = "2024-10-16T11:20:56.819Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/4d6f8c255f0dfffb410db2b3f9ac5218d959a66c715c34cac31081e19b95/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", size = 2872334, upload-time = "2024-10-16T11:21:02.411Z" }, + { url = "https://files.pythonhosted.org/packages/05/f7/20d7bf796593c4fea95e12119d6cc384ff1f6141a24fbb7df5a668d29d29/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", size = 2822722, upload-time = "2024-10-16T11:21:09.01Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e4/0c407ae919ef626dbdb32835a03b6737013c3cc7240169843965cada2bdf/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", size = 2920132, upload-time = "2024-10-16T11:21:16.339Z" }, + { url = "https://files.pythonhosted.org/packages/2d/70/aa69c9f69cf09a01da224909ff6ce8b68faeef476f00f7ec377e8f03be70/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", size = 2959312, upload-time = "2024-10-16T11:21:25.584Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/213e59854fafe87ba47814bf413ace0dcee33a89c8c8c814faca6bc7cf3c/psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", size = 1025191, upload-time = "2024-10-16T11:21:29.912Z" }, + { url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", size = 1164031, upload-time = "2024-10-16T11:21:34.211Z" }, + { url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", size = 3044699, upload-time = "2024-10-16T11:21:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", size = 3275245, upload-time = "2024-10-16T11:21:51.989Z" }, + { url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", size = 2851631, upload-time = "2024-10-16T11:21:57.584Z" }, + { url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", size = 3082140, upload-time = "2024-10-16T11:22:02.005Z" }, + { url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", size = 3264762, upload-time = "2024-10-16T11:22:06.412Z" }, + { url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", size = 3020967, upload-time = "2024-10-16T11:22:11.583Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", size = 2872326, upload-time = "2024-10-16T11:22:16.406Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712, upload-time = "2024-10-16T11:22:21.366Z" }, + { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155, upload-time = "2024-10-16T11:22:25.684Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356, upload-time = "2024-10-16T11:22:30.562Z" }, + { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224, upload-time = "2025-01-04T20:09:19.234Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bc/e77648009b6e61af327c607543f65fdf25bcfb4100f5a6f3bdb62ddac03c/psycopg2_binary-2.9.10-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b", size = 3043437, upload-time = "2024-10-16T11:23:42.946Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e8/5a12211a1f5b959f3e3ccd342eace60c1f26422f53e06d687821dc268780/psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc", size = 2851340, upload-time = "2024-10-16T11:23:50.038Z" }, + { url = "https://files.pythonhosted.org/packages/47/ed/5932b0458a7fc61237b653df050513c8d18a6f4083cc7f90dcef967f7bce/psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697", size = 3080905, upload-time = "2024-10-16T11:23:57.932Z" }, + { url = "https://files.pythonhosted.org/packages/71/df/8047d85c3d23864aca4613c3be1ea0fe61dbe4e050a89ac189f9dce4403e/psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481", size = 3264640, upload-time = "2024-10-16T11:24:06.122Z" }, + { url = "https://files.pythonhosted.org/packages/f3/de/6157e4ef242920e8f2749f7708d5cc8815414bdd4a27a91996e7cd5c80df/psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648", size = 3019812, upload-time = "2024-10-16T11:24:17.025Z" }, + { url = "https://files.pythonhosted.org/packages/25/f9/0fc49efd2d4d6db3a8d0a3f5749b33a0d3fdd872cad49fbf5bfce1c50027/psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d", size = 2871933, upload-time = "2024-10-16T11:24:24.858Z" }, + { url = "https://files.pythonhosted.org/packages/57/bc/2ed1bd182219065692ed458d218d311b0b220b20662d25d913bc4e8d3549/psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30", size = 2820990, upload-time = "2024-10-16T11:24:29.571Z" }, + { url = "https://files.pythonhosted.org/packages/71/2a/43f77a9b8ee0b10e2de784d97ddc099d9fe0d9eec462a006e4d2cc74756d/psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c", size = 2919352, upload-time = "2024-10-16T11:24:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/57/86/d2943df70469e6afab3b5b8e1367fccc61891f46de436b24ddee6f2c8404/psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287", size = 2957614, upload-time = "2024-10-16T11:24:44.423Z" }, + { url = "https://files.pythonhosted.org/packages/85/21/195d69371330983aa16139e60ba855d0a18164c9295f3a3696be41bbcd54/psycopg2_binary-2.9.10-cp39-cp39-win32.whl", hash = "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8", size = 1025341, upload-time = "2024-10-16T11:24:48.056Z" }, + { url = "https://files.pythonhosted.org/packages/ad/53/73196ebc19d6fbfc22427b982fbc98698b7b9c361e5e7707e3a3247cf06d/psycopg2_binary-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5", size = 1163958, upload-time = "2024-10-16T11:24:51.882Z" }, +] + [[package]] name = "ptyprocess" version = "0.7.0" @@ -998,7 +1094,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.4.1" +version = "8.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1009,9 +1105,9 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] [[package]] @@ -1042,55 +1138,75 @@ wheels = [ [[package]] name = "pyyaml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, - { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, - { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, - { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, - { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, - { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, - { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, - { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, - { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload-time = "2024-08-06T20:33:25.896Z" }, - { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload-time = "2024-08-06T20:33:27.212Z" }, - { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload-time = "2024-08-06T20:33:28.974Z" }, - { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload-time = "2024-08-06T20:33:34.157Z" }, - { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload-time = "2024-08-06T20:33:35.84Z" }, - { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload-time = "2024-08-06T20:33:37.501Z" }, - { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload-time = "2024-08-06T20:33:39.389Z" }, - { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload-time = "2024-08-06T20:33:46.63Z" }, - { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" }, +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" }, + { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" }, + { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" }, + { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" }, + { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" }, + { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" }, ] [[package]] @@ -1122,28 +1238,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.12.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/de/55/16ab6a7d88d93001e1ae4c34cbdcfb376652d761799459ff27c1dc20f6fa/ruff-0.12.11.tar.gz", hash = "sha256:c6b09ae8426a65bbee5425b9d0b82796dbb07cb1af045743c79bfb163001165d", size = 5347103, upload-time = "2025-08-28T13:59:08.87Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/a2/3b3573e474de39a7a475f3fbaf36a25600bfeb238e1a90392799163b64a0/ruff-0.12.11-py3-none-linux_armv6l.whl", hash = "sha256:93fce71e1cac3a8bf9200e63a38ac5c078f3b6baebffb74ba5274fb2ab276065", size = 11979885, upload-time = "2025-08-28T13:58:26.654Z" }, - { url = "https://files.pythonhosted.org/packages/76/e4/235ad6d1785a2012d3ded2350fd9bc5c5af8c6f56820e696b0118dfe7d24/ruff-0.12.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8e33ac7b28c772440afa80cebb972ffd823621ded90404f29e5ab6d1e2d4b93", size = 12742364, upload-time = "2025-08-28T13:58:30.256Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0d/15b72c5fe6b1e402a543aa9d8960e0a7e19dfb079f5b0b424db48b7febab/ruff-0.12.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d69fb9d4937aa19adb2e9f058bc4fbfe986c2040acb1a4a9747734834eaa0bfd", size = 11920111, upload-time = "2025-08-28T13:58:33.677Z" }, - { url = "https://files.pythonhosted.org/packages/3e/c0/f66339d7893798ad3e17fa5a1e587d6fd9806f7c1c062b63f8b09dda6702/ruff-0.12.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:411954eca8464595077a93e580e2918d0a01a19317af0a72132283e28ae21bee", size = 12160060, upload-time = "2025-08-28T13:58:35.74Z" }, - { url = "https://files.pythonhosted.org/packages/03/69/9870368326db26f20c946205fb2d0008988aea552dbaec35fbacbb46efaa/ruff-0.12.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a2c0a2e1a450f387bf2c6237c727dd22191ae8c00e448e0672d624b2bbd7fb0", size = 11799848, upload-time = "2025-08-28T13:58:38.051Z" }, - { url = "https://files.pythonhosted.org/packages/25/8c/dd2c7f990e9b3a8a55eee09d4e675027d31727ce33cdb29eab32d025bdc9/ruff-0.12.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ca4c3a7f937725fd2413c0e884b5248a19369ab9bdd850b5781348ba283f644", size = 13536288, upload-time = "2025-08-28T13:58:40.046Z" }, - { url = "https://files.pythonhosted.org/packages/7a/30/d5496fa09aba59b5e01ea76775a4c8897b13055884f56f1c35a4194c2297/ruff-0.12.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4d1df0098124006f6a66ecf3581a7f7e754c4df7644b2e6704cd7ca80ff95211", size = 14490633, upload-time = "2025-08-28T13:58:42.285Z" }, - { url = "https://files.pythonhosted.org/packages/9b/2f/81f998180ad53445d403c386549d6946d0748e536d58fce5b5e173511183/ruff-0.12.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a8dd5f230efc99a24ace3b77e3555d3fbc0343aeed3fc84c8d89e75ab2ff793", size = 13888430, upload-time = "2025-08-28T13:58:44.641Z" }, - { url = "https://files.pythonhosted.org/packages/87/71/23a0d1d5892a377478c61dbbcffe82a3476b050f38b5162171942a029ef3/ruff-0.12.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dc75533039d0ed04cd33fb8ca9ac9620b99672fe7ff1533b6402206901c34ee", size = 12913133, upload-time = "2025-08-28T13:58:47.039Z" }, - { url = "https://files.pythonhosted.org/packages/80/22/3c6cef96627f89b344c933781ed38329bfb87737aa438f15da95907cbfd5/ruff-0.12.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fc58f9266d62c6eccc75261a665f26b4ef64840887fc6cbc552ce5b29f96cc8", size = 13169082, upload-time = "2025-08-28T13:58:49.157Z" }, - { url = "https://files.pythonhosted.org/packages/05/b5/68b3ff96160d8b49e8dd10785ff3186be18fd650d356036a3770386e6c7f/ruff-0.12.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5a0113bd6eafd545146440225fe60b4e9489f59eb5f5f107acd715ba5f0b3d2f", size = 13139490, upload-time = "2025-08-28T13:58:51.593Z" }, - { url = "https://files.pythonhosted.org/packages/59/b9/050a3278ecd558f74f7ee016fbdf10591d50119df8d5f5da45a22c6afafc/ruff-0.12.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0d737b4059d66295c3ea5720e6efc152623bb83fde5444209b69cd33a53e2000", size = 11958928, upload-time = "2025-08-28T13:58:53.943Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bc/93be37347db854806904a43b0493af8d6873472dfb4b4b8cbb27786eb651/ruff-0.12.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:916fc5defee32dbc1fc1650b576a8fed68f5e8256e2180d4d9855aea43d6aab2", size = 11764513, upload-time = "2025-08-28T13:58:55.976Z" }, - { url = "https://files.pythonhosted.org/packages/7a/a1/1471751e2015a81fd8e166cd311456c11df74c7e8769d4aabfbc7584c7ac/ruff-0.12.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c984f07d7adb42d3ded5be894fb4007f30f82c87559438b4879fe7aa08c62b39", size = 12745154, upload-time = "2025-08-28T13:58:58.16Z" }, - { url = "https://files.pythonhosted.org/packages/68/ab/2542b14890d0f4872dd81b7b2a6aed3ac1786fae1ce9b17e11e6df9e31e3/ruff-0.12.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e07fbb89f2e9249f219d88331c833860489b49cdf4b032b8e4432e9b13e8a4b9", size = 13227653, upload-time = "2025-08-28T13:59:00.276Z" }, - { url = "https://files.pythonhosted.org/packages/22/16/2fbfc61047dbfd009c58a28369a693a1484ad15441723be1cd7fe69bb679/ruff-0.12.11-py3-none-win32.whl", hash = "sha256:c792e8f597c9c756e9bcd4d87cf407a00b60af77078c96f7b6366ea2ce9ba9d3", size = 11944270, upload-time = "2025-08-28T13:59:02.347Z" }, - { url = "https://files.pythonhosted.org/packages/08/a5/34276984705bfe069cd383101c45077ee029c3fe3b28225bf67aa35f0647/ruff-0.12.11-py3-none-win_amd64.whl", hash = "sha256:a3283325960307915b6deb3576b96919ee89432ebd9c48771ca12ee8afe4a0fd", size = 13046600, upload-time = "2025-08-28T13:59:04.751Z" }, - { url = "https://files.pythonhosted.org/packages/84/a8/001d4a7c2b37623a3fd7463208267fb906df40ff31db496157549cfd6e72/ruff-0.12.11-py3-none-win_arm64.whl", hash = "sha256:bae4d6e6a2676f8fb0f98b74594a048bae1b944aab17e9f5d504062303c6dbea", size = 12135290, upload-time = "2025-08-28T13:59:06.933Z" }, +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/df/8d7d8c515d33adfc540e2edf6c6021ea1c5a58a678d8cfce9fae59aabcab/ruff-0.13.2.tar.gz", hash = "sha256:cb12fffd32fb16d32cef4ed16d8c7cdc27ed7c944eaa98d99d01ab7ab0b710ff", size = 5416417, upload-time = "2025-09-25T14:54:09.936Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/84/5716a7fa4758e41bf70e603e13637c42cfb9dbf7ceb07180211b9bbf75ef/ruff-0.13.2-py3-none-linux_armv6l.whl", hash = "sha256:3796345842b55f033a78285e4f1641078f902020d8450cade03aad01bffd81c3", size = 12343254, upload-time = "2025-09-25T14:53:27.784Z" }, + { url = "https://files.pythonhosted.org/packages/9b/77/c7042582401bb9ac8eff25360e9335e901d7a1c0749a2b28ba4ecb239991/ruff-0.13.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ff7e4dda12e683e9709ac89e2dd436abf31a4d8a8fc3d89656231ed808e231d2", size = 13040891, upload-time = "2025-09-25T14:53:31.38Z" }, + { url = "https://files.pythonhosted.org/packages/c6/15/125a7f76eb295cb34d19c6778e3a82ace33730ad4e6f28d3427e134a02e0/ruff-0.13.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c75e9d2a2fafd1fdd895d0e7e24b44355984affdde1c412a6f6d3f6e16b22d46", size = 12243588, upload-time = "2025-09-25T14:53:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/9e/eb/0093ae04a70f81f8be7fd7ed6456e926b65d238fc122311293d033fdf91e/ruff-0.13.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cceac74e7bbc53ed7d15d1042ffe7b6577bf294611ad90393bf9b2a0f0ec7cb6", size = 12491359, upload-time = "2025-09-25T14:53:35.892Z" }, + { url = "https://files.pythonhosted.org/packages/43/fe/72b525948a6956f07dad4a6f122336b6a05f2e3fd27471cea612349fedb9/ruff-0.13.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ae3f469b5465ba6d9721383ae9d49310c19b452a161b57507764d7ef15f4b07", size = 12162486, upload-time = "2025-09-25T14:53:38.171Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e3/0fac422bbbfb2ea838023e0d9fcf1f30183d83ab2482800e2cb892d02dfe/ruff-0.13.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f8f9e3cd6714358238cd6626b9d43026ed19c0c018376ac1ef3c3a04ffb42d8", size = 13871203, upload-time = "2025-09-25T14:53:41.943Z" }, + { url = "https://files.pythonhosted.org/packages/6b/82/b721c8e3ec5df6d83ba0e45dcf00892c4f98b325256c42c38ef136496cbf/ruff-0.13.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c6ed79584a8f6cbe2e5d7dbacf7cc1ee29cbdb5df1172e77fbdadc8bb85a1f89", size = 14929635, upload-time = "2025-09-25T14:53:43.953Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a0/ad56faf6daa507b83079a1ad7a11694b87d61e6bf01c66bd82b466f21821/ruff-0.13.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aed130b2fde049cea2019f55deb939103123cdd191105f97a0599a3e753d61b0", size = 14338783, upload-time = "2025-09-25T14:53:46.205Z" }, + { url = "https://files.pythonhosted.org/packages/47/77/ad1d9156db8f99cd01ee7e29d74b34050e8075a8438e589121fcd25c4b08/ruff-0.13.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1887c230c2c9d65ed1b4e4cfe4d255577ea28b718ae226c348ae68df958191aa", size = 13355322, upload-time = "2025-09-25T14:53:48.164Z" }, + { url = "https://files.pythonhosted.org/packages/64/8b/e87cfca2be6f8b9f41f0bb12dc48c6455e2d66df46fe61bb441a226f1089/ruff-0.13.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bcb10276b69b3cfea3a102ca119ffe5c6ba3901e20e60cf9efb53fa417633c3", size = 13354427, upload-time = "2025-09-25T14:53:50.486Z" }, + { url = "https://files.pythonhosted.org/packages/7f/df/bf382f3fbead082a575edb860897287f42b1b3c694bafa16bc9904c11ed3/ruff-0.13.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:afa721017aa55a555b2ff7944816587f1cb813c2c0a882d158f59b832da1660d", size = 13537637, upload-time = "2025-09-25T14:53:52.887Z" }, + { url = "https://files.pythonhosted.org/packages/51/70/1fb7a7c8a6fc8bd15636288a46e209e81913b87988f26e1913d0851e54f4/ruff-0.13.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1dbc875cf3720c64b3990fef8939334e74cb0ca65b8dbc61d1f439201a38101b", size = 12340025, upload-time = "2025-09-25T14:53:54.88Z" }, + { url = "https://files.pythonhosted.org/packages/4c/27/1e5b3f1c23ca5dd4106d9d580e5c13d9acb70288bff614b3d7b638378cc9/ruff-0.13.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939a1b2a960e9742e9a347e5bbc9b3c3d2c716f86c6ae273d9cbd64f193f22", size = 12133449, upload-time = "2025-09-25T14:53:57.089Z" }, + { url = "https://files.pythonhosted.org/packages/2d/09/b92a5ccee289f11ab128df57d5911224197d8d55ef3bd2043534ff72ca54/ruff-0.13.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:50e2d52acb8de3804fc5f6e2fa3ae9bdc6812410a9e46837e673ad1f90a18736", size = 13051369, upload-time = "2025-09-25T14:53:59.124Z" }, + { url = "https://files.pythonhosted.org/packages/89/99/26c9d1c7d8150f45e346dc045cc49f23e961efceb4a70c47dea0960dea9a/ruff-0.13.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3196bc13ab2110c176b9a4ae5ff7ab676faaa1964b330a1383ba20e1e19645f2", size = 13523644, upload-time = "2025-09-25T14:54:01.622Z" }, + { url = "https://files.pythonhosted.org/packages/f7/00/e7f1501e81e8ec290e79527827af1d88f541d8d26151751b46108978dade/ruff-0.13.2-py3-none-win32.whl", hash = "sha256:7c2a0b7c1e87795fec3404a485096bcd790216c7c146a922d121d8b9c8f1aaac", size = 12245990, upload-time = "2025-09-25T14:54:03.647Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bd/d9f33a73de84fafd0146c6fba4f497c4565fe8fa8b46874b8e438869abc2/ruff-0.13.2-py3-none-win_amd64.whl", hash = "sha256:17d95fb32218357c89355f6f6f9a804133e404fc1f65694372e02a557edf8585", size = 13324004, upload-time = "2025-09-25T14:54:06.05Z" }, + { url = "https://files.pythonhosted.org/packages/c3/12/28fa2f597a605884deb0f65c1b1ae05111051b2a7030f5d8a4ff7f4599ba/ruff-0.13.2-py3-none-win_arm64.whl", hash = "sha256:da711b14c530412c827219312b7d7fbb4877fb31150083add7e8c5336549cea7", size = 12484437, upload-time = "2025-09-25T14:54:08.022Z" }, ] [[package]] @@ -1226,6 +1342,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, ] +[[package]] +name = "types-psycopg2" +version = "2.9.21.20250915" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/20/3dcb89df8d1661cf6c4c2d9f84d4ba94dde48559cdcf7b536a380a9c387f/types_psycopg2-2.9.21.20250915.tar.gz", hash = "sha256:bfeb8f54c32490e7b5edc46215ab4163693192bc90407b4a023822de9239f5c8", size = 26678, upload-time = "2025-09-15T03:01:08.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/4d/ebf1c72809a30150ad142074e1ad5101304f7569c0df2fa872906d76d0af/types_psycopg2-2.9.21.20250915-py3-none-any.whl", hash = "sha256:eefe5ccdc693fc086146e84c9ba437bb278efe1ef330b299a0cb71169dc6c55f", size = 24868, upload-time = "2025-09-15T03:01:07.613Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -1283,11 +1408,11 @@ wheels = [ [[package]] name = "wcwidth" -version = "0.2.13" +version = "0.2.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, ] [[package]] From 95c7274934960d1594d0b07f95efe1f40059f9c0 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Wed, 1 Oct 2025 16:01:11 +0200 Subject: [PATCH 17/58] cocalc-api: jupyter tests, add endpoints to list/stop kernels --- src/packages/conat/project/api/system.ts | 9 + src/packages/jupyter/kernel/launch-kernel.ts | 47 ++++ src/packages/next/pages/api/conat/project.ts | 2 +- src/packages/project/conat/api/system.ts | 16 +- .../server/projects/control/single-user.ts | 23 +- src/python/cocalc-api/docs/index.md | 14 +- src/python/cocalc-api/src/cocalc_api/hub.py | 15 +- .../cocalc-api/src/cocalc_api/project.py | 74 +++++- src/python/cocalc-api/tests/README.md | 128 +++++++-- src/python/cocalc-api/tests/conftest.py | 1 - src/python/cocalc-api/tests/test_jupyter.py | 251 ++++++++++++++++++ src/python/cocalc-api/uv.lock | 2 +- 12 files changed, 534 insertions(+), 48 deletions(-) create mode 100644 src/python/cocalc-api/tests/test_jupyter.py diff --git a/src/packages/conat/project/api/system.ts b/src/packages/conat/project/api/system.ts index 3e780381c86..ae086eb2508 100644 --- a/src/packages/conat/project/api/system.ts +++ b/src/packages/conat/project/api/system.ts @@ -34,6 +34,10 @@ export const system = { // jupyter stateless API jupyterExecute: true, + + // jupyter kernel management + listJupyterKernels: true, + stopJupyterKernel: true, }; export interface System { @@ -74,4 +78,9 @@ export interface System { }) => Promise; jupyterExecute: (opts: ProjectJupyterApiOptions) => Promise; + + listJupyterKernels: () => Promise< + { pid: number; connectionFile: string; kernel_name?: string }[] + >; + stopJupyterKernel: (opts: { pid: number }) => Promise<{ success: boolean }>; } diff --git a/src/packages/jupyter/kernel/launch-kernel.ts b/src/packages/jupyter/kernel/launch-kernel.ts index e4e179610d9..21b39e599b5 100644 --- a/src/packages/jupyter/kernel/launch-kernel.ts +++ b/src/packages/jupyter/kernel/launch-kernel.ts @@ -168,6 +168,11 @@ async function launchKernelSpec( } else { running_kernel = spawn(argv[0], argv.slice(1), full_spawn_options); } + + // Store kernel info for tracking + running_kernel.connectionFile = connectionFile; + running_kernel.kernel_spec = kernel_spec; + spawned.push(running_kernel); running_kernel.on("error", (code, signal) => { @@ -221,6 +226,48 @@ async function ensureDirectoryExists(path: string) { // Clean up after any children created here const spawned: any[] = []; + +export interface RunningKernel { + pid: number; + connectionFile: string; + kernel_name?: string; +} + +export function listRunningKernels(): RunningKernel[] { + return spawned + .filter((child) => child.pid) + .map((child) => ({ + pid: child.pid, + connectionFile: child.connectionFile || "unknown", + kernel_name: child.kernel_spec?.name, + })); +} + +export function stopKernel(pid: number): boolean { + const index = spawned.findIndex((child) => child.pid === pid); + if (index === -1) { + return false; + } + + const child = spawned[index]; + try { + // Try to kill the process group first (negative PID) + process.kill(-child.pid, "SIGKILL"); + } catch (err) { + // If that fails, try killing the process directly + try { + child.kill("SIGKILL"); + } catch (err2) { + logger.debug(`stopKernel: failed to kill ${child.pid}: ${err2}`); + return false; + } + } + + // Remove from spawned array + spawned.splice(index, 1); + return true; +} + export function closeAll() { for (const child of spawned) { if (child.pid) { diff --git a/src/packages/next/pages/api/conat/project.ts b/src/packages/next/pages/api/conat/project.ts index 64445ec5a52..8d6dd8fc124 100644 --- a/src/packages/next/pages/api/conat/project.ts +++ b/src/packages/next/pages/api/conat/project.ts @@ -40,7 +40,7 @@ export default async function handle(req, res) { throw Error("must specify project_id or use project-specific api key"); } if (project_id0) { - // auth via project_id + // auth via project-specific API key if (project_id0 != project_id) { throw Error("project specific api key must match requested project"); } diff --git a/src/packages/project/conat/api/system.ts b/src/packages/project/conat/api/system.ts index 60bedcc537e..274d85172a2 100644 --- a/src/packages/project/conat/api/system.ts +++ b/src/packages/project/conat/api/system.ts @@ -50,7 +50,7 @@ export async function renameFile({ src, dest }: { src: string; dest: string }) { import { get_configuration } from "@cocalc/project/configuration"; export { get_configuration as configuration }; -import { canonical_paths } from "../../browser-websocket/canonical-path"; +import { canonical_paths } from "@cocalc/project/browser-websocket/canonical-path"; export { canonical_paths as canonicalPaths }; import ensureContainingDirectoryExists from "@cocalc/backend/misc/ensure-containing-directory-exists"; @@ -107,3 +107,17 @@ export async function signal({ import jupyterExecute from "@cocalc/jupyter/stateless-api/execute"; export { jupyterExecute }; + +import { + listRunningKernels, + stopKernel, +} from "@cocalc/jupyter/kernel/launch-kernel"; + +export async function listJupyterKernels() { + return listRunningKernels(); +} + +export async function stopJupyterKernel({ pid }: { pid: number }) { + const success = stopKernel(pid); + return { success }; +} diff --git a/src/packages/server/projects/control/single-user.ts b/src/packages/server/projects/control/single-user.ts index 64103e9e909..f61f444d9e9 100644 --- a/src/packages/server/projects/control/single-user.ts +++ b/src/packages/server/projects/control/single-user.ts @@ -153,20 +153,33 @@ class Project extends BaseProject { this.stateChanging = { state: "stopping" }; await this.saveStateToDatabase(this.stateChanging); const pid = await getProjectPID(this.HOME); - const killProject = () => { + + // First attempt: graceful shutdown with SIGTERM + // This allows the process to clean up child processes (e.g., Jupyter kernels) + let usedSigterm = false; + const killProject = (signal: NodeJS.Signals = "SIGTERM") => { try { - logger.debug(`stop: sending kill -${pid}`); - kill(-pid, "SIGKILL"); + logger.debug(`stop: sending kill -${pid} with ${signal}`); + kill(-pid, signal); } catch (err) { // expected exception if no pid logger.debug(`stop: kill err ${err}`); } }; - killProject(); + + // Try SIGTERM first for graceful shutdown + killProject("SIGTERM"); + usedSigterm = true; + await this.wait({ until: async () => { if (await isProjectRunning(this.HOME)) { - killProject(); + // After 5 seconds, escalate to SIGKILL + if (usedSigterm) { + logger.debug("stop: escalating to SIGKILL"); + killProject("SIGKILL"); + usedSigterm = false; + } return false; } else { return true; diff --git a/src/python/cocalc-api/docs/index.md b/src/python/cocalc-api/docs/index.md index 2d5b8680ce1..058372026a8 100644 --- a/src/python/cocalc-api/docs/index.md +++ b/src/python/cocalc-api/docs/index.md @@ -10,13 +10,13 @@ Obtain a [CoCalc account API Key](https://doc.cocalc.com/apikeys.html) by going Using an account level API key, the cocalc_api Python library enabled you to do all of the following very easily from a Python script: -- [search](api/system/) for other cocalc users by name or email address, and get the name associated to an account_id -- list [your projects](api/projects), add and remove collaborators, copy files between projects and start, stop and create projects. -- use the [Jupyter API](api/jupyter) to evaluate code using a kernel, either in an anonymous sandbox or in one of your projects. -- read or write any data you have access to in the CoCalc [PostgreSQL database](api/database), as defined by [this schema](https://github.com/sagemathinc/cocalc/tree/master/src/packages/util/db-schema). -- instantly send and receive [messages](api/messages) with any other cocalc users -- create and manage users in an [organization](api/organizations), including automatically generating authentication links, so you're users do not have explicitly create a CoCalc account. You can see when they are active and send them messages. +- [search](api/system.md) for other cocalc users by name or email address, and get the name associated to an account_id +- list [your projects](api/projects.md), add and remove collaborators, copy files between projects and create, start, stop and delete projects. +- use the [Jupyter API](api/jupyter.md) to list available kernels and evaluate code using a kernel, either in an anonymous sandbox or in one of your projects. +- read or write any data you have access to in the CoCalc [PostgreSQL database](api/database.md), as defined by [this schema](https://github.com/sagemathinc/cocalc/tree/master/src/packages/util/db-schema). +- instantly send and receive [messages](api/messages.md) with any other cocalc users +- create and manage users in an [organization](api/organizations.md), including automatically generating authentication links, so you're users do not have explicitly create a CoCalc account. You can see when they are active and send them messages. Currently a project specific API key can be used to: -- Run [shell commands](api/project) and Jupyter code in a specific project. +- Run [shell commands](api/project.md) and Jupyter code in a specific project, and manage running Jupyter kernels (list and stop). diff --git a/src/python/cocalc-api/src/cocalc_api/hub.py b/src/python/cocalc-api/src/cocalc_api/hub.py index 10394941ca9..9a9e5afa24d 100644 --- a/src/python/cocalc-api/src/cocalc_api/hub.py +++ b/src/python/cocalc-api/src/cocalc_api/hub.py @@ -289,7 +289,7 @@ def __init__(self, parent: "Hub"): self._parent = parent @api_method("jupyter.kernels") - def kernels(self, project_id: Optional[str] = None) -> dict[str, Any]: + def kernels(self, project_id: Optional[str] = None) -> list[dict[str, Any]]: """ Get specifications of available Jupyter kernels. @@ -298,7 +298,18 @@ def kernels(self, project_id: Optional[str] = None) -> dict[str, Any]: If not given, a global anonymous project may be used. Returns: - dict[str, Any]: JSON response containing kernel specs. + list[dict[str, Any]]: List of kernel specification objects. Each kernel object + contains information like 'name', 'display_name', 'language', etc. + + Examples: + Get available kernels for a project: + + >>> import cocalc_api; hub = cocalc_api.Hub(api_key="sk-...") + >>> kernels = hub.jupyter.kernels(project_id='6e75dbf1-0342-4249-9dce-6b21648656e9') + >>> # Extract kernel names + >>> kernel_names = [k['name'] for k in kernels] + >>> 'python3' in kernel_names + True """ ... diff --git a/src/python/cocalc-api/src/cocalc_api/project.py b/src/python/cocalc-api/src/cocalc_api/project.py index f9b109e4f8c..c3b8c0ae40b 100644 --- a/src/python/cocalc-api/src/cocalc_api/project.py +++ b/src/python/cocalc-api/src/cocalc_api/project.py @@ -116,26 +116,84 @@ def jupyter_execute( kernel: str, history: Optional[list[str]] = None, path: Optional[str] = None, - ) -> dict[str, Any]: # type: ignore[empty-body] + ) -> list[dict[str, Any]]: # type: ignore[empty-body] """ Execute code using a Jupyter kernel. Args: input (str): Code to execute. - kernel (str): Name of kernel to use. Get options using jupyter.kernels(). + kernel (str): Name of kernel to use. Get options using hub.jupyter.kernels(). history (Optional[list[str]]): Array of previous inputs (they get evaluated every time, but without output being captured). path (Optional[str]): File path context for execution. Returns: - dict[str, Any]: JSON response containing execution results. + list[dict[str, Any]]: List of output items. Each output item contains + execution results with 'data' field containing output by MIME type + (e.g., 'text/plain' for text output) or 'name'/'text' fields for + stream output (stdout/stderr). Examples: Execute a simple sum using a Jupyter kernel: - >>> import cocalc_api; project = cocalc_api.Project(api_key="sk-...") - >>> project.jupyter.execute(history=['a=100;print(a)'], - input='sum(range(a+1))', - kernel='python3') - {'output': [{'data': {'text/plain': '5050'}}], ...} + >>> import cocalc_api; project = cocalc_api.Project(api_key="sk-...", project_id='...') + >>> result = project.system.jupyter_execute(input='sum(range(100))', kernel='python3') + >>> result + [{'data': {'text/plain': '4950'}}] + + Execute with history context: + + >>> result = project.system.jupyter_execute( + ... history=['a = 100'], + ... input='sum(range(a + 1))', + ... kernel='python3') + >>> result + [{'data': {'text/plain': '5050'}}] + + Print statements produce stream output: + + >>> result = project.system.jupyter_execute(input='print("Hello")', kernel='python3') + >>> result + [{'name': 'stdout', 'text': 'Hello\\n'}] + """ + ... + + @api_method("system.listJupyterKernels") + def list_jupyter_kernels(self) -> list[dict[str, Any]]: # type: ignore[empty-body] + """ + List all running Jupyter kernels in the project. + + Returns: + list[dict[str, Any]]: List of running kernels. Each kernel has: + - pid (int): Process ID of the kernel + - connectionFile (str): Path to the kernel connection file + - kernel_name (str, optional): Name of the kernel (e.g., 'python3') + + Examples: + List all running kernels: + + >>> import cocalc_api; project = cocalc_api.Project(api_key="sk-...", project_id='...') + >>> kernels = project.system.list_jupyter_kernels() + >>> kernels + [{'pid': 12345, 'connectionFile': '/run/user/1000/jupyter/kernel-abc123.json', 'kernel_name': 'python3'}] + """ + ... + + @api_method("system.stopJupyterKernel") + def stop_jupyter_kernel(self, pid: int) -> dict[str, bool]: # type: ignore[empty-body] + """ + Stop a specific Jupyter kernel by process ID. + + Args: + pid (int): Process ID of the kernel to stop + + Returns: + dict[str, bool]: Dictionary with 'success' key indicating if the kernel was stopped + + Examples: + Stop a kernel by PID: + + >>> import cocalc_api; project = cocalc_api.Project(api_key="sk-...", project_id='...') + >>> project.system.stop_jupyter_kernel(pid=12345) + {'success': True} """ ... diff --git a/src/python/cocalc-api/tests/README.md b/src/python/cocalc-api/tests/README.md index 667b751589e..b367d153838 100644 --- a/src/python/cocalc-api/tests/README.md +++ b/src/python/cocalc-api/tests/README.md @@ -5,7 +5,8 @@ This directory contains pytest tests for the cocalc-api Python package. ## Prerequisites 1. **Required**: Set the `COCALC_API_KEY` environment variable with a valid CoCalc API key (tests will fail if not set) -2. Optionally set `COCALC_HOST` to specify the CoCalc server URL (defaults to `http://localhost:5000`) +2. **Recommended**: Set `PGHOST` for database cleanup (see [Automatic Cleanup](#automatic-cleanup) below) +3. Optionally set `COCALC_HOST` to specify the CoCalc server URL (defaults to `http://localhost:5000`) ## Running Tests @@ -19,37 +20,120 @@ make test-verbose # Or use pytest directly uv run pytest uv run pytest -v + +# Run specific test files +uv run pytest tests/test_hub.py -v +uv run pytest tests/test_jupyter.py -v ``` ## Test Structure -- `conftest.py` - Pytest configuration and fixtures (includes temporary project creation) -- `test_hub.py` - Tests for Hub client functionality including project creation -- `test_project.py` - Tests for Project client functionality using auto-created temporary projects +- `conftest.py` - Pytest configuration and fixtures (includes resource tracking and cleanup) +- `test_hub.py` - Tests for Hub client functionality (projects, database queries, messages) +- `test_project.py` - Tests for Project client functionality (ping, exec commands) +- `test_jupyter.py` - Tests for Jupyter kernel installation and code execution +- `test_org.py` - Tests for organization management (create, users, licenses) +- `test_org_basic.py` - Basic organization API tests -## Test Markers +## Environment Variables -- `@pytest.mark.integration` - Marks tests that require a live CoCalc server +### Required -## Environment Variables +- `COCALC_API_KEY` - Your CoCalc API key + +### Optional + +- `COCALC_HOST` - CoCalc server URL (default: `http://localhost:5000`) +- `COCALC_TESTS_CLEANUP` - Enable/disable automatic cleanup (default: `true`) + +### For Database Cleanup (Recommended) + +- `PGHOST` - PostgreSQL host (socket path or hostname) +- `PGUSER` - PostgreSQL user (default: `smc`) +- `PGDATABASE` - PostgreSQL database (default: `smc`) +- `PGPORT` - PostgreSQL port for network connections (default: `5432`) +- `PGPASSWORD` - PostgreSQL password (only needed for network connections) + +## Resource Tracking and Cleanup + +### Tracked Resource System + +The test suite uses a **resource tracking system** to automatically manage all created resources. When writing tests, use the provided helper functions to ensure proper cleanup: + +```python +def test_my_feature(hub, resource_tracker): + # Create tracked resources using helper functions + org_id = create_tracked_org(hub, resource_tracker, "test-org") + user_id = create_tracked_user(hub, resource_tracker, "test-org", email="test@example.com") + project_id = create_tracked_project(hub, resource_tracker, title="Test Project") + + # Run your tests... + + # No manual cleanup needed - happens automatically! +``` + +**Available Helper Functions:** + +- `create_tracked_project(hub, resource_tracker, **kwargs)` - Create and track a project +- `create_tracked_user(hub, resource_tracker, org_name, **kwargs)` - Create and track a user account +- `create_tracked_org(hub, resource_tracker, org_name)` - Create and track an organization + +All tracked resources are automatically cleaned up at the end of the test session. + +### Shared Fixtures -- `COCALC_API_KEY` (required) - Your CoCalc API key -- `COCALC_HOST` (optional) - CoCalc server URL (default: `http://localhost:5000`) +**Session-scoped fixtures** (created once, shared across all tests): + +- `temporary_project` - A single test project used by all tests in the session +- `project_client` - A Project client instance connected to the temporary project +- `hub` - A Hub client instance + +These fixtures ensure efficient resource usage by reusing the same project across all tests. + +### Automatic Cleanup + +At the end of each test session, the cleanup system automatically: + +1. **Stops** all tracked projects (via API to gracefully shut them down) +2. **Hard-deletes** all tracked resources from the PostgreSQL database in order: + - Projects (removed first) + - Accounts (including all owned projects like "My First Project") + - Organizations (removed last) + +#### Cleanup Configuration + +**Socket Connection (Local Development - Recommended):** + +```bash +export PGHOST=/path/to/cocalc-data/socket +export PGUSER=smc +# No password needed for Unix socket authentication +``` + +**Network Connection:** + +```bash +export PGHOST=localhost +export PGPORT=5432 +export PGUSER=smc +export PGPASSWORD=your_password +``` + +**Disable Cleanup (Not Recommended):** + +```bash +export COCALC_TESTS_CLEANUP=false +``` -## Automatic Project Lifecycle Management +When cleanup is disabled, test resources will remain in the database and must be manually removed. -The test suite automatically manages project lifecycle for testing via the `temporary_project` fixture: +#### Why Direct Database Cleanup? -### Project Creation -- Projects are created with unique names like `CoCalc API Test YYYYMMDD-HHMMSS` -- Projects include a description: "Temporary project created by cocalc-api tests" -- Projects are automatically started and tested for readiness before tests run +The test suite uses **direct PostgreSQL deletion** instead of API calls because: -### Project Cleanup (NEW) -After all tests complete, the test suite now automatically performs cleanup: -- **Stops** the test project to free up resources -- **Attempts deletion** if the delete API becomes available (currently not implemented) -- Provides clear feedback about cleanup actions and any failures -- If deletion is not available, projects remain stopped but can be manually deleted from the CoCalc interface +- API deletion only sets `deleted=true` (soft delete), leaving data in the database +- Tests create many resources (projects, accounts, orgs) that need complete removal +- Direct SQL ensures thorough cleanup including auto-created projects (e.g., "My First Project") +- Prevents database bloat from repeated test runs -This ensures test projects don't continue consuming server resources after tests complete. +The cleanup process is safe and only removes resources that were explicitly tracked during test execution. diff --git a/src/python/cocalc-api/tests/conftest.py b/src/python/cocalc-api/tests/conftest.py index bec968a51f0..92c8c1ab816 100644 --- a/src/python/cocalc-api/tests/conftest.py +++ b/src/python/cocalc-api/tests/conftest.py @@ -174,7 +174,6 @@ def check_cleanup_config(): # Cleanup is enabled - verify required configuration pghost = os.environ.get("PGHOST") - pgpassword = os.environ.get("PGPASSWORD") # PGHOST is mandatory if not pghost: diff --git a/src/python/cocalc-api/tests/test_jupyter.py b/src/python/cocalc-api/tests/test_jupyter.py new file mode 100644 index 00000000000..2148bcfc389 --- /dev/null +++ b/src/python/cocalc-api/tests/test_jupyter.py @@ -0,0 +1,251 @@ +""" +Tests for Jupyter kernel functionality. +""" +import pytest +import time +from typing import Optional + + +class TestJupyterKernelSetup: + """Tests for Jupyter kernel installation and availability.""" + + def test_install_ipykernel(self, project_client): + """Test installing ipykernel in the project.""" + # Install ipykernel package + result = project_client.system.exec( + command="python3", + args=["-m", "pip", "install", "ipykernel"], + timeout=120 # 2 minutes should be enough for pip install + ) + + # Check that installation succeeded + assert result['exit_code'] == 0 + assert 'stderr' in result + + def test_install_jupyter_kernel(self, project_client): + """Test installing the Python 3 Jupyter kernel.""" + # Install the kernel spec + result = project_client.system.exec( + command="python3", + args=[ + "-m", + "ipykernel", + "install", + "--user", # Install to user location, not system + "--name=python3", + "--display-name=Python 3" + ], + timeout=30) + + # Check that kernel installation succeeded + assert result['exit_code'] == 0 + + +class TestJupyterKernels: + """Tests for Jupyter kernel availability.""" + + def test_kernels_list_with_project(self, hub, temporary_project): + """Test getting kernel specs for a specific project.""" + project_id = temporary_project['project_id'] + kernels = hub.jupyter.kernels(project_id=project_id) + + # Should return a list of kernel specs + assert isinstance(kernels, list) + assert len(kernels) > 0 + + def test_python3_kernel_available(self, hub, temporary_project): + """Test that the python3 kernel is available after installation.""" + project_id = temporary_project['project_id'] + kernels = hub.jupyter.kernels(project_id=project_id) + + # Extract kernel names from the list + kernel_names = [k.get('name') for k in kernels if isinstance(k, dict)] + assert 'python3' in kernel_names + + +class TestJupyterExecuteViaHub: + """Tests for executing code via hub.jupyter.execute().""" + + @pytest.mark.skip(reason="hub.jupyter.execute() has timeout issues - use project.system.jupyter_execute() instead") + def test_execute_simple_sum(self, hub, temporary_project): + """Test executing a simple sum using the python3 kernel.""" + project_id = temporary_project['project_id'] + + result = hub.jupyter.execute(input='sum(range(100))', kernel='python3', project_id=project_id) + + # Check the result structure + assert isinstance(result, dict) + assert 'output' in result + + # Check that we got the correct result (sum of 0..99 = 4950) + output = result['output'] + assert len(output) > 0 + + # Extract the result from the output + # Format: [{'data': {'text/plain': '4950'}}] + first_output = output[0] + assert 'data' in first_output + assert 'text/plain' in first_output['data'] + assert first_output['data']['text/plain'] == '4950' + + @pytest.mark.skip(reason="hub.jupyter.execute() has timeout issues - use project.system.jupyter_execute() instead") + def test_execute_with_history(self, hub, temporary_project): + """Test executing code with history context.""" + project_id = temporary_project['project_id'] + + result = hub.jupyter.execute(history=['a = 100'], input='sum(range(a + 1))', kernel='python3', project_id=project_id) + + # Check the result (sum of 0..100 = 5050) + assert isinstance(result, dict) + assert 'output' in result + + output = result['output'] + assert len(output) > 0 + + first_output = output[0] + assert 'data' in first_output + assert 'text/plain' in first_output['data'] + assert first_output['data']['text/plain'] == '5050' + + @pytest.mark.skip(reason="hub.jupyter.execute() has timeout issues - use project.system.jupyter_execute() instead") + def test_execute_print_statement(self, hub, temporary_project): + """Test executing code that prints output.""" + project_id = temporary_project['project_id'] + + result = hub.jupyter.execute(input='print("Hello from Jupyter")', kernel='python3', project_id=project_id) + + # Check that we got output + assert isinstance(result, dict) + assert 'output' in result + + output = result['output'] + assert len(output) > 0 + + # Print statements produce stream output + first_output = output[0] + assert 'name' in first_output + assert first_output['name'] == 'stdout' + assert 'text' in first_output + assert 'Hello from Jupyter' in first_output['text'] + + +class TestJupyterExecuteViaProject: + """Tests for executing code via project.system.jupyter_execute().""" + + def test_jupyter_execute_simple_sum(self, project_client): + """ + Test executing a simple sum via project API. + + The result is a list of output items directly (not wrapped in a dict). + + Note: First execution may take longer as kernel needs to start up (30+ seconds). + """ + # Retry logic for first kernel startup + max_retries = 3 + retry_delay = 15 + result: Optional[list] = None + + for attempt in range(max_retries): + try: + result = project_client.system.jupyter_execute(input='sum(range(100))', kernel='python3') + break + except RuntimeError as e: + if 'timeout' in str(e).lower() and attempt < max_retries - 1: + print(f"Attempt {attempt + 1} timed out, retrying in {retry_delay}s...") + time.sleep(retry_delay) + else: + raise + + # Result is a list, not a dict with 'output' key + assert isinstance(result, list) + assert len(result) > 0 + + # Check that we got the correct result (sum of 0..99 = 4950) + first_output = result[0] + assert 'data' in first_output + assert 'text/plain' in first_output['data'] + assert first_output['data']['text/plain'] == '4950' + + def test_jupyter_execute_with_history(self, project_client): + """ + Test executing code with history via project API. + + The result is a list of output items directly. + """ + result = project_client.system.jupyter_execute(history=['b = 50'], input='b * 2', kernel='python3') + + # Result is a list + assert isinstance(result, list) + assert len(result) > 0 + + # Check the result (50 * 2 = 100) + first_output = result[0] + assert 'data' in first_output + assert 'text/plain' in first_output['data'] + assert first_output['data']['text/plain'] == '100' + + def test_jupyter_execute_list_operation(self, project_client): + """ + Test executing code that works with lists. + + The result is a list of output items directly. + """ + result = project_client.system.jupyter_execute(input='[x**2 for x in range(5)]', kernel='python3') + + # Result is a list + assert isinstance(result, list) + assert len(result) > 0 + + # Check the result ([0, 1, 4, 9, 16]) + first_output = result[0] + assert 'data' in first_output + assert 'text/plain' in first_output['data'] + assert first_output['data']['text/plain'] == '[0, 1, 4, 9, 16]' + + +class TestJupyterKernelManagement: + """Tests for Jupyter kernel management (list and stop kernels).""" + + def test_list_jupyter_kernels(self, project_client): + """Test listing running Jupyter kernels.""" + # First execute some code to ensure a kernel is running + project_client.system.jupyter_execute(input='1+1', kernel='python3') + + # List kernels + kernels = project_client.system.list_jupyter_kernels() + + # Should return a list + assert isinstance(kernels, list) + + # Should have at least one kernel running (from previous tests) + assert len(kernels) > 0 + + # Each kernel should have required fields + for kernel in kernels: + assert 'pid' in kernel + assert 'connectionFile' in kernel + assert isinstance(kernel['pid'], int) + assert isinstance(kernel['connectionFile'], str) + + def test_stop_jupyter_kernel(self, project_client): + """Test stopping a specific Jupyter kernel.""" + # Execute code to start a kernel + project_client.system.jupyter_execute(input='1+1', kernel='python3') + + # List kernels + kernels = project_client.system.list_jupyter_kernels() + assert len(kernels) > 0 + + # Stop the first kernel + kernel_to_stop = kernels[0] + result = project_client.system.stop_jupyter_kernel(pid=kernel_to_stop['pid']) + + # Should return success + assert isinstance(result, dict) + assert 'success' in result + assert result['success'] is True + + # Verify kernel is no longer in the list + kernels_after = project_client.system.list_jupyter_kernels() + remaining_pids = [k['pid'] for k in kernels_after] + assert kernel_to_stop['pid'] not in remaining_pids diff --git a/src/python/cocalc-api/uv.lock b/src/python/cocalc-api/uv.lock index 109f4a3aef9..3643b57fd32 100644 --- a/src/python/cocalc-api/uv.lock +++ b/src/python/cocalc-api/uv.lock @@ -171,7 +171,7 @@ wheels = [ [[package]] name = "cocalc-api" -version = "0.4.0" +version = "0.5.0" source = { virtual = "." } dependencies = [ { name = "httpx" }, From fc0d26ac72ffdf707ff4af082b43375f617e085f Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Thu, 2 Oct 2025 18:21:38 +0200 Subject: [PATCH 18/58] cocalc-api/tests: jupyterExecute increase timeout + retry logic --- .../cocalc-api/src/cocalc_api/project.py | 6 +- src/python/cocalc-api/tests/test_jupyter.py | 141 +++++++++++------- 2 files changed, 88 insertions(+), 59 deletions(-) diff --git a/src/python/cocalc-api/src/cocalc_api/project.py b/src/python/cocalc-api/src/cocalc_api/project.py index c3b8c0ae40b..5f9779834fd 100644 --- a/src/python/cocalc-api/src/cocalc_api/project.py +++ b/src/python/cocalc-api/src/cocalc_api/project.py @@ -10,8 +10,8 @@ def __init__(self, api_key: str, host: str = "https://cocalc.com", project_id: O self.project_id = project_id self.api_key = api_key self.host = host - # Use longer timeout for API calls (30 seconds instead of default 5) - self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}, timeout=30.0) + # Use longer timeout for API calls (60 seconds to handle slow kernel startups in CI) + self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}, timeout=60.0) def call(self, name: str, arguments: list[Any], timeout: Optional[int] = None) -> Any: """ @@ -28,7 +28,7 @@ def call(self, name: str, arguments: list[Any], timeout: Optional[int] = None) - payload: dict[str, Any] = {"name": name, "args": arguments, "project_id": self.project_id} if timeout is not None: payload["timeout"] = timeout - resp = self.client.post(self.host + '/api/conat/project', json=payload) + resp = self.client.post(self.host + "/api/conat/project", json=payload) resp.raise_for_status() return handle_error(resp.json()) diff --git a/src/python/cocalc-api/tests/test_jupyter.py b/src/python/cocalc-api/tests/test_jupyter.py index 2148bcfc389..dbc39f5109b 100644 --- a/src/python/cocalc-api/tests/test_jupyter.py +++ b/src/python/cocalc-api/tests/test_jupyter.py @@ -1,6 +1,7 @@ """ Tests for Jupyter kernel functionality. """ + import pytest import time from typing import Optional @@ -15,12 +16,12 @@ def test_install_ipykernel(self, project_client): result = project_client.system.exec( command="python3", args=["-m", "pip", "install", "ipykernel"], - timeout=120 # 2 minutes should be enough for pip install + timeout=120, # 2 minutes should be enough for pip install ) # Check that installation succeeded - assert result['exit_code'] == 0 - assert 'stderr' in result + assert result["exit_code"] == 0 + assert "stderr" in result def test_install_jupyter_kernel(self, project_client): """Test installing the Python 3 Jupyter kernel.""" @@ -33,12 +34,13 @@ def test_install_jupyter_kernel(self, project_client): "install", "--user", # Install to user location, not system "--name=python3", - "--display-name=Python 3" + "--display-name=Python 3", ], - timeout=30) + timeout=30, + ) # Check that kernel installation succeeded - assert result['exit_code'] == 0 + assert result["exit_code"] == 0 class TestJupyterKernels: @@ -46,7 +48,7 @@ class TestJupyterKernels: def test_kernels_list_with_project(self, hub, temporary_project): """Test getting kernel specs for a specific project.""" - project_id = temporary_project['project_id'] + project_id = temporary_project["project_id"] kernels = hub.jupyter.kernels(project_id=project_id) # Should return a list of kernel specs @@ -55,12 +57,12 @@ def test_kernels_list_with_project(self, hub, temporary_project): def test_python3_kernel_available(self, hub, temporary_project): """Test that the python3 kernel is available after installation.""" - project_id = temporary_project['project_id'] + project_id = temporary_project["project_id"] kernels = hub.jupyter.kernels(project_id=project_id) # Extract kernel names from the list - kernel_names = [k.get('name') for k in kernels if isinstance(k, dict)] - assert 'python3' in kernel_names + kernel_names = [k.get("name") for k in kernels if isinstance(k, dict)] + assert "python3" in kernel_names class TestJupyterExecuteViaHub: @@ -69,64 +71,64 @@ class TestJupyterExecuteViaHub: @pytest.mark.skip(reason="hub.jupyter.execute() has timeout issues - use project.system.jupyter_execute() instead") def test_execute_simple_sum(self, hub, temporary_project): """Test executing a simple sum using the python3 kernel.""" - project_id = temporary_project['project_id'] + project_id = temporary_project["project_id"] - result = hub.jupyter.execute(input='sum(range(100))', kernel='python3', project_id=project_id) + result = hub.jupyter.execute(input="sum(range(100))", kernel="python3", project_id=project_id) # Check the result structure assert isinstance(result, dict) - assert 'output' in result + assert "output" in result # Check that we got the correct result (sum of 0..99 = 4950) - output = result['output'] + output = result["output"] assert len(output) > 0 # Extract the result from the output # Format: [{'data': {'text/plain': '4950'}}] first_output = output[0] - assert 'data' in first_output - assert 'text/plain' in first_output['data'] - assert first_output['data']['text/plain'] == '4950' + assert "data" in first_output + assert "text/plain" in first_output["data"] + assert first_output["data"]["text/plain"] == "4950" @pytest.mark.skip(reason="hub.jupyter.execute() has timeout issues - use project.system.jupyter_execute() instead") def test_execute_with_history(self, hub, temporary_project): """Test executing code with history context.""" - project_id = temporary_project['project_id'] + project_id = temporary_project["project_id"] - result = hub.jupyter.execute(history=['a = 100'], input='sum(range(a + 1))', kernel='python3', project_id=project_id) + result = hub.jupyter.execute(history=["a = 100"], input="sum(range(a + 1))", kernel="python3", project_id=project_id) # Check the result (sum of 0..100 = 5050) assert isinstance(result, dict) - assert 'output' in result + assert "output" in result - output = result['output'] + output = result["output"] assert len(output) > 0 first_output = output[0] - assert 'data' in first_output - assert 'text/plain' in first_output['data'] - assert first_output['data']['text/plain'] == '5050' + assert "data" in first_output + assert "text/plain" in first_output["data"] + assert first_output["data"]["text/plain"] == "5050" @pytest.mark.skip(reason="hub.jupyter.execute() has timeout issues - use project.system.jupyter_execute() instead") def test_execute_print_statement(self, hub, temporary_project): """Test executing code that prints output.""" - project_id = temporary_project['project_id'] + project_id = temporary_project["project_id"] - result = hub.jupyter.execute(input='print("Hello from Jupyter")', kernel='python3', project_id=project_id) + result = hub.jupyter.execute(input='print("Hello from Jupyter")', kernel="python3", project_id=project_id) # Check that we got output assert isinstance(result, dict) - assert 'output' in result + assert "output" in result - output = result['output'] + output = result["output"] assert len(output) > 0 # Print statements produce stream output first_output = output[0] - assert 'name' in first_output - assert first_output['name'] == 'stdout' - assert 'text' in first_output - assert 'Hello from Jupyter' in first_output['text'] + assert "name" in first_output + assert first_output["name"] == "stdout" + assert "text" in first_output + assert "Hello from Jupyter" in first_output["text"] class TestJupyterExecuteViaProject: @@ -147,10 +149,10 @@ def test_jupyter_execute_simple_sum(self, project_client): for attempt in range(max_retries): try: - result = project_client.system.jupyter_execute(input='sum(range(100))', kernel='python3') + result = project_client.system.jupyter_execute(input="sum(range(100))", kernel="python3") break except RuntimeError as e: - if 'timeout' in str(e).lower() and attempt < max_retries - 1: + if "timeout" in str(e).lower() and attempt < max_retries - 1: print(f"Attempt {attempt + 1} timed out, retrying in {retry_delay}s...") time.sleep(retry_delay) else: @@ -162,9 +164,9 @@ def test_jupyter_execute_simple_sum(self, project_client): # Check that we got the correct result (sum of 0..99 = 4950) first_output = result[0] - assert 'data' in first_output - assert 'text/plain' in first_output['data'] - assert first_output['data']['text/plain'] == '4950' + assert "data" in first_output + assert "text/plain" in first_output["data"] + assert first_output["data"]["text/plain"] == "4950" def test_jupyter_execute_with_history(self, project_client): """ @@ -172,7 +174,7 @@ def test_jupyter_execute_with_history(self, project_client): The result is a list of output items directly. """ - result = project_client.system.jupyter_execute(history=['b = 50'], input='b * 2', kernel='python3') + result = project_client.system.jupyter_execute(history=["b = 50"], input="b * 2", kernel="python3") # Result is a list assert isinstance(result, list) @@ -180,9 +182,9 @@ def test_jupyter_execute_with_history(self, project_client): # Check the result (50 * 2 = 100) first_output = result[0] - assert 'data' in first_output - assert 'text/plain' in first_output['data'] - assert first_output['data']['text/plain'] == '100' + assert "data" in first_output + assert "text/plain" in first_output["data"] + assert first_output["data"]["text/plain"] == "100" def test_jupyter_execute_list_operation(self, project_client): """ @@ -190,7 +192,21 @@ def test_jupyter_execute_list_operation(self, project_client): The result is a list of output items directly. """ - result = project_client.system.jupyter_execute(input='[x**2 for x in range(5)]', kernel='python3') + # Retry logic for kernel startup + max_retries = 3 + retry_delay = 15 + result: Optional[list] = None + + for attempt in range(max_retries): + try: + result = project_client.system.jupyter_execute(input="[x**2 for x in range(5)]", kernel="python3") + break + except RuntimeError as e: + if "timeout" in str(e).lower() and attempt < max_retries - 1: + print(f"Attempt {attempt + 1} timed out, retrying in {retry_delay}s...") + time.sleep(retry_delay) + else: + raise # Result is a list assert isinstance(result, list) @@ -198,9 +214,9 @@ def test_jupyter_execute_list_operation(self, project_client): # Check the result ([0, 1, 4, 9, 16]) first_output = result[0] - assert 'data' in first_output - assert 'text/plain' in first_output['data'] - assert first_output['data']['text/plain'] == '[0, 1, 4, 9, 16]' + assert "data" in first_output + assert "text/plain" in first_output["data"] + assert first_output["data"]["text/plain"] == "[0, 1, 4, 9, 16]" class TestJupyterKernelManagement: @@ -209,7 +225,20 @@ class TestJupyterKernelManagement: def test_list_jupyter_kernels(self, project_client): """Test listing running Jupyter kernels.""" # First execute some code to ensure a kernel is running - project_client.system.jupyter_execute(input='1+1', kernel='python3') + # Retry logic for first kernel startup (may take longer in CI) + max_retries = 3 + retry_delay = 15 + + for attempt in range(max_retries): + try: + project_client.system.jupyter_execute(input="1+1", kernel="python3") + break + except RuntimeError as e: + if "timeout" in str(e).lower() and attempt < max_retries - 1: + print(f"Attempt {attempt + 1} timed out, retrying in {retry_delay}s...") + time.sleep(retry_delay) + else: + raise # List kernels kernels = project_client.system.list_jupyter_kernels() @@ -222,15 +251,15 @@ def test_list_jupyter_kernels(self, project_client): # Each kernel should have required fields for kernel in kernels: - assert 'pid' in kernel - assert 'connectionFile' in kernel - assert isinstance(kernel['pid'], int) - assert isinstance(kernel['connectionFile'], str) + assert "pid" in kernel + assert "connectionFile" in kernel + assert isinstance(kernel["pid"], int) + assert isinstance(kernel["connectionFile"], str) def test_stop_jupyter_kernel(self, project_client): """Test stopping a specific Jupyter kernel.""" # Execute code to start a kernel - project_client.system.jupyter_execute(input='1+1', kernel='python3') + project_client.system.jupyter_execute(input="1+1", kernel="python3") # List kernels kernels = project_client.system.list_jupyter_kernels() @@ -238,14 +267,14 @@ def test_stop_jupyter_kernel(self, project_client): # Stop the first kernel kernel_to_stop = kernels[0] - result = project_client.system.stop_jupyter_kernel(pid=kernel_to_stop['pid']) + result = project_client.system.stop_jupyter_kernel(pid=kernel_to_stop["pid"]) # Should return success assert isinstance(result, dict) - assert 'success' in result - assert result['success'] is True + assert "success" in result + assert result["success"] is True # Verify kernel is no longer in the list kernels_after = project_client.system.list_jupyter_kernels() - remaining_pids = [k['pid'] for k in kernels_after] - assert kernel_to_stop['pid'] not in remaining_pids + remaining_pids = [k["pid"] for k in kernels_after] + assert kernel_to_stop["pid"] not in remaining_pids From f5677b69a6e5aff3c7737537720b7dead71411ae Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Thu, 9 Oct 2025 16:34:00 +0200 Subject: [PATCH 19/58] ci/cocalc-api: restart hub to enable jupyter api --- .github/workflows/make-and-test.yml | 80 ++++++++++++++++++++- src/packages/hub/run/test-create-admin.ts | 4 +- src/python/cocalc-api/tests/test_jupyter.py | 3 - 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/.github/workflows/make-and-test.yml b/.github/workflows/make-and-test.yml index 2d9dc67b439..4ef9c133398 100644 --- a/.github/workflows/make-and-test.yml +++ b/.github/workflows/make-and-test.yml @@ -178,23 +178,99 @@ jobs: - name: Create CI admin user and API key run: | cd src/packages/hub - node dist/run/test-create-admin.js > ../../api_key.txt + OUTPUT=$(node dist/run/test-create-admin.js) + # Split output into account_id and API key (format: UUID;api-key) + ACCOUNT_ID=$(echo "$OUTPUT" | cut -d';' -f1) + API_KEY=$(echo "$OUTPUT" | cut -d';' -f2) + + # Store in separate files + echo "$ACCOUNT_ID" > ../../account_id.txt + echo "$API_KEY" > ../../api_key.txt + + # Validate account ID (UUID format) + if ! echo "$ACCOUNT_ID" | grep -qE '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'; then + echo "Error: Invalid account ID format: $ACCOUNT_ID" + exit 1 + fi + # Validate API key was created if [ ! -s ../../api_key.txt ]; then echo "Error: API key file is empty or missing" exit 1 fi - API_KEY=$(cat ../../api_key.txt) if ! echo "$API_KEY" | grep -qE '^sk-[A-Za-z0-9]+$'; then echo "Error: Invalid API key format: $API_KEY" exit 1 fi + + echo "Account ID: $ACCOUNT_ID" echo "API key created successfully" env: PGDATABASE: smc PGUSER: smc PGHOST: localhost + - name: Restart CoCalc Hub + run: | + HUB_PID=$(cat src/packages/hub/hub.pid) + echo "Stopping hub with PID $HUB_PID" + kill $HUB_PID || true + # Wait a bit for graceful shutdown + sleep 5 + # Force kill if still running + kill -9 $HUB_PID 2>/dev/null || true + + # Read the admin account ID created in the previous step + cd src/packages/hub + ADMIN_ACCOUNT_ID=$(cat ../../account_id.txt) + echo "Using admin account ID: $ADMIN_ACCOUNT_ID" + + # Export env vars for the hub process + export COCALC_SETTING_JUPYTER_API_ENABLED=yes + export COCALC_SETTING_JUPYTER_ACCOUNT_ID=$ADMIN_ACCOUNT_ID + export COCALC_SETTING_JUPYTER_PROJECT_POOL_SIZE=1 + + # Start hub with new configuration + pnpm run hub-project-dev-nobuild > hub.log 2>&1 & + HUB_PID=$! + echo $HUB_PID > hub.pid + echo "Hub restarted with PID $HUB_PID" + + # Check if process is still running after a moment + sleep 2 + if ! kill -0 $HUB_PID 2>/dev/null; then + echo "Error: Hub process died immediately after restarting" + echo "Hub log:" + cat hub.log + exit 1 + fi + + # Wait for hub to be ready + MAX_ATTEMPTS=30 + READY=false + for i in $(seq 1 $MAX_ATTEMPTS); do + if curl -sf --max-time 3 http://localhost:5000/healthcheck > /dev/null; then + echo "Hub is ready after restart" + READY=true + break + fi + echo "Waiting for hub to be ready... ($i/$MAX_ATTEMPTS)" + sleep 3 + done + if [ "$READY" = "false" ]; then + echo "Hub failed to become ready after restart" + echo "Hub log:" + cat hub.log + exit 1 + fi + env: + PGDATABASE: smc + PGUSER: smc + PGHOST: localhost + COCALC_MODE: single-user + COCALC_TEST_MODE: yes + DEBUG: 'cocalc:*,-cocalc:silly:*,hub:*,project:*' + - name: Install uv for cocalc-api tests run: curl -LsSf https://astral.sh/uv/install.sh | sh && echo "$HOME/.local/bin" >> $GITHUB_PATH diff --git a/src/packages/hub/run/test-create-admin.ts b/src/packages/hub/run/test-create-admin.ts index 9002df5420c..a84db7b7c83 100644 --- a/src/packages/hub/run/test-create-admin.ts +++ b/src/packages/hub/run/test-create-admin.ts @@ -59,8 +59,8 @@ async function main() { console.error(`API key created with id=${apiKey.id}: ${apiKey.secret}`); console.error(`Last 6 chars: ${apiKey.secret.slice(-6)}`); - // Output the key for CI - process.stdout.write(apiKey.secret); + // Output the account_id and API key for CI in format: UUID;api-key + process.stdout.write(`${account_id};${apiKey.secret}`); } main().catch((err) => { diff --git a/src/python/cocalc-api/tests/test_jupyter.py b/src/python/cocalc-api/tests/test_jupyter.py index dbc39f5109b..84b7606db86 100644 --- a/src/python/cocalc-api/tests/test_jupyter.py +++ b/src/python/cocalc-api/tests/test_jupyter.py @@ -68,7 +68,6 @@ def test_python3_kernel_available(self, hub, temporary_project): class TestJupyterExecuteViaHub: """Tests for executing code via hub.jupyter.execute().""" - @pytest.mark.skip(reason="hub.jupyter.execute() has timeout issues - use project.system.jupyter_execute() instead") def test_execute_simple_sum(self, hub, temporary_project): """Test executing a simple sum using the python3 kernel.""" project_id = temporary_project["project_id"] @@ -90,7 +89,6 @@ def test_execute_simple_sum(self, hub, temporary_project): assert "text/plain" in first_output["data"] assert first_output["data"]["text/plain"] == "4950" - @pytest.mark.skip(reason="hub.jupyter.execute() has timeout issues - use project.system.jupyter_execute() instead") def test_execute_with_history(self, hub, temporary_project): """Test executing code with history context.""" project_id = temporary_project["project_id"] @@ -109,7 +107,6 @@ def test_execute_with_history(self, hub, temporary_project): assert "text/plain" in first_output["data"] assert first_output["data"]["text/plain"] == "5050" - @pytest.mark.skip(reason="hub.jupyter.execute() has timeout issues - use project.system.jupyter_execute() instead") def test_execute_print_statement(self, hub, temporary_project): """Test executing code that prints output.""" project_id = temporary_project["project_id"] From 9f74c3312a37111e1726d0aebdc3e45c1a75a213 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Tue, 21 Oct 2025 19:47:06 +0200 Subject: [PATCH 20/58] cocalc-api: MCP server stub --- src/python/cocalc-api/.vscode/settings.json | 11 +- src/python/cocalc-api/Makefile | 4 + src/python/cocalc-api/pyproject.toml | 7 +- .../src/cocalc_api/mcp/DEVELOPMENT.md | 343 +++++ .../cocalc-api/src/cocalc_api/mcp/README.md | 105 ++ .../cocalc-api/src/cocalc_api/mcp/__init__.py | 11 + .../cocalc-api/src/cocalc_api/mcp/__main__.py | 14 + .../src/cocalc_api/mcp/resources/__init__.py | 9 + .../cocalc_api/mcp/resources/file_listing.py | 187 +++ .../cocalc-api/src/cocalc_api/mcp/server.py | 163 +++ .../src/cocalc_api/mcp/tools/__init__.py | 9 + .../src/cocalc_api/mcp/tools/exec.py | 101 ++ src/python/cocalc-api/tests/test_jupyter.py | 1 - src/python/cocalc-api/uv.lock | 1231 +++++++++++------ 14 files changed, 1754 insertions(+), 442 deletions(-) create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/README.md create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/__init__.py create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/__main__.py create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/resources/__init__.py create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/server.py create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py diff --git a/src/python/cocalc-api/.vscode/settings.json b/src/python/cocalc-api/.vscode/settings.json index df1bc0a0a7f..88b717bda8f 100644 --- a/src/python/cocalc-api/.vscode/settings.json +++ b/src/python/cocalc-api/.vscode/settings.json @@ -1,7 +1,14 @@ { - "python.defaultInterpreterPath": "./.venv/bin/python", + "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", + "python.linting.enabled": true, "python.analysis.extraPaths": ["./src"], "python.analysis.autoSearchPaths": true, "python.analysis.autoImportCompletions": true, - "pylance.insidersChannel": "off" + "python.analysis.typeCheckingMode": "basic", + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true + }, + "pylance.insidersChannel": "off", + "python.venvFolderPath": ".venv" } \ No newline at end of file diff --git a/src/python/cocalc-api/Makefile b/src/python/cocalc-api/Makefile index 4f1dc094b41..19c6fb79586 100644 --- a/src/python/cocalc-api/Makefile +++ b/src/python/cocalc-api/Makefile @@ -11,6 +11,7 @@ help: @echo " coverage - Run tests with coverage reporting (HTML + terminal)" @echo " coverage-report - Show coverage report in terminal" @echo " coverage-html - Generate HTML coverage report only" + @echo " mcp - Start the MCP server (requires COCALC_API_KEY and COCALC_PROJECT_ID)" @echo " serve-docs - Serve documentation locally" @echo " build-docs - Build documentation" @echo " publish - Build and publish package" @@ -47,6 +48,9 @@ coverage-report: coverage-html: uv run coverage html +mcp: + uv run cocalc-mcp-server + serve-docs: uv run mkdocs serve diff --git a/src/python/cocalc-api/pyproject.toml b/src/python/cocalc-api/pyproject.toml index dbd4e8a8f17..444a1e5d249 100644 --- a/src/python/cocalc-api/pyproject.toml +++ b/src/python/cocalc-api/pyproject.toml @@ -4,8 +4,8 @@ version = "0.5.0" description = "Python client for the CoCalc API" authors = [{ name="William Stein", email="wstein@sagemath.com" }, {name="Harald Schilly", email="hsy@sagemath.com"}] readme = "README.md" -requires-python = ">=3.9" -dependencies = ["httpx"] +requires-python = ">=3.10" +dependencies = ["httpx", "mcp>=0.1.0"] license = "MIT" [tool.hatch.build.targets.wheel] @@ -19,6 +19,9 @@ Documentation = "https://cocalc.com/api/python" Source = "https://github.com/sagemathinc/cocalc/src/python/cocalc-api" Issues = "https://github.com/sagemathinc/cocalc/issues" +[project.scripts] +cocalc-mcp-server = "cocalc_api.mcp.server:main" + [tool.mypy] python_version = "3.13" # strict = true diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md b/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md new file mode 100644 index 00000000000..b9d08f7f8ac --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md @@ -0,0 +1,343 @@ +# CoCalc API MCP Server - Development Guide + +## Overview + +This directory contains the **Model Context Protocol (MCP) server** implementation for the CoCalc API. The MCP server allows LLMs (via clients like Claude Desktop) to interact with CoCalc projects through a standardized protocol. + +Learn more about MCP: https://modelcontextprotocol.io/docs/getting-started/intro + +## Architecture + +### Design Principles + +1. **Single Project Scope**: Each MCP server instance is tied to a specific CoCalc project via `project_id`. This eliminates the need to repeatedly specify which project an operation should run in. + +2. **Configuration via Environment & Config**: Parameters are configured through: + - Environment variables (for local development) + - MCP server configuration files (for LLM clients like Claude Desktop) + +3. **Minimal but Powerful**: Start with essential tools and resources; expand incrementally. + +4. **Type-Safe**: Leverage Python type hints and MCP's type system for robust integration. + +### Required Configuration + +#### Parameters + +- **`COCALC_API_KEY`** (required) + - The API key for authenticating with CoCalc + - Source: Environment variable or MCP config (environment field) + - Example: `sk-...` + +- **`project_id`** (required) + - UUID of the target CoCalc project + - Source: Environment variable or MCP config (environment field) + - Format: UUID string (e.g., `6e75dbf1-0342-4249-9dce-6b21648656e9`) + +- **`COCALC_HOST`** (optional) + - Base URL for the CoCalc instance + - Default: `https://cocalc.com` + - Source: Environment variable or MCP config + +#### Example Local Usage + +```bash +export COCALC_API_KEY="sk-your-api-key-here" +export COCALC_PROJECT_ID="6e75dbf1-0342-4249-9dce-6b21648656e9" +export COCALC_HOST="https://cocalc.com" # Optional + +uv run cocalc-mcp-server +``` + +#### Example Claude Desktop Config + +Add to `~/.config/Claude/claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "cocalc": { + "command": "uv", + "args": ["run", "cocalc-mcp-server"], + "env": { + "COCALC_API_KEY": "sk-your-api-key-here", + "COCALC_PROJECT_ID": "6e75dbf1-0342-4249-9dce-6b21648656e9", + "COCALC_HOST": "https://cocalc.com" + } + } + } +} +``` + +## Features + +### Phase 1: MVP (Current Development) + +#### Tools + +##### 1. `exec` - Execute Shell Commands + +Execute arbitrary shell commands in the target CoCalc project. + +**Purpose**: Run any command line operation in the project's Linux environment. + +**Parameters**: +- `command` (string, required): Command to execute (e.g., `date -Ins`, `python script.py`) +- `args` (array of strings, optional): Arguments to pass to the command +- `bash` (boolean, optional): If true, interpret command as bash script +- `timeout` (integer, optional): Timeout in seconds +- `cwd` (string, optional): Working directory (relative to home or absolute) + +**Returns**: Object with: +- `stdout` (string): Command output +- `stderr` (string): Error output +- `exit_code` (integer): Process exit code + +**Examples**: +```json +// Get current date +{ + "tool": "exec", + "command": "date -Ins" +} + +// Run Python script with arguments +{ + "tool": "exec", + "command": "python", + "args": ["script.py", "--verbose", "input.txt"] +} + +// Execute a bash script +{ + "tool": "exec", + "command": "for i in {1..5}; do echo \"Iteration $i\"; done", + "bash": true +} + +// Run with timeout +{ + "tool": "exec", + "command": "sleep 100", + "timeout": 5 +} +``` + +#### Resources + +##### 1. `project-files` - File Listing & Browsing + +Browse and list files in the project directory structure with filtering and pagination. + +**Purpose**: Allow the LLM to understand the project's file structure without running commands. + +**URI Scheme**: `cocalc://project-files/{path}` + +**Query Parameters**: +- `path` (string): Directory path to list (relative to home, default: `.`) +- `glob` (string, optional): Glob pattern to filter files (e.g., `*.py`, `**/*.txt`) +- `limit` (integer, optional): Maximum number of files to return (default: 100, max: 1000) +- `recurse` (boolean, optional): Recursively list subdirectories (default: false) + +**Returns**: Array of file objects with: +- `name` (string): Filename +- `path` (string): Full path relative to home +- `type` (string): `file` or `directory` +- `size` (integer): File size in bytes (0 for directories) +- `modified` (string): Last modified timestamp (ISO 8601) + +**Examples**: + +```uri +// List current directory +cocalc://project-files/ + +// List Python files with recursion +cocalc://project-files/?glob=*.py&recurse=true + +// List all markdown files, limited to 50 results +cocalc://project-files/?glob=*.md&limit=50 + +// Browse a subdirectory +cocalc://project-files/notebooks?limit=20 +``` + +**Return Example**: +```json +{ + "uri": "cocalc://project-files/", + "contents": [ + { + "name": "script.py", + "path": "script.py", + "type": "file", + "size": 2048, + "modified": "2025-10-21T14:30:00Z" + }, + { + "name": "data", + "path": "data", + "type": "directory", + "size": 0, + "modified": "2025-10-21T14:25:00Z" + } + ] +} +``` + +## Implementation Structure + +``` +src/cocalc_api/mcp/ +├── DEVELOPMENT.md # This file +├── __init__.py # Package initialization +├── server.py # Main MCP server entry point +├── tools/ +│ ├── __init__.py +│ ├── exec.py # Shell execution tool +│ └── base.py # Base tool class (if needed) +└── resources/ + ├── __init__.py + ├── project_files.py # File listing resource + └── base.py # Base resource class (if needed) +``` + +### File Responsibilities + +- **`server.py`**: Initializes MCP server, registers tools/resources, handles configuration +- **`tools/exec.py`**: Implementation of the `exec` tool +- **`resources/project_files.py`**: Implementation of file listing resource + +## Configuration & Initialization + +### Server Initialization Flow + +1. **Read Configuration**: + - Check environment variables: `COCALC_API_KEY`, `COCALC_PROJECT_ID`, `COCALC_HOST` + - Validate all required parameters are set + - Initialize HTTP client with auth + +2. **Create Project Client**: + - Instantiate `Project(api_key, project_id, host)` + - Verify project is accessible (ping test) + +3. **Register Tools**: + - `exec`: Shell command execution + +4. **Register Resources**: + - `project-files`: File listing + +5. **Start Server**: + - Begin listening for MCP requests + +### Error Handling + +- Configuration errors → Exit with clear error message +- Project authentication errors → Cannot access project +- Runtime errors in tools → Return error in MCP format +- Network errors → Retry logic with exponential backoff (future enhancement) + +## Testing Strategy + +### Unit Tests + +- **`test_exec.py`**: Test command execution with various inputs +- **`test_project_files.py`**: Test file listing and filtering +- **`test_server.py`**: Test server initialization and configuration + +### Integration Tests + +- Full flow: Initialize server → Execute command → List files +- Error cases: Invalid project_id, auth failures, malformed requests +- Performance: Large directory listings, recursive traversal + +### Manual Testing + +```bash +# Start the MCP server locally +export COCALC_API_KEY="sk-..." +export COCALC_PROJECT_ID="..." +python -m cocalc_api.mcp.server + +# In another terminal, test with a client (future) +``` + +## Future Enhancements + +### Phase 2: File Operations + +- **`file-read`** resource: Read file contents +- **`file-write`** tool: Write/create files +- **`file-delete`** tool: Delete files +- **`file-rename`** tool: Rename/move files + +### Phase 3: Advanced Features + +- **`jupyter-execute`** tool: Run Jupyter code +- **`git-status`** resource: Git repository status +- **`project-info`** resource: Project metadata and state +- **Caching**: Cache directory listings and file metadata + +### Phase 4: Optimization + +- Connection pooling for multiple concurrent requests +- Request queuing to prevent project overload +- Streaming responses for large file operations +- Metrics collection and logging + +## Dependencies + +- **`mcp>=1.0`**: Model Context Protocol SDK +- **`httpx`**: HTTP client (already in project) +- **`pydantic>=2.0`**: Data validation (via mcp dependency) +- **`python-dotenv`**: Environment variable loading (via mcp dependency) + +## Development Commands + +```bash +# Install development dependencies +uv pip install -e ".[dev]" + +# Run tests +pytest tests/test_mcp_*.py -v + +# Run with debugging +export COCALC_API_KEY="sk-..." +export COCALC_PROJECT_ID="..." +python -m cocalc_api.mcp.server + +# Type checking +mypy src/cocalc_api/mcp/ + +# Code formatting +ruff format src/cocalc_api/mcp/ +``` + +## References + +- **MCP Specification**: https://modelcontextprotocol.io/ +- **MCP Python SDK**: https://github.com/modelcontextprotocol/python-sdk +- **CoCalc API**: See parent directory documentation +- **Claude Desktop Config**: https://modelcontextprotocol.io/docs/tools/resources + +## Security Considerations + +1. **API Key Security**: Never commit API keys to version control. Use environment variables or secure config files with restricted permissions (600). + +2. **Project Isolation**: Each server instance targets only one project. Different projects require different MCP server instances. + +3. **Command Execution**: The `exec` tool runs arbitrary commands in the project. Ensure the API key has appropriate permissions. + +4. **File Access**: File listing respects project filesystem permissions. Only files accessible to the project user are listed. + +5. **Rate Limiting**: Consider implementing rate limiting in production to prevent overload (future enhancement). + +## Next Steps + +1. Implement `server.py` with MCP server initialization +2. Implement `tools/exec.py` with shell command execution +3. Implement `resources/project_files.py` with file listing +4. Add comprehensive error handling and validation +5. Write tests for all components +6. Document usage examples in README +7. Test with actual LLM clients (Claude, etc.) diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/README.md b/src/python/cocalc-api/src/cocalc_api/mcp/README.md new file mode 100644 index 00000000000..a453e1b08bd --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/README.md @@ -0,0 +1,105 @@ +# CoCalc API MCP Server + +A Model Context Protocol (MCP) server that allows LLMs to interact with CoCalc projects. + +## Quick Start + +### 1. Configuration + +Set environment variables: + +```bash +export COCALC_API_KEY="sk-your-api-key" +export COCALC_PROJECT_ID="your-project-uuid" +export COCALC_HOST="https://cocalc.com" # optional, defaults to https://cocalc.com +``` + +### 2. Run the Server + +```bash +uv run cocalc-mcp-server +``` + +### 3. Use with Claude Desktop + +Add to `~/.config/Claude/claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "cocalc": { + "command": "uv", + "args": ["run", "cocalc-mcp-server"], + "env": { + "COCALC_API_KEY": "sk-your-api-key", + "COCALC_PROJECT_ID": "your-project-uuid", + "COCALC_HOST": "https://cocalc.com" + } + } + } +} +``` + +Then restart Claude Desktop and you'll have access to the CoCalc tools. + +## Features + +### Tools + +- **`exec`** - Execute shell commands in the project + ``` + Tool: exec + Params: command (required), args, bash, timeout, cwd + Returns: {stdout, stderr, exit_code} + ``` + +### Resources + +- **`project-files`** - Browse project files with filtering and pagination + ``` + URI: cocalc://project-files/{path}?glob=*.py&limit=100&recurse=true + Returns: File listing with metadata + ``` + +## Documentation + +See [DEVELOPMENT.md](./DEVELOPMENT.md) for: +- Architecture and design principles +- Detailed API specifications +- Configuration options +- Testing strategy +- Future roadmap + +## Directory Structure + +``` +src/cocalc_api/mcp/ +├── README.md # This file +├── DEVELOPMENT.md # Architecture & design documentation +├── server.py # Main MCP server +├── __main__.py # Module entry point +├── tools/ +│ ├── exec.py # Shell command execution tool +│ └── __init__.py +└── resources/ + ├── project_files.py # File listing resource + └── __init__.py +``` + +## Requirements + +- Python 3.10+ +- mcp>=1.0 +- httpx +- pydantic (via mcp) + +## Security + +- API keys should never be committed to version control +- Use restricted file permissions (600) on config files containing API keys +- Each server instance is scoped to a single project +- Commands execute with the permissions of the CoCalc project user + +## Next Steps + +See [DEVELOPMENT.md](./DEVELOPMENT.md#next-steps) for implementation roadmap and upcoming features. diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/__init__.py b/src/python/cocalc-api/src/cocalc_api/mcp/__init__.py new file mode 100644 index 00000000000..3d6436a9bca --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/__init__.py @@ -0,0 +1,11 @@ +""" +CoCalc API MCP Server package. + +Provides Model Context Protocol (MCP) integration for CoCalc projects. +Allows LLMs to interact with CoCalc through a standardized protocol. + +See DEVELOPMENT.md for architecture and design documentation. +""" + +__version__ = "0.1.0" +__all__ = [] diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/__main__.py b/src/python/cocalc-api/src/cocalc_api/mcp/__main__.py new file mode 100644 index 00000000000..b8ffc1fb828 --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/__main__.py @@ -0,0 +1,14 @@ +""" +Entry point for running the MCP server as a module. + +Usage: + python -m cocalc_api.mcp + +Recommended usage (runs in local uv environment): + uv run cocalc-mcp-server +""" + +from .server import main + +if __name__ == "__main__": + main() diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/resources/__init__.py b/src/python/cocalc-api/src/cocalc_api/mcp/resources/__init__.py new file mode 100644 index 00000000000..5ef870637e2 --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/resources/__init__.py @@ -0,0 +1,9 @@ +""" +MCP Resources for CoCalc API. + +Resources provide information that the LLM can read about the CoCalc project. +""" + +from .file_listing import ProjectFilesResource + +__all__ = ["ProjectFilesResource"] diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py b/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py new file mode 100644 index 00000000000..87be9094127 --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py @@ -0,0 +1,187 @@ +""" +File listing resource for CoCalc projects. + +Provides the 'project-files' resource that allows browsing and listing +files in the project directory structure with filtering and pagination. +""" + +from typing import Any, Optional, cast +from datetime import datetime +from urllib.parse import urlparse, parse_qs + +from mcp.types import TextResourceContents, ReadResourceResult +from pydantic.networks import AnyUrl + + +class ProjectFilesResource: + """Resource for listing and browsing project files.""" + + def __init__(self, project_client): + """ + Initialize the project files resource. + + Args: + project_client: Initialized Project client from cocalc_api + """ + self.project_client = project_client + + def uri_template(self) -> str: + """Return the URI template for this resource.""" + return "cocalc://project-files/{path}" + + def description(self) -> str: + """Return the resource description.""" + return ( + "Browse and list files in the CoCalc project. " + "Supports glob filtering and pagination. " + "Query parameters: glob=*.py (filter), limit=100 (max results), recurse=true (recursive)" + ) + + async def read(self, uri: str) -> ReadResourceResult: + """ + Read and list files from the project. + + Args: + uri: Resource URI like cocalc://project-files/path?glob=*.py&limit=100 + + Returns: + ReadResourceResult with file listing + """ + try: + # Parse URI + parsed = urlparse(uri) + path = parsed.path.replace("project-files/", "").lstrip("/") or "." + + # Parse query parameters + query = parse_qs(parsed.query) + glob_pattern = query.get("glob", [None])[0] + limit = int(query.get("limit", [100])[0]) if query.get("limit") else 100 + recurse_str = query.get("recurse", [False])[0] + recurse = recurse_str.lower() == "true" if isinstance(recurse_str, str) else False + + # TODO: Implement file listing logic + # This is a placeholder that will be implemented in the next phase + files_info = self._list_files(path, glob_pattern, limit, recurse) + + content = self._format_files_output(files_info, uri) + + return ReadResourceResult( + contents=[TextResourceContents(uri=cast(AnyUrl, uri), text=content)] + ) + + except Exception as e: + return ReadResourceResult( + contents=[ + TextResourceContents( + uri=cast(AnyUrl, uri), + text=f"Error listing files: {str(e)}", + ) + ] + ) + + def _list_files( + self, path: str, glob_pattern: Optional[str], limit: int, recurse: bool + ) -> list[dict[str, Any]]: + """ + List files in the given path. + + Args: + path: Directory path to list + glob_pattern: Optional glob pattern to filter files (case-insensitive) + limit: Maximum number of files to return + recurse: Whether to recursively list subdirectories + + Returns: + List of file information dictionaries with keys: name, path, type, size, modified + + Notes: + - Glob patterns are case-insensitive (e.g., "*.py", "*.PY", "*.Py" all match) + - Uses find -iname for case-insensitive matching on Unix/Linux systems + """ + try: + # Build find command + depth_opt = "" if recurse else "-maxdepth 1" + # Use -iname for case-insensitive glob pattern matching + glob_opt = f"-iname '{glob_pattern}'" if glob_pattern else "" + + # Use find with -printf for structured output + # Format: filename|type|size|mtime + # where type is 'f' for file or 'd' for directory + cmd = f"find {path} {depth_opt} -type f -o -type d" + if glob_pattern: + cmd = f"find {path} {depth_opt} {glob_opt} -type f -o {glob_opt} -type d" + + cmd += " -printf '%P|%y|%s|%T@\\n' 2>/dev/null | head -n {limit}".format(limit=limit) + + result = self.project_client.system.exec(command=cmd, bash=True) + + if result['exit_code'] != 0: + return [] + + files_info: list[dict[str, Any]] = [] + for line in result['stdout'].strip().split('\n'): + if not line: + continue + + try: + parts = line.split('|') + if len(parts) < 4: + continue + + filename, file_type, size_str, mtime_str = parts[0], parts[1], parts[2], parts[3] + + # Skip the root directory entry (empty filename with type 'd') + if not filename and file_type == 'd': + continue + + try: + size = int(size_str) if size_str else 0 + except ValueError: + size = 0 + + try: + mtime_float = float(mtime_str) + modified = datetime.fromtimestamp(mtime_float).isoformat() + 'Z' + except (ValueError, OverflowError): + modified = "unknown" + + files_info.append({ + 'name': filename.split('/')[-1] if filename else path, + 'path': filename if filename else path, + 'type': 'directory' if file_type == 'd' else 'file', + 'size': size, + 'modified': modified, + }) + + if len(files_info) >= limit: + break + except (IndexError, ValueError): + # Skip malformed lines + continue + + return files_info + + except Exception: + # Return empty list on error (error will be shown in read() method) + return [] + + def _format_files_output(self, files_info: list[dict[str, Any]], uri: str) -> str: + """ + Format file information for output. + + Args: + files_info: List of file information + uri: The requested URI + + Returns: + Formatted text output + """ + if not files_info: + return f"No files found for {uri}" + + output = f"Files in {uri}:\n\n" + for file_info in files_info: + file_type = "[DIR]" if file_info["type"] == "directory" else "[FILE]" + output += f"{file_type} {file_info['path']} ({file_info['size']} bytes)\n" + + return output diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/server.py b/src/python/cocalc-api/src/cocalc_api/mcp/server.py new file mode 100644 index 00000000000..36bdd6d6b14 --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/server.py @@ -0,0 +1,163 @@ +""" +MCP Server for CoCalc API. + +Main entry point for the Model Context Protocol server that provides +LLM access to CoCalc projects. + +Usage (local): + export COCALC_API_KEY="sk-..." + export COCALC_PROJECT_ID="project-uuid" + uv run cocalc-mcp-server + +Usage (Claude Desktop config): + { + "mcpServers": { + "cocalc": { + "command": "uv", + "args": ["run", "cocalc-mcp-server"], + "env": { + "COCALC_API_KEY": "sk-...", + "COCALC_PROJECT_ID": "..." + } + } + } + } +""" + +import os +import sys +import asyncio + +from mcp.server import Server +from mcp.types import Resource, TextContent, TextResourceContents +from pydantic.networks import AnyUrl + +from cocalc_api import Project +from .tools.exec import ExecTool +from .resources.file_listing import ProjectFilesResource + + +def get_config() -> tuple[str, str, str]: + """ + Get and validate MCP server configuration. + + Returns: + Tuple of (api_key, project_id, host) + + Raises: + RuntimeError: If required configuration is missing + """ + api_key = os.environ.get("COCALC_API_KEY") + project_id = os.environ.get("COCALC_PROJECT_ID") + host = os.environ.get("COCALC_HOST", "https://cocalc.com") + + if not api_key: + raise RuntimeError( + "COCALC_API_KEY environment variable is required but not set" + ) + if not project_id: + raise RuntimeError( + "COCALC_PROJECT_ID environment variable is required but not set" + ) + + return api_key, project_id, host + + +async def _run_server(): + """Async implementation of the MCP server.""" + + # Get configuration + try: + api_key, project_id, host = get_config() + except RuntimeError as e: + print(f"Configuration Error: {e}", file=sys.stderr) + sys.exit(1) + + # Initialize Project client + try: + project_client = Project(api_key=api_key, project_id=project_id, host=host) + + # Verify project is accessible + print(f"Connecting to project {project_id}...", file=sys.stderr) + project_client.system.ping() + print(f"✓ Connected to project {project_id}", file=sys.stderr) + except Exception as e: + print( + f"Error: Could not connect to project {project_id}: {e}", + file=sys.stderr, + ) + sys.exit(1) + + # Initialize MCP server + server = Server("cocalc-api") + + # Initialize tools + exec_tool = ExecTool(project_client) + project_files_resource = ProjectFilesResource(project_client) + + # Register tools + @server.list_tools() + async def list_tools(): + """List available tools.""" + return [exec_tool.definition()] + + @server.call_tool() + async def call_tool(name: str, arguments: dict): + """Execute a tool.""" + if name == "exec": + return await exec_tool.execute(arguments) + else: + return [ + TextContent( + type="text", + text=f"Unknown tool: {name}", + ) + ] + + # Register resources + @server.list_resources() + async def list_resources(): + """List available resources.""" + return [ + Resource( + uri=project_files_resource.uri_template(), # type: ignore + name="project-files", + description=project_files_resource.description(), + mimeType="text/plain", + ) + ] + + @server.read_resource() # type: ignore + async def read_resource(uri: AnyUrl): + """Read a resource.""" + if str(uri).startswith("cocalc://project-files"): + result = await project_files_resource.read(str(uri)) + return result.contents # type: ignore + else: + # Return error for unknown resources + return [ + TextResourceContents( + uri=uri, + text=f"Unknown resource: {uri}", + ) + ] + + # Start server + print( + f"Starting CoCalc API MCP Server for project {project_id}...", + file=sys.stderr, + ) + async with server: # type: ignore + print("Server is running. Press Ctrl+C to exit.", file=sys.stderr) + # Run the server indefinitely + while True: + await asyncio.sleep(1) + + +def main(): + """Entry point for the MCP server.""" + asyncio.run(_run_server()) + + +if __name__ == "__main__": + main() diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py new file mode 100644 index 00000000000..5e32088e336 --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py @@ -0,0 +1,9 @@ +""" +MCP Tools for CoCalc API. + +Tools allow the LLM to perform actions in the CoCalc project. +""" + +from .exec import ExecTool + +__all__ = ["ExecTool"] diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py new file mode 100644 index 00000000000..cc55df2404c --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py @@ -0,0 +1,101 @@ +""" +Execute shell commands in CoCalc project. + +Provides the 'exec' tool that allows running arbitrary shell commands +in the target CoCalc project environment. +""" + +from typing import Any +from mcp.types import Tool, TextContent + + +class ExecTool: + """Tool for executing shell commands in a CoCalc project.""" + + def __init__(self, project_client): + """ + Initialize the exec tool. + + Args: + project_client: Initialized Project client from cocalc_api + """ + self.project_client = project_client + + def definition(self) -> Tool: + """Return the MCP tool definition.""" + return Tool( + name="exec", + description="Execute a shell command in the CoCalc project", + inputSchema={ + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The command to execute (e.g., 'ls -la', 'python script.py')", + }, + "args": { + "type": "array", + "items": {"type": "string"}, + "description": "Optional command arguments", + }, + "bash": { + "type": "boolean", + "description": "If true, interpret command as bash script", + }, + "timeout": { + "type": "integer", + "description": "Timeout in seconds", + }, + "cwd": { + "type": "string", + "description": "Working directory (relative to home or absolute)", + }, + }, + "required": ["command"], + }, + ) + + async def execute(self, arguments: dict[str, Any]) -> list[TextContent]: + """ + Execute the command and return results. + + Args: + arguments: Tool arguments from MCP request + + Returns: + List of TextContent with stdout, stderr, and exit_code + """ + try: + command = arguments.get("command") + args = arguments.get("args") + bash = arguments.get("bash", False) + timeout = arguments.get("timeout") + cwd = arguments.get("cwd") + + if not command: + return [ + TextContent( + type="text", + text="Error: 'command' parameter is required", + ) + ] + + result = self.project_client.system.exec( + command=command, + args=args, + bash=bash, + timeout=timeout, + cwd=cwd, + ) + + output = f"stdout:\n{result['stdout']}\n\nstderr:\n{result['stderr']}\n\nexit_code: {result['exit_code']}" + + return [TextContent(type="text", text=output)] + + except Exception as e: + return [ + TextContent( + type="text", + text=f"Error executing command: {str(e)}", + ) + ] diff --git a/src/python/cocalc-api/tests/test_jupyter.py b/src/python/cocalc-api/tests/test_jupyter.py index 84b7606db86..01b03e33e01 100644 --- a/src/python/cocalc-api/tests/test_jupyter.py +++ b/src/python/cocalc-api/tests/test_jupyter.py @@ -2,7 +2,6 @@ Tests for Jupyter kernel functionality. """ -import pytest import time from typing import Optional diff --git a/src/python/cocalc-api/uv.lock b/src/python/cocalc-api/uv.lock index 3643b57fd32..94adb01a2f4 100644 --- a/src/python/cocalc-api/uv.lock +++ b/src/python/cocalc-api/uv.lock @@ -1,10 +1,18 @@ version = 1 revision = 3 -requires-python = ">=3.9" +requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.11'", - "python_full_version == '3.10.*'", - "python_full_version < '3.10'", + "python_full_version < '3.11'", +] + +[[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]] @@ -31,6 +39,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, ] +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + [[package]] name = "babel" version = "2.17.0" @@ -56,113 +73,108 @@ wheels = [ [[package]] name = "certifi" -version = "2025.8.3" +version = "2025.10.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/98/f3b8013223728a99b908c9344da3aa04ee6e3fa235f19409033eda92fb78/charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", size = 207695, upload-time = "2025-08-09T07:55:36.452Z" }, - { url = "https://files.pythonhosted.org/packages/21/40/5188be1e3118c82dcb7c2a5ba101b783822cfb413a0268ed3be0468532de/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", size = 147153, upload-time = "2025-08-09T07:55:38.467Z" }, - { url = "https://files.pythonhosted.org/packages/37/60/5d0d74bc1e1380f0b72c327948d9c2aca14b46a9efd87604e724260f384c/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", size = 160428, upload-time = "2025-08-09T07:55:40.072Z" }, - { url = "https://files.pythonhosted.org/packages/85/9a/d891f63722d9158688de58d050c59dc3da560ea7f04f4c53e769de5140f5/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", size = 157627, upload-time = "2025-08-09T07:55:41.706Z" }, - { url = "https://files.pythonhosted.org/packages/65/1a/7425c952944a6521a9cfa7e675343f83fd82085b8af2b1373a2409c683dc/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", size = 152388, upload-time = "2025-08-09T07:55:43.262Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c9/a2c9c2a355a8594ce2446085e2ec97fd44d323c684ff32042e2a6b718e1d/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", size = 150077, upload-time = "2025-08-09T07:55:44.903Z" }, - { url = "https://files.pythonhosted.org/packages/3b/38/20a1f44e4851aa1c9105d6e7110c9d020e093dfa5836d712a5f074a12bf7/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", size = 161631, upload-time = "2025-08-09T07:55:46.346Z" }, - { url = "https://files.pythonhosted.org/packages/a4/fa/384d2c0f57edad03d7bec3ebefb462090d8905b4ff5a2d2525f3bb711fac/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", size = 159210, upload-time = "2025-08-09T07:55:47.539Z" }, - { url = "https://files.pythonhosted.org/packages/33/9e/eca49d35867ca2db336b6ca27617deed4653b97ebf45dfc21311ce473c37/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", size = 153739, upload-time = "2025-08-09T07:55:48.744Z" }, - { url = "https://files.pythonhosted.org/packages/2a/91/26c3036e62dfe8de8061182d33be5025e2424002125c9500faff74a6735e/charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", size = 99825, upload-time = "2025-08-09T07:55:50.305Z" }, - { url = "https://files.pythonhosted.org/packages/e2/c6/f05db471f81af1fa01839d44ae2a8bfeec8d2a8b4590f16c4e7393afd323/charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", size = 107452, upload-time = "2025-08-09T07:55:51.461Z" }, - { url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483, upload-time = "2025-08-09T07:55:53.12Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520, upload-time = "2025-08-09T07:55:54.712Z" }, - { url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876, upload-time = "2025-08-09T07:55:56.024Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083, upload-time = "2025-08-09T07:55:57.582Z" }, - { url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295, upload-time = "2025-08-09T07:55:59.147Z" }, - { url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379, upload-time = "2025-08-09T07:56:00.364Z" }, - { url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018, upload-time = "2025-08-09T07:56:01.678Z" }, - { url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430, upload-time = "2025-08-09T07:56:02.87Z" }, - { url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600, upload-time = "2025-08-09T07:56:04.089Z" }, - { url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616, upload-time = "2025-08-09T07:56:05.658Z" }, - { url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108, upload-time = "2025-08-09T07:56:07.176Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, - { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, - { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, - { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, - { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, - { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, - { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, - { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, - { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, - { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, - { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, - { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, - { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, - { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, - { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, - { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, - { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, - { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, - { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, - { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, - { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, - { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, - { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, - { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, - { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, - { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ca/9a0983dd5c8e9733565cf3db4df2b0a2e9a82659fd8aa2a868ac6e4a991f/charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05", size = 207520, upload-time = "2025-08-09T07:57:11.026Z" }, - { url = "https://files.pythonhosted.org/packages/39/c6/99271dc37243a4f925b09090493fb96c9333d7992c6187f5cfe5312008d2/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e", size = 147307, upload-time = "2025-08-09T07:57:12.4Z" }, - { url = "https://files.pythonhosted.org/packages/e4/69/132eab043356bba06eb333cc2cc60c6340857d0a2e4ca6dc2b51312886b3/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99", size = 160448, upload-time = "2025-08-09T07:57:13.712Z" }, - { url = "https://files.pythonhosted.org/packages/04/9a/914d294daa4809c57667b77470533e65def9c0be1ef8b4c1183a99170e9d/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7", size = 157758, upload-time = "2025-08-09T07:57:14.979Z" }, - { url = "https://files.pythonhosted.org/packages/b0/a8/6f5bcf1bcf63cb45625f7c5cadca026121ff8a6c8a3256d8d8cd59302663/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7", size = 152487, upload-time = "2025-08-09T07:57:16.332Z" }, - { url = "https://files.pythonhosted.org/packages/c4/72/d3d0e9592f4e504f9dea08b8db270821c909558c353dc3b457ed2509f2fb/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19", size = 150054, upload-time = "2025-08-09T07:57:17.576Z" }, - { url = "https://files.pythonhosted.org/packages/20/30/5f64fe3981677fe63fa987b80e6c01042eb5ff653ff7cec1b7bd9268e54e/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312", size = 161703, upload-time = "2025-08-09T07:57:20.012Z" }, - { url = "https://files.pythonhosted.org/packages/e1/ef/dd08b2cac9284fd59e70f7d97382c33a3d0a926e45b15fc21b3308324ffd/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc", size = 159096, upload-time = "2025-08-09T07:57:21.329Z" }, - { url = "https://files.pythonhosted.org/packages/45/8c/dcef87cfc2b3f002a6478f38906f9040302c68aebe21468090e39cde1445/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34", size = 153852, upload-time = "2025-08-09T07:57:22.608Z" }, - { url = "https://files.pythonhosted.org/packages/63/86/9cbd533bd37883d467fcd1bd491b3547a3532d0fbb46de2b99feeebf185e/charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432", size = 99840, upload-time = "2025-08-09T07:57:23.883Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d6/7e805c8e5c46ff9729c49950acc4ee0aeb55efb8b3a56687658ad10c3216/charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca", size = 107438, upload-time = "2025-08-09T07:57:25.287Z" }, - { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, -] - -[[package]] -name = "click" -version = "8.1.8" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] [[package]] name = "click" version = "8.3.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.11'", - "python_full_version == '3.10.*'", -] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } wheels = [ @@ -175,13 +187,13 @@ version = "0.5.0" source = { virtual = "." } dependencies = [ { name = "httpx" }, + { name = "mcp" }, ] [package.dev-dependencies] dev = [ { name = "coverage", extra = ["toml"] }, - { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "ipython", version = "9.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "mkdocs" }, { name = "mkdocs-material" }, @@ -197,7 +209,10 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "httpx" }] +requires-dist = [ + { name = "httpx" }, + { name = "mcp", specifier = ">=0.1.0" }, +] [package.metadata.requires-dev] dev = [ @@ -227,113 +242,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.10.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987, upload-time = "2025-09-21T20:00:57.218Z" }, - { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388, upload-time = "2025-09-21T20:01:00.081Z" }, - { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148, upload-time = "2025-09-21T20:01:01.768Z" }, - { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958, upload-time = "2025-09-21T20:01:03.355Z" }, - { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819, upload-time = "2025-09-21T20:01:04.968Z" }, - { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754, upload-time = "2025-09-21T20:01:06.321Z" }, - { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860, upload-time = "2025-09-21T20:01:07.605Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877, upload-time = "2025-09-21T20:01:08.829Z" }, - { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108, upload-time = "2025-09-21T20:01:10.527Z" }, - { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752, upload-time = "2025-09-21T20:01:11.857Z" }, - { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497, upload-time = "2025-09-21T20:01:13.459Z" }, - { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392, upload-time = "2025-09-21T20:01:14.722Z" }, - { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" }, - { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" }, - { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" }, - { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" }, - { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" }, - { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" }, - { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" }, - { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" }, - { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" }, - { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" }, - { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" }, - { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" }, - { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" }, - { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" }, - { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" }, - { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" }, - { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" }, - { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" }, - { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" }, - { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" }, - { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" }, - { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, - { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, - { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, - { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, - { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, - { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, - { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, - { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, - { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, - { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, - { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, - { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, - { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, - { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, - { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, - { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, - { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, - { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, - { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, - { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, - { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, - { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, - { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, - { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" }, - { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" }, - { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" }, - { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" }, - { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" }, - { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" }, - { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" }, - { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" }, - { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" }, - { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" }, - { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" }, - { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" }, - { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" }, - { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" }, - { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" }, - { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" }, - { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" }, - { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" }, - { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" }, - { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" }, - { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978, upload-time = "2025-09-21T20:03:30.362Z" }, - { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370, upload-time = "2025-09-21T20:03:32.147Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802, upload-time = "2025-09-21T20:03:33.919Z" }, - { url = "https://files.pythonhosted.org/packages/b0/49/8a070782ce7e6b94ff6a0b6d7c65ba6bc3091d92a92cef4cd4eb0767965c/coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40", size = 246625, upload-time = "2025-09-21T20:03:36.09Z" }, - { url = "https://files.pythonhosted.org/packages/6a/92/1c1c5a9e8677ce56d42b97bdaca337b2d4d9ebe703d8c174ede52dbabd5f/coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594", size = 248399, upload-time = "2025-09-21T20:03:38.342Z" }, - { url = "https://files.pythonhosted.org/packages/c0/54/b140edee7257e815de7426d5d9846b58505dffc29795fff2dfb7f8a1c5a0/coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a", size = 245142, upload-time = "2025-09-21T20:03:40.591Z" }, - { url = "https://files.pythonhosted.org/packages/e4/9e/6d6b8295940b118e8b7083b29226c71f6154f7ff41e9ca431f03de2eac0d/coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b", size = 246284, upload-time = "2025-09-21T20:03:42.355Z" }, - { url = "https://files.pythonhosted.org/packages/db/e5/5e957ca747d43dbe4d9714358375c7546cb3cb533007b6813fc20fce37ad/coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3", size = 244353, upload-time = "2025-09-21T20:03:44.218Z" }, - { url = "https://files.pythonhosted.org/packages/9a/45/540fc5cc92536a1b783b7ef99450bd55a4b3af234aae35a18a339973ce30/coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0", size = 244430, upload-time = "2025-09-21T20:03:46.065Z" }, - { url = "https://files.pythonhosted.org/packages/75/0b/8287b2e5b38c8fe15d7e3398849bb58d382aedc0864ea0fa1820e8630491/coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f", size = 245311, upload-time = "2025-09-21T20:03:48.19Z" }, - { url = "https://files.pythonhosted.org/packages/0c/1d/29724999984740f0c86d03e6420b942439bf5bd7f54d4382cae386a9d1e9/coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431", size = 220500, upload-time = "2025-09-21T20:03:50.024Z" }, - { url = "https://files.pythonhosted.org/packages/43/11/4b1e6b129943f905ca54c339f343877b55b365ae2558806c1be4f7476ed5/coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07", size = 221408, upload-time = "2025-09-21T20:03:51.803Z" }, - { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, +version = "7.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/38/ee22495420457259d2f3390309505ea98f98a5eed40901cf62196abad006/coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050", size = 811905, upload-time = "2025-10-15T15:15:08.542Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/95/c49df0aceb5507a80b9fe5172d3d39bf23f05be40c23c8d77d556df96cec/coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31", size = 215800, upload-time = "2025-10-15T15:12:19.824Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c6/7bb46ce01ed634fff1d7bb53a54049f539971862cc388b304ff3c51b4f66/coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075", size = 216198, upload-time = "2025-10-15T15:12:22.549Z" }, + { url = "https://files.pythonhosted.org/packages/94/b2/75d9d8fbf2900268aca5de29cd0a0fe671b0f69ef88be16767cc3c828b85/coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab", size = 242953, upload-time = "2025-10-15T15:12:24.139Z" }, + { url = "https://files.pythonhosted.org/packages/65/ac/acaa984c18f440170525a8743eb4b6c960ace2dbad80dc22056a437fc3c6/coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0", size = 244766, upload-time = "2025-10-15T15:12:25.974Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0d/938d0bff76dfa4a6b228c3fc4b3e1c0e2ad4aa6200c141fcda2bd1170227/coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785", size = 246625, upload-time = "2025-10-15T15:12:27.387Z" }, + { url = "https://files.pythonhosted.org/packages/38/54/8f5f5e84bfa268df98f46b2cb396b1009734cfb1e5d6adb663d284893b32/coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591", size = 243568, upload-time = "2025-10-15T15:12:28.799Z" }, + { url = "https://files.pythonhosted.org/packages/68/30/8ba337c2877fe3f2e1af0ed7ff4be0c0c4aca44d6f4007040f3ca2255e99/coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088", size = 244665, upload-time = "2025-10-15T15:12:30.297Z" }, + { url = "https://files.pythonhosted.org/packages/cc/fb/c6f1d6d9a665536b7dde2333346f0cc41dc6a60bd1ffc10cd5c33e7eb000/coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f", size = 242681, upload-time = "2025-10-15T15:12:32.326Z" }, + { url = "https://files.pythonhosted.org/packages/be/38/1b532319af5f991fa153c20373291dc65c2bf532af7dbcffdeef745c8f79/coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866", size = 242912, upload-time = "2025-10-15T15:12:34.079Z" }, + { url = "https://files.pythonhosted.org/packages/67/3d/f39331c60ef6050d2a861dc1b514fa78f85f792820b68e8c04196ad733d6/coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841", size = 243559, upload-time = "2025-10-15T15:12:35.809Z" }, + { url = "https://files.pythonhosted.org/packages/4b/55/cb7c9df9d0495036ce582a8a2958d50c23cd73f84a23284bc23bd4711a6f/coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf", size = 218266, upload-time = "2025-10-15T15:12:37.429Z" }, + { url = "https://files.pythonhosted.org/packages/68/a8/b79cb275fa7bd0208767f89d57a1b5f6ba830813875738599741b97c2e04/coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969", size = 219169, upload-time = "2025-10-15T15:12:39.25Z" }, + { url = "https://files.pythonhosted.org/packages/49/3a/ee1074c15c408ddddddb1db7dd904f6b81bc524e01f5a1c5920e13dbde23/coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847", size = 215912, upload-time = "2025-10-15T15:12:40.665Z" }, + { url = "https://files.pythonhosted.org/packages/70/c4/9f44bebe5cb15f31608597b037d78799cc5f450044465bcd1ae8cb222fe1/coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc", size = 216310, upload-time = "2025-10-15T15:12:42.461Z" }, + { url = "https://files.pythonhosted.org/packages/42/01/5e06077cfef92d8af926bdd86b84fb28bf9bc6ad27343d68be9b501d89f2/coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0", size = 246706, upload-time = "2025-10-15T15:12:44.001Z" }, + { url = "https://files.pythonhosted.org/packages/40/b8/7a3f1f33b35cc4a6c37e759137533119560d06c0cc14753d1a803be0cd4a/coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7", size = 248634, upload-time = "2025-10-15T15:12:45.768Z" }, + { url = "https://files.pythonhosted.org/packages/7a/41/7f987eb33de386bc4c665ab0bf98d15fcf203369d6aacae74f5dd8ec489a/coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623", size = 250741, upload-time = "2025-10-15T15:12:47.222Z" }, + { url = "https://files.pythonhosted.org/packages/23/c1/a4e0ca6a4e83069fb8216b49b30a7352061ca0cb38654bd2dc96b7b3b7da/coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287", size = 246837, upload-time = "2025-10-15T15:12:48.904Z" }, + { url = "https://files.pythonhosted.org/packages/5d/03/ced062a17f7c38b4728ff76c3acb40d8465634b20b4833cdb3cc3a74e115/coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552", size = 248429, upload-time = "2025-10-15T15:12:50.73Z" }, + { url = "https://files.pythonhosted.org/packages/97/af/a7c6f194bb8c5a2705ae019036b8fe7f49ea818d638eedb15fdb7bed227c/coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de", size = 246490, upload-time = "2025-10-15T15:12:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c3/aab4df02b04a8fde79068c3c41ad7a622b0ef2b12e1ed154da986a727c3f/coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601", size = 246208, upload-time = "2025-10-15T15:12:54.586Z" }, + { url = "https://files.pythonhosted.org/packages/30/d8/e282ec19cd658238d60ed404f99ef2e45eed52e81b866ab1518c0d4163cf/coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e", size = 247126, upload-time = "2025-10-15T15:12:56.485Z" }, + { url = "https://files.pythonhosted.org/packages/d1/17/a635fa07fac23adb1a5451ec756216768c2767efaed2e4331710342a3399/coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c", size = 218314, upload-time = "2025-10-15T15:12:58.365Z" }, + { url = "https://files.pythonhosted.org/packages/2a/29/2ac1dfcdd4ab9a70026edc8d715ece9b4be9a1653075c658ee6f271f394d/coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9", size = 219203, upload-time = "2025-10-15T15:12:59.902Z" }, + { url = "https://files.pythonhosted.org/packages/03/21/5ce8b3a0133179115af4c041abf2ee652395837cb896614beb8ce8ddcfd9/coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745", size = 217879, upload-time = "2025-10-15T15:13:01.35Z" }, + { url = "https://files.pythonhosted.org/packages/c4/db/86f6906a7c7edc1a52b2c6682d6dd9be775d73c0dfe2b84f8923dfea5784/coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1", size = 216098, upload-time = "2025-10-15T15:13:02.916Z" }, + { url = "https://files.pythonhosted.org/packages/21/54/e7b26157048c7ba555596aad8569ff903d6cd67867d41b75287323678ede/coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007", size = 216331, upload-time = "2025-10-15T15:13:04.403Z" }, + { url = "https://files.pythonhosted.org/packages/b9/19/1ce6bf444f858b83a733171306134a0544eaddf1ca8851ede6540a55b2ad/coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46", size = 247825, upload-time = "2025-10-15T15:13:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/71/0b/d3bcbbc259fcced5fb67c5d78f6e7ee965f49760c14afd931e9e663a83b2/coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893", size = 250573, upload-time = "2025-10-15T15:13:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/58/8d/b0ff3641a320abb047258d36ed1c21d16be33beed4152628331a1baf3365/coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115", size = 251706, upload-time = "2025-10-15T15:13:09.4Z" }, + { url = "https://files.pythonhosted.org/packages/59/c8/5a586fe8c7b0458053d9c687f5cff515a74b66c85931f7fe17a1c958b4ac/coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415", size = 248221, upload-time = "2025-10-15T15:13:10.964Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ff/3a25e3132804ba44cfa9a778cdf2b73dbbe63ef4b0945e39602fc896ba52/coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186", size = 249624, upload-time = "2025-10-15T15:13:12.5Z" }, + { url = "https://files.pythonhosted.org/packages/c5/12/ff10c8ce3895e1b17a73485ea79ebc1896a9e466a9d0f4aef63e0d17b718/coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d", size = 247744, upload-time = "2025-10-15T15:13:14.554Z" }, + { url = "https://files.pythonhosted.org/packages/16/02/d500b91f5471b2975947e0629b8980e5e90786fe316b6d7299852c1d793d/coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d", size = 247325, upload-time = "2025-10-15T15:13:16.438Z" }, + { url = "https://files.pythonhosted.org/packages/77/11/dee0284fbbd9cd64cfce806b827452c6df3f100d9e66188e82dfe771d4af/coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2", size = 249180, upload-time = "2025-10-15T15:13:17.959Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/cdf1def928f0a150a057cab03286774e73e29c2395f0d30ce3d9e9f8e697/coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5", size = 218479, upload-time = "2025-10-15T15:13:19.608Z" }, + { url = "https://files.pythonhosted.org/packages/ff/55/e5884d55e031da9c15b94b90a23beccc9d6beee65e9835cd6da0a79e4f3a/coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0", size = 219290, upload-time = "2025-10-15T15:13:21.593Z" }, + { url = "https://files.pythonhosted.org/packages/23/a8/faa930cfc71c1d16bc78f9a19bb73700464f9c331d9e547bfbc1dbd3a108/coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad", size = 217924, upload-time = "2025-10-15T15:13:23.39Z" }, + { url = "https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1", size = 216129, upload-time = "2025-10-15T15:13:25.371Z" }, + { url = "https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be", size = 216380, upload-time = "2025-10-15T15:13:26.976Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f5/3da9cc9596708273385189289c0e4d8197d37a386bdf17619013554b3447/coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d", size = 247375, upload-time = "2025-10-15T15:13:28.923Z" }, + { url = "https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82", size = 249978, upload-time = "2025-10-15T15:13:30.525Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8c/042dede2e23525e863bf1ccd2b92689692a148d8b5fd37c37899ba882645/coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52", size = 251253, upload-time = "2025-10-15T15:13:32.174Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a9/3c58df67bfa809a7bddd786356d9c5283e45d693edb5f3f55d0986dd905a/coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b", size = 247591, upload-time = "2025-10-15T15:13:34.147Z" }, + { url = "https://files.pythonhosted.org/packages/26/5b/c7f32efd862ee0477a18c41e4761305de6ddd2d49cdeda0c1116227570fd/coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4", size = 249411, upload-time = "2025-10-15T15:13:38.425Z" }, + { url = "https://files.pythonhosted.org/packages/76/b5/78cb4f1e86c1611431c990423ec0768122905b03837e1b4c6a6f388a858b/coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd", size = 247303, upload-time = "2025-10-15T15:13:40.464Z" }, + { url = "https://files.pythonhosted.org/packages/87/c9/23c753a8641a330f45f221286e707c427e46d0ffd1719b080cedc984ec40/coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc", size = 247157, upload-time = "2025-10-15T15:13:42.087Z" }, + { url = "https://files.pythonhosted.org/packages/c5/42/6e0cc71dc8a464486e944a4fa0d85bdec031cc2969e98ed41532a98336b9/coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48", size = 248921, upload-time = "2025-10-15T15:13:43.715Z" }, + { url = "https://files.pythonhosted.org/packages/e8/1c/743c2ef665e6858cccb0f84377dfe3a4c25add51e8c7ef19249be92465b6/coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040", size = 218526, upload-time = "2025-10-15T15:13:45.336Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05", size = 219317, upload-time = "2025-10-15T15:13:47.401Z" }, + { url = "https://files.pythonhosted.org/packages/97/54/47db81dcbe571a48a298f206183ba8a7ba79200a37cd0d9f4788fcd2af4a/coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a", size = 217948, upload-time = "2025-10-15T15:13:49.096Z" }, + { url = "https://files.pythonhosted.org/packages/e5/8b/cb68425420154e7e2a82fd779a8cc01549b6fa83c2ad3679cd6c088ebd07/coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b", size = 216837, upload-time = "2025-10-15T15:13:51.09Z" }, + { url = "https://files.pythonhosted.org/packages/33/55/9d61b5765a025685e14659c8d07037247de6383c0385757544ffe4606475/coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37", size = 217061, upload-time = "2025-10-15T15:13:52.747Z" }, + { url = "https://files.pythonhosted.org/packages/52/85/292459c9186d70dcec6538f06ea251bc968046922497377bf4a1dc9a71de/coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de", size = 258398, upload-time = "2025-10-15T15:13:54.45Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e2/46edd73fb8bf51446c41148d81944c54ed224854812b6ca549be25113ee0/coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f", size = 260574, upload-time = "2025-10-15T15:13:56.145Z" }, + { url = "https://files.pythonhosted.org/packages/07/5e/1df469a19007ff82e2ca8fe509822820a31e251f80ee7344c34f6cd2ec43/coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c", size = 262797, upload-time = "2025-10-15T15:13:58.635Z" }, + { url = "https://files.pythonhosted.org/packages/f9/50/de216b31a1434b94d9b34a964c09943c6be45069ec704bfc379d8d89a649/coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa", size = 257361, upload-time = "2025-10-15T15:14:00.409Z" }, + { url = "https://files.pythonhosted.org/packages/82/1e/3f9f8344a48111e152e0fd495b6fff13cc743e771a6050abf1627a7ba918/coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740", size = 260349, upload-time = "2025-10-15T15:14:02.188Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/3f52741f9e7d82124272f3070bbe316006a7de1bad1093f88d59bfc6c548/coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef", size = 258114, upload-time = "2025-10-15T15:14:03.907Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8b/918f0e15f0365d50d3986bbd3338ca01178717ac5678301f3f547b6619e6/coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0", size = 256723, upload-time = "2025-10-15T15:14:06.324Z" }, + { url = "https://files.pythonhosted.org/packages/44/9e/7776829f82d3cf630878a7965a7d70cc6ca94f22c7d20ec4944f7148cb46/coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca", size = 259238, upload-time = "2025-10-15T15:14:08.002Z" }, + { url = "https://files.pythonhosted.org/packages/9a/b8/49cf253e1e7a3bedb85199b201862dd7ca4859f75b6cf25ffa7298aa0760/coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2", size = 219180, upload-time = "2025-10-15T15:14:09.786Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e1/1a541703826be7ae2125a0fb7f821af5729d56bb71e946e7b933cc7a89a4/coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268", size = 220241, upload-time = "2025-10-15T15:14:11.471Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/5ee0e0a08621140fd418ec4020f595b4d52d7eb429ae6a0c6542b4ba6f14/coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836", size = 218510, upload-time = "2025-10-15T15:14:13.46Z" }, + { url = "https://files.pythonhosted.org/packages/f4/06/e923830c1985ce808e40a3fa3eb46c13350b3224b7da59757d37b6ce12b8/coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497", size = 216110, upload-time = "2025-10-15T15:14:15.157Z" }, + { url = "https://files.pythonhosted.org/packages/42/82/cdeed03bfead45203fb651ed756dfb5266028f5f939e7f06efac4041dad5/coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e", size = 216395, upload-time = "2025-10-15T15:14:16.863Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ba/e1c80caffc3199aa699813f73ff097bc2df7b31642bdbc7493600a8f1de5/coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1", size = 247433, upload-time = "2025-10-15T15:14:18.589Z" }, + { url = "https://files.pythonhosted.org/packages/80/c0/5b259b029694ce0a5bbc1548834c7ba3db41d3efd3474489d7efce4ceb18/coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca", size = 249970, upload-time = "2025-10-15T15:14:20.307Z" }, + { url = "https://files.pythonhosted.org/packages/8c/86/171b2b5e1aac7e2fd9b43f7158b987dbeb95f06d1fbecad54ad8163ae3e8/coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd", size = 251324, upload-time = "2025-10-15T15:14:22.419Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/7e10414d343385b92024af3932a27a1caf75c6e27ee88ba211221ff1a145/coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43", size = 247445, upload-time = "2025-10-15T15:14:24.205Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3b/e4f966b21f5be8c4bf86ad75ae94efa0de4c99c7bbb8114476323102e345/coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777", size = 249324, upload-time = "2025-10-15T15:14:26.234Z" }, + { url = "https://files.pythonhosted.org/packages/00/a2/8479325576dfcd909244d0df215f077f47437ab852ab778cfa2f8bf4d954/coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2", size = 247261, upload-time = "2025-10-15T15:14:28.42Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d8/3a9e2db19d94d65771d0f2e21a9ea587d11b831332a73622f901157cc24b/coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d", size = 247092, upload-time = "2025-10-15T15:14:30.784Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b1/bbca3c472544f9e2ad2d5116b2379732957048be4b93a9c543fcd0207e5f/coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4", size = 248755, upload-time = "2025-10-15T15:14:32.585Z" }, + { url = "https://files.pythonhosted.org/packages/89/49/638d5a45a6a0f00af53d6b637c87007eb2297042186334e9923a61aa8854/coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721", size = 218793, upload-time = "2025-10-15T15:14:34.972Z" }, + { url = "https://files.pythonhosted.org/packages/30/cc/b675a51f2d068adb3cdf3799212c662239b0ca27f4691d1fff81b92ea850/coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad", size = 219587, upload-time = "2025-10-15T15:14:37.047Z" }, + { url = "https://files.pythonhosted.org/packages/93/98/5ac886876026de04f00820e5094fe22166b98dcb8b426bf6827aaf67048c/coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479", size = 218168, upload-time = "2025-10-15T15:14:38.861Z" }, + { url = "https://files.pythonhosted.org/packages/14/d1/b4145d35b3e3ecf4d917e97fc8895bcf027d854879ba401d9ff0f533f997/coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f", size = 216850, upload-time = "2025-10-15T15:14:40.651Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d1/7f645fc2eccd318369a8a9948acc447bb7c1ade2911e31d3c5620544c22b/coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e", size = 217071, upload-time = "2025-10-15T15:14:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/54/7d/64d124649db2737ceced1dfcbdcb79898d5868d311730f622f8ecae84250/coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44", size = 258570, upload-time = "2025-10-15T15:14:44.542Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3f/6f5922f80dc6f2d8b2c6f974835c43f53eb4257a7797727e6ca5b7b2ec1f/coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3", size = 260738, upload-time = "2025-10-15T15:14:46.436Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5f/9e883523c4647c860b3812b417a2017e361eca5b635ee658387dc11b13c1/coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b", size = 262994, upload-time = "2025-10-15T15:14:48.3Z" }, + { url = "https://files.pythonhosted.org/packages/07/bb/43b5a8e94c09c8bf51743ffc65c4c841a4ca5d3ed191d0a6919c379a1b83/coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d", size = 257282, upload-time = "2025-10-15T15:14:50.236Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e5/0ead8af411411330b928733e1d201384b39251a5f043c1612970310e8283/coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2", size = 260430, upload-time = "2025-10-15T15:14:52.413Z" }, + { url = "https://files.pythonhosted.org/packages/ae/66/03dd8bb0ba5b971620dcaac145461950f6d8204953e535d2b20c6b65d729/coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e", size = 258190, upload-time = "2025-10-15T15:14:54.268Z" }, + { url = "https://files.pythonhosted.org/packages/45/ae/28a9cce40bf3174426cb2f7e71ee172d98e7f6446dff936a7ccecee34b14/coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996", size = 256658, upload-time = "2025-10-15T15:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/5c/7c/3a44234a8599513684bfc8684878fd7b126c2760f79712bb78c56f19efc4/coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11", size = 259342, upload-time = "2025-10-15T15:14:58.538Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e6/0108519cba871af0351725ebdb8660fd7a0fe2ba3850d56d32490c7d9b4b/coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73", size = 219568, upload-time = "2025-10-15T15:15:00.382Z" }, + { url = "https://files.pythonhosted.org/packages/c9/76/44ba876e0942b4e62fdde23ccb029ddb16d19ba1bef081edd00857ba0b16/coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547", size = 220687, upload-time = "2025-10-15T15:15:02.322Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0c/0df55ecb20d0d0ed5c322e10a441775e1a3a5d78c60f0c4e1abfe6fcf949/coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3", size = 218711, upload-time = "2025-10-15T15:15:04.575Z" }, + { url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", size = 207761, upload-time = "2025-10-15T15:15:06.439Z" }, ] [package.optional-dependencies] @@ -433,58 +436,30 @@ wheels = [ ] [[package]] -name = "idna" -version = "3.10" +name = "httpx-sse" +version = "0.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, ] [[package]] -name = "importlib-metadata" -version = "8.7.0" +name = "idna" +version = "3.11" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, -] - -[[package]] -name = "ipython" -version = "8.18.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version < '3.10'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, - { name = "jedi", marker = "python_full_version < '3.10'" }, - { name = "matplotlib-inline", marker = "python_full_version < '3.10'" }, - { name = "pexpect", marker = "python_full_version < '3.10' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version < '3.10'" }, - { name = "pygments", marker = "python_full_version < '3.10'" }, - { name = "stack-data", marker = "python_full_version < '3.10'" }, - { name = "traitlets", marker = "python_full_version < '3.10'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/b9/3ba6c45a6df813c09a48bac313c22ff83efa26cbb55011218d925a46e2ad/ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27", size = 5486330, upload-time = "2023-11-27T09:58:34.596Z" } +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/47/6b/d9fdcdef2eb6a23f391251fde8781c38d42acd82abe84d054cb74f7863b0/ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397", size = 808161, upload-time = "2023-11-27T09:58:30.538Z" }, + { 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]] @@ -492,20 +467,20 @@ name = "ipython" version = "8.37.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.10.*'", + "python_full_version < '3.11'", ] dependencies = [ - { name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version == '3.10.*'" }, - { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, - { name = "jedi", marker = "python_full_version == '3.10.*'" }, - { name = "matplotlib-inline", marker = "python_full_version == '3.10.*'" }, - { name = "pexpect", marker = "python_full_version == '3.10.*' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version == '3.10.*'" }, - { name = "pygments", marker = "python_full_version == '3.10.*'" }, - { name = "stack-data", marker = "python_full_version == '3.10.*'" }, - { name = "traitlets", marker = "python_full_version == '3.10.*'" }, - { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.11'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi", marker = "python_full_version < '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, + { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "stack-data", marker = "python_full_version < '3.11'" }, + { name = "traitlets", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/85/31/10ac88f3357fc276dc8a64e8880c82e80e7459326ae1d0a211b40abf6665/ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216", size = 5606088, upload-time = "2025-05-31T16:39:09.613Z" } wheels = [ @@ -574,12 +549,36 @@ wheels = [ ] [[package]] -name = "markdown" -version = "3.9" +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] + +[[package]] +name = "markdown" +version = "3.9" +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, @@ -668,17 +667,6 @@ wheels = [ { 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" }, - { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" }, - { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" }, - { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543, upload-time = "2025-09-27T18:37:32.168Z" }, - { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585, upload-time = "2025-09-27T18:37:33.166Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387, upload-time = "2025-09-27T18:37:34.185Z" }, - { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133, upload-time = "2025-09-27T18:37:35.138Z" }, - { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588, upload-time = "2025-09-27T18:37:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566, upload-time = "2025-09-27T18:37:37.09Z" }, - { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053, upload-time = "2025-09-27T18:37:38.054Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928, upload-time = "2025-09-27T18:37:39.037Z" }, ] [[package]] @@ -693,6 +681,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" }, ] +[[package]] +name = "mcp" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/e0/fe34ce16ea2bacce489ab859abd1b47ae28b438c3ef60b9c5eee6c02592f/mcp-1.18.0.tar.gz", hash = "sha256:aa278c44b1efc0a297f53b68df865b988e52dd08182d702019edcf33a8e109f6", size = 482926, upload-time = "2025-10-16T19:19:55.125Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/44/f5970e3e899803823826283a70b6003afd46f28e082544407e24575eccd3/mcp-1.18.0-py3-none-any.whl", hash = "sha256:42f10c270de18e7892fdf9da259029120b1ea23964ff688248c69db9d72b1d0a", size = 168762, upload-time = "2025-10-16T19:19:53.2Z" }, +] + [[package]] name = "mergedeep" version = "1.3.4" @@ -707,11 +717,9 @@ name = "mkdocs" version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "click", version = "8.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "click" }, { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "ghp-import" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "jinja2" }, { name = "markdown" }, { name = "markupsafe" }, @@ -747,7 +755,6 @@ name = "mkdocs-get-deps" version = "0.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "mergedeep" }, { name = "platformdirs" }, { name = "pyyaml" }, @@ -759,7 +766,7 @@ wheels = [ [[package]] name = "mkdocs-material" -version = "9.6.21" +version = "9.6.22" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, @@ -774,9 +781,9 @@ dependencies = [ { name = "pymdown-extensions" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/d5/ab83ca9aa314954b0a9e8849780bdd01866a3cfcb15ffb7e3a61ca06ff0b/mkdocs_material-9.6.21.tar.gz", hash = "sha256:b01aa6d2731322438056f360f0e623d3faae981f8f2d8c68b1b973f4f2657870", size = 4043097, upload-time = "2025-09-30T19:11:27.517Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/5d/317e37b6c43325cb376a1d6439df9cc743b8ee41c84603c2faf7286afc82/mkdocs_material-9.6.22.tar.gz", hash = "sha256:87c158b0642e1ada6da0cbd798a3389b0bc5516b90e5ece4a0fb939f00bacd1c", size = 4044968, upload-time = "2025-10-15T09:21:15.409Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/4f/98681c2030375fe9b057dbfb9008b68f46c07dddf583f4df09bf8075e37f/mkdocs_material-9.6.21-py3-none-any.whl", hash = "sha256:aa6a5ab6fb4f6d381588ac51da8782a4d3757cb3d1b174f81a2ec126e1f22c92", size = 9203097, upload-time = "2025-09-30T19:11:24.063Z" }, + { url = "https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl", hash = "sha256:14ac5f72d38898b2f98ac75a5531aaca9366eaa427b0f49fc2ecf04d99b7ad84", size = 9206252, upload-time = "2025-10-15T09:21:12.175Z" }, ] [[package]] @@ -793,7 +800,6 @@ name = "mkdocstrings" version = "0.30.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "jinja2" }, { name = "markdown" }, { name = "markupsafe" }, @@ -868,12 +874,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" }, { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" }, { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" }, - { url = "https://files.pythonhosted.org/packages/3f/a6/490ff491d8ecddf8ab91762d4f67635040202f76a44171420bcbe38ceee5/mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b", size = 12807230, upload-time = "2025-09-19T00:09:49.471Z" }, - { url = "https://files.pythonhosted.org/packages/eb/2e/60076fc829645d167ece9e80db9e8375648d210dab44cc98beb5b322a826/mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133", size = 11895666, upload-time = "2025-09-19T00:10:53.678Z" }, - { url = "https://files.pythonhosted.org/packages/97/4a/1e2880a2a5dda4dc8d9ecd1a7e7606bc0b0e14813637eeda40c38624e037/mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6", size = 12499608, upload-time = "2025-09-19T00:09:36.204Z" }, - { url = "https://files.pythonhosted.org/packages/00/81/a117f1b73a3015b076b20246b1f341c34a578ebd9662848c6b80ad5c4138/mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac", size = 13244551, upload-time = "2025-09-19T00:10:17.531Z" }, - { url = "https://files.pythonhosted.org/packages/9b/61/b9f48e1714ce87c7bf0358eb93f60663740ebb08f9ea886ffc670cea7933/mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b", size = 13491552, upload-time = "2025-09-19T00:10:13.753Z" }, - { url = "https://files.pythonhosted.org/packages/c9/66/b2c0af3b684fa80d1b27501a8bdd3d2daa467ea3992a8aa612f5ca17c2db/mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0", size = 9765635, upload-time = "2025-09-19T00:10:30.993Z" }, { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, ] @@ -945,11 +945,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.4.0" +version = "4.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, ] [[package]] @@ -975,68 +975,55 @@ wheels = [ [[package]] name = "psycopg2-binary" -version = "2.9.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload-time = "2024-10-16T11:24:58.126Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/81/331257dbf2801cdb82105306042f7a1637cc752f65f2bb688188e0de5f0b/psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f", size = 3043397, upload-time = "2024-10-16T11:18:58.647Z" }, - { url = "https://files.pythonhosted.org/packages/e7/9a/7f4f2f031010bbfe6a02b4a15c01e12eb6b9b7b358ab33229f28baadbfc1/psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906", size = 3274806, upload-time = "2024-10-16T11:19:03.935Z" }, - { url = "https://files.pythonhosted.org/packages/e5/57/8ddd4b374fa811a0b0a0f49b6abad1cde9cb34df73ea3348cc283fcd70b4/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92", size = 2851361, upload-time = "2024-10-16T11:19:07.277Z" }, - { url = "https://files.pythonhosted.org/packages/f9/66/d1e52c20d283f1f3a8e7e5c1e06851d432f123ef57b13043b4f9b21ffa1f/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007", size = 3080836, upload-time = "2024-10-16T11:19:11.033Z" }, - { url = "https://files.pythonhosted.org/packages/a0/cb/592d44a9546aba78f8a1249021fe7c59d3afb8a0ba51434d6610cc3462b6/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0", size = 3264552, upload-time = "2024-10-16T11:19:14.606Z" }, - { url = "https://files.pythonhosted.org/packages/64/33/c8548560b94b7617f203d7236d6cdf36fe1a5a3645600ada6efd79da946f/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4", size = 3019789, upload-time = "2024-10-16T11:19:18.889Z" }, - { url = "https://files.pythonhosted.org/packages/b0/0e/c2da0db5bea88a3be52307f88b75eec72c4de62814cbe9ee600c29c06334/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1", size = 2871776, upload-time = "2024-10-16T11:19:23.023Z" }, - { url = "https://files.pythonhosted.org/packages/15/d7/774afa1eadb787ddf41aab52d4c62785563e29949613c958955031408ae6/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5", size = 2820959, upload-time = "2024-10-16T11:19:26.906Z" }, - { url = "https://files.pythonhosted.org/packages/5e/ed/440dc3f5991a8c6172a1cde44850ead0e483a375277a1aef7cfcec00af07/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5", size = 2919329, upload-time = "2024-10-16T11:19:30.027Z" }, - { url = "https://files.pythonhosted.org/packages/03/be/2cc8f4282898306732d2ae7b7378ae14e8df3c1231b53579efa056aae887/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53", size = 2957659, upload-time = "2024-10-16T11:19:32.864Z" }, - { url = "https://files.pythonhosted.org/packages/d0/12/fb8e4f485d98c570e00dad5800e9a2349cfe0f71a767c856857160d343a5/psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b", size = 1024605, upload-time = "2024-10-16T11:19:35.462Z" }, - { url = "https://files.pythonhosted.org/packages/22/4f/217cd2471ecf45d82905dd09085e049af8de6cfdc008b6663c3226dc1c98/psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1", size = 1163817, upload-time = "2024-10-16T11:19:37.384Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8f/9feb01291d0d7a0a4c6a6bab24094135c2b59c6a81943752f632c75896d6/psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", size = 3043397, upload-time = "2024-10-16T11:19:40.033Z" }, - { url = "https://files.pythonhosted.org/packages/15/30/346e4683532011561cd9c8dfeac6a8153dd96452fee0b12666058ab7893c/psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", size = 3274806, upload-time = "2024-10-16T11:19:43.5Z" }, - { url = "https://files.pythonhosted.org/packages/66/6e/4efebe76f76aee7ec99166b6c023ff8abdc4e183f7b70913d7c047701b79/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", size = 2851370, upload-time = "2024-10-16T11:19:46.986Z" }, - { url = "https://files.pythonhosted.org/packages/7f/fd/ff83313f86b50f7ca089b161b8e0a22bb3c319974096093cd50680433fdb/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", size = 3080780, upload-time = "2024-10-16T11:19:50.242Z" }, - { url = "https://files.pythonhosted.org/packages/e6/c4/bfadd202dcda8333a7ccafdc51c541dbdfce7c2c7cda89fa2374455d795f/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", size = 3264583, upload-time = "2024-10-16T11:19:54.424Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f1/09f45ac25e704ac954862581f9f9ae21303cc5ded3d0b775532b407f0e90/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", size = 3019831, upload-time = "2024-10-16T11:19:57.762Z" }, - { url = "https://files.pythonhosted.org/packages/9e/2e/9beaea078095cc558f215e38f647c7114987d9febfc25cb2beed7c3582a5/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", size = 2871822, upload-time = "2024-10-16T11:20:04.693Z" }, - { url = "https://files.pythonhosted.org/packages/01/9e/ef93c5d93f3dc9fc92786ffab39e323b9aed066ba59fdc34cf85e2722271/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", size = 2820975, upload-time = "2024-10-16T11:20:11.401Z" }, - { url = "https://files.pythonhosted.org/packages/a5/f0/049e9631e3268fe4c5a387f6fc27e267ebe199acf1bc1bc9cbde4bd6916c/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", size = 2919320, upload-time = "2024-10-16T11:20:17.959Z" }, - { url = "https://files.pythonhosted.org/packages/dc/9a/bcb8773b88e45fb5a5ea8339e2104d82c863a3b8558fbb2aadfe66df86b3/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", size = 2957617, upload-time = "2024-10-16T11:20:24.711Z" }, - { url = "https://files.pythonhosted.org/packages/e2/6b/144336a9bf08a67d217b3af3246abb1d027095dab726f0687f01f43e8c03/psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", size = 1024618, upload-time = "2024-10-16T11:20:27.718Z" }, - { url = "https://files.pythonhosted.org/packages/61/69/3b3d7bd583c6d3cbe5100802efa5beacaacc86e37b653fc708bf3d6853b8/psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", size = 1163816, upload-time = "2024-10-16T11:20:30.777Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/465cc9795cf76f6d329efdafca74693714556ea3891813701ac1fee87545/psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", size = 3044771, upload-time = "2024-10-16T11:20:35.234Z" }, - { url = "https://files.pythonhosted.org/packages/8b/31/6d225b7b641a1a2148e3ed65e1aa74fc86ba3fee850545e27be9e1de893d/psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", size = 3275336, upload-time = "2024-10-16T11:20:38.742Z" }, - { url = "https://files.pythonhosted.org/packages/30/b7/a68c2b4bff1cbb1728e3ec864b2d92327c77ad52edcd27922535a8366f68/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", size = 2851637, upload-time = "2024-10-16T11:20:42.145Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b1/cfedc0e0e6f9ad61f8657fd173b2f831ce261c02a08c0b09c652b127d813/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", size = 3082097, upload-time = "2024-10-16T11:20:46.185Z" }, - { url = "https://files.pythonhosted.org/packages/18/ed/0a8e4153c9b769f59c02fb5e7914f20f0b2483a19dae7bf2db54b743d0d0/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", size = 3264776, upload-time = "2024-10-16T11:20:50.879Z" }, - { url = "https://files.pythonhosted.org/packages/10/db/d09da68c6a0cdab41566b74e0a6068a425f077169bed0946559b7348ebe9/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", size = 3020968, upload-time = "2024-10-16T11:20:56.819Z" }, - { url = "https://files.pythonhosted.org/packages/94/28/4d6f8c255f0dfffb410db2b3f9ac5218d959a66c715c34cac31081e19b95/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", size = 2872334, upload-time = "2024-10-16T11:21:02.411Z" }, - { url = "https://files.pythonhosted.org/packages/05/f7/20d7bf796593c4fea95e12119d6cc384ff1f6141a24fbb7df5a668d29d29/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", size = 2822722, upload-time = "2024-10-16T11:21:09.01Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e4/0c407ae919ef626dbdb32835a03b6737013c3cc7240169843965cada2bdf/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", size = 2920132, upload-time = "2024-10-16T11:21:16.339Z" }, - { url = "https://files.pythonhosted.org/packages/2d/70/aa69c9f69cf09a01da224909ff6ce8b68faeef476f00f7ec377e8f03be70/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", size = 2959312, upload-time = "2024-10-16T11:21:25.584Z" }, - { url = "https://files.pythonhosted.org/packages/d3/bd/213e59854fafe87ba47814bf413ace0dcee33a89c8c8c814faca6bc7cf3c/psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", size = 1025191, upload-time = "2024-10-16T11:21:29.912Z" }, - { url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", size = 1164031, upload-time = "2024-10-16T11:21:34.211Z" }, - { url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", size = 3044699, upload-time = "2024-10-16T11:21:42.841Z" }, - { url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", size = 3275245, upload-time = "2024-10-16T11:21:51.989Z" }, - { url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", size = 2851631, upload-time = "2024-10-16T11:21:57.584Z" }, - { url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", size = 3082140, upload-time = "2024-10-16T11:22:02.005Z" }, - { url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", size = 3264762, upload-time = "2024-10-16T11:22:06.412Z" }, - { url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", size = 3020967, upload-time = "2024-10-16T11:22:11.583Z" }, - { url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", size = 2872326, upload-time = "2024-10-16T11:22:16.406Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712, upload-time = "2024-10-16T11:22:21.366Z" }, - { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155, upload-time = "2024-10-16T11:22:25.684Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356, upload-time = "2024-10-16T11:22:30.562Z" }, - { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224, upload-time = "2025-01-04T20:09:19.234Z" }, - { url = "https://files.pythonhosted.org/packages/a2/bc/e77648009b6e61af327c607543f65fdf25bcfb4100f5a6f3bdb62ddac03c/psycopg2_binary-2.9.10-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b", size = 3043437, upload-time = "2024-10-16T11:23:42.946Z" }, - { url = "https://files.pythonhosted.org/packages/e0/e8/5a12211a1f5b959f3e3ccd342eace60c1f26422f53e06d687821dc268780/psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc", size = 2851340, upload-time = "2024-10-16T11:23:50.038Z" }, - { url = "https://files.pythonhosted.org/packages/47/ed/5932b0458a7fc61237b653df050513c8d18a6f4083cc7f90dcef967f7bce/psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697", size = 3080905, upload-time = "2024-10-16T11:23:57.932Z" }, - { url = "https://files.pythonhosted.org/packages/71/df/8047d85c3d23864aca4613c3be1ea0fe61dbe4e050a89ac189f9dce4403e/psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481", size = 3264640, upload-time = "2024-10-16T11:24:06.122Z" }, - { url = "https://files.pythonhosted.org/packages/f3/de/6157e4ef242920e8f2749f7708d5cc8815414bdd4a27a91996e7cd5c80df/psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648", size = 3019812, upload-time = "2024-10-16T11:24:17.025Z" }, - { url = "https://files.pythonhosted.org/packages/25/f9/0fc49efd2d4d6db3a8d0a3f5749b33a0d3fdd872cad49fbf5bfce1c50027/psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d", size = 2871933, upload-time = "2024-10-16T11:24:24.858Z" }, - { url = "https://files.pythonhosted.org/packages/57/bc/2ed1bd182219065692ed458d218d311b0b220b20662d25d913bc4e8d3549/psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30", size = 2820990, upload-time = "2024-10-16T11:24:29.571Z" }, - { url = "https://files.pythonhosted.org/packages/71/2a/43f77a9b8ee0b10e2de784d97ddc099d9fe0d9eec462a006e4d2cc74756d/psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c", size = 2919352, upload-time = "2024-10-16T11:24:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/57/86/d2943df70469e6afab3b5b8e1367fccc61891f46de436b24ddee6f2c8404/psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287", size = 2957614, upload-time = "2024-10-16T11:24:44.423Z" }, - { url = "https://files.pythonhosted.org/packages/85/21/195d69371330983aa16139e60ba855d0a18164c9295f3a3696be41bbcd54/psycopg2_binary-2.9.10-cp39-cp39-win32.whl", hash = "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8", size = 1025341, upload-time = "2024-10-16T11:24:48.056Z" }, - { url = "https://files.pythonhosted.org/packages/ad/53/73196ebc19d6fbfc22427b982fbc98698b7b9c361e5e7707e3a3247cf06d/psycopg2_binary-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5", size = 1163958, upload-time = "2024-10-16T11:24:51.882Z" }, +version = "2.9.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba424643dc87348c6f1754dd9f48e80fdc1b9f7ca5c3a7c213/psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c", size = 379620, upload-time = "2025-10-10T11:14:48.041Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/f2/8e377d29c2ecf99f6062d35ea606b036e8800720eccfec5fe3dd672c2b24/psycopg2_binary-2.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6fe6b47d0b42ce1c9f1fa3e35bb365011ca22e39db37074458f27921dca40f2", size = 3756506, upload-time = "2025-10-10T11:10:30.144Z" }, + { url = "https://files.pythonhosted.org/packages/24/cc/dc143ea88e4ec9d386106cac05023b69668bd0be20794c613446eaefafe5/psycopg2_binary-2.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c0e4262e089516603a09474ee13eabf09cb65c332277e39af68f6233911087", size = 3863943, upload-time = "2025-10-10T11:10:34.586Z" }, + { url = "https://files.pythonhosted.org/packages/8c/df/16848771155e7c419c60afeb24950b8aaa3ab09c0a091ec3ccca26a574d0/psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c47676e5b485393f069b4d7a811267d3168ce46f988fa602658b8bb901e9e64d", size = 4410873, upload-time = "2025-10-10T11:10:38.951Z" }, + { url = "https://files.pythonhosted.org/packages/43/79/5ef5f32621abd5a541b89b04231fe959a9b327c874a1d41156041c75494b/psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a28d8c01a7b27a1e3265b11250ba7557e5f72b5ee9e5f3a2fa8d2949c29bf5d2", size = 4468016, upload-time = "2025-10-10T11:10:43.319Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9b/d7542d0f7ad78f57385971f426704776d7b310f5219ed58da5d605b1892e/psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f3f2732cf504a1aa9e9609d02f79bea1067d99edf844ab92c247bbca143303b", size = 4164996, upload-time = "2025-10-10T11:10:46.705Z" }, + { url = "https://files.pythonhosted.org/packages/bf/30/50e330e63bb05efc6fa7c1447df3e08954894025ca3dcb396ecc6739bc26/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91537a8df2bde69b1c1db01d6d944c831ca793952e4f57892600e96cee95f2cd", size = 3650857, upload-time = "2025-10-10T11:10:50.112Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e0/4026e4c12bb49dd028756c5b0bc4c572319f2d8f1c9008e0dad8cc9addd7/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4dca1f356a67ecb68c81a7bc7809f1569ad9e152ce7fd02c2f2036862ca9f66b", size = 3296063, upload-time = "2025-10-10T11:10:54.089Z" }, + { url = "https://files.pythonhosted.org/packages/18/1c/532c5d2cb11986372f14b798a95f2eaafe5779334f6a80589a68b5fcf769/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37d8412565a7267f7d79e29ab66876e55cb5e8e7b3bbf94f8206f6795f8f7e7e", size = 3345378, upload-time = "2025-10-10T11:11:01.039Z" }, + { url = "https://files.pythonhosted.org/packages/70/e7/de420e1cf16f838e1fa17b1120e83afff374c7c0130d088dba6286fcf8ea/psycopg2_binary-2.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:c665f01ec8ab273a61c62beeb8cce3014c214429ced8a308ca1fc410ecac3a39", size = 2713904, upload-time = "2025-10-10T11:11:04.81Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ae/8d8266f6dd183ab4d48b95b9674034e1b482a3f8619b33a0d86438694577/psycopg2_binary-2.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0e8480afd62362d0a6a27dd09e4ca2def6fa50ed3a4e7c09165266106b2ffa10", size = 3756452, upload-time = "2025-10-10T11:11:11.583Z" }, + { url = "https://files.pythonhosted.org/packages/4b/34/aa03d327739c1be70e09d01182619aca8ebab5970cd0cfa50dd8b9cec2ac/psycopg2_binary-2.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:763c93ef1df3da6d1a90f86ea7f3f806dc06b21c198fa87c3c25504abec9404a", size = 3863957, upload-time = "2025-10-10T11:11:16.932Z" }, + { url = "https://files.pythonhosted.org/packages/48/89/3fdb5902bdab8868bbedc1c6e6023a4e08112ceac5db97fc2012060e0c9a/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e164359396576a3cc701ba8af4751ae68a07235d7a380c631184a611220d9a4", size = 4410955, upload-time = "2025-10-10T11:11:21.21Z" }, + { url = "https://files.pythonhosted.org/packages/ce/24/e18339c407a13c72b336e0d9013fbbbde77b6fd13e853979019a1269519c/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d57c9c387660b8893093459738b6abddbb30a7eab058b77b0d0d1c7d521ddfd7", size = 4468007, upload-time = "2025-10-10T11:11:24.831Z" }, + { url = "https://files.pythonhosted.org/packages/91/7e/b8441e831a0f16c159b5381698f9f7f7ed54b77d57bc9c5f99144cc78232/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2c226ef95eb2250974bf6fa7a842082b31f68385c4f3268370e3f3870e7859ee", size = 4165012, upload-time = "2025-10-10T11:11:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/76/a1/2f5841cae4c635a9459fe7aca8ed771336e9383b6429e05c01267b0774cf/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb415404821b6d1c47353ebe9c8645967a5235e6d88f914147e7fd411419e6f", size = 3650985, upload-time = "2025-10-10T11:11:34.975Z" }, + { url = "https://files.pythonhosted.org/packages/84/74/4defcac9d002bca5709951b975173c8c2fa968e1a95dc713f61b3a8d3b6a/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f07c9c4a5093258a03b28fab9b4f151aa376989e7f35f855088234e656ee6a94", size = 3296039, upload-time = "2025-10-10T11:11:40.432Z" }, + { url = "https://files.pythonhosted.org/packages/c8/31/36a1d8e702aa35c38fc117c2b8be3f182613faa25d794b8aeaab948d4c03/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cffe9d7697ae7456649617e8bb8d7a45afb71cd13f7ab22af3e5c61f04840908", size = 3345842, upload-time = "2025-10-10T11:11:45.366Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b4/a5375cda5b54cb95ee9b836930fea30ae5a8f14aa97da7821722323d979b/psycopg2_binary-2.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:304fd7b7f97eef30e91b8f7e720b3db75fee010b520e434ea35ed1ff22501d03", size = 2713894, upload-time = "2025-10-10T11:11:48.775Z" }, + { url = "https://files.pythonhosted.org/packages/d8/91/f870a02f51be4a65987b45a7de4c2e1897dd0d01051e2b559a38fa634e3e/psycopg2_binary-2.9.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be9b840ac0525a283a96b556616f5b4820e0526addb8dcf6525a0fa162730be4", size = 3756603, upload-time = "2025-10-10T11:11:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/27/fa/cae40e06849b6c9a95eb5c04d419942f00d9eaac8d81626107461e268821/psycopg2_binary-2.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f090b7ddd13ca842ebfe301cd587a76a4cf0913b1e429eb92c1be5dbeb1a19bc", size = 3864509, upload-time = "2025-10-10T11:11:56.452Z" }, + { url = "https://files.pythonhosted.org/packages/2d/75/364847b879eb630b3ac8293798e380e441a957c53657995053c5ec39a316/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a", size = 4411159, upload-time = "2025-10-10T11:12:00.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a0/567f7ea38b6e1c62aafd58375665a547c00c608a471620c0edc364733e13/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e", size = 4468234, upload-time = "2025-10-10T11:12:04.892Z" }, + { url = "https://files.pythonhosted.org/packages/30/da/4e42788fb811bbbfd7b7f045570c062f49e350e1d1f3df056c3fb5763353/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db", size = 4166236, upload-time = "2025-10-10T11:12:11.674Z" }, + { url = "https://files.pythonhosted.org/packages/bd/42/c9a21edf0e3daa7825ed04a4a8588686c6c14904344344a039556d78aa58/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3", size = 3652281, upload-time = "2025-10-10T11:12:17.713Z" }, + { url = "https://files.pythonhosted.org/packages/12/22/dedfbcfa97917982301496b6b5e5e6c5531d1f35dd2b488b08d1ebc52482/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a", size = 3298010, upload-time = "2025-10-10T11:12:22.671Z" }, + { url = "https://files.pythonhosted.org/packages/12/9a/0402ded6cbd321da0c0ba7d34dc12b29b14f5764c2fc10750daa38e825fc/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d", size = 3347940, upload-time = "2025-10-10T11:12:26.529Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d2/99b55e85832ccde77b211738ff3925a5d73ad183c0b37bcbbe5a8ff04978/psycopg2_binary-2.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:b33fabeb1fde21180479b2d4667e994de7bbf0eec22832ba5d9b5e4cf65b6c6d", size = 2714147, upload-time = "2025-10-10T11:12:29.535Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a8/a2709681b3ac11b0b1786def10006b8995125ba268c9a54bea6f5ae8bd3e/psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c", size = 3756572, upload-time = "2025-10-10T11:12:32.873Z" }, + { url = "https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee", size = 3864529, upload-time = "2025-10-10T11:12:36.791Z" }, + { url = "https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0", size = 4411242, upload-time = "2025-10-10T11:12:42.388Z" }, + { url = "https://files.pythonhosted.org/packages/10/04/6ca7477e6160ae258dc96f67c371157776564679aefd247b66f4661501a2/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766", size = 4468258, upload-time = "2025-10-10T11:12:48.654Z" }, + { url = "https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3", size = 4166295, upload-time = "2025-10-10T11:12:52.525Z" }, + { url = "https://files.pythonhosted.org/packages/82/56/993b7104cb8345ad7d4516538ccf8f0d0ac640b1ebd8c754a7b024e76878/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4", size = 3652383, upload-time = "2025-10-10T11:12:56.387Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ac/eaeb6029362fd8d454a27374d84c6866c82c33bfc24587b4face5a8e43ef/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c", size = 3298168, upload-time = "2025-10-10T11:13:00.403Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8e/b7de019a1f562f72ada81081a12823d3c1590bedc48d7d2559410a2763fe/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1", size = 3347549, upload-time = "2025-10-10T11:13:03.971Z" }, + { url = "https://files.pythonhosted.org/packages/80/2d/1bb683f64737bbb1f86c82b7359db1eb2be4e2c0c13b947f80efefa7d3e5/psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa", size = 2714215, upload-time = "2025-10-10T11:13:07.14Z" }, + { url = "https://files.pythonhosted.org/packages/64/12/93ef0098590cf51d9732b4f139533732565704f45bdc1ffa741b7c95fb54/psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1", size = 3756567, upload-time = "2025-10-10T11:13:11.885Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a9/9d55c614a891288f15ca4b5209b09f0f01e3124056924e17b81b9fa054cc/psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f", size = 3864755, upload-time = "2025-10-10T11:13:17.727Z" }, + { url = "https://files.pythonhosted.org/packages/13/1e/98874ce72fd29cbde93209977b196a2edae03f8490d1bd8158e7f1daf3a0/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5", size = 4411646, upload-time = "2025-10-10T11:13:24.432Z" }, + { url = "https://files.pythonhosted.org/packages/5a/bd/a335ce6645334fb8d758cc358810defca14a1d19ffbc8a10bd38a2328565/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8", size = 4468701, upload-time = "2025-10-10T11:13:29.266Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/c8b4f53f34e295e45709b7568bf9b9407a612ea30387d35eb9fa84f269b4/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c", size = 4166293, upload-time = "2025-10-10T11:13:33.336Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/2a8fe18a4e61cfb3417da67b6318e12691772c0696d79434184a511906dc/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747", size = 3652650, upload-time = "2025-10-10T11:13:38.181Z" }, + { url = "https://files.pythonhosted.org/packages/76/36/03801461b31b29fe58d228c24388f999fe814dfc302856e0d17f97d7c54d/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f", size = 3298663, upload-time = "2025-10-10T11:13:44.878Z" }, + { url = "https://files.pythonhosted.org/packages/67/69/f36abe5f118c1dca6d3726ceae164b9356985805480731ac6712a63f24f0/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d", size = 3347643, upload-time = "2025-10-10T11:13:53.499Z" }, + { url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913, upload-time = "2025-10-10T11:13:57.058Z" }, ] [[package]] @@ -1057,6 +1044,149 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, ] +[[package]] +name = "pydantic" +version = "2.12.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/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/3d/9b8ca77b0f76fcdbf8bc6b72474e264283f461284ca84ac3fde570c6c49a/pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e", size = 2111197, upload-time = "2025-10-14T10:19:43.303Z" }, + { url = "https://files.pythonhosted.org/packages/59/92/b7b0fe6ed4781642232755cb7e56a86e2041e1292f16d9ae410a0ccee5ac/pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b", size = 1917909, upload-time = "2025-10-14T10:19:45.194Z" }, + { url = "https://files.pythonhosted.org/packages/52/8c/3eb872009274ffa4fb6a9585114e161aa1a0915af2896e2d441642929fe4/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd", size = 1969905, upload-time = "2025-10-14T10:19:46.567Z" }, + { url = "https://files.pythonhosted.org/packages/f4/21/35adf4a753bcfaea22d925214a0c5b880792e3244731b3f3e6fec0d124f7/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945", size = 2051938, upload-time = "2025-10-14T10:19:48.237Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d0/cdf7d126825e36d6e3f1eccf257da8954452934ede275a8f390eac775e89/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706", size = 2250710, upload-time = "2025-10-14T10:19:49.619Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1c/af1e6fd5ea596327308f9c8d1654e1285cc3d8de0d584a3c9d7705bf8a7c/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba", size = 2367445, upload-time = "2025-10-14T10:19:51.269Z" }, + { url = "https://files.pythonhosted.org/packages/d3/81/8cece29a6ef1b3a92f956ea6da6250d5b2d2e7e4d513dd3b4f0c7a83dfea/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b", size = 2072875, upload-time = "2025-10-14T10:19:52.671Z" }, + { url = "https://files.pythonhosted.org/packages/e3/37/a6a579f5fc2cd4d5521284a0ab6a426cc6463a7b3897aeb95b12f1ba607b/pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d", size = 2191329, upload-time = "2025-10-14T10:19:54.214Z" }, + { url = "https://files.pythonhosted.org/packages/ae/03/505020dc5c54ec75ecba9f41119fd1e48f9e41e4629942494c4a8734ded1/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700", size = 2151658, upload-time = "2025-10-14T10:19:55.843Z" }, + { url = "https://files.pythonhosted.org/packages/cb/5d/2c0d09fb53aa03bbd2a214d89ebfa6304be7df9ed86ee3dc7770257f41ee/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6", size = 2316777, upload-time = "2025-10-14T10:19:57.607Z" }, + { url = "https://files.pythonhosted.org/packages/ea/4b/c2c9c8f5e1f9c864b57d08539d9d3db160e00491c9f5ee90e1bfd905e644/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9", size = 2320705, upload-time = "2025-10-14T10:19:59.016Z" }, + { url = "https://files.pythonhosted.org/packages/28/c3/a74c1c37f49c0a02c89c7340fafc0ba816b29bd495d1a31ce1bdeacc6085/pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57", size = 1975464, upload-time = "2025-10-14T10:20:00.581Z" }, + { url = "https://files.pythonhosted.org/packages/d6/23/5dd5c1324ba80303368f7569e2e2e1a721c7d9eb16acb7eb7b7f85cb1be2/pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc", size = 2024497, upload-time = "2025-10-14T10:20:03.018Z" }, + { url = "https://files.pythonhosted.org/packages/62/4c/f6cbfa1e8efacd00b846764e8484fe173d25b8dab881e277a619177f3384/pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80", size = 2109062, upload-time = "2025-10-14T10:20:04.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/f8/40b72d3868896bfcd410e1bd7e516e762d326201c48e5b4a06446f6cf9e8/pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae", size = 1916301, upload-time = "2025-10-14T10:20:06.857Z" }, + { url = "https://files.pythonhosted.org/packages/94/4d/d203dce8bee7faeca791671c88519969d98d3b4e8f225da5b96dad226fc8/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827", size = 1968728, upload-time = "2025-10-14T10:20:08.353Z" }, + { url = "https://files.pythonhosted.org/packages/65/f5/6a66187775df87c24d526985b3a5d78d861580ca466fbd9d4d0e792fcf6c/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f", size = 2050238, upload-time = "2025-10-14T10:20:09.766Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b9/78336345de97298cf53236b2f271912ce11f32c1e59de25a374ce12f9cce/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def", size = 2249424, upload-time = "2025-10-14T10:20:11.732Z" }, + { url = "https://files.pythonhosted.org/packages/99/bb/a4584888b70ee594c3d374a71af5075a68654d6c780369df269118af7402/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2", size = 2366047, upload-time = "2025-10-14T10:20:13.647Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8d/17fc5de9d6418e4d2ae8c675f905cdafdc59d3bf3bf9c946b7ab796a992a/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8", size = 2071163, upload-time = "2025-10-14T10:20:15.307Z" }, + { url = "https://files.pythonhosted.org/packages/54/e7/03d2c5c0b8ed37a4617430db68ec5e7dbba66358b629cd69e11b4d564367/pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265", size = 2190585, upload-time = "2025-10-14T10:20:17.3Z" }, + { url = "https://files.pythonhosted.org/packages/be/fc/15d1c9fe5ad9266a5897d9b932b7f53d7e5cfc800573917a2c5d6eea56ec/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c", size = 2150109, upload-time = "2025-10-14T10:20:19.143Z" }, + { url = "https://files.pythonhosted.org/packages/26/ef/e735dd008808226c83ba56972566138665b71477ad580fa5a21f0851df48/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a", size = 2315078, upload-time = "2025-10-14T10:20:20.742Z" }, + { url = "https://files.pythonhosted.org/packages/90/00/806efdcf35ff2ac0f938362350cd9827b8afb116cc814b6b75cf23738c7c/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e", size = 2318737, upload-time = "2025-10-14T10:20:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/41/7e/6ac90673fe6cb36621a2283552897838c020db343fa86e513d3f563b196f/pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03", size = 1974160, upload-time = "2025-10-14T10:20:23.817Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9d/7c5e24ee585c1f8b6356e1d11d40ab807ffde44d2db3b7dfd6d20b09720e/pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e", size = 2021883, upload-time = "2025-10-14T10:20:25.48Z" }, + { url = "https://files.pythonhosted.org/packages/33/90/5c172357460fc28b2871eb4a0fb3843b136b429c6fa827e4b588877bf115/pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db", size = 1968026, upload-time = "2025-10-14T10:20:27.039Z" }, + { url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887", size = 2099043, upload-time = "2025-10-14T10:20:28.561Z" }, + { url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2", size = 1910699, upload-time = "2025-10-14T10:20:30.217Z" }, + { url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999", size = 1952121, upload-time = "2025-10-14T10:20:32.246Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a7/e5fc60a6f781fc634ecaa9ecc3c20171d238794cef69ae0af79ac11b89d7/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4", size = 2041590, upload-time = "2025-10-14T10:20:34.332Z" }, + { url = "https://files.pythonhosted.org/packages/70/69/dce747b1d21d59e85af433428978a1893c6f8a7068fa2bb4a927fba7a5ff/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f", size = 2219869, upload-time = "2025-10-14T10:20:35.965Z" }, + { url = "https://files.pythonhosted.org/packages/83/6a/c070e30e295403bf29c4df1cb781317b6a9bac7cd07b8d3acc94d501a63c/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b", size = 2345169, upload-time = "2025-10-14T10:20:37.627Z" }, + { url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47", size = 2070165, upload-time = "2025-10-14T10:20:39.246Z" }, + { url = "https://files.pythonhosted.org/packages/14/0a/e567c2883588dd12bcbc110232d892cf385356f7c8a9910311ac997ab715/pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970", size = 2189067, upload-time = "2025-10-14T10:20:41.015Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1d/3d9fca34273ba03c9b1c5289f7618bc4bd09c3ad2289b5420481aa051a99/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed", size = 2132997, upload-time = "2025-10-14T10:20:43.106Z" }, + { url = "https://files.pythonhosted.org/packages/52/70/d702ef7a6cd41a8afc61f3554922b3ed8d19dd54c3bd4bdbfe332e610827/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8", size = 2307187, upload-time = "2025-10-14T10:20:44.849Z" }, + { url = "https://files.pythonhosted.org/packages/68/4c/c06be6e27545d08b802127914156f38d10ca287a9e8489342793de8aae3c/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431", size = 2305204, upload-time = "2025-10-14T10:20:46.781Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e5/35ae4919bcd9f18603419e23c5eaf32750224a89d41a8df1a3704b69f77e/pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd", size = 1972536, upload-time = "2025-10-14T10:20:48.39Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c2/49c5bb6d2a49eb2ee3647a93e3dae7080c6409a8a7558b075027644e879c/pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff", size = 2031132, upload-time = "2025-10-14T10:20:50.421Z" }, + { url = "https://files.pythonhosted.org/packages/06/23/936343dbcba6eec93f73e95eb346810fc732f71ba27967b287b66f7b7097/pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8", size = 1969483, upload-time = "2025-10-14T10:20:52.35Z" }, + { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" }, + { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" }, + { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" }, + { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" }, + { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" }, + { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" }, + { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" }, + { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" }, + { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" }, + { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" }, + { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" }, + { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" }, + { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" }, + { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" }, + { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" }, + { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" }, + { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" }, + { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" }, + { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" }, + { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" }, + { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" }, + { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" }, + { url = "https://files.pythonhosted.org/packages/b0/12/5ba58daa7f453454464f92b3ca7b9d7c657d8641c48e370c3ebc9a82dd78/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b", size = 2122139, upload-time = "2025-10-14T10:22:47.288Z" }, + { url = "https://files.pythonhosted.org/packages/21/fb/6860126a77725c3108baecd10fd3d75fec25191d6381b6eb2ac660228eac/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42", size = 1936674, upload-time = "2025-10-14T10:22:49.555Z" }, + { url = "https://files.pythonhosted.org/packages/de/be/57dcaa3ed595d81f8757e2b44a38240ac5d37628bce25fb20d02c7018776/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee", size = 1956398, upload-time = "2025-10-14T10:22:52.19Z" }, + { url = "https://files.pythonhosted.org/packages/2f/1d/679a344fadb9695f1a6a294d739fbd21d71fa023286daeea8c0ed49e7c2b/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c", size = 2138674, upload-time = "2025-10-14T10:22:54.499Z" }, + { url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537", size = 2112087, upload-time = "2025-10-14T10:22:56.818Z" }, + { url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94", size = 1920387, upload-time = "2025-10-14T10:22:59.342Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c", size = 1951495, upload-time = "2025-10-14T10:23:02.089Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008, upload-time = "2025-10-14T10:23:04.539Z" }, + { url = "https://files.pythonhosted.org/packages/5d/d4/912e976a2dd0b49f31c98a060ca90b353f3b73ee3ea2fd0030412f6ac5ec/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00", size = 2106739, upload-time = "2025-10-14T10:23:06.934Z" }, + { url = "https://files.pythonhosted.org/packages/71/f0/66ec5a626c81eba326072d6ee2b127f8c139543f1bf609b4842978d37833/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9", size = 1932549, upload-time = "2025-10-14T10:23:09.24Z" }, + { url = "https://files.pythonhosted.org/packages/c4/af/625626278ca801ea0a658c2dcf290dc9f21bb383098e99e7c6a029fccfc0/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2", size = 2135093, upload-time = "2025-10-14T10:23:11.626Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/2fba049f54e0f4975fef66be654c597a1d005320fa141863699180c7697d/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258", size = 2187971, upload-time = "2025-10-14T10:23:14.437Z" }, + { url = "https://files.pythonhosted.org/packages/0e/80/65ab839a2dfcd3b949202f9d920c34f9de5a537c3646662bdf2f7d999680/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347", size = 2147939, upload-time = "2025-10-14T10:23:16.831Z" }, + { url = "https://files.pythonhosted.org/packages/44/58/627565d3d182ce6dfda18b8e1c841eede3629d59c9d7cbc1e12a03aeb328/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa", size = 2311400, upload-time = "2025-10-14T10:23:19.234Z" }, + { url = "https://files.pythonhosted.org/packages/24/06/8a84711162ad5a5f19a88cead37cca81b4b1f294f46260ef7334ae4f24d3/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a", size = 2316840, upload-time = "2025-10-14T10:23:21.738Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8b/b7bb512a4682a2f7fbfae152a755d37351743900226d29bd953aaf870eaa/pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d", size = 2149135, upload-time = "2025-10-14T10:23:24.379Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7d/138e902ed6399b866f7cfe4435d22445e16fff888a1c00560d9dc79a780f/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5", size = 2104721, upload-time = "2025-10-14T10:23:26.906Z" }, + { url = "https://files.pythonhosted.org/packages/47/13/0525623cf94627f7b53b4c2034c81edc8491cbfc7c28d5447fa318791479/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2", size = 1931608, upload-time = "2025-10-14T10:23:29.306Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f9/744bc98137d6ef0a233f808bfc9b18cf94624bf30836a18d3b05d08bf418/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd", size = 2132986, upload-time = "2025-10-14T10:23:32.057Z" }, + { url = "https://files.pythonhosted.org/packages/17/c8/629e88920171173f6049386cc71f893dff03209a9ef32b4d2f7e7c264bcf/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c", size = 2187516, upload-time = "2025-10-14T10:23:34.871Z" }, + { url = "https://files.pythonhosted.org/packages/2e/0f/4f2734688d98488782218ca61bcc118329bf5de05bb7fe3adc7dd79b0b86/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405", size = 2146146, upload-time = "2025-10-14T10:23:37.342Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f2/ab385dbd94a052c62224b99cf99002eee99dbec40e10006c78575aead256/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8", size = 2311296, upload-time = "2025-10-14T10:23:40.145Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8e/e4f12afe1beeb9823bba5375f8f258df0cc61b056b0195fb1cf9f62a1a58/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308", size = 2315386, upload-time = "2025-10-14T10:23:42.624Z" }, + { url = "https://files.pythonhosted.org/packages/48/f7/925f65d930802e3ea2eb4d5afa4cb8730c8dc0d2cb89a59dc4ed2fcb2d74/pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f", size = 2147775, upload-time = "2025-10-14T10:23:45.406Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/c5/dbbc27b814c71676593d1c3f718e6cd7d4f00652cefa24b75f7aa3efb25e/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180", size = 188394, upload-time = "2025-09-24T14:19:11.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c", size = 48608, upload-time = "2025-09-24T14:19:10.015Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -1081,15 +1211,15 @@ wheels = [ [[package]] name = "pyright" -version = "1.1.405" +version = "1.1.406" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/6c/ba4bbee22e76af700ea593a1d8701e3225080956753bee9750dcc25e2649/pyright-1.1.405.tar.gz", hash = "sha256:5c2a30e1037af27eb463a1cc0b9f6d65fec48478ccf092c1ac28385a15c55763", size = 4068319, upload-time = "2025-09-04T03:37:06.776Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/16/6b4fbdd1fef59a0292cbb99f790b44983e390321eccbc5921b4d161da5d1/pyright-1.1.406.tar.gz", hash = "sha256:c4872bc58c9643dac09e8a2e74d472c62036910b3bd37a32813989ef7576ea2c", size = 4113151, upload-time = "2025-10-02T01:04:45.488Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/1a/524f832e1ff1962a22a1accc775ca7b143ba2e9f5924bb6749dce566784a/pyright-1.1.405-py3-none-any.whl", hash = "sha256:a2cb13700b5508ce8e5d4546034cb7ea4aedb60215c6c33f56cec7f53996035a", size = 5905038, upload-time = "2025-09-04T03:37:04.913Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a2/e309afbb459f50507103793aaef85ca4348b66814c86bc73908bdeb66d12/pyright-1.1.406-py3-none-any.whl", hash = "sha256:1d81fb43c2407bf566e97e57abb01c811973fdb21b2df8df59f870f688bdca71", size = 5980982, upload-time = "2025-10-02T01:04:43.137Z" }, ] [[package]] @@ -1136,6 +1266,46 @@ 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.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, + { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, + { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, + { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -1198,15 +1368,6 @@ wheels = [ { 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" }, - { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" }, - { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" }, - { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" }, - { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" }, - { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" }, - { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" }, - { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" }, - { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" }, - { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" }, ] [[package]] @@ -1221,6 +1382,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, ] +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + [[package]] name = "requests" version = "2.32.5" @@ -1236,30 +1411,165 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "rpds-py" +version = "0.27.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/ed/3aef893e2dd30e77e35d20d4ddb45ca459db59cead748cad9796ad479411/rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef", size = 371606, upload-time = "2025-08-27T12:12:25.189Z" }, + { url = "https://files.pythonhosted.org/packages/6d/82/9818b443e5d3eb4c83c3994561387f116aae9833b35c484474769c4a8faf/rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be", size = 353452, upload-time = "2025-08-27T12:12:27.433Z" }, + { url = "https://files.pythonhosted.org/packages/99/c7/d2a110ffaaa397fc6793a83c7bd3545d9ab22658b7cdff05a24a4535cc45/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61", size = 381519, upload-time = "2025-08-27T12:12:28.719Z" }, + { url = "https://files.pythonhosted.org/packages/5a/bc/e89581d1f9d1be7d0247eaef602566869fdc0d084008ba139e27e775366c/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb", size = 394424, upload-time = "2025-08-27T12:12:30.207Z" }, + { url = "https://files.pythonhosted.org/packages/ac/2e/36a6861f797530e74bb6ed53495f8741f1ef95939eed01d761e73d559067/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657", size = 523467, upload-time = "2025-08-27T12:12:31.808Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/c1bc2be32564fa499f988f0a5c6505c2f4746ef96e58e4d7de5cf923d77e/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013", size = 402660, upload-time = "2025-08-27T12:12:33.444Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ec/ef8bf895f0628dd0a59e54d81caed6891663cb9c54a0f4bb7da918cb88cf/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a", size = 384062, upload-time = "2025-08-27T12:12:34.857Z" }, + { url = "https://files.pythonhosted.org/packages/69/f7/f47ff154be8d9a5e691c083a920bba89cef88d5247c241c10b9898f595a1/rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1", size = 401289, upload-time = "2025-08-27T12:12:36.085Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d9/ca410363efd0615814ae579f6829cafb39225cd63e5ea5ed1404cb345293/rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10", size = 417718, upload-time = "2025-08-27T12:12:37.401Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a0/8cb5c2ff38340f221cc067cc093d1270e10658ba4e8d263df923daa18e86/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808", size = 558333, upload-time = "2025-08-27T12:12:38.672Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8c/1b0de79177c5d5103843774ce12b84caa7164dfc6cd66378768d37db11bf/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8", size = 589127, upload-time = "2025-08-27T12:12:41.48Z" }, + { url = "https://files.pythonhosted.org/packages/c8/5e/26abb098d5e01266b0f3a2488d299d19ccc26849735d9d2b95c39397e945/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9", size = 554899, upload-time = "2025-08-27T12:12:42.925Z" }, + { url = "https://files.pythonhosted.org/packages/de/41/905cc90ced13550db017f8f20c6d8e8470066c5738ba480d7ba63e3d136b/rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4", size = 217450, upload-time = "2025-08-27T12:12:44.813Z" }, + { url = "https://files.pythonhosted.org/packages/75/3d/6bef47b0e253616ccdf67c283e25f2d16e18ccddd38f92af81d5a3420206/rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1", size = 228447, upload-time = "2025-08-27T12:12:46.204Z" }, + { url = "https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881", size = 371063, upload-time = "2025-08-27T12:12:47.856Z" }, + { url = "https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5", size = 353210, upload-time = "2025-08-27T12:12:49.187Z" }, + { url = "https://files.pythonhosted.org/packages/3a/57/f5eb3ecf434342f4f1a46009530e93fd201a0b5b83379034ebdb1d7c1a58/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e", size = 381636, upload-time = "2025-08-27T12:12:50.492Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f4/ef95c5945e2ceb5119571b184dd5a1cc4b8541bbdf67461998cfeac9cb1e/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c", size = 394341, upload-time = "2025-08-27T12:12:52.024Z" }, + { url = "https://files.pythonhosted.org/packages/5a/7e/4bd610754bf492d398b61725eb9598ddd5eb86b07d7d9483dbcd810e20bc/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195", size = 523428, upload-time = "2025-08-27T12:12:53.779Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e5/059b9f65a8c9149361a8b75094864ab83b94718344db511fd6117936ed2a/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52", size = 402923, upload-time = "2025-08-27T12:12:55.15Z" }, + { url = "https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed", size = 384094, upload-time = "2025-08-27T12:12:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e1/dc9094d6ff566bff87add8a510c89b9e158ad2ecd97ee26e677da29a9e1b/rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a", size = 401093, upload-time = "2025-08-27T12:12:58.985Z" }, + { url = "https://files.pythonhosted.org/packages/37/8e/ac8577e3ecdd5593e283d46907d7011618994e1d7ab992711ae0f78b9937/rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde", size = 417969, upload-time = "2025-08-27T12:13:00.367Z" }, + { url = "https://files.pythonhosted.org/packages/66/6d/87507430a8f74a93556fe55c6485ba9c259949a853ce407b1e23fea5ba31/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21", size = 558302, upload-time = "2025-08-27T12:13:01.737Z" }, + { url = "https://files.pythonhosted.org/packages/3a/bb/1db4781ce1dda3eecc735e3152659a27b90a02ca62bfeea17aee45cc0fbc/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9", size = 589259, upload-time = "2025-08-27T12:13:03.127Z" }, + { url = "https://files.pythonhosted.org/packages/7b/0e/ae1c8943d11a814d01b482e1f8da903f88047a962dff9bbdadf3bd6e6fd1/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948", size = 554983, upload-time = "2025-08-27T12:13:04.516Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d5/0b2a55415931db4f112bdab072443ff76131b5ac4f4dc98d10d2d357eb03/rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39", size = 217154, upload-time = "2025-08-27T12:13:06.278Z" }, + { url = "https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15", size = 228627, upload-time = "2025-08-27T12:13:07.625Z" }, + { url = "https://files.pythonhosted.org/packages/8d/3f/4fd04c32abc02c710f09a72a30c9a55ea3cc154ef8099078fd50a0596f8e/rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746", size = 220998, upload-time = "2025-08-27T12:13:08.972Z" }, + { url = "https://files.pythonhosted.org/packages/bd/fe/38de28dee5df58b8198c743fe2bea0c785c6d40941b9950bac4cdb71a014/rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", size = 361887, upload-time = "2025-08-27T12:13:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/4b6c7eedc7dd90986bf0fab6ea2a091ec11c01b15f8ba0a14d3f80450468/rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", size = 345795, upload-time = "2025-08-27T12:13:11.65Z" }, + { url = "https://files.pythonhosted.org/packages/6f/0e/e650e1b81922847a09cca820237b0edee69416a01268b7754d506ade11ad/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", size = 385121, upload-time = "2025-08-27T12:13:13.008Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ea/b306067a712988e2bff00dcc7c8f31d26c29b6d5931b461aa4b60a013e33/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", size = 398976, upload-time = "2025-08-27T12:13:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0a/26dc43c8840cb8fe239fe12dbc8d8de40f2365e838f3d395835dde72f0e5/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", size = 525953, upload-time = "2025-08-27T12:13:15.774Z" }, + { url = "https://files.pythonhosted.org/packages/22/14/c85e8127b573aaf3a0cbd7fbb8c9c99e735a4a02180c84da2a463b766e9e/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", size = 407915, upload-time = "2025-08-27T12:13:17.379Z" }, + { url = "https://files.pythonhosted.org/packages/ed/7b/8f4fee9ba1fb5ec856eb22d725a4efa3deb47f769597c809e03578b0f9d9/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", size = 386883, upload-time = "2025-08-27T12:13:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/86/47/28fa6d60f8b74fcdceba81b272f8d9836ac0340570f68f5df6b41838547b/rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", size = 405699, upload-time = "2025-08-27T12:13:20.089Z" }, + { url = "https://files.pythonhosted.org/packages/d0/fd/c5987b5e054548df56953a21fe2ebed51fc1ec7c8f24fd41c067b68c4a0a/rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", size = 423713, upload-time = "2025-08-27T12:13:21.436Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ba/3c4978b54a73ed19a7d74531be37a8bcc542d917c770e14d372b8daea186/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", size = 562324, upload-time = "2025-08-27T12:13:22.789Z" }, + { url = "https://files.pythonhosted.org/packages/b5/6c/6943a91768fec16db09a42b08644b960cff540c66aab89b74be6d4a144ba/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", size = 593646, upload-time = "2025-08-27T12:13:24.122Z" }, + { url = "https://files.pythonhosted.org/packages/11/73/9d7a8f4be5f4396f011a6bb7a19fe26303a0dac9064462f5651ced2f572f/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", size = 558137, upload-time = "2025-08-27T12:13:25.557Z" }, + { url = "https://files.pythonhosted.org/packages/6e/96/6772cbfa0e2485bcceef8071de7821f81aeac8bb45fbfd5542a3e8108165/rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", size = 221343, upload-time = "2025-08-27T12:13:26.967Z" }, + { url = "https://files.pythonhosted.org/packages/67/b6/c82f0faa9af1c6a64669f73a17ee0eeef25aff30bb9a1c318509efe45d84/rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", size = 232497, upload-time = "2025-08-27T12:13:28.326Z" }, + { url = "https://files.pythonhosted.org/packages/e1/96/2817b44bd2ed11aebacc9251da03689d56109b9aba5e311297b6902136e2/rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", size = 222790, upload-time = "2025-08-27T12:13:29.71Z" }, + { url = "https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", size = 361741, upload-time = "2025-08-27T12:13:31.039Z" }, + { url = "https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", size = 345574, upload-time = "2025-08-27T12:13:32.902Z" }, + { url = "https://files.pythonhosted.org/packages/20/42/ee2b2ca114294cd9847d0ef9c26d2b0851b2e7e00bf14cc4c0b581df0fc3/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", size = 385051, upload-time = "2025-08-27T12:13:34.228Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e8/1e430fe311e4799e02e2d1af7c765f024e95e17d651612425b226705f910/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", size = 398395, upload-time = "2025-08-27T12:13:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/82/95/9dc227d441ff2670651c27a739acb2535ccaf8b351a88d78c088965e5996/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", size = 524334, upload-time = "2025-08-27T12:13:37.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/01/a670c232f401d9ad461d9a332aa4080cd3cb1d1df18213dbd0d2a6a7ab51/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", size = 407691, upload-time = "2025-08-27T12:13:38.94Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", size = 386868, upload-time = "2025-08-27T12:13:40.192Z" }, + { url = "https://files.pythonhosted.org/packages/3b/03/8c897fb8b5347ff6c1cc31239b9611c5bf79d78c984430887a353e1409a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", size = 405469, upload-time = "2025-08-27T12:13:41.496Z" }, + { url = "https://files.pythonhosted.org/packages/da/07/88c60edc2df74850d496d78a1fdcdc7b54360a7f610a4d50008309d41b94/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", size = 422125, upload-time = "2025-08-27T12:13:42.802Z" }, + { url = "https://files.pythonhosted.org/packages/6b/86/5f4c707603e41b05f191a749984f390dabcbc467cf833769b47bf14ba04f/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", size = 562341, upload-time = "2025-08-27T12:13:44.472Z" }, + { url = "https://files.pythonhosted.org/packages/b2/92/3c0cb2492094e3cd9baf9e49bbb7befeceb584ea0c1a8b5939dca4da12e5/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", size = 592511, upload-time = "2025-08-27T12:13:45.898Z" }, + { url = "https://files.pythonhosted.org/packages/10/bb/82e64fbb0047c46a168faa28d0d45a7851cd0582f850b966811d30f67ad8/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", size = 557736, upload-time = "2025-08-27T12:13:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/00/95/3c863973d409210da7fb41958172c6b7dbe7fc34e04d3cc1f10bb85e979f/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", size = 221462, upload-time = "2025-08-27T12:13:48.742Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", size = 232034, upload-time = "2025-08-27T12:13:50.11Z" }, + { url = "https://files.pythonhosted.org/packages/c7/78/3958f3f018c01923823f1e47f1cc338e398814b92d83cd278364446fac66/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", size = 222392, upload-time = "2025-08-27T12:13:52.587Z" }, + { url = "https://files.pythonhosted.org/packages/01/76/1cdf1f91aed5c3a7bf2eba1f1c4e4d6f57832d73003919a20118870ea659/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", size = 358355, upload-time = "2025-08-27T12:13:54.012Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6f/bf142541229374287604caf3bb2a4ae17f0a580798fd72d3b009b532db4e/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", size = 342138, upload-time = "2025-08-27T12:13:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/1a/77/355b1c041d6be40886c44ff5e798b4e2769e497b790f0f7fd1e78d17e9a8/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", size = 380247, upload-time = "2025-08-27T12:13:57.683Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a4/d9cef5c3946ea271ce2243c51481971cd6e34f21925af2783dd17b26e815/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", size = 390699, upload-time = "2025-08-27T12:13:59.137Z" }, + { url = "https://files.pythonhosted.org/packages/3a/06/005106a7b8c6c1a7e91b73169e49870f4af5256119d34a361ae5240a0c1d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", size = 521852, upload-time = "2025-08-27T12:14:00.583Z" }, + { url = "https://files.pythonhosted.org/packages/e5/3e/50fb1dac0948e17a02eb05c24510a8fe12d5ce8561c6b7b7d1339ab7ab9c/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", size = 402582, upload-time = "2025-08-27T12:14:02.034Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b0/f4e224090dc5b0ec15f31a02d746ab24101dd430847c4d99123798661bfc/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", size = 384126, upload-time = "2025-08-27T12:14:03.437Z" }, + { url = "https://files.pythonhosted.org/packages/54/77/ac339d5f82b6afff1df8f0fe0d2145cc827992cb5f8eeb90fc9f31ef7a63/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", size = 399486, upload-time = "2025-08-27T12:14:05.443Z" }, + { url = "https://files.pythonhosted.org/packages/d6/29/3e1c255eee6ac358c056a57d6d6869baa00a62fa32eea5ee0632039c50a3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", size = 414832, upload-time = "2025-08-27T12:14:06.902Z" }, + { url = "https://files.pythonhosted.org/packages/3f/db/6d498b844342deb3fa1d030598db93937a9964fcf5cb4da4feb5f17be34b/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", size = 557249, upload-time = "2025-08-27T12:14:08.37Z" }, + { url = "https://files.pythonhosted.org/packages/60/f3/690dd38e2310b6f68858a331399b4d6dbb9132c3e8ef8b4333b96caf403d/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", size = 587356, upload-time = "2025-08-27T12:14:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/86/e3/84507781cccd0145f35b1dc32c72675200c5ce8d5b30f813e49424ef68fc/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", size = 555300, upload-time = "2025-08-27T12:14:11.783Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ee/375469849e6b429b3516206b4580a79e9ef3eb12920ddbd4492b56eaacbe/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", size = 216714, upload-time = "2025-08-27T12:14:13.629Z" }, + { url = "https://files.pythonhosted.org/packages/21/87/3fc94e47c9bd0742660e84706c311a860dcae4374cf4a03c477e23ce605a/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", size = 228943, upload-time = "2025-08-27T12:14:14.937Z" }, + { url = "https://files.pythonhosted.org/packages/70/36/b6e6066520a07cf029d385de869729a895917b411e777ab1cde878100a1d/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", size = 362472, upload-time = "2025-08-27T12:14:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/af/07/b4646032e0dcec0df9c73a3bd52f63bc6c5f9cda992f06bd0e73fe3fbebd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", size = 345676, upload-time = "2025-08-27T12:14:17.764Z" }, + { url = "https://files.pythonhosted.org/packages/b0/16/2f1003ee5d0af4bcb13c0cf894957984c32a6751ed7206db2aee7379a55e/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", size = 385313, upload-time = "2025-08-27T12:14:19.829Z" }, + { url = "https://files.pythonhosted.org/packages/05/cd/7eb6dd7b232e7f2654d03fa07f1414d7dfc980e82ba71e40a7c46fd95484/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", size = 399080, upload-time = "2025-08-27T12:14:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/20/51/5829afd5000ec1cb60f304711f02572d619040aa3ec033d8226817d1e571/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", size = 523868, upload-time = "2025-08-27T12:14:23.485Z" }, + { url = "https://files.pythonhosted.org/packages/05/2c/30eebca20d5db95720ab4d2faec1b5e4c1025c473f703738c371241476a2/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", size = 408750, upload-time = "2025-08-27T12:14:24.924Z" }, + { url = "https://files.pythonhosted.org/packages/90/1a/cdb5083f043597c4d4276eae4e4c70c55ab5accec078da8611f24575a367/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", size = 387688, upload-time = "2025-08-27T12:14:27.537Z" }, + { url = "https://files.pythonhosted.org/packages/7c/92/cf786a15320e173f945d205ab31585cc43969743bb1a48b6888f7a2b0a2d/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", size = 407225, upload-time = "2025-08-27T12:14:28.981Z" }, + { url = "https://files.pythonhosted.org/packages/33/5c/85ee16df5b65063ef26017bef33096557a4c83fbe56218ac7cd8c235f16d/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", size = 423361, upload-time = "2025-08-27T12:14:30.469Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8e/1c2741307fcabd1a334ecf008e92c4f47bb6f848712cf15c923becfe82bb/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", size = 562493, upload-time = "2025-08-27T12:14:31.987Z" }, + { url = "https://files.pythonhosted.org/packages/04/03/5159321baae9b2222442a70c1f988cbbd66b9be0675dd3936461269be360/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", size = 592623, upload-time = "2025-08-27T12:14:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/ff/39/c09fd1ad28b85bc1d4554a8710233c9f4cefd03d7717a1b8fbfd171d1167/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", size = 558800, upload-time = "2025-08-27T12:14:35.436Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d6/99228e6bbcf4baa764b18258f519a9035131d91b538d4e0e294313462a98/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", size = 221943, upload-time = "2025-08-27T12:14:36.898Z" }, + { url = "https://files.pythonhosted.org/packages/be/07/c802bc6b8e95be83b79bdf23d1aa61d68324cb1006e245d6c58e959e314d/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", size = 233739, upload-time = "2025-08-27T12:14:38.386Z" }, + { url = "https://files.pythonhosted.org/packages/c8/89/3e1b1c16d4c2d547c5717377a8df99aee8099ff050f87c45cb4d5fa70891/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", size = 223120, upload-time = "2025-08-27T12:14:39.82Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/dc7931dc2fa4a6e46b2a4fa744a9fe5c548efd70e0ba74f40b39fa4a8c10/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", size = 358944, upload-time = "2025-08-27T12:14:41.199Z" }, + { url = "https://files.pythonhosted.org/packages/e6/22/4af76ac4e9f336bfb1a5f240d18a33c6b2fcaadb7472ac7680576512b49a/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", size = 342283, upload-time = "2025-08-27T12:14:42.699Z" }, + { url = "https://files.pythonhosted.org/packages/1c/15/2a7c619b3c2272ea9feb9ade67a45c40b3eeb500d503ad4c28c395dc51b4/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", size = 380320, upload-time = "2025-08-27T12:14:44.157Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7d/4c6d243ba4a3057e994bb5bedd01b5c963c12fe38dde707a52acdb3849e7/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", size = 391760, upload-time = "2025-08-27T12:14:45.845Z" }, + { url = "https://files.pythonhosted.org/packages/b4/71/b19401a909b83bcd67f90221330bc1ef11bc486fe4e04c24388d28a618ae/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", size = 522476, upload-time = "2025-08-27T12:14:47.364Z" }, + { url = "https://files.pythonhosted.org/packages/e4/44/1a3b9715c0455d2e2f0f6df5ee6d6f5afdc423d0773a8a682ed2b43c566c/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", size = 403418, upload-time = "2025-08-27T12:14:49.991Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4b/fb6c4f14984eb56673bc868a66536f53417ddb13ed44b391998100a06a96/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", size = 384771, upload-time = "2025-08-27T12:14:52.159Z" }, + { url = "https://files.pythonhosted.org/packages/c0/56/d5265d2d28b7420d7b4d4d85cad8ef891760f5135102e60d5c970b976e41/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", size = 400022, upload-time = "2025-08-27T12:14:53.859Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e9/9f5fc70164a569bdd6ed9046486c3568d6926e3a49bdefeeccfb18655875/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", size = 416787, upload-time = "2025-08-27T12:14:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/d4/64/56dd03430ba491db943a81dcdef115a985aac5f44f565cd39a00c766d45c/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", size = 557538, upload-time = "2025-08-27T12:14:57.245Z" }, + { url = "https://files.pythonhosted.org/packages/3f/36/92cc885a3129993b1d963a2a42ecf64e6a8e129d2c7cc980dbeba84e55fb/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", size = 588512, upload-time = "2025-08-27T12:14:58.728Z" }, + { url = "https://files.pythonhosted.org/packages/dd/10/6b283707780a81919f71625351182b4f98932ac89a09023cb61865136244/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", size = 555813, upload-time = "2025-08-27T12:15:00.334Z" }, + { url = "https://files.pythonhosted.org/packages/04/2e/30b5ea18c01379da6272a92825dd7e53dc9d15c88a19e97932d35d430ef7/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", size = 217385, upload-time = "2025-08-27T12:15:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" }, + { url = "https://files.pythonhosted.org/packages/d5/63/b7cc415c345625d5e62f694ea356c58fb964861409008118f1245f8c3347/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf", size = 371360, upload-time = "2025-08-27T12:15:29.218Z" }, + { url = "https://files.pythonhosted.org/packages/e5/8c/12e1b24b560cf378b8ffbdb9dc73abd529e1adcfcf82727dfd29c4a7b88d/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3", size = 353933, upload-time = "2025-08-27T12:15:30.837Z" }, + { url = "https://files.pythonhosted.org/packages/9b/85/1bb2210c1f7a1b99e91fea486b9f0f894aa5da3a5ec7097cbad7dec6d40f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636", size = 382962, upload-time = "2025-08-27T12:15:32.348Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c9/a839b9f219cf80ed65f27a7f5ddbb2809c1b85c966020ae2dff490e0b18e/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8", size = 394412, upload-time = "2025-08-27T12:15:33.839Z" }, + { url = "https://files.pythonhosted.org/packages/02/2d/b1d7f928b0b1f4fc2e0133e8051d199b01d7384875adc63b6ddadf3de7e5/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc", size = 523972, upload-time = "2025-08-27T12:15:35.377Z" }, + { url = "https://files.pythonhosted.org/packages/a9/af/2cbf56edd2d07716df1aec8a726b3159deb47cb5c27e1e42b71d705a7c2f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8", size = 403273, upload-time = "2025-08-27T12:15:37.051Z" }, + { url = "https://files.pythonhosted.org/packages/c0/93/425e32200158d44ff01da5d9612c3b6711fe69f606f06e3895511f17473b/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc", size = 385278, upload-time = "2025-08-27T12:15:38.571Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1a/1a04a915ecd0551bfa9e77b7672d1937b4b72a0fc204a17deef76001cfb2/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71", size = 402084, upload-time = "2025-08-27T12:15:40.529Z" }, + { url = "https://files.pythonhosted.org/packages/51/f7/66585c0fe5714368b62951d2513b684e5215beaceab2c6629549ddb15036/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad", size = 419041, upload-time = "2025-08-27T12:15:42.191Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7e/83a508f6b8e219bba2d4af077c35ba0e0cdd35a751a3be6a7cba5a55ad71/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab", size = 560084, upload-time = "2025-08-27T12:15:43.839Z" }, + { url = "https://files.pythonhosted.org/packages/66/66/bb945683b958a1b19eb0fe715594630d0f36396ebdef4d9b89c2fa09aa56/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059", size = 590115, upload-time = "2025-08-27T12:15:46.647Z" }, + { url = "https://files.pythonhosted.org/packages/12/00/ccfaafaf7db7e7adace915e5c2f2c2410e16402561801e9c7f96683002d3/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b", size = 556561, upload-time = "2025-08-27T12:15:48.219Z" }, + { url = "https://files.pythonhosted.org/packages/e1/b7/92b6ed9aad103bfe1c45df98453dfae40969eef2cb6c6239c58d7e96f1b3/rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819", size = 229125, upload-time = "2025-08-27T12:15:49.956Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ed/e1fba02de17f4f76318b834425257c8ea297e415e12c68b4361f63e8ae92/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df", size = 371402, upload-time = "2025-08-27T12:15:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/af/7c/e16b959b316048b55585a697e94add55a4ae0d984434d279ea83442e460d/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3", size = 354084, upload-time = "2025-08-27T12:15:53.219Z" }, + { url = "https://files.pythonhosted.org/packages/de/c1/ade645f55de76799fdd08682d51ae6724cb46f318573f18be49b1e040428/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9", size = 383090, upload-time = "2025-08-27T12:15:55.158Z" }, + { url = "https://files.pythonhosted.org/packages/1f/27/89070ca9b856e52960da1472efcb6c20ba27cfe902f4f23ed095b9cfc61d/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc", size = 394519, upload-time = "2025-08-27T12:15:57.238Z" }, + { url = "https://files.pythonhosted.org/packages/b3/28/be120586874ef906aa5aeeae95ae8df4184bc757e5b6bd1c729ccff45ed5/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4", size = 523817, upload-time = "2025-08-27T12:15:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/70cc197bc11cfcde02a86f36ac1eed15c56667c2ebddbdb76a47e90306da/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66", size = 403240, upload-time = "2025-08-27T12:16:00.923Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/46936cca449f7f518f2f4996e0e8344db4b57e2081e752441154089d2a5f/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e", size = 385194, upload-time = "2025-08-27T12:16:02.802Z" }, + { url = "https://files.pythonhosted.org/packages/e1/62/29c0d3e5125c3270b51415af7cbff1ec587379c84f55a5761cc9efa8cd06/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c", size = 402086, upload-time = "2025-08-27T12:16:04.806Z" }, + { url = "https://files.pythonhosted.org/packages/8f/66/03e1087679227785474466fdd04157fb793b3b76e3fcf01cbf4c693c1949/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf", size = 419272, upload-time = "2025-08-27T12:16:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/6a/24/e3e72d265121e00b063aef3e3501e5b2473cf1b23511d56e529531acf01e/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf", size = 560003, upload-time = "2025-08-27T12:16:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/26/ca/f5a344c534214cc2d41118c0699fffbdc2c1bc7046f2a2b9609765ab9c92/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6", size = 590482, upload-time = "2025-08-27T12:16:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/ce/08/4349bdd5c64d9d193c360aa9db89adeee6f6682ab8825dca0a3f535f434f/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", size = 556523, upload-time = "2025-08-27T12:16:12.188Z" }, +] + [[package]] name = "ruff" -version = "0.13.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/df/8d7d8c515d33adfc540e2edf6c6021ea1c5a58a678d8cfce9fae59aabcab/ruff-0.13.2.tar.gz", hash = "sha256:cb12fffd32fb16d32cef4ed16d8c7cdc27ed7c944eaa98d99d01ab7ab0b710ff", size = 5416417, upload-time = "2025-09-25T14:54:09.936Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/84/5716a7fa4758e41bf70e603e13637c42cfb9dbf7ceb07180211b9bbf75ef/ruff-0.13.2-py3-none-linux_armv6l.whl", hash = "sha256:3796345842b55f033a78285e4f1641078f902020d8450cade03aad01bffd81c3", size = 12343254, upload-time = "2025-09-25T14:53:27.784Z" }, - { url = "https://files.pythonhosted.org/packages/9b/77/c7042582401bb9ac8eff25360e9335e901d7a1c0749a2b28ba4ecb239991/ruff-0.13.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ff7e4dda12e683e9709ac89e2dd436abf31a4d8a8fc3d89656231ed808e231d2", size = 13040891, upload-time = "2025-09-25T14:53:31.38Z" }, - { url = "https://files.pythonhosted.org/packages/c6/15/125a7f76eb295cb34d19c6778e3a82ace33730ad4e6f28d3427e134a02e0/ruff-0.13.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c75e9d2a2fafd1fdd895d0e7e24b44355984affdde1c412a6f6d3f6e16b22d46", size = 12243588, upload-time = "2025-09-25T14:53:33.543Z" }, - { url = "https://files.pythonhosted.org/packages/9e/eb/0093ae04a70f81f8be7fd7ed6456e926b65d238fc122311293d033fdf91e/ruff-0.13.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cceac74e7bbc53ed7d15d1042ffe7b6577bf294611ad90393bf9b2a0f0ec7cb6", size = 12491359, upload-time = "2025-09-25T14:53:35.892Z" }, - { url = "https://files.pythonhosted.org/packages/43/fe/72b525948a6956f07dad4a6f122336b6a05f2e3fd27471cea612349fedb9/ruff-0.13.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ae3f469b5465ba6d9721383ae9d49310c19b452a161b57507764d7ef15f4b07", size = 12162486, upload-time = "2025-09-25T14:53:38.171Z" }, - { url = "https://files.pythonhosted.org/packages/6a/e3/0fac422bbbfb2ea838023e0d9fcf1f30183d83ab2482800e2cb892d02dfe/ruff-0.13.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f8f9e3cd6714358238cd6626b9d43026ed19c0c018376ac1ef3c3a04ffb42d8", size = 13871203, upload-time = "2025-09-25T14:53:41.943Z" }, - { url = "https://files.pythonhosted.org/packages/6b/82/b721c8e3ec5df6d83ba0e45dcf00892c4f98b325256c42c38ef136496cbf/ruff-0.13.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c6ed79584a8f6cbe2e5d7dbacf7cc1ee29cbdb5df1172e77fbdadc8bb85a1f89", size = 14929635, upload-time = "2025-09-25T14:53:43.953Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a0/ad56faf6daa507b83079a1ad7a11694b87d61e6bf01c66bd82b466f21821/ruff-0.13.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aed130b2fde049cea2019f55deb939103123cdd191105f97a0599a3e753d61b0", size = 14338783, upload-time = "2025-09-25T14:53:46.205Z" }, - { url = "https://files.pythonhosted.org/packages/47/77/ad1d9156db8f99cd01ee7e29d74b34050e8075a8438e589121fcd25c4b08/ruff-0.13.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1887c230c2c9d65ed1b4e4cfe4d255577ea28b718ae226c348ae68df958191aa", size = 13355322, upload-time = "2025-09-25T14:53:48.164Z" }, - { url = "https://files.pythonhosted.org/packages/64/8b/e87cfca2be6f8b9f41f0bb12dc48c6455e2d66df46fe61bb441a226f1089/ruff-0.13.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bcb10276b69b3cfea3a102ca119ffe5c6ba3901e20e60cf9efb53fa417633c3", size = 13354427, upload-time = "2025-09-25T14:53:50.486Z" }, - { url = "https://files.pythonhosted.org/packages/7f/df/bf382f3fbead082a575edb860897287f42b1b3c694bafa16bc9904c11ed3/ruff-0.13.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:afa721017aa55a555b2ff7944816587f1cb813c2c0a882d158f59b832da1660d", size = 13537637, upload-time = "2025-09-25T14:53:52.887Z" }, - { url = "https://files.pythonhosted.org/packages/51/70/1fb7a7c8a6fc8bd15636288a46e209e81913b87988f26e1913d0851e54f4/ruff-0.13.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1dbc875cf3720c64b3990fef8939334e74cb0ca65b8dbc61d1f439201a38101b", size = 12340025, upload-time = "2025-09-25T14:53:54.88Z" }, - { url = "https://files.pythonhosted.org/packages/4c/27/1e5b3f1c23ca5dd4106d9d580e5c13d9acb70288bff614b3d7b638378cc9/ruff-0.13.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939a1b2a960e9742e9a347e5bbc9b3c3d2c716f86c6ae273d9cbd64f193f22", size = 12133449, upload-time = "2025-09-25T14:53:57.089Z" }, - { url = "https://files.pythonhosted.org/packages/2d/09/b92a5ccee289f11ab128df57d5911224197d8d55ef3bd2043534ff72ca54/ruff-0.13.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:50e2d52acb8de3804fc5f6e2fa3ae9bdc6812410a9e46837e673ad1f90a18736", size = 13051369, upload-time = "2025-09-25T14:53:59.124Z" }, - { url = "https://files.pythonhosted.org/packages/89/99/26c9d1c7d8150f45e346dc045cc49f23e961efceb4a70c47dea0960dea9a/ruff-0.13.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3196bc13ab2110c176b9a4ae5ff7ab676faaa1964b330a1383ba20e1e19645f2", size = 13523644, upload-time = "2025-09-25T14:54:01.622Z" }, - { url = "https://files.pythonhosted.org/packages/f7/00/e7f1501e81e8ec290e79527827af1d88f541d8d26151751b46108978dade/ruff-0.13.2-py3-none-win32.whl", hash = "sha256:7c2a0b7c1e87795fec3404a485096bcd790216c7c146a922d121d8b9c8f1aaac", size = 12245990, upload-time = "2025-09-25T14:54:03.647Z" }, - { url = "https://files.pythonhosted.org/packages/ee/bd/d9f33a73de84fafd0146c6fba4f497c4565fe8fa8b46874b8e438869abc2/ruff-0.13.2-py3-none-win_amd64.whl", hash = "sha256:17d95fb32218357c89355f6f6f9a804133e404fc1f65694372e02a557edf8585", size = 13324004, upload-time = "2025-09-25T14:54:06.05Z" }, - { url = "https://files.pythonhosted.org/packages/c3/12/28fa2f597a605884deb0f65c1b1ae05111051b2a7030f5d8a4ff7f4599ba/ruff-0.13.2-py3-none-win_arm64.whl", hash = "sha256:da711b14c530412c827219312b7d7fbb4877fb31150083add7e8c5336549cea7", size = 12484437, upload-time = "2025-09-25T14:54:08.022Z" }, +version = "0.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/58/6ca66896635352812de66f71cdf9ff86b3a4f79071ca5730088c0cd0fc8d/ruff-0.14.1.tar.gz", hash = "sha256:1dd86253060c4772867c61791588627320abcb6ed1577a90ef432ee319729b69", size = 5513429, upload-time = "2025-10-16T18:05:41.766Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/39/9cc5ab181478d7a18adc1c1e051a84ee02bec94eb9bdfd35643d7c74ca31/ruff-0.14.1-py3-none-linux_armv6l.whl", hash = "sha256:083bfc1f30f4a391ae09c6f4f99d83074416b471775b59288956f5bc18e82f8b", size = 12445415, upload-time = "2025-10-16T18:04:48.227Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2e/1226961855ccd697255988f5a2474890ac7c5863b080b15bd038df820818/ruff-0.14.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f6fa757cd717f791009f7669fefb09121cc5f7d9bd0ef211371fad68c2b8b224", size = 12784267, upload-time = "2025-10-16T18:04:52.515Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ea/fd9e95863124ed159cd0667ec98449ae461de94acda7101f1acb6066da00/ruff-0.14.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6191903d39ac156921398e9c86b7354d15e3c93772e7dbf26c9fcae59ceccd5", size = 11781872, upload-time = "2025-10-16T18:04:55.396Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5a/e890f7338ff537dba4589a5e02c51baa63020acfb7c8cbbaea4831562c96/ruff-0.14.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed04f0e04f7a4587244e5c9d7df50e6b5bf2705d75059f409a6421c593a35896", size = 12226558, upload-time = "2025-10-16T18:04:58.166Z" }, + { url = "https://files.pythonhosted.org/packages/a6/7a/8ab5c3377f5bf31e167b73651841217542bcc7aa1c19e83030835cc25204/ruff-0.14.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5c9e6cf6cd4acae0febbce29497accd3632fe2025c0c583c8b87e8dbdeae5f61", size = 12187898, upload-time = "2025-10-16T18:05:01.455Z" }, + { url = "https://files.pythonhosted.org/packages/48/8d/ba7c33aa55406955fc124e62c8259791c3d42e3075a71710fdff9375134f/ruff-0.14.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fa2458527794ecdfbe45f654e42c61f2503a230545a91af839653a0a93dbc6", size = 12939168, upload-time = "2025-10-16T18:05:04.397Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c2/70783f612b50f66d083380e68cbd1696739d88e9b4f6164230375532c637/ruff-0.14.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:39f1c392244e338b21d42ab29b8a6392a722c5090032eb49bb4d6defcdb34345", size = 14386942, upload-time = "2025-10-16T18:05:07.102Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/cd7abb9c776b66d332119d67f96acf15830d120f5b884598a36d9d3f4d83/ruff-0.14.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7382fa12a26cce1f95070ce450946bec357727aaa428983036362579eadcc5cf", size = 13990622, upload-time = "2025-10-16T18:05:09.882Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/4259b696db12ac152fe472764b4f78bbdd9b477afd9bc3a6d53c01300b37/ruff-0.14.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0bf2be3ae8521e1093a487c4aa3b455882f139787770698530d28ed3fbb37c", size = 13431143, upload-time = "2025-10-16T18:05:13.46Z" }, + { url = "https://files.pythonhosted.org/packages/e0/35/266a80d0eb97bd224b3265b9437bd89dde0dcf4faf299db1212e81824e7e/ruff-0.14.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabcaa9ccf8089fb4fdb78d17cc0e28241520f50f4c2e88cb6261ed083d85151", size = 13132844, upload-time = "2025-10-16T18:05:16.1Z" }, + { url = "https://files.pythonhosted.org/packages/65/6e/d31ce218acc11a8d91ef208e002a31acf315061a85132f94f3df7a252b18/ruff-0.14.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:747d583400f6125ec11a4c14d1c8474bf75d8b419ad22a111a537ec1a952d192", size = 13401241, upload-time = "2025-10-16T18:05:19.395Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b5/dbc4221bf0b03774b3b2f0d47f39e848d30664157c15b965a14d890637d2/ruff-0.14.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5a6e74c0efd78515a1d13acbfe6c90f0f5bd822aa56b4a6d43a9ffb2ae6e56cd", size = 12132476, upload-time = "2025-10-16T18:05:22.163Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/ac99194e790ccd092d6a8b5f341f34b6e597d698e3077c032c502d75ea84/ruff-0.14.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0ea6a864d2fb41a4b6d5b456ed164302a0d96f4daac630aeba829abfb059d020", size = 12139749, upload-time = "2025-10-16T18:05:25.162Z" }, + { url = "https://files.pythonhosted.org/packages/47/26/7df917462c3bb5004e6fdfcc505a49e90bcd8a34c54a051953118c00b53a/ruff-0.14.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0826b8764f94229604fa255918d1cc45e583e38c21c203248b0bfc9a0e930be5", size = 12544758, upload-time = "2025-10-16T18:05:28.018Z" }, + { url = "https://files.pythonhosted.org/packages/64/d0/81e7f0648e9764ad9b51dd4be5e5dac3fcfff9602428ccbae288a39c2c22/ruff-0.14.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cbc52160465913a1a3f424c81c62ac8096b6a491468e7d872cb9444a860bc33d", size = 13221811, upload-time = "2025-10-16T18:05:30.707Z" }, + { url = "https://files.pythonhosted.org/packages/c3/07/3c45562c67933cc35f6d5df4ca77dabbcd88fddaca0d6b8371693d29fd56/ruff-0.14.1-py3-none-win32.whl", hash = "sha256:e037ea374aaaff4103240ae79168c0945ae3d5ae8db190603de3b4012bd1def6", size = 12319467, upload-time = "2025-10-16T18:05:33.261Z" }, + { url = "https://files.pythonhosted.org/packages/02/88/0ee4ca507d4aa05f67e292d2e5eb0b3e358fbcfe527554a2eda9ac422d6b/ruff-0.14.1-py3-none-win_amd64.whl", hash = "sha256:59d599cdff9c7f925a017f6f2c256c908b094e55967f93f2821b1439928746a1", size = 13401123, upload-time = "2025-10-16T18:05:35.984Z" }, + { url = "https://files.pythonhosted.org/packages/b8/81/4b6387be7014858d924b843530e1b2a8e531846807516e9bea2ee0936bf7/ruff-0.14.1-py3-none-win_arm64.whl", hash = "sha256:e3b443c4c9f16ae850906b8d0a707b2a4c16f8d2f0a7fe65c475c5886665ce44", size = 12436636, upload-time = "2025-10-16T18:05:38.995Z" }, ] [[package]] @@ -1280,6 +1590,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "sse-starlette" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/6f/22ed6e33f8a9e76ca0a412405f31abb844b779d52c5f96660766edcd737c/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a", size = 20985, upload-time = "2025-07-27T09:07:44.565Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/10/c78f463b4ef22eef8491f218f692be838282cd65480f6e423d7730dfd1fb/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a", size = 11297, upload-time = "2025-07-27T09:07:43.268Z" }, +] + [[package]] name = "stack-data" version = "0.6.3" @@ -1295,42 +1617,65 @@ wheels = [ ] [[package]] -name = "tomli" -version = "2.2.1" +name = "starlette" +version = "0.48.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46", size = 2652949, upload-time = "2025-09-13T08:41:05.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" }, +] + +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, ] [[package]] @@ -1344,11 +1689,11 @@ wheels = [ [[package]] name = "types-psycopg2" -version = "2.9.21.20250915" +version = "2.9.21.20251012" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8f/20/3dcb89df8d1661cf6c4c2d9f84d4ba94dde48559cdcf7b536a380a9c387f/types_psycopg2-2.9.21.20250915.tar.gz", hash = "sha256:bfeb8f54c32490e7b5edc46215ab4163693192bc90407b4a023822de9239f5c8", size = 26678, upload-time = "2025-09-15T03:01:08.863Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/b3/2d09eaf35a084cffd329c584970a3fa07101ca465c13cad1576d7c392587/types_psycopg2-2.9.21.20251012.tar.gz", hash = "sha256:4cdafd38927da0cfde49804f39ab85afd9c6e9c492800e42f1f0c1a1b0312935", size = 26710, upload-time = "2025-10-12T02:55:39.5Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/4d/ebf1c72809a30150ad142074e1ad5101304f7569c0df2fa872906d76d0af/types_psycopg2-2.9.21.20250915-py3-none-any.whl", hash = "sha256:eefe5ccdc693fc086146e84c9ba437bb278efe1ef330b299a0cb71169dc6c55f", size = 24868, upload-time = "2025-09-15T03:01:07.613Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0c/05feaf8cb51159f2c0af04b871dab7e98a2f83a3622f5f216331d2dd924c/types_psycopg2-2.9.21.20251012-py3-none-any.whl", hash = "sha256:712bad5c423fe979e357edbf40a07ca40ef775d74043de72bd4544ca328cc57e", size = 24883, upload-time = "2025-10-12T02:55:38.439Z" }, ] [[package]] @@ -1360,6 +1705,18 @@ 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.5.0" @@ -1369,6 +1726,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] +[[package]] +name = "uvicorn" +version = "0.38.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/cb/ce/f06b84e2697fef4688ca63bdb2fdf113ca0a3be33f94488f2cadb690b0cf/uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d", size = 80605, upload-time = "2025-10-18T13:46:44.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" }, +] + [[package]] name = "watchdog" version = "6.0.0" @@ -1387,13 +1758,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, - { url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390, upload-time = "2024-11-01T14:06:49.325Z" }, - { url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386, upload-time = "2024-11-01T14:06:50.536Z" }, - { url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017, upload-time = "2024-11-01T14:06:51.717Z" }, { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, - { url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903, upload-time = "2024-11-01T14:06:57.052Z" }, - { url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381, upload-time = "2024-11-01T14:06:58.193Z" }, { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, @@ -1427,12 +1793,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25 wheels = [ { url = "https://files.pythonhosted.org/packages/37/81/6acd6601f61e31cfb8729d3da6d5df966f80f374b78eff83760714487338/yapf-0.43.0-py3-none-any.whl", hash = "sha256:224faffbc39c428cb095818cf6ef5511fdab6f7430a10783fdfb292ccf2852ca", size = 256158, upload-time = "2024-11-14T00:11:39.37Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] From cf71ae9ded0888a2f7435f52446713f76d27dbe4 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Wed, 22 Oct 2025 20:09:07 +0200 Subject: [PATCH 21/58] cocalc-api/mcp: first sign of life --- src/.claude/settings.json | 4 +- src/packages/server/api/manage.ts | 51 ++- src/python/cocalc-api/Makefile | 2 +- src/python/cocalc-api/README.md | 8 +- .../src/cocalc_api/mcp/DEVELOPMENT.md | 393 ++++++------------ .../cocalc-api/src/cocalc_api/mcp/README.md | 72 +++- .../src/cocalc_api/mcp/mcp_server.py | 121 ++++++ .../src/cocalc_api/mcp/resources/__init__.py | 17 +- .../cocalc_api/mcp/resources/file_listing.py | 213 +++------- .../cocalc-api/src/cocalc_api/mcp/server.py | 158 ++----- .../src/cocalc_api/mcp/tools/__init__.py | 17 +- .../src/cocalc_api/mcp/tools/exec.py | 110 ++--- 12 files changed, 508 insertions(+), 658 deletions(-) create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py diff --git a/src/.claude/settings.json b/src/.claude/settings.json index 9503b7e3cbb..7f16b24302c 100644 --- a/src/.claude/settings.json +++ b/src/.claude/settings.json @@ -52,14 +52,12 @@ "Bash(prettier -w:*)", "Bash(psql:*)", "Bash(python3:*)", -<<<<<<< HEAD "Bash(uv:*)", "Bash(timeout:*)", -======= "Bash(uv sync:*)", ->>>>>>> origin/master "WebFetch", "WebSearch", + "mcp__cocalc__*", "mcp__cclsp__find_definition", "mcp__cclsp__find_references", "mcp__github__get_issue", diff --git a/src/packages/server/api/manage.ts b/src/packages/server/api/manage.ts index c0c4e2536d1..17693bb8d39 100644 --- a/src/packages/server/api/manage.ts +++ b/src/packages/server/api/manage.ts @@ -27,11 +27,17 @@ import isBanned from "@cocalc/server/accounts/is-banned"; const log = getLogger("server:api:manage"); +// API key format: new keys start with this prefix (old ones used "sk_") +const API_KEY_PREFIX = "sk-"; + // Global per user limit to avoid abuse/bugs. Nobody should ever hit this. // Since we use a separate key per compute server, and definitely want some users // to create 5K compute servers at once, don't make this too small. const MAX_API_KEYS = 100000; +// PostgreSQL SERIAL type max value (32-bit signed integer) +const MAX_SERIAL = 2147483647; + // Converts any 32-bit nonnegative integer as a 6-character base-62 string. function encode62(n: number): string { if (!Number.isInteger(n)) { @@ -184,8 +190,8 @@ async function createApiKey({ // We encode the id in the secret so when the user presents the secret we can find the record. // Note that passwordHash is NOT a "function" -- due to salt every time you call it, the output is different! // Thus we have to do this little trick. - // New ones start with sk- and old with sk_. - const secret = `sk-${generate(16)}${encode62(id)}`; + // New ones start with API_KEY_PREFIX and old with sk_. + const secret = `${API_KEY_PREFIX}${generate(16)}${encode62(id)}`; const trunc = secret.slice(0, 3) + "..." + secret.slice(-6); const hash = passwordHash(secret); await pool.query("UPDATE api_keys SET trunc=$1,hash=$2 WHERE id=$3", [ @@ -283,7 +289,13 @@ export async function getAccountWithApiKey( log.debug("getAccountWithApiKey"); const pool = getPool("medium"); - // Check for legacy account api key: + // Validate secret format + if (!secret || typeof secret !== "string") { + log.debug("getAccountWithApiKey: invalid secret - not a string"); + return; + } + + // Check for legacy account api key (format: sk_*) if (secret.startsWith("sk_")) { const { rows } = await pool.query( "SELECT account_id FROM accounts WHERE api_key = $1::TEXT", @@ -301,8 +313,37 @@ export async function getAccountWithApiKey( } } - // Check new api_keys table - const id = decode62(secret.slice(-6)); + // Check new api_keys table (format: {API_KEY_PREFIX}{random_16_chars}{base62_encoded_id}) + // Expected length: 3 + 16 + 6 = 25 characters minimum + if (!secret.startsWith(API_KEY_PREFIX) || secret.length < 9) { + log.debug("getAccountWithApiKey: invalid api key format", { + startsWithPrefix: secret.startsWith(API_KEY_PREFIX), + length: secret.length, + }); + return; + } + + // Decode the last 6 characters as base62 to get the ID + let id: number; + try { + id = decode62(secret.slice(-6)); + } catch (err) { + log.debug("getAccountWithApiKey: failed to decode api key id", { + suffix: secret.slice(-6), + error: err instanceof Error ? err.message : String(err), + }); + return; + } + + // Validate that ID is within valid PostgreSQL SERIAL (32-bit) range + if (!Number.isInteger(id) || id < 0 || id > MAX_SERIAL) { + log.debug("getAccountWithApiKey: decoded id out of valid range", { + id, + max: MAX_SERIAL, + }); + return; + } + const { rows } = await pool.query( "SELECT account_id,project_id,hash,expire FROM api_keys WHERE id=$1", [id], diff --git a/src/python/cocalc-api/Makefile b/src/python/cocalc-api/Makefile index 19c6fb79586..5b6dd7f9995 100644 --- a/src/python/cocalc-api/Makefile +++ b/src/python/cocalc-api/Makefile @@ -11,7 +11,7 @@ help: @echo " coverage - Run tests with coverage reporting (HTML + terminal)" @echo " coverage-report - Show coverage report in terminal" @echo " coverage-html - Generate HTML coverage report only" - @echo " mcp - Start the MCP server (requires COCALC_API_KEY and COCALC_PROJECT_ID)" + @echo " mcp - Start the MCP server (requires COCALC_API_KEY, COCALC_PROJECT_ID, and COCALC_HOST)" @echo " serve-docs - Serve documentation locally" @echo " build-docs - Build documentation" @echo " publish - Build and publish package" diff --git a/src/python/cocalc-api/README.md b/src/python/cocalc-api/README.md index d530d3ce948..7ed003e1503 100644 --- a/src/python/cocalc-api/README.md +++ b/src/python/cocalc-api/README.md @@ -49,6 +49,12 @@ The `Project` class provides project-specific operations: - **System**: Execute shell commands and Jupyter code within a specific project +## MCP Server + +The CoCalc API includes a **Model Context Protocol (MCP) server** that allows LLMs (like Claude) to interact with CoCalc projects through a standardized protocol. + +For detailed setup instructions and usage guide, see [src/cocalc_api/mcp/README.md](src/cocalc_api/mcp/README.md). + ## Authentication The client supports two types of API keys: @@ -165,4 +171,4 @@ MIT License. See the [LICENSE](LICENSE) file for details. - [CoCalc Website](https://cocalc.com) - [Documentation](https://cocalc.com/api/python) - [Source Code](https://github.com/sagemathinc/cocalc/tree/master/src/python/cocalc-api) -- [Issue Tracker](https://github.com/sagemathinc/cocalc/issues) \ No newline at end of file +- [Issue Tracker](https://github.com/sagemathinc/cocalc/issues) diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md b/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md index b9d08f7f8ac..51a40ec78d1 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md +++ b/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md @@ -2,342 +2,197 @@ ## Overview -This directory contains the **Model Context Protocol (MCP) server** implementation for the CoCalc API. The MCP server allows LLMs (via clients like Claude Desktop) to interact with CoCalc projects through a standardized protocol. +This directory contains the **Model Context Protocol (MCP) server** for CoCalc API. It allows LLMs (via Claude Code, Claude Desktop) to interact with CoCalc projects through a standardized protocol. -Learn more about MCP: https://modelcontextprotocol.io/docs/getting-started/intro +Learn more: https://modelcontextprotocol.io/docs/getting-started/intro -## Architecture - -### Design Principles - -1. **Single Project Scope**: Each MCP server instance is tied to a specific CoCalc project via `project_id`. This eliminates the need to repeatedly specify which project an operation should run in. - -2. **Configuration via Environment & Config**: Parameters are configured through: - - Environment variables (for local development) - - MCP server configuration files (for LLM clients like Claude Desktop) - -3. **Minimal but Powerful**: Start with essential tools and resources; expand incrementally. +## Configuration -4. **Type-Safe**: Leverage Python type hints and MCP's type system for robust integration. +### Required Environment Variables -### Required Configuration +- **`COCALC_API_KEY`** - API key for CoCalc authentication (format: `sk-...`) +- **`COCALC_PROJECT_ID`** - UUID of the target CoCalc project +- **`COCALC_HOST`** (optional) - CoCalc instance URL (default: `https://cocalc.com`) -#### Parameters - -- **`COCALC_API_KEY`** (required) - - The API key for authenticating with CoCalc - - Source: Environment variable or MCP config (environment field) - - Example: `sk-...` - -- **`project_id`** (required) - - UUID of the target CoCalc project - - Source: Environment variable or MCP config (environment field) - - Format: UUID string (e.g., `6e75dbf1-0342-4249-9dce-6b21648656e9`) - -- **`COCALC_HOST`** (optional) - - Base URL for the CoCalc instance - - Default: `https://cocalc.com` - - Source: Environment variable or MCP config - -#### Example Local Usage +### Setup Examples +**Local Development:** ```bash export COCALC_API_KEY="sk-your-api-key-here" export COCALC_PROJECT_ID="6e75dbf1-0342-4249-9dce-6b21648656e9" -export COCALC_HOST="https://cocalc.com" # Optional - +export COCALC_HOST="http://localhost:5000" # For local CoCalc uv run cocalc-mcp-server ``` -#### Example Claude Desktop Config +**Claude Code CLI:** +```bash +claude mcp add \ + --transport stdio \ + cocalc \ + --env COCALC_API_KEY="sk-your-api-key-here" \ + --env COCALC_PROJECT_ID="6e75dbf1-0342-4249-9dce-6b21648656e9" \ + --env COCALC_HOST="http://localhost:5000" \ + -- uv --directory /path/to/cocalc-api run cocalc-mcp-server +``` +**Claude Desktop:** Add to `~/.config/Claude/claude_desktop_config.json`: - ```json { "mcpServers": { "cocalc": { "command": "uv", - "args": ["run", "cocalc-mcp-server"], + "args": ["--directory", "/path/to/cocalc-api", "run", "cocalc-mcp-server"], "env": { "COCALC_API_KEY": "sk-your-api-key-here", "COCALC_PROJECT_ID": "6e75dbf1-0342-4249-9dce-6b21648656e9", - "COCALC_HOST": "https://cocalc.com" + "COCALC_HOST": "http://localhost:5000" } } } } ``` -## Features - -### Phase 1: MVP (Current Development) - -#### Tools - -##### 1. `exec` - Execute Shell Commands - -Execute arbitrary shell commands in the target CoCalc project. - -**Purpose**: Run any command line operation in the project's Linux environment. - -**Parameters**: -- `command` (string, required): Command to execute (e.g., `date -Ins`, `python script.py`) -- `args` (array of strings, optional): Arguments to pass to the command -- `bash` (boolean, optional): If true, interpret command as bash script -- `timeout` (integer, optional): Timeout in seconds -- `cwd` (string, optional): Working directory (relative to home or absolute) - -**Returns**: Object with: -- `stdout` (string): Command output -- `stderr` (string): Error output -- `exit_code` (integer): Process exit code - -**Examples**: -```json -// Get current date -{ - "tool": "exec", - "command": "date -Ins" -} - -// Run Python script with arguments -{ - "tool": "exec", - "command": "python", - "args": ["script.py", "--verbose", "input.txt"] -} - -// Execute a bash script -{ - "tool": "exec", - "command": "for i in {1..5}; do echo \"Iteration $i\"; done", - "bash": true -} - -// Run with timeout -{ - "tool": "exec", - "command": "sleep 100", - "timeout": 5 -} -``` - -#### Resources - -##### 1. `project-files` - File Listing & Browsing - -Browse and list files in the project directory structure with filtering and pagination. - -**Purpose**: Allow the LLM to understand the project's file structure without running commands. - -**URI Scheme**: `cocalc://project-files/{path}` - -**Query Parameters**: -- `path` (string): Directory path to list (relative to home, default: `.`) -- `glob` (string, optional): Glob pattern to filter files (e.g., `*.py`, `**/*.txt`) -- `limit` (integer, optional): Maximum number of files to return (default: 100, max: 1000) -- `recurse` (boolean, optional): Recursively list subdirectories (default: false) - -**Returns**: Array of file objects with: -- `name` (string): Filename -- `path` (string): Full path relative to home -- `type` (string): `file` or `directory` -- `size` (integer): File size in bytes (0 for directories) -- `modified` (string): Last modified timestamp (ISO 8601) - -**Examples**: - -```uri -// List current directory -cocalc://project-files/ - -// List Python files with recursion -cocalc://project-files/?glob=*.py&recurse=true - -// List all markdown files, limited to 50 results -cocalc://project-files/?glob=*.md&limit=50 - -// Browse a subdirectory -cocalc://project-files/notebooks?limit=20 -``` - -**Return Example**: -```json -{ - "uri": "cocalc://project-files/", - "contents": [ - { - "name": "script.py", - "path": "script.py", - "type": "file", - "size": 2048, - "modified": "2025-10-21T14:30:00Z" - }, - { - "name": "data", - "path": "data", - "type": "directory", - "size": 0, - "modified": "2025-10-21T14:25:00Z" - } - ] -} -``` +## Architecture -## Implementation Structure +### Module Structure ``` src/cocalc_api/mcp/ -├── DEVELOPMENT.md # This file -├── __init__.py # Package initialization -├── server.py # Main MCP server entry point +├── server.py # Entry point, imports mcp_server +├── mcp_server.py # Central coordination hub ├── tools/ -│ ├── __init__.py -│ ├── exec.py # Shell execution tool -│ └── base.py # Base tool class (if needed) +│ ├── __init__.py # register_tools(mcp) +│ └── exec.py # register_exec_tool(mcp) └── resources/ - ├── __init__.py - ├── project_files.py # File listing resource - └── base.py # Base resource class (if needed) + ├── __init__.py # register_resources(mcp) + └── file_listing.py # register_file_listing_resource(mcp) ``` -### File Responsibilities - -- **`server.py`**: Initializes MCP server, registers tools/resources, handles configuration -- **`tools/exec.py`**: Implementation of the `exec` tool -- **`resources/project_files.py`**: Implementation of file listing resource - -## Configuration & Initialization - -### Server Initialization Flow +### Initialization Flow -1. **Read Configuration**: - - Check environment variables: `COCALC_API_KEY`, `COCALC_PROJECT_ID`, `COCALC_HOST` - - Validate all required parameters are set - - Initialize HTTP client with auth +1. **`server.py`** imports `mcp_server` module +2. **`mcp_server.py`** initializes at import time: + - Creates `FastMCP("cocalc-api")` instance + - Initializes `Project` client (lazy, cached) + - Calls `tools.register_tools(mcp)` + - Calls `resources.register_resources(mcp)` +3. **`tools/` and `resources/`** register their handlers with the shared `mcp` object +4. **`server.py`** calls `mcp.run(transport="stdio")` -2. **Create Project Client**: - - Instantiate `Project(api_key, project_id, host)` - - Verify project is accessible (ping test) +### Key Design Decisions -3. **Register Tools**: - - `exec`: Shell command execution +- **Single Project Client**: Initialized once, shared across all tools/resources +- **FastMCP Framework**: Automatic JSON-RPC handling, clean decorator pattern +- **No Wrapper Functions**: Tools/resources decorated directly in their modules +- **Dependency Injection**: mcp object passed to registration functions +- **Easy Extension**: Add new tool by creating `tools/my_tool.py` with `register_my_tool(mcp)` function -4. **Register Resources**: - - `project-files`: File listing +## Available Tools & Resources -5. **Start Server**: - - Begin listening for MCP requests +### Tools -### Error Handling +#### `exec` - Execute Shell Commands -- Configuration errors → Exit with clear error message -- Project authentication errors → Cannot access project -- Runtime errors in tools → Return error in MCP format -- Network errors → Retry logic with exponential backoff (future enhancement) +Execute arbitrary shell commands in the CoCalc project. -## Testing Strategy - -### Unit Tests - -- **`test_exec.py`**: Test command execution with various inputs -- **`test_project_files.py`**: Test file listing and filtering -- **`test_server.py`**: Test server initialization and configuration - -### Integration Tests - -- Full flow: Initialize server → Execute command → List files -- Error cases: Invalid project_id, auth failures, malformed requests -- Performance: Large directory listings, recursive traversal - -### Manual Testing +**Parameters:** +- `command` (string, required): Command to execute +- `args` (list, optional): Command arguments +- `bash` (boolean, optional): Interpret as bash script +- `timeout` (integer, optional): Timeout in seconds +- `cwd` (string, optional): Working directory -```bash -# Start the MCP server locally -export COCALC_API_KEY="sk-..." -export COCALC_PROJECT_ID="..." -python -m cocalc_api.mcp.server +**Returns:** stdout, stderr, and exit_code -# In another terminal, test with a client (future) +**Examples:** +```json +{"command": "echo 'Hello'"} +{"command": "python", "args": ["script.py", "--verbose"]} +{"command": "for i in {1..3}; do echo $i; done", "bash": true} ``` -## Future Enhancements - -### Phase 2: File Operations +### Resources -- **`file-read`** resource: Read file contents -- **`file-write`** tool: Write/create files -- **`file-delete`** tool: Delete files -- **`file-rename`** tool: Rename/move files +#### `project-files` - List Files -### Phase 3: Advanced Features +Browse project directory structure. -- **`jupyter-execute`** tool: Run Jupyter code -- **`git-status`** resource: Git repository status -- **`project-info`** resource: Project metadata and state -- **Caching**: Cache directory listings and file metadata +**URI:** `cocalc://project-files/{path}` -### Phase 4: Optimization +**Parameters:** +- `path` (string, optional): Directory path (default: `.`) -- Connection pooling for multiple concurrent requests -- Request queuing to prevent project overload -- Streaming responses for large file operations -- Metrics collection and logging +**Returns:** Formatted list of files and directories -## Dependencies +## Development Workflow -- **`mcp>=1.0`**: Model Context Protocol SDK -- **`httpx`**: HTTP client (already in project) -- **`pydantic>=2.0`**: Data validation (via mcp dependency) -- **`python-dotenv`**: Environment variable loading (via mcp dependency) +### Adding a New Tool -## Development Commands +1. Create `tools/my_tool.py`: +```python +def register_my_tool(mcp) -> None: + """Register my tool with FastMCP instance.""" + @mcp.tool() + async def my_tool(param: str) -> str: + """Tool description.""" + from ..mcp_server import get_project_client + project = get_project_client() + # Implementation using project client + return result +``` -```bash -# Install development dependencies -uv pip install -e ".[dev]" +2. Update `tools/__init__.py`: +```python +def register_tools(mcp) -> None: + from .exec import register_exec_tool + from .my_tool import register_my_tool # Add this -# Run tests -pytest tests/test_mcp_*.py -v + register_exec_tool(mcp) + register_my_tool(mcp) # Add this +``` -# Run with debugging -export COCALC_API_KEY="sk-..." -export COCALC_PROJECT_ID="..." -python -m cocalc_api.mcp.server +3. Done! The tool is automatically registered when `mcp_server` imports tools. -# Type checking -mypy src/cocalc_api/mcp/ +### Testing -# Code formatting -ruff format src/cocalc_api/mcp/ +```bash +# Run MCP server in one terminal +make mcp + +# Test with another terminal (example) +python3 << 'EOF' +import json, subprocess +proc = subprocess.Popen(['uv', 'run', 'cocalc-mcp-server'], ...) +init = {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {...}} +proc.stdin.write(json.dumps(init) + '\n') +# ... test tool calls +EOF ``` -## References +## Error Handling -- **MCP Specification**: https://modelcontextprotocol.io/ -- **MCP Python SDK**: https://github.com/modelcontextprotocol/python-sdk -- **CoCalc API**: See parent directory documentation -- **Claude Desktop Config**: https://modelcontextprotocol.io/docs/tools/resources +- Configuration errors → Exit with error message +- Project authentication errors → Connection failure +- Tool runtime errors → Returned as error in response -## Security Considerations +## Security Notes -1. **API Key Security**: Never commit API keys to version control. Use environment variables or secure config files with restricted permissions (600). +1. **API Keys** - Never commit to version control; use environment variables +2. **Project Isolation** - Each server instance is bound to one project +3. **Command Execution** - `exec` tool runs arbitrary commands; verify API key permissions +4. **File Access** - File listing respects project filesystem permissions -2. **Project Isolation**: Each server instance targets only one project. Different projects require different MCP server instances. - -3. **Command Execution**: The `exec` tool runs arbitrary commands in the project. Ensure the API key has appropriate permissions. - -4. **File Access**: File listing respects project filesystem permissions. Only files accessible to the project user are listed. +## Future Enhancements -5. **Rate Limiting**: Consider implementing rate limiting in production to prevent overload (future enhancement). +- File read/write operations +- Jupyter code execution +- Git repository operations +- Directory caching and recursion +- Rate limiting -## Next Steps +## References -1. Implement `server.py` with MCP server initialization -2. Implement `tools/exec.py` with shell command execution -3. Implement `resources/project_files.py` with file listing -4. Add comprehensive error handling and validation -5. Write tests for all components -6. Document usage examples in README -7. Test with actual LLM clients (Claude, etc.) +- **MCP Spec**: https://modelcontextprotocol.io/ +- **FastMCP Docs**: https://github.com/modelcontextprotocol/python-sdk +- **CoCalc API**: See parent directory README diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/README.md b/src/python/cocalc-api/src/cocalc_api/mcp/README.md index a453e1b08bd..104b8c3da87 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/README.md +++ b/src/python/cocalc-api/src/cocalc_api/mcp/README.md @@ -20,7 +20,45 @@ export COCALC_HOST="https://cocalc.com" # optional, defaults to https://cocalc. uv run cocalc-mcp-server ``` -### 3. Use with Claude Desktop +### 3. Setup with Claude Code CLI + +```bash +# Set your credentials +export COCALC_API_KEY="sk-your-api-key-here" +export COCALC_PROJECT_ID="[UUID]" +export COCALC_API_PATH="/path/to/cocalc/src/python/cocalc-api" +# OPTIONAL: set the host, defaults to cocalc.com. for development use localhost:5000 +export COCALC_HOST="http://localhost:5000" + +# Add the MCP server to Claude Code +claude mcp add \ + --transport stdio \ + cocalc \ + --env COCALC_API_KEY="$COCALC_API_KEY" \ + --env COCALC_PROJECT_ID="$COCALC_PROJECT_ID" \ + --env COCALC_HOST="$COCALC_HOST" \ + -- uv --directory "$COCALC_API_PATH" run cocalc-mcp-server +``` + +Alternatively, using JSON configuration: + +```bash +claude mcp add-json cocalc '{ + "command": "uv", + "args": ["--directory", "/path/to/cocalc/src/python/cocalc-api", "run", "cocalc-mcp-server"], + "env": { + "COCALC_API_KEY": "sk-your-api-key-here", + "COCALC_PROJECT_ID": "[UUID]", + "COCALC_HOST": "http://localhost:5000" + } +}' +``` + +**Important:** +- Replace `/path/to/cocalc/src/python/cocalc-api` with the absolute path to your cocalc-api directory. +- Replace `http://localhost:5000` with your CoCalc instance URL (defaults to `https://cocalc.com` if not set). + +### 4. Setup with Claude Desktop Add to `~/.config/Claude/claude_desktop_config.json`: @@ -29,18 +67,37 @@ Add to `~/.config/Claude/claude_desktop_config.json`: "mcpServers": { "cocalc": { "command": "uv", - "args": ["run", "cocalc-mcp-server"], + "args": ["--directory", "/path/to/cocalc/src/python/cocalc-api", "run", "cocalc-mcp-server"], "env": { - "COCALC_API_KEY": "sk-your-api-key", - "COCALC_PROJECT_ID": "your-project-uuid", - "COCALC_HOST": "https://cocalc.com" + "COCALC_API_KEY": "sk-your-api-key-here", + "COCALC_PROJECT_ID": "[UUID]", + "COCALC_HOST": "http://localhost:5000" } } } } ``` -Then restart Claude Desktop and you'll have access to the CoCalc tools. +**Important:** +- Replace `/path/to/cocalc/src/python/cocalc-api` with the absolute path to your cocalc-api directory. +- Replace `http://localhost:5000` with your CoCalc instance URL (defaults to `https://cocalc.com` if not set). + +### 5. Allow MCP Tools in Claude Code Settings + +To automatically allow all CoCalc MCP tools without prompts, add this to `.claude/settings.json`: + +```json +{ + "allowedTools": [ + "mcp__cocalc__*" + ] +} +``` + +This wildcard pattern (`mcp__cocalc__*`) automatically allows: +- `mcp__cocalc__exec` - Execute shell commands +- `mcp__cocalc__project_files` - Browse project files +- Any future tools added to the MCP server ## Features @@ -77,12 +134,13 @@ src/cocalc_api/mcp/ ├── README.md # This file ├── DEVELOPMENT.md # Architecture & design documentation ├── server.py # Main MCP server +├── mcp_server.py # MCP instance and project client coordination ├── __main__.py # Module entry point ├── tools/ │ ├── exec.py # Shell command execution tool │ └── __init__.py └── resources/ - ├── project_files.py # File listing resource + ├── file_listing.py # File listing resource └── __init__.py ``` diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py new file mode 100644 index 00000000000..0b9976eb78f --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py @@ -0,0 +1,121 @@ +""" +CoCalc MCP (Model Context Protocol) Server - Central Coordination Module + +This MCP server gives you direct access to a CoCalc project environment. + +AVAILABLE TOOLS (actions you can perform): +- exec: Run shell commands, scripts, and programs in the project + Use for: running code, data processing, build/test commands, git operations, etc. + +AVAILABLE RESOURCES (information you can read): +- project-files: Browse the project file structure + Use for: exploring what files exist, understanding project layout, locating files to work with + +HOW IT WORKS: +- You can use these tools and resources to understand, modify, and manage files in the project +- The project runs in an Ubuntu Linux container with common development tools pre-installed +- Commands execute with the permissions of the CoCalc project user +- All operations are scoped to this single project + +WHEN TO USE WHICH: +1. First, use project-files to explore and understand the project structure +2. Then, use exec to run commands, edit files, run tests, etc. +3. Use project-files again if you need to navigate to new directories +4. Use exec for anything the project-files resource can't show (recursive listings, complex queries, etc.) + +AUTHENTICATION & CONFIGURATION: +Required environment variables (already set when this server is running): +- COCALC_API_KEY: Your CoCalc API authentication token +- COCALC_PROJECT_ID: The UUID of your CoCalc project +- COCALC_HOST: (optional) Your CoCalc instance URL (defaults to https://cocalc.com) +""" + +import os +import sys +from typing import Optional + +from mcp.server.fastmcp import FastMCP + +from cocalc_api import Project + + +def get_config() -> tuple[str, str, str]: + """ + Get and validate MCP server configuration. + + Returns: + Tuple of (api_key, project_id, host) + + Raises: + RuntimeError: If required configuration is missing + """ + api_key = os.environ.get("COCALC_API_KEY") + project_id = os.environ.get("COCALC_PROJECT_ID") + host = os.environ.get("COCALC_HOST", "https://cocalc.com") + + if not api_key: + raise RuntimeError( + "COCALC_API_KEY environment variable is required but not set" + ) + if not project_id: + raise RuntimeError( + "COCALC_PROJECT_ID environment variable is required but not set" + ) + + return api_key, project_id, host + + +# Initialize FastMCP server +mcp = FastMCP("cocalc-api") + +# Project client (initialized below) +_project_client: Optional[Project] = None + + +def initialize_project_client() -> Project: + """Initialize and return the Project client.""" + global _project_client + + if _project_client is not None: + return _project_client + + # Get configuration + try: + api_key, project_id, host = get_config() + except RuntimeError as e: + print(f"Configuration Error: {e}", file=sys.stderr) + sys.exit(1) + + # Initialize Project client + try: + _project_client = Project(api_key=api_key, project_id=project_id, host=host) + + # Verify project is accessible + print(f"Connecting to project {project_id}...", file=sys.stderr) + _project_client.system.ping() + print(f"✓ Connected to project {project_id}", file=sys.stderr) + except Exception as e: + print( + f"Error: Could not connect to project {project_id}: {e}", + file=sys.stderr, + ) + sys.exit(1) + + return _project_client + + +def get_project_client() -> Project: + """Get the initialized Project client.""" + global _project_client + if _project_client is None: + return initialize_project_client() + return _project_client + + +# Register tools and resources +# This happens at module import time, auto-registering with the mcp instance +from . import tools as tools_module # noqa: E402 +from . import resources as resources_module # noqa: E402 + +tools_module.register_tools(mcp) +resources_module.register_resources(mcp) diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/resources/__init__.py b/src/python/cocalc-api/src/cocalc_api/mcp/resources/__init__.py index 5ef870637e2..da1cbd8a12e 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/resources/__init__.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/resources/__init__.py @@ -1,9 +1,18 @@ """ -MCP Resources for CoCalc API. +CoCalc MCP Resources - Available Information -Resources provide information that the LLM can read about the CoCalc project. +Resources are read-only information you can access about the CoCalc project. + +Available Resources: +- project-files: Browse and list files in the project directory structure + +See mcp_server.py for overview of all available tools and resources, and guidance +on when to use each one. """ -from .file_listing import ProjectFilesResource -__all__ = ["ProjectFilesResource"] +def register_resources(mcp) -> None: + """Register all resources with the given FastMCP instance.""" + from .file_listing import register_file_listing_resource + + register_file_listing_resource(mcp) diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py b/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py index 87be9094127..7a889894e9b 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py @@ -2,186 +2,75 @@ File listing resource for CoCalc projects. Provides the 'project-files' resource that allows browsing and listing -files in the project directory structure with filtering and pagination. +files in the project directory structure. This resource helps LLMs discover +and understand the contents of the CoCalc project filesystem. """ -from typing import Any, Optional, cast -from datetime import datetime -from urllib.parse import urlparse, parse_qs +from typing import TYPE_CHECKING, Optional -from mcp.types import TextResourceContents, ReadResourceResult -from pydantic.networks import AnyUrl +if TYPE_CHECKING: + from ..mcp_server import mcp as _mcp -class ProjectFilesResource: - """Resource for listing and browsing project files.""" +def register_file_listing_resource(mcp) -> None: + """Register the file listing resource with the given FastMCP instance.""" - def __init__(self, project_client): + @mcp.resource("cocalc://project-files") + async def project_files() -> str: """ - Initialize the project files resource. + Browse and list files in the CoCalc project home directory. - Args: - project_client: Initialized Project client from cocalc_api - """ - self.project_client = project_client - - def uri_template(self) -> str: - """Return the URI template for this resource.""" - return "cocalc://project-files/{path}" - - def description(self) -> str: - """Return the resource description.""" - return ( - "Browse and list files in the CoCalc project. " - "Supports glob filtering and pagination. " - "Query parameters: glob=*.py (filter), limit=100 (max results), recurse=true (recursive)" - ) - - async def read(self, uri: str) -> ReadResourceResult: - """ - Read and list files from the project. + Use this resource when you need to: + - Explore the project layout and understand what files exist + - Locate specific files by name or type + - Understand the overall project organization before making changes + - Determine which files you need to examine or modify + - Check file sizes to understand project scope - Args: - uri: Resource URI like cocalc://project-files/path?glob=*.py&limit=100 + This lists immediate children (files and directories) in the project home directory, + showing their type and size. Use the exec tool with 'find' or 'ls -R' to + get recursive directory listings or more detailed information about subdirectories. Returns: - ReadResourceResult with file listing + Formatted listing with: + - [FILE] or [DIR] indicator + - Item name + - Size in bytes """ - try: - # Parse URI - parsed = urlparse(uri) - path = parsed.path.replace("project-files/", "").lstrip("/") or "." - - # Parse query parameters - query = parse_qs(parsed.query) - glob_pattern = query.get("glob", [None])[0] - limit = int(query.get("limit", [100])[0]) if query.get("limit") else 100 - recurse_str = query.get("recurse", [False])[0] - recurse = recurse_str.lower() == "true" if isinstance(recurse_str, str) else False - - # TODO: Implement file listing logic - # This is a placeholder that will be implemented in the next phase - files_info = self._list_files(path, glob_pattern, limit, recurse) - - content = self._format_files_output(files_info, uri) - - return ReadResourceResult( - contents=[TextResourceContents(uri=cast(AnyUrl, uri), text=content)] - ) - - except Exception as e: - return ReadResourceResult( - contents=[ - TextResourceContents( - uri=cast(AnyUrl, uri), - text=f"Error listing files: {str(e)}", - ) - ] - ) - - def _list_files( - self, path: str, glob_pattern: Optional[str], limit: int, recurse: bool - ) -> list[dict[str, Any]]: - """ - List files in the given path. - - Args: - path: Directory path to list - glob_pattern: Optional glob pattern to filter files (case-insensitive) - limit: Maximum number of files to return - recurse: Whether to recursively list subdirectories + from ..mcp_server import get_project_client - Returns: - List of file information dictionaries with keys: name, path, type, size, modified - - Notes: - - Glob patterns are case-insensitive (e.g., "*.py", "*.PY", "*.Py" all match) - - Uses find -iname for case-insensitive matching on Unix/Linux systems - """ try: - # Build find command - depth_opt = "" if recurse else "-maxdepth 1" - # Use -iname for case-insensitive glob pattern matching - glob_opt = f"-iname '{glob_pattern}'" if glob_pattern else "" - - # Use find with -printf for structured output - # Format: filename|type|size|mtime - # where type is 'f' for file or 'd' for directory - cmd = f"find {path} {depth_opt} -type f -o -type d" - if glob_pattern: - cmd = f"find {path} {depth_opt} {glob_opt} -type f -o {glob_opt} -type d" - - cmd += " -printf '%P|%y|%s|%T@\\n' 2>/dev/null | head -n {limit}".format(limit=limit) - - result = self.project_client.system.exec(command=cmd, bash=True) + project = get_project_client() + # Use the exec tool to list files with basic information + result = project.system.exec( + command="find . -maxdepth 1 \\( -type f -o -type d \\) -printf '%f %s %T@ %y\\n' 2>/dev/null | sort", + bash=True, + ) - if result['exit_code'] != 0: - return [] + if result["exit_code"] != 0: + return f"Error listing files: {result['stderr']}" - files_info: list[dict[str, Any]] = [] - for line in result['stdout'].strip().split('\n'): - if not line: - continue + files_output = result["stdout"].strip() + if not files_output: + return "No files found in project home directory" - try: - parts = line.split('|') - if len(parts) < 4: - continue - - filename, file_type, size_str, mtime_str = parts[0], parts[1], parts[2], parts[3] - - # Skip the root directory entry (empty filename with type 'd') - if not filename and file_type == 'd': - continue - - try: - size = int(size_str) if size_str else 0 - except ValueError: - size = 0 - - try: - mtime_float = float(mtime_str) - modified = datetime.fromtimestamp(mtime_float).isoformat() + 'Z' - except (ValueError, OverflowError): - modified = "unknown" - - files_info.append({ - 'name': filename.split('/')[-1] if filename else path, - 'path': filename if filename else path, - 'type': 'directory' if file_type == 'd' else 'file', - 'size': size, - 'modified': modified, - }) - - if len(files_info) >= limit: - break - except (IndexError, ValueError): - # Skip malformed lines + # Format the output + lines = files_output.split('\n') + formatted_lines = [] + for line in lines: + if not line.strip(): continue + parts = line.split() + if len(parts) >= 4: + name, size, mtime, ftype = parts[0], parts[1], parts[2], parts[3] + file_type_str = "[DIR]" if ftype == "d" else "[FILE]" + formatted_lines.append(f"{file_type_str} {name:30} {size:>10} bytes") - return files_info + if not formatted_lines: + return "No files found in project home directory" - except Exception: - # Return empty list on error (error will be shown in read() method) - return [] + header = "Files in project home directory:\n" + "=" * 60 + "\n" + return header + "\n".join(formatted_lines) - def _format_files_output(self, files_info: list[dict[str, Any]], uri: str) -> str: - """ - Format file information for output. - - Args: - files_info: List of file information - uri: The requested URI - - Returns: - Formatted text output - """ - if not files_info: - return f"No files found for {uri}" - - output = f"Files in {uri}:\n\n" - for file_info in files_info: - file_type = "[DIR]" if file_info["type"] == "directory" else "[FILE]" - output += f"{file_type} {file_info['path']} ({file_info['size']} bytes)\n" - - return output + except Exception as e: + return f"Error listing files: {str(e)}" diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/server.py b/src/python/cocalc-api/src/cocalc_api/mcp/server.py index 36bdd6d6b14..9922edc0659 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/server.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/server.py @@ -1,15 +1,26 @@ """ MCP Server for CoCalc API. -Main entry point for the Model Context Protocol server that provides -LLM access to CoCalc projects. +Main entry point for the Model Context Protocol server that provides LLM access +to CoCalc projects through a standardized protocol. This server bridges LLMs +(like Claude) with CoCalc project environments. -Usage (local): +CoCalc MCP enables: +- Executing shell commands and scripts in a CoCalc project +- Browsing and reading project files +- Providing context about project structure and contents +- Complete project lifecycle automation + +For detailed architecture and configuration documentation, see mcp_server.py. + +Configuration Examples: + +1. Local Testing: export COCALC_API_KEY="sk-..." export COCALC_PROJECT_ID="project-uuid" uv run cocalc-mcp-server -Usage (Claude Desktop config): +2. Claude Desktop config (~/.config/Claude/claude_desktop_config.json): { "mcpServers": { "cocalc": { @@ -22,141 +33,26 @@ } } } + +3. Claude Code CLI: + export COCALC_API_KEY="sk-..." + export COCALC_PROJECT_ID="..." + export COCALC_HOST="https://cocalc.com" + claude mcp add --transport stdio cocalc --env COCALC_API_KEY --env COCALC_PROJECT_ID --env COCALC_HOST -- uv run cocalc-mcp-server """ -import os import sys -import asyncio - -from mcp.server import Server -from mcp.types import Resource, TextContent, TextResourceContents -from pydantic.networks import AnyUrl - -from cocalc_api import Project -from .tools.exec import ExecTool -from .resources.file_listing import ProjectFilesResource - - -def get_config() -> tuple[str, str, str]: - """ - Get and validate MCP server configuration. - - Returns: - Tuple of (api_key, project_id, host) - - Raises: - RuntimeError: If required configuration is missing - """ - api_key = os.environ.get("COCALC_API_KEY") - project_id = os.environ.get("COCALC_PROJECT_ID") - host = os.environ.get("COCALC_HOST", "https://cocalc.com") - - if not api_key: - raise RuntimeError( - "COCALC_API_KEY environment variable is required but not set" - ) - if not project_id: - raise RuntimeError( - "COCALC_PROJECT_ID environment variable is required but not set" - ) - - return api_key, project_id, host - - -async def _run_server(): - """Async implementation of the MCP server.""" - - # Get configuration - try: - api_key, project_id, host = get_config() - except RuntimeError as e: - print(f"Configuration Error: {e}", file=sys.stderr) - sys.exit(1) - - # Initialize Project client - try: - project_client = Project(api_key=api_key, project_id=project_id, host=host) - - # Verify project is accessible - print(f"Connecting to project {project_id}...", file=sys.stderr) - project_client.system.ping() - print(f"✓ Connected to project {project_id}", file=sys.stderr) - except Exception as e: - print( - f"Error: Could not connect to project {project_id}: {e}", - file=sys.stderr, - ) - sys.exit(1) - - # Initialize MCP server - server = Server("cocalc-api") - - # Initialize tools - exec_tool = ExecTool(project_client) - project_files_resource = ProjectFilesResource(project_client) - - # Register tools - @server.list_tools() - async def list_tools(): - """List available tools.""" - return [exec_tool.definition()] - - @server.call_tool() - async def call_tool(name: str, arguments: dict): - """Execute a tool.""" - if name == "exec": - return await exec_tool.execute(arguments) - else: - return [ - TextContent( - type="text", - text=f"Unknown tool: {name}", - ) - ] - - # Register resources - @server.list_resources() - async def list_resources(): - """List available resources.""" - return [ - Resource( - uri=project_files_resource.uri_template(), # type: ignore - name="project-files", - description=project_files_resource.description(), - mimeType="text/plain", - ) - ] - - @server.read_resource() # type: ignore - async def read_resource(uri: AnyUrl): - """Read a resource.""" - if str(uri).startswith("cocalc://project-files"): - result = await project_files_resource.read(str(uri)) - return result.contents # type: ignore - else: - # Return error for unknown resources - return [ - TextResourceContents( - uri=uri, - text=f"Unknown resource: {uri}", - ) - ] - # Start server - print( - f"Starting CoCalc API MCP Server for project {project_id}...", - file=sys.stderr, - ) - async with server: # type: ignore - print("Server is running. Press Ctrl+C to exit.", file=sys.stderr) - # Run the server indefinitely - while True: - await asyncio.sleep(1) +# Import the mcp_server module which initializes and registers everything +from .mcp_server import mcp # noqa: F401 def main(): """Entry point for the MCP server.""" - asyncio.run(_run_server()) + print("Starting CoCalc API MCP Server...", file=sys.stderr) + # mcp is already initialized and has all tools/resources registered + # We just need to run it + mcp.run(transport="stdio") if __name__ == "__main__": diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py index 5e32088e336..d626c5c105b 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py @@ -1,9 +1,18 @@ """ -MCP Tools for CoCalc API. +CoCalc MCP Tools - Available Actions -Tools allow the LLM to perform actions in the CoCalc project. +Tools are actions you can perform in the CoCalc project. + +Available Tools: +- exec: Execute shell commands, scripts, and programs in the project environment + +See mcp_server.py for overview of all available tools and resources, and guidance +on when to use each one. """ -from .exec import ExecTool -__all__ = ["ExecTool"] +def register_tools(mcp) -> None: + """Register all tools with the given FastMCP instance.""" + from .exec import register_exec_tool + + register_exec_tool(mcp) diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py index cc55df2404c..82f8a48c4c0 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py @@ -5,82 +5,56 @@ in the target CoCalc project environment. """ -from typing import Any -from mcp.types import Tool, TextContent +from typing import Optional +# Import will happen at end to avoid circular imports +from typing import TYPE_CHECKING -class ExecTool: - """Tool for executing shell commands in a CoCalc project.""" +if TYPE_CHECKING: + from ..mcp_server import mcp as _mcp - def __init__(self, project_client): - """ - Initialize the exec tool. - Args: - project_client: Initialized Project client from cocalc_api +def register_exec_tool(mcp) -> None: + """Register the exec tool with the given FastMCP instance.""" + + @mcp.tool() + async def exec( + command: str, + args: Optional[list[str]] = None, + bash: bool = False, + timeout: Optional[int] = None, + cwd: Optional[str] = None, + ) -> str: """ - self.project_client = project_client + Execute shell commands in a CoCalc project environment. - def definition(self) -> Tool: - """Return the MCP tool definition.""" - return Tool( - name="exec", - description="Execute a shell command in the CoCalc project", - inputSchema={ - "type": "object", - "properties": { - "command": { - "type": "string", - "description": "The command to execute (e.g., 'ls -la', 'python script.py')", - }, - "args": { - "type": "array", - "items": {"type": "string"}, - "description": "Optional command arguments", - }, - "bash": { - "type": "boolean", - "description": "If true, interpret command as bash script", - }, - "timeout": { - "type": "integer", - "description": "Timeout in seconds", - }, - "cwd": { - "type": "string", - "description": "Working directory (relative to home or absolute)", - }, - }, - "required": ["command"], - }, - ) + A CoCalc project is a containerized Linux environment (Ubuntu-based) where you can run + arbitrary command-line tools and scripts. This tool allows you to execute any shell + command available in that environment, including: + - Programming language interpreters (Python, Node.js, R, Julia, etc.) + - System utilities (grep, awk, sed, find, etc.) + - Development tools (git, npm, pip, cargo, etc.) + - Data processing tools (bc, jq, imagemagick, etc.) + - Custom scripts and compiled programs - async def execute(self, arguments: dict[str, Any]) -> list[TextContent]: - """ - Execute the command and return results. + The command executes in the project's Linux shell environment with access to the + project's file system and all installed packages/tools. Args: - arguments: Tool arguments from MCP request + command: The command to execute (e.g., 'ls -la', 'python script.py', 'echo 2 + 3 | bc') + args: Optional list of arguments to pass to the command + bash: If true, interpret command as a bash script (enables pipes, redirects, etc.) + timeout: Timeout in seconds for command execution + cwd: Working directory (relative to home or absolute) Returns: - List of TextContent with stdout, stderr, and exit_code + A string containing stdout, stderr, and exit code information """ - try: - command = arguments.get("command") - args = arguments.get("args") - bash = arguments.get("bash", False) - timeout = arguments.get("timeout") - cwd = arguments.get("cwd") - - if not command: - return [ - TextContent( - type="text", - text="Error: 'command' parameter is required", - ) - ] + from ..mcp_server import get_project_client - result = self.project_client.system.exec( + try: + project = get_project_client() + result = project.system.exec( command=command, args=args, bash=bash, @@ -89,13 +63,7 @@ async def execute(self, arguments: dict[str, Any]) -> list[TextContent]: ) output = f"stdout:\n{result['stdout']}\n\nstderr:\n{result['stderr']}\n\nexit_code: {result['exit_code']}" - - return [TextContent(type="text", text=output)] + return output except Exception as e: - return [ - TextContent( - type="text", - text=f"Error executing command: {str(e)}", - ) - ] + return f"Error executing command: {str(e)}" From 9756779295d3f282845fffd43c6f96e270dba567 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Thu, 23 Oct 2025 17:45:38 +0200 Subject: [PATCH 22/58] cocalc-api/mcp: refine authentication --- src/packages/conat/hub/api/system.ts | 3 + src/packages/conat/project/api/system.ts | 3 + src/packages/next/pages/api/conat/project.ts | 5 + src/packages/project/conat/api/system.ts | 11 + src/packages/server/api/project-bridge.ts | 7 +- src/packages/server/conat/api/system.ts | 20 ++ src/python/cocalc-api/Makefile | 6 +- src/python/cocalc-api/pyproject.toml | 1 + .../cocalc-api/src/cocalc_api/api_types.py | 6 + src/python/cocalc-api/src/cocalc_api/hub.py | 15 +- .../src/cocalc_api/mcp/DEVELOPMENT.md | 117 +++++++-- .../src/cocalc_api/mcp/mcp_debug.py | 135 +++++++++++ .../src/cocalc_api/mcp/mcp_server.py | 225 ++++++++++++++---- .../cocalc_api/mcp/resources/file_listing.py | 6 +- .../cocalc-api/src/cocalc_api/mcp/server.py | 46 +++- .../src/cocalc_api/mcp/tools/exec.py | 7 +- .../cocalc-api/src/cocalc_api/project.py | 17 +- 17 files changed, 546 insertions(+), 84 deletions(-) create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/mcp_debug.py diff --git a/src/packages/conat/hub/api/system.ts b/src/packages/conat/hub/api/system.ts index ad8d961107d..d18c9da67d3 100644 --- a/src/packages/conat/hub/api/system.ts +++ b/src/packages/conat/hub/api/system.ts @@ -9,6 +9,7 @@ import { type UserSearchResult } from "@cocalc/util/db-schema/accounts"; export const system = { getCustomize: noAuth, ping: noAuth, + test: authFirst, terminate: authFirst, userTracking: authFirst, logClientError: authFirst, @@ -31,6 +32,8 @@ export interface System { getCustomize: (fields?: string[]) => Promise; // ping server and get back the current time ping: () => { now: number }; + // test API key and return scope information (account_id or project_id) and server time + test: () => Promise<{ account_id?: string; project_id?: string; server_time: number }>; // terminate a service: // - only admin can do this. // - useful for development diff --git a/src/packages/conat/project/api/system.ts b/src/packages/conat/project/api/system.ts index ae086eb2508..c5746bd1209 100644 --- a/src/packages/conat/project/api/system.ts +++ b/src/packages/conat/project/api/system.ts @@ -28,6 +28,7 @@ export const system = { configuration: true, ping: true, + test: true, exec: true, signal: true, @@ -69,6 +70,8 @@ export interface System { ping: () => Promise<{ now: number }>; + test: () => Promise<{ project_id: string; server_time: number }>; + exec: (opts: ExecuteCodeOptions) => Promise; signal: (opts: { diff --git a/src/packages/next/pages/api/conat/project.ts b/src/packages/next/pages/api/conat/project.ts index 8d6dd8fc124..ff07ff78ad5 100644 --- a/src/packages/next/pages/api/conat/project.ts +++ b/src/packages/next/pages/api/conat/project.ts @@ -58,6 +58,11 @@ export default async function handle(req, res) { args, timeout, }); + // For project-scoped API keys, include the project_id in the response + // so the client can discover it + if (project_id0 && !resp.project_id) { + resp.project_id = project_id0; + } res.json(resp); } catch (err) { res.json({ error: err.message }); diff --git a/src/packages/project/conat/api/system.ts b/src/packages/project/conat/api/system.ts index 274d85172a2..80c40a08550 100644 --- a/src/packages/project/conat/api/system.ts +++ b/src/packages/project/conat/api/system.ts @@ -2,6 +2,17 @@ export async function ping() { return { now: Date.now() }; } +export async function test({ + project_id, +}: { + project_id?: string; +} = {}) { + return { + project_id: project_id ?? "", + server_time: Date.now(), + }; +} + export async function terminate() {} import { handleExecShellCode } from "@cocalc/project/exec_shell_code"; diff --git a/src/packages/server/api/project-bridge.ts b/src/packages/server/api/project-bridge.ts index 3699583d950..2939a3fbbcc 100644 --- a/src/packages/server/api/project-bridge.ts +++ b/src/packages/server/api/project-bridge.ts @@ -51,7 +51,12 @@ async function callProject({ service: "api", }); try { - const data = { name, args }; + // For system.test(), inject project_id into args[0] if not already present + let finalArgs = args; + if (name === "system.test" && (!args || args.length === 0)) { + finalArgs = [{ project_id }]; + } + const data = { name, args: finalArgs }; // we use waitForInterest because often the project hasn't // quite fully started. const resp = await client.request(subject, data, { diff --git a/src/packages/server/conat/api/system.ts b/src/packages/server/conat/api/system.ts index d7faedbaf4f..79fdd2795d7 100644 --- a/src/packages/server/conat/api/system.ts +++ b/src/packages/server/conat/api/system.ts @@ -18,6 +18,26 @@ export function ping() { return { now: Date.now() }; } +export async function test({ + account_id, + project_id, +}: { account_id?: string; project_id?: string } = {}) { + // Return API key scope information and server time + // The authFirst decorator determines the scope from the API key and injects + // either account_id (for account-scoped keys) or project_id (for project-scoped keys) + // into this parameter object. + const response: { account_id?: string; project_id?: string; server_time: number } = { + server_time: Date.now(), + }; + if (account_id) { + response.account_id = account_id; + } + if (project_id) { + response.project_id = project_id; + } + return response; +} + export async function terminate() {} export async function userTracking({ diff --git a/src/python/cocalc-api/Makefile b/src/python/cocalc-api/Makefile index 5b6dd7f9995..5cc0ed2170b 100644 --- a/src/python/cocalc-api/Makefile +++ b/src/python/cocalc-api/Makefile @@ -11,7 +11,8 @@ help: @echo " coverage - Run tests with coverage reporting (HTML + terminal)" @echo " coverage-report - Show coverage report in terminal" @echo " coverage-html - Generate HTML coverage report only" - @echo " mcp - Start the MCP server (requires COCALC_API_KEY, COCALC_PROJECT_ID, and COCALC_HOST)" + @echo " mcp - Start the MCP server (requires COCALC_API_KEY)" + @echo " mcp-debug - Debug the running MCP server and show tools/resources" @echo " serve-docs - Serve documentation locally" @echo " build-docs - Build documentation" @echo " publish - Build and publish package" @@ -51,6 +52,9 @@ coverage-html: mcp: uv run cocalc-mcp-server +mcp-debug: + uv run cocalc-mcp-debug + serve-docs: uv run mkdocs serve diff --git a/src/python/cocalc-api/pyproject.toml b/src/python/cocalc-api/pyproject.toml index 444a1e5d249..eeaa6684f34 100644 --- a/src/python/cocalc-api/pyproject.toml +++ b/src/python/cocalc-api/pyproject.toml @@ -21,6 +21,7 @@ Issues = "https://github.com/sagemathinc/cocalc/issues" [project.scripts] cocalc-mcp-server = "cocalc_api.mcp.server:main" +cocalc-mcp-debug = "cocalc_api.mcp.mcp_debug:main" [tool.mypy] python_version = "3.13" diff --git a/src/python/cocalc-api/src/cocalc_api/api_types.py b/src/python/cocalc-api/src/cocalc_api/api_types.py index f16763ae7ca..906582ec706 100644 --- a/src/python/cocalc-api/src/cocalc_api/api_types.py +++ b/src/python/cocalc-api/src/cocalc_api/api_types.py @@ -5,6 +5,12 @@ class PingResponse(TypedDict): now: int +class TestResponse(TypedDict, total=False): + account_id: Optional[str] + project_id: Optional[str] + server_time: int + + class ExecuteCodeOutput(TypedDict): stdout: str stderr: str diff --git a/src/python/cocalc-api/src/cocalc_api/hub.py b/src/python/cocalc-api/src/cocalc_api/hub.py index 9a9e5afa24d..f787e02b423 100644 --- a/src/python/cocalc-api/src/cocalc_api/hub.py +++ b/src/python/cocalc-api/src/cocalc_api/hub.py @@ -1,7 +1,7 @@ import httpx from typing import Any, Literal, Optional from .util import api_method, handle_error -from .api_types import PingResponse, UserSearchResult, MessageType +from .api_types import PingResponse, TestResponse, UserSearchResult, MessageType from .org import Organizations @@ -83,6 +83,19 @@ def ping(self) -> PingResponse: """ raise NotImplementedError + @api_method("system.test") + def test(self) -> TestResponse: + """ + Test the API key and get its scope information. + + Returns: + TestResponse: JSON object containing: + - account_id (if account-scoped key) + - project_id (if project-scoped key) + - server_time (current server time in milliseconds since epoch) + """ + raise NotImplementedError + def get_names(self, account_ids: list[str]) -> list[str]: """ Get the displayed names of CoCalc accounts with given IDs. diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md b/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md index 51a40ec78d1..682bc6ae502 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md +++ b/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md @@ -10,49 +10,126 @@ Learn more: https://modelcontextprotocol.io/docs/getting-started/intro ### Required Environment Variables -- **`COCALC_API_KEY`** - API key for CoCalc authentication (format: `sk-...`) -- **`COCALC_PROJECT_ID`** - UUID of the target CoCalc project +- **`COCALC_API_KEY`** - API key for CoCalc authentication (format: `sk-...`, can be account-scoped or project-scoped) - **`COCALC_HOST`** (optional) - CoCalc instance URL (default: `https://cocalc.com`) ### Setup Examples -**Local Development:** +**Local Development (Recommended: Project-Scoped API Key):** + +Create a project-scoped API key in your CoCalc project settings: + ```bash -export COCALC_API_KEY="sk-your-api-key-here" -export COCALC_PROJECT_ID="6e75dbf1-0342-4249-9dce-6b21648656e9" -export COCALC_HOST="http://localhost:5000" # For local CoCalc +export COCALC_API_KEY="sk-your-project-api-key-here" +export COCALC_HOST="http://localhost:5000" # For local CoCalc, or omit for cocalc.com uv run cocalc-mcp-server ``` -**Claude Code CLI:** +When started, the server will report: + +``` +✓ Connected with project-scoped API key (project: 6e75dbf1-0342-4249-9dce-6b21648656e9) +``` + +**Alternative: Account-Scoped API Key:** + +Create an account-scoped API key in your CoCalc account settings (Settings → API keys): + +```bash +export COCALC_API_KEY="sk-your-account-api-key-here" +uv run cocalc-mcp-server +``` + +When started, the server will report: + +``` +✓ Connected with account-scoped API key (account: d0bdabfd-850e-4c8d-8510-f6f1ecb9a5eb) +``` + +**Claude Code CLI (Project-Scoped Key - Recommended):** + ```bash claude mcp add \ --transport stdio \ cocalc \ - --env COCALC_API_KEY="sk-your-api-key-here" \ - --env COCALC_PROJECT_ID="6e75dbf1-0342-4249-9dce-6b21648656e9" \ - --env COCALC_HOST="http://localhost:5000" \ + --env COCALC_API_KEY="sk-your-project-api-key-here" \ -- uv --directory /path/to/cocalc-api run cocalc-mcp-server ``` -**Claude Desktop:** +**Claude Code CLI (Account-Scoped Key):** + +```bash +claude mcp add \ + --transport stdio \ + cocalc \ + --env COCALC_API_KEY="sk-your-account-api-key-here" \ + -- uv --directory /path/to/cocalc-api run cocalc-mcp-server +``` + +**Claude Desktop (Project-Scoped Key - Recommended):** + +Add to `~/.config/Claude/claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "cocalc": { + "command": "uv", + "args": [ + "--directory", + "/path/to/cocalc-api", + "run", + "cocalc-mcp-server" + ], + "env": { + "COCALC_API_KEY": "sk-your-project-api-key-here" + } + } + } +} +``` + +**Claude Desktop (Account-Scoped Key):** + Add to `~/.config/Claude/claude_desktop_config.json`: + ```json { "mcpServers": { "cocalc": { "command": "uv", - "args": ["--directory", "/path/to/cocalc-api", "run", "cocalc-mcp-server"], + "args": [ + "--directory", + "/path/to/cocalc-api", + "run", + "cocalc-mcp-server" + ], "env": { - "COCALC_API_KEY": "sk-your-api-key-here", - "COCALC_PROJECT_ID": "6e75dbf1-0342-4249-9dce-6b21648656e9", - "COCALC_HOST": "http://localhost:5000" + "COCALC_API_KEY": "sk-your-account-api-key-here" } } } } ``` +## Server Metadata + +The MCP server exposes high-level metadata that clients can query to understand what the server provides: + +**Server Instructions** - A comprehensive guide to the server's capabilities, usage, and examples. Sent to LLMs to provide context about what tools and resources are available. + +**Website URL** - Link to documentation (https://cocalc.com/api/python) + +**Protocol Version** - MCP 2025-06-18 + +Clients can retrieve this metadata using the MCP `initialize` call, which returns: +- `serverInfo.name` - "cocalc-api" +- `serverInfo.version` - Version from package metadata +- `instructions` - High-level guide (see above) +- `capabilities` - What features the server supports (tools, resources, etc.) + +This allows LLM clients to understand the server's purpose and capabilities before making any requests. + ## Architecture ### Module Structure @@ -73,12 +150,15 @@ src/cocalc_api/mcp/ 1. **`server.py`** imports `mcp_server` module 2. **`mcp_server.py`** initializes at import time: - - Creates `FastMCP("cocalc-api")` instance + - Creates `FastMCP("cocalc-api")` instance with: + - `instructions` - High-level guide for LLM clients + - `website_url` - Link to documentation - Initializes `Project` client (lazy, cached) - Calls `tools.register_tools(mcp)` - Calls `resources.register_resources(mcp)` 3. **`tools/` and `resources/`** register their handlers with the shared `mcp` object 4. **`server.py`** calls `mcp.run(transport="stdio")` +5. When clients connect, the server responds to `initialize` with metadata including instructions ### Key Design Decisions @@ -97,6 +177,7 @@ src/cocalc_api/mcp/ Execute arbitrary shell commands in the CoCalc project. **Parameters:** + - `command` (string, required): Command to execute - `args` (list, optional): Command arguments - `bash` (boolean, optional): Interpret as bash script @@ -106,6 +187,7 @@ Execute arbitrary shell commands in the CoCalc project. **Returns:** stdout, stderr, and exit_code **Examples:** + ```json {"command": "echo 'Hello'"} {"command": "python", "args": ["script.py", "--verbose"]} @@ -121,6 +203,7 @@ Browse project directory structure. **URI:** `cocalc://project-files/{path}` **Parameters:** + - `path` (string, optional): Directory path (default: `.`) **Returns:** Formatted list of files and directories @@ -130,6 +213,7 @@ Browse project directory structure. ### Adding a New Tool 1. Create `tools/my_tool.py`: + ```python def register_my_tool(mcp) -> None: """Register my tool with FastMCP instance.""" @@ -143,6 +227,7 @@ def register_my_tool(mcp) -> None: ``` 2. Update `tools/__init__.py`: + ```python def register_tools(mcp) -> None: from .exec import register_exec_tool diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_debug.py b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_debug.py new file mode 100644 index 00000000000..a0fdce5547c --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_debug.py @@ -0,0 +1,135 @@ +""" +MCP Server Debug Tool + +Starts an instance of the MCP server and introspects it to report +available tools, resources, and capabilities. + +Usage: + Set up your API key and run this debug tool: + export COCALC_API_KEY="sk-..." + make mcp-debug + + This will spawn a temporary instance of the MCP server, connect to it, + and print detailed information about available tools and resources. + +Note: + This tool starts its own MCP server instance for introspection. + You can also run the server persistently with: + make mcp +""" + +import asyncio +import json +import os +import sys + +from mcp import ClientSession +from mcp.client.stdio import stdio_client, StdioServerParameters + + +async def debug_mcp_server() -> None: + """Start a temporary MCP server, query it, and report results.""" + try: + # Start a temporary MCP server instance for introspection + print("Starting MCP server for introspection...", file=sys.stderr) + + # Connect to the server via stdio + # Pass the current environment to the subprocess so it inherits API key and host settings + server_params = StdioServerParameters( + command="uv", + args=["run", "cocalc-mcp-server"], + env=dict(os.environ), + ) + async with stdio_client(server_params) as client: + read_stream, write_stream = client + async with ClientSession(read_stream, write_stream) as session: + # Initialize the connection + print("\n=== MCP Server Debug Information ===\n", + file=sys.stderr) + + # Get capabilities + info = await session.initialize() + print("✓ Server Initialized Successfully\n", file=sys.stderr) + + # Print server information + print("Server Information:") + print("=" * 50) + print(f"Protocol Version: {info.protocolVersion}") + print(f"Server Name: {info.serverInfo.name}") + print(f"Server Version: {info.serverInfo.version}") + if info.serverInfo.websiteUrl: + print(f"Website: {info.serverInfo.websiteUrl}") + if info.serverInfo.title: + print(f"Title: {info.serverInfo.title}") + print("=" * 50 + "\n") + + # Extract and display instructions if available + if info.instructions: + print("\n" + "=" * 50) + print("SERVER INSTRUCTIONS") + print("=" * 50) + print() + print(info.instructions) + print() + print("=" * 50 + "\n") + + # List tools + print("\n\nAvailable Tools:") + print("-" * 50) + tools_list = await session.list_tools() + if tools_list.tools: + for tool in tools_list.tools: + print(f"\n Tool: {tool.name}") + print(f" Description: {tool.description}") + else: + print(" (no tools available)") + + # List resources + print("\n\nAvailable Resources:") + print("-" * 50) + resources_list = await session.list_resources() + if resources_list.resources: + for resource in resources_list.resources: + print(f"\n Resource: {resource.uri}") + print(f" Name: {resource.name}") + if resource.description: + print(f" Description: {resource.description}") + if hasattr(resource, "mimeType") and resource.mimeType: + print(f" MIME Type: {resource.mimeType}") + else: + print(" (no resources available)") + + print("\n\n✓ Debug Information Complete", file=sys.stderr) + + # Process is automatically terminated when stdio_client context exits + + except Exception as e: + error_str = str(e) + if "COCALC_API_KEY" in error_str or "not set" in error_str: + print("Error: COCALC_API_KEY environment variable is not set", + file=sys.stderr) + print("\nUsage:", file=sys.stderr) + print(" export COCALC_API_KEY='sk-...'", file=sys.stderr) + print(" make mcp-debug", file=sys.stderr) + elif "project_id" in error_str.lower(): + print("Error: Project-scoped API key requires COCALC_PROJECT_ID", + file=sys.stderr) + print("\nFor project-scoped API keys, provide the project ID:", + file=sys.stderr) + print(" export COCALC_API_KEY='sk-...'", file=sys.stderr) + print(" export COCALC_PROJECT_ID='uuid-...'", file=sys.stderr) + print(" make mcp-debug", file=sys.stderr) + else: + print(f"Error: {e}", file=sys.stderr) + import traceback + traceback.print_exc(file=sys.stderr) + sys.exit(1) + + +def main() -> None: + """Entry point for mcp-debug.""" + asyncio.run(debug_mcp_server()) + + +if __name__ == "__main__": + main() diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py index 0b9976eb78f..ef219ac5769 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py @@ -1,7 +1,7 @@ """ CoCalc MCP (Model Context Protocol) Server - Central Coordination Module -This MCP server gives you direct access to a CoCalc project environment. +This MCP server gives you direct access to a CoCalc project or account environment. AVAILABLE TOOLS (actions you can perform): - exec: Run shell commands, scripts, and programs in the project @@ -25,9 +25,10 @@ AUTHENTICATION & CONFIGURATION: Required environment variables (already set when this server is running): -- COCALC_API_KEY: Your CoCalc API authentication token -- COCALC_PROJECT_ID: The UUID of your CoCalc project +- COCALC_API_KEY: Your CoCalc API authentication token (account-scoped or project-scoped) - COCALC_HOST: (optional) Your CoCalc instance URL (defaults to https://cocalc.com) + +The server will validate your API key on startup and report whether it's account-scoped or project-scoped. """ import os @@ -36,80 +37,215 @@ from mcp.server.fastmcp import FastMCP -from cocalc_api import Project +from cocalc_api import Project, Hub -def get_config() -> tuple[str, str, str]: +def get_config() -> tuple[str, str, Optional[str]]: """ Get and validate MCP server configuration. Returns: - Tuple of (api_key, project_id, host) + Tuple of (api_key, host, project_id) Raises: RuntimeError: If required configuration is missing """ api_key = os.environ.get("COCALC_API_KEY") - project_id = os.environ.get("COCALC_PROJECT_ID") host = os.environ.get("COCALC_HOST", "https://cocalc.com") + project_id = os.environ.get("COCALC_PROJECT_ID") if not api_key: - raise RuntimeError( - "COCALC_API_KEY environment variable is required but not set" - ) - if not project_id: - raise RuntimeError( - "COCALC_PROJECT_ID environment variable is required but not set" - ) + raise RuntimeError("COCALC_API_KEY environment variable is required but not set") + + return api_key, host, project_id + + +def check_api_key_scope(api_key: str, host: str) -> dict[str, str]: + """ + Check if the API key is account-scoped or project-scoped. + + Args: + api_key: The API key to check + host: The CoCalc host URL + + Returns: + dict with either 'account_id' (for account-scoped) or 'project_id' (for project-scoped) + + Raises: + RuntimeError: If the API key is invalid or scope cannot be determined + """ + try: + hub = Hub(api_key=api_key, host=host) + + # Try the hub.system.test() method (only works for account-scoped keys) + result = hub.system.test() + + # Check which scope is returned + if "account_id" in result and result["account_id"]: + return {"account_id": result["account_id"]} + elif "project_id" in result and result["project_id"]: + return {"project_id": result["project_id"]} + else: + raise RuntimeError("API key test returned neither account_id nor project_id") + + except Exception as e: + # Check if this looks like a project-scoped key error + error_msg = str(e) + if "must be signed in and MUST provide an api key" in error_msg: + raise RuntimeError("API key appears to be project-scoped. " + "Project-scoped keys require the project_id to be specified at the OS level. " + "Please set the COCALC_PROJECT_ID environment variable and try again.") from e + raise RuntimeError(f"API key validation failed: {e}") from e + + +# Initialize FastMCP server with instructions and documentation +mcp = FastMCP( + name="CoCalc API MCP Server", + instructions="""CoCalc MCP Server - Direct Access to CoCalc Projects - return api_key, project_id, host +This server gives you direct access to a CoCalc project or account environment through the Model Context Protocol (MCP). +WHAT YOU CAN DO: +- Execute arbitrary shell commands in a Linux environment with Python, Node.js, R, Julia, and 100+ tools +- Browse and explore project files to understand structure and contents +- Run code, scripts, and build/test commands +- Work with git repositories, manage packages, process data +- Automate any task that can run in a terminal -# Initialize FastMCP server -mcp = FastMCP("cocalc-api") +HOW TO USE: +1. Start by exploring the project structure using the project-files resource +2. Use the exec tool to run commands, scripts, or programs +3. Combine multiple commands to accomplish complex workflows -# Project client (initialized below) -_project_client: Optional[Project] = None +EXAMPLES: +- Execute Python: exec with command="python3 script.py --verbose" +- List files: use project-files resource or exec with command="ls -la" +- Run tests: exec with command="pytest tests/" bash=true +- Git operations: exec with command="git log --oneline" in your repository +The project runs in an Ubuntu Linux container with access to the full filesystem and all installed tools. +All operations are scoped to this single project and execute with project user permissions.""", + website_url="https://cocalc.com/api/python", +) -def initialize_project_client() -> Project: - """Initialize and return the Project client.""" - global _project_client +# Configuration (initialized at startup) +_api_key: Optional[str] = None +_host: Optional[str] = None +_api_key_scope: Optional[dict[str, str]] = None # Either {"account_id": ...} or {"project_id": ...} - if _project_client is not None: - return _project_client +# Lazy-initialized project clients map: project_id -> Project +_project_clients: dict[str, Project] = {} + + +def _initialize_config() -> None: + """Initialize configuration and validate API key at startup.""" + global _api_key, _host, _api_key_scope, _project_clients + + if _api_key is not None: + return # Already initialized # Get configuration + project_id_config: Optional[str] = None try: - api_key, project_id, host = get_config() + _api_key, _host, project_id_config = get_config() except RuntimeError as e: print(f"Configuration Error: {e}", file=sys.stderr) sys.exit(1) - # Initialize Project client + # Validate API key and determine scope try: - _project_client = Project(api_key=api_key, project_id=project_id, host=host) + try: + _api_key_scope = check_api_key_scope(_api_key, _host) + except RuntimeError as check_error: + # If it's a project-scoped key error, try the project API to discover the project_id + if "project-scoped" in str(check_error): + try: + # Try with empty project_id - project-scoped keys will use their own + project = Project(api_key=_api_key, project_id="", host=_host) + result = project.system.ping() + # Check if the response includes project_id (it shouldn't from ping, but try anyway) + if isinstance(result, dict) and "project_id" in result: + _api_key_scope = {"project_id": result["project_id"]} + else: + # If we still don't have it, this is an error + raise RuntimeError("Could not determine project_id from project-scoped API key. " + "Please restart with COCALC_PROJECT_ID environment variable.") + except Exception as project_error: + raise RuntimeError(f"Project-scoped API key detected but could not determine project_id. " + f"Error: {project_error}") from project_error + else: + raise - # Verify project is accessible - print(f"Connecting to project {project_id}...", file=sys.stderr) - _project_client.system.ping() - print(f"✓ Connected to project {project_id}", file=sys.stderr) - except Exception as e: - print( - f"Error: Could not connect to project {project_id}: {e}", - file=sys.stderr, - ) + if "account_id" in _api_key_scope: + account_id = _api_key_scope["account_id"] + print(f"✓ Connected with account-scoped API key (account: {account_id})", file=sys.stderr) + elif "project_id" in _api_key_scope: + project_id = _api_key_scope["project_id"] + if not project_id: + raise RuntimeError("Project ID not found for project-scoped API key") + print(f"✓ Connected with project-scoped API key (project: {project_id})", file=sys.stderr) + # For project-scoped keys, eagerly create the project client + client = Project(api_key=_api_key, project_id=project_id, host=_host) + _project_clients[project_id] = client + else: + # If we got here with no project_id but it might be project-scoped, check if COCALC_PROJECT_ID was provided + if project_id_config: + _api_key_scope = {"project_id": project_id_config} + print(f"✓ Using project-scoped API key with explicitly provided project_id (project: {project_id_config})", file=sys.stderr) + client = Project(api_key=_api_key, project_id=project_id_config, host=_host) + _project_clients[project_id_config] = client + else: + raise RuntimeError("Could not determine API key scope") + + except RuntimeError as e: + print(f"Error: {e}", file=sys.stderr) sys.exit(1) - return _project_client +def get_project_client(project_id: Optional[str] = None) -> Project: + """ + Get or create a Project client for the given project. + + For project-scoped API keys, project_id is optional (uses the key's project). + For account-scoped API keys, project_id is required. + + Args: + project_id: The project UUID. If None, uses the project-scoped key's project. + + Returns: + Project client for the specified project + + Raises: + RuntimeError: If project_id cannot be determined or account-scoped key without project_id + """ + global _project_clients + + _initialize_config() -def get_project_client() -> Project: - """Get the initialized Project client.""" - global _project_client - if _project_client is None: - return initialize_project_client() - return _project_client + # Determine which project_id to use + if project_id is None: + # If no project_id provided, try to use the one from project-scoped key + if _api_key_scope and "project_id" in _api_key_scope: + project_id = _api_key_scope["project_id"] + else: + # Account-scoped key requires explicit project_id + raise RuntimeError("Account-scoped API key requires an explicit project_id argument. " + "No project_id provided to get_project_client().") + + if not project_id: + raise RuntimeError("Project ID cannot be empty") + + # Return cached client if available + if project_id in _project_clients: + return _project_clients[project_id] + + # Create new project client + # At this point, _api_key and _host are guaranteed to be non-None (set in _initialize_config) + assert _api_key is not None + assert _host is not None + client = Project(api_key=_api_key, project_id=project_id, host=_host) + _project_clients[project_id] = client + return client # Register tools and resources @@ -119,3 +255,6 @@ def get_project_client() -> Project: tools_module.register_tools(mcp) resources_module.register_resources(mcp) + +# Initialize configuration and validate API key at startup +_initialize_config() diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py b/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py index 7a889894e9b..1b2f61679c6 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py @@ -6,10 +6,10 @@ and understand the contents of the CoCalc project filesystem. """ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING if TYPE_CHECKING: - from ..mcp_server import mcp as _mcp + pass def register_file_listing_resource(mcp) -> None: @@ -62,7 +62,7 @@ async def project_files() -> str: continue parts = line.split() if len(parts) >= 4: - name, size, mtime, ftype = parts[0], parts[1], parts[2], parts[3] + name, size, _, ftype = parts[0], parts[1], parts[2], parts[3] file_type_str = "[DIR]" if ftype == "d" else "[FILE]" formatted_lines.append(f"{file_type_str} {name:30} {size:>10} bytes") diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/server.py b/src/python/cocalc-api/src/cocalc_api/mcp/server.py index 9922edc0659..bc8d92b1367 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/server.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/server.py @@ -15,41 +15,61 @@ Configuration Examples: -1. Local Testing: +1. Local Testing (Project-Scoped API Key - Recommended): export COCALC_API_KEY="sk-..." - export COCALC_PROJECT_ID="project-uuid" uv run cocalc-mcp-server -2. Claude Desktop config (~/.config/Claude/claude_desktop_config.json): +2. Local Testing (Account-Scoped API Key): + export COCALC_API_KEY="sk-..." + uv run cocalc-mcp-server + +3. Claude Desktop config (~/.config/Claude/claude_desktop_config.json): { "mcpServers": { "cocalc": { "command": "uv", "args": ["run", "cocalc-mcp-server"], "env": { - "COCALC_API_KEY": "sk-...", - "COCALC_PROJECT_ID": "..." + "COCALC_API_KEY": "sk-..." } } } } -3. Claude Code CLI: +4. Claude Code CLI: export COCALC_API_KEY="sk-..." - export COCALC_PROJECT_ID="..." - export COCALC_HOST="https://cocalc.com" - claude mcp add --transport stdio cocalc --env COCALC_API_KEY --env COCALC_PROJECT_ID --env COCALC_HOST -- uv run cocalc-mcp-server + claude mcp add --transport stdio cocalc --env COCALC_API_KEY -- uv run cocalc-mcp-server """ +import os import sys -# Import the mcp_server module which initializes and registers everything -from .mcp_server import mcp # noqa: F401 - def main(): """Entry point for the MCP server.""" - print("Starting CoCalc API MCP Server...", file=sys.stderr) + # Check and display configuration BEFORE importing mcp_server + api_key = os.environ.get("COCALC_API_KEY") + host = os.environ.get("COCALC_HOST") + + print("Starting CoCalc MCP Server...", file=sys.stderr) + + if not api_key: + print("Error: COCALC_API_KEY environment variable is not set", file=sys.stderr) + print("Required: COCALC_API_KEY - CoCalc API key (account-scoped or project-scoped)", file=sys.stderr) + print("Optional: COCALC_HOST - CoCalc instance URL (defaults to https://cocalc.com)", file=sys.stderr) + sys.exit(1) + + print("Configuration:", file=sys.stderr) + # Obfuscate API key: show only last 6 characters + obfuscated_key = f"*****{api_key[-6:]}" if len(api_key) > 6 else "*****" + print(f" COCALC_API_KEY: {obfuscated_key}", file=sys.stderr) + if host: + print(f" COCALC_HOST: {host}", file=sys.stderr) + + # Import the mcp_server module which initializes and registers everything + # This must happen AFTER configuration validation + from .mcp_server import mcp # noqa: F401, E402 + # mcp is already initialized and has all tools/resources registered # We just need to run it mcp.run(transport="stdio") diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py index 82f8a48c4c0..2eae9174e41 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py @@ -5,13 +5,10 @@ in the target CoCalc project environment. """ -from typing import Optional - -# Import will happen at end to avoid circular imports -from typing import TYPE_CHECKING +from typing import Optional, TYPE_CHECKING if TYPE_CHECKING: - from ..mcp_server import mcp as _mcp + pass def register_exec_tool(mcp) -> None: diff --git a/src/python/cocalc-api/src/cocalc_api/project.py b/src/python/cocalc-api/src/cocalc_api/project.py index 5f9779834fd..4a974c5b5e6 100644 --- a/src/python/cocalc-api/src/cocalc_api/project.py +++ b/src/python/cocalc-api/src/cocalc_api/project.py @@ -25,7 +25,11 @@ def call(self, name: str, arguments: list[Any], timeout: Optional[int] = None) - Returns: Any: JSON-decoded response from the API. """ - payload: dict[str, Any] = {"name": name, "args": arguments, "project_id": self.project_id} + payload: dict[str, Any] = {"name": name, "args": arguments} + # Only include project_id if it's not empty. For project-scoped API keys, + # the project_id is extracted from the key itself by the backend. + if self.project_id: + payload["project_id"] = self.project_id if timeout is not None: payload["timeout"] = timeout resp = self.client.post(self.host + "/api/conat/project", json=payload) @@ -62,6 +66,17 @@ def ping(self) -> PingResponse: """ ... + @api_method("system.test") + def test(self) -> dict[str, Any]: + """ + Test the API key and get the project_id. + + Returns: + dict: JSON object containing project_id and server_time. + + """ + ... + @api_method("system.exec", timeout_seconds=True) def exec( self, From 0d70752b0ce1b19014193850420518801092e7ea Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Tue, 18 Nov 2025 15:56:52 +0100 Subject: [PATCH 23/58] cocalc-api: update, tweaks for jupyter testing, detecting older api key --- src/packages/server/api/manage.ts | 5 +- src/python/cocalc-api/src/cocalc_api/hub.py | 8 +- .../cocalc-api/src/cocalc_api/project.py | 4 +- src/python/cocalc-api/tests/conftest.py | 57 +- src/python/cocalc-api/uv.lock | 1008 ++++++++++------- 5 files changed, 657 insertions(+), 425 deletions(-) diff --git a/src/packages/server/api/manage.ts b/src/packages/server/api/manage.ts index 17693bb8d39..99e85fd8b4b 100644 --- a/src/packages/server/api/manage.ts +++ b/src/packages/server/api/manage.ts @@ -295,8 +295,9 @@ export async function getAccountWithApiKey( return; } - // Check for legacy account api key (format: sk_*) - if (secret.startsWith("sk_")) { + // Check for legacy account api key (format historically documented as sk-*, but + // some deployments used sk_*, so accept both to avoid breaking existing keys) + if (secret.startsWith(API_KEY_PREFIX) || secret.startsWith("sk_")) { const { rows } = await pool.query( "SELECT account_id FROM accounts WHERE api_key = $1::TEXT", [secret], diff --git a/src/python/cocalc-api/src/cocalc_api/hub.py b/src/python/cocalc-api/src/cocalc_api/hub.py index f787e02b423..491bd2b1994 100644 --- a/src/python/cocalc-api/src/cocalc_api/hub.py +++ b/src/python/cocalc-api/src/cocalc_api/hub.py @@ -10,8 +10,8 @@ class Hub: def __init__(self, api_key: str, host: str = "https://cocalc.com"): self.api_key = api_key self.host = host - # Use longer timeout for API calls (30 seconds instead of default 5) - self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}, timeout=30.0) + # Use longer timeout for API calls (90 seconds instead of default 5) to handle slow operations like Jupyter + self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}, timeout=90.0) def call(self, name: str, arguments: list[Any], timeout: Optional[int] = None) -> Any: """ @@ -326,7 +326,7 @@ def kernels(self, project_id: Optional[str] = None) -> list[dict[str, Any]]: """ ... - @api_method("jupyter.execute") + @api_method("jupyter.execute", timeout_seconds=True) def execute( self, input: str, @@ -334,6 +334,7 @@ def execute( history: Optional[list[str]] = None, project_id: Optional[str] = None, path: Optional[str] = None, + timeout: Optional[int] = 30, ) -> dict[str, Any]: # type: ignore[empty-body] """ Execute code using a Jupyter kernel. @@ -344,6 +345,7 @@ def execute( history (Optional[list[str]]): Array of previous inputs (they get evaluated every time, but without output being captured). project_id (Optional[str]): Project in which to run the code -- if not given, global anonymous project is used, if available. path (Optional[str]): File path context for execution. + timeout (Optional[int]): Timeout in SECONDS for the execute call (defaults to 30 seconds). Returns: dict[str, Any]: JSON response containing execution results. diff --git a/src/python/cocalc-api/src/cocalc_api/project.py b/src/python/cocalc-api/src/cocalc_api/project.py index 4a974c5b5e6..2cd59d1ce4b 100644 --- a/src/python/cocalc-api/src/cocalc_api/project.py +++ b/src/python/cocalc-api/src/cocalc_api/project.py @@ -124,13 +124,14 @@ def exec( """ ... - @api_method("system.jupyterExecute") + @api_method("system.jupyterExecute", timeout_seconds=True) def jupyter_execute( self, input: str, kernel: str, history: Optional[list[str]] = None, path: Optional[str] = None, + timeout: Optional[int] = 30, ) -> list[dict[str, Any]]: # type: ignore[empty-body] """ Execute code using a Jupyter kernel. @@ -140,6 +141,7 @@ def jupyter_execute( kernel (str): Name of kernel to use. Get options using hub.jupyter.kernels(). history (Optional[list[str]]): Array of previous inputs (they get evaluated every time, but without output being captured). path (Optional[str]): File path context for execution. + timeout (Optional[int]): Timeout in SECONDS for the execute call (defaults to 30 seconds). Returns: list[dict[str, Any]]: List of output items. Each output item contains diff --git a/src/python/cocalc-api/tests/conftest.py b/src/python/cocalc-api/tests/conftest.py index 92c8c1ab816..9fa1eede30d 100644 --- a/src/python/cocalc-api/tests/conftest.py +++ b/src/python/cocalc-api/tests/conftest.py @@ -1,6 +1,7 @@ """ Pytest configuration and fixtures for cocalc-api tests. """ +import json import os import time import uuid @@ -93,16 +94,20 @@ def temporary_project(hub, resource_tracker, request): # Wait for project to be ready (can take 10-15 seconds) from cocalc_api import Project + test_project = Project(project_id=project_id, api_key=hub.api_key, host=hub.host) for attempt in range(10): time.sleep(5) # Wait 5 seconds before checking try: # Try to ping the project to see if it's ready - test_project = Project(project_id=project_id, api_key=hub.api_key, host=hub.host) test_project.system.ping() # If this succeeds, project is ready break except Exception: if attempt == 9: # Last attempt print(f"Warning: Project {project_id} did not become ready within 50 seconds") + else: + print(f"Warning: Project {project_id} may not be ready yet") + + ensure_python3_kernel(test_project) except Exception as e: print(f"Warning: Failed to start project {project_id}: {e}") @@ -120,6 +125,53 @@ def project_client(temporary_project, api_key, cocalc_host): return Project(project_id=temporary_project['project_id'], api_key=api_key, host=cocalc_host) +def ensure_python3_kernel(project_client: Project): + """ + Ensure the default python3 Jupyter kernel is installed in the project. + + If not available, install ipykernel and register the kernelspec. + """ + + def has_python_kernel() -> bool: + try: + result = project_client.system.exec( + command="python3", + args=["-m", "jupyter", "kernelspec", "list", "--json"], + timeout=60, + ) + data = json.loads(result["stdout"]) + kernelspecs = data.get("kernelspecs", {}) + return "python3" in kernelspecs + except Exception as err: + print(f"Warning: Failed to list kernelspecs: {err}") + return False + + if has_python_kernel(): + return + + print("Installing python3 kernelspec in project...") + project_client.system.exec( + command="python3", + args=["-m", "pip", "install", "--user", "ipykernel"], + timeout=300, + ) + project_client.system.exec( + command="python3", + args=[ + "-m", + "ipykernel", + "install", + "--user", + "--name=python3", + "--display-name=Python 3", + ], + timeout=120, + ) + + if not has_python_kernel(): + raise RuntimeError("Failed to ensure python3 kernelspec is installed in project") + + # ============================================================================ # Database Cleanup Infrastructure # ============================================================================ @@ -218,7 +270,8 @@ def db_pool(check_cleanup_config): if not cleanup_enabled: print("\n⚠ Database cleanup DISABLED via COCALC_TESTS_CLEANUP=false") print(" Test resources will remain in the database.") - return None + yield None + return # Get connection parameters with defaults pguser = os.environ.get("PGUSER", "smc") diff --git a/src/python/cocalc-api/uv.lock b/src/python/cocalc-api/uv.lock index 94adb01a2f4..6db02df1215 100644 --- a/src/python/cocalc-api/uv.lock +++ b/src/python/cocalc-api/uv.lock @@ -32,11 +32,11 @@ wheels = [ [[package]] name = "asttokens" -version = "3.0.0" +version = "3.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, ] [[package]] @@ -59,25 +59,107 @@ wheels = [ [[package]] name = "backrefs" -version = "5.9" +version = "6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/eb/a7/312f673df6a79003279e1f55619abbe7daebbb87c17c976ddc0345c04c7b/backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59", size = 5765857, upload-time = "2025-06-22T19:34:13.97Z" } +sdist = { url = "https://files.pythonhosted.org/packages/86/e3/bb3a439d5cb255c4774724810ad8073830fac9c9dee123555820c1bcc806/backrefs-6.1.tar.gz", hash = "sha256:3bba1749aafe1db9b915f00e0dd166cba613b6f788ffd63060ac3485dc9be231", size = 7011962, upload-time = "2025-11-15T14:52:08.323Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/4d/798dc1f30468134906575156c089c492cf79b5a5fd373f07fe26c4d046bf/backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f", size = 380267, upload-time = "2025-06-22T19:34:05.252Z" }, - { url = "https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf", size = 392072, upload-time = "2025-06-22T19:34:06.743Z" }, - { url = "https://files.pythonhosted.org/packages/9d/12/4f345407259dd60a0997107758ba3f221cf89a9b5a0f8ed5b961aef97253/backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa", size = 397947, upload-time = "2025-06-22T19:34:08.172Z" }, - { url = "https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b", size = 399843, upload-time = "2025-06-22T19:34:09.68Z" }, - { url = "https://files.pythonhosted.org/packages/fc/24/b29af34b2c9c41645a9f4ff117bae860291780d73880f449e0b5d948c070/backrefs-5.9-py314-none-any.whl", hash = "sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9", size = 411762, upload-time = "2025-06-22T19:34:11.037Z" }, - { url = "https://files.pythonhosted.org/packages/41/ff/392bff89415399a979be4a65357a41d92729ae8580a66073d8ec8d810f98/backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60", size = 380265, upload-time = "2025-06-22T19:34:12.405Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ee/c216d52f58ea75b5e1841022bbae24438b19834a29b163cb32aa3a2a7c6e/backrefs-6.1-py310-none-any.whl", hash = "sha256:2a2ccb96302337ce61ee4717ceacfbf26ba4efb1d55af86564b8bbaeda39cac1", size = 381059, upload-time = "2025-11-15T14:51:59.758Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl", hash = "sha256:e82bba3875ee4430f4de4b6db19429a27275d95a5f3773c57e9e18abc23fd2b7", size = 392854, upload-time = "2025-11-15T14:52:01.194Z" }, + { url = "https://files.pythonhosted.org/packages/37/c9/fd117a6f9300c62bbc33bc337fd2b3c6bfe28b6e9701de336b52d7a797ad/backrefs-6.1-py312-none-any.whl", hash = "sha256:c64698c8d2269343d88947c0735cb4b78745bd3ba590e10313fbf3f78c34da5a", size = 398770, upload-time = "2025-11-15T14:52:02.584Z" }, + { url = "https://files.pythonhosted.org/packages/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl", hash = "sha256:4c9d3dc1e2e558965202c012304f33d4e0e477e1c103663fd2c3cc9bb18b0d05", size = 400726, upload-time = "2025-11-15T14:52:04.093Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/6296bad135bfafd3254ae3648cd152980a424bd6fed64a101af00cc7ba31/backrefs-6.1-py314-none-any.whl", hash = "sha256:13eafbc9ccd5222e9c1f0bec563e6d2a6d21514962f11e7fc79872fd56cbc853", size = 412584, upload-time = "2025-11-15T14:52:05.233Z" }, + { url = "https://files.pythonhosted.org/packages/02/e3/a4fa1946722c4c7b063cc25043a12d9ce9b4323777f89643be74cef2993c/backrefs-6.1-py39-none-any.whl", hash = "sha256:a9e99b8a4867852cad177a6430e31b0f6e495d65f8c6c134b68c14c3c95bf4b0", size = 381058, upload-time = "2025-11-15T14:52:06.698Z" }, ] [[package]] name = "certifi" -version = "2025.10.5" +version = "2025.11.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, +] + +[[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]] @@ -171,14 +253,14 @@ wheels = [ [[package]] name = "click" -version = "8.3.0" +version = "8.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] @@ -194,7 +276,7 @@ dependencies = [ dev = [ { name = "coverage", extra = ["toml"] }, { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "ipython", version = "9.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "ipython", version = "9.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "mkdocs" }, { name = "mkdocs-material" }, { name = "mkdocstrings", extra = ["python"] }, @@ -242,101 +324,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/38/ee22495420457259d2f3390309505ea98f98a5eed40901cf62196abad006/coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050", size = 811905, upload-time = "2025-10-15T15:15:08.542Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/95/c49df0aceb5507a80b9fe5172d3d39bf23f05be40c23c8d77d556df96cec/coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31", size = 215800, upload-time = "2025-10-15T15:12:19.824Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c6/7bb46ce01ed634fff1d7bb53a54049f539971862cc388b304ff3c51b4f66/coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075", size = 216198, upload-time = "2025-10-15T15:12:22.549Z" }, - { url = "https://files.pythonhosted.org/packages/94/b2/75d9d8fbf2900268aca5de29cd0a0fe671b0f69ef88be16767cc3c828b85/coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab", size = 242953, upload-time = "2025-10-15T15:12:24.139Z" }, - { url = "https://files.pythonhosted.org/packages/65/ac/acaa984c18f440170525a8743eb4b6c960ace2dbad80dc22056a437fc3c6/coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0", size = 244766, upload-time = "2025-10-15T15:12:25.974Z" }, - { url = "https://files.pythonhosted.org/packages/d8/0d/938d0bff76dfa4a6b228c3fc4b3e1c0e2ad4aa6200c141fcda2bd1170227/coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785", size = 246625, upload-time = "2025-10-15T15:12:27.387Z" }, - { url = "https://files.pythonhosted.org/packages/38/54/8f5f5e84bfa268df98f46b2cb396b1009734cfb1e5d6adb663d284893b32/coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591", size = 243568, upload-time = "2025-10-15T15:12:28.799Z" }, - { url = "https://files.pythonhosted.org/packages/68/30/8ba337c2877fe3f2e1af0ed7ff4be0c0c4aca44d6f4007040f3ca2255e99/coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088", size = 244665, upload-time = "2025-10-15T15:12:30.297Z" }, - { url = "https://files.pythonhosted.org/packages/cc/fb/c6f1d6d9a665536b7dde2333346f0cc41dc6a60bd1ffc10cd5c33e7eb000/coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f", size = 242681, upload-time = "2025-10-15T15:12:32.326Z" }, - { url = "https://files.pythonhosted.org/packages/be/38/1b532319af5f991fa153c20373291dc65c2bf532af7dbcffdeef745c8f79/coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866", size = 242912, upload-time = "2025-10-15T15:12:34.079Z" }, - { url = "https://files.pythonhosted.org/packages/67/3d/f39331c60ef6050d2a861dc1b514fa78f85f792820b68e8c04196ad733d6/coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841", size = 243559, upload-time = "2025-10-15T15:12:35.809Z" }, - { url = "https://files.pythonhosted.org/packages/4b/55/cb7c9df9d0495036ce582a8a2958d50c23cd73f84a23284bc23bd4711a6f/coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf", size = 218266, upload-time = "2025-10-15T15:12:37.429Z" }, - { url = "https://files.pythonhosted.org/packages/68/a8/b79cb275fa7bd0208767f89d57a1b5f6ba830813875738599741b97c2e04/coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969", size = 219169, upload-time = "2025-10-15T15:12:39.25Z" }, - { url = "https://files.pythonhosted.org/packages/49/3a/ee1074c15c408ddddddb1db7dd904f6b81bc524e01f5a1c5920e13dbde23/coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847", size = 215912, upload-time = "2025-10-15T15:12:40.665Z" }, - { url = "https://files.pythonhosted.org/packages/70/c4/9f44bebe5cb15f31608597b037d78799cc5f450044465bcd1ae8cb222fe1/coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc", size = 216310, upload-time = "2025-10-15T15:12:42.461Z" }, - { url = "https://files.pythonhosted.org/packages/42/01/5e06077cfef92d8af926bdd86b84fb28bf9bc6ad27343d68be9b501d89f2/coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0", size = 246706, upload-time = "2025-10-15T15:12:44.001Z" }, - { url = "https://files.pythonhosted.org/packages/40/b8/7a3f1f33b35cc4a6c37e759137533119560d06c0cc14753d1a803be0cd4a/coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7", size = 248634, upload-time = "2025-10-15T15:12:45.768Z" }, - { url = "https://files.pythonhosted.org/packages/7a/41/7f987eb33de386bc4c665ab0bf98d15fcf203369d6aacae74f5dd8ec489a/coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623", size = 250741, upload-time = "2025-10-15T15:12:47.222Z" }, - { url = "https://files.pythonhosted.org/packages/23/c1/a4e0ca6a4e83069fb8216b49b30a7352061ca0cb38654bd2dc96b7b3b7da/coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287", size = 246837, upload-time = "2025-10-15T15:12:48.904Z" }, - { url = "https://files.pythonhosted.org/packages/5d/03/ced062a17f7c38b4728ff76c3acb40d8465634b20b4833cdb3cc3a74e115/coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552", size = 248429, upload-time = "2025-10-15T15:12:50.73Z" }, - { url = "https://files.pythonhosted.org/packages/97/af/a7c6f194bb8c5a2705ae019036b8fe7f49ea818d638eedb15fdb7bed227c/coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de", size = 246490, upload-time = "2025-10-15T15:12:52.646Z" }, - { url = "https://files.pythonhosted.org/packages/ab/c3/aab4df02b04a8fde79068c3c41ad7a622b0ef2b12e1ed154da986a727c3f/coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601", size = 246208, upload-time = "2025-10-15T15:12:54.586Z" }, - { url = "https://files.pythonhosted.org/packages/30/d8/e282ec19cd658238d60ed404f99ef2e45eed52e81b866ab1518c0d4163cf/coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e", size = 247126, upload-time = "2025-10-15T15:12:56.485Z" }, - { url = "https://files.pythonhosted.org/packages/d1/17/a635fa07fac23adb1a5451ec756216768c2767efaed2e4331710342a3399/coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c", size = 218314, upload-time = "2025-10-15T15:12:58.365Z" }, - { url = "https://files.pythonhosted.org/packages/2a/29/2ac1dfcdd4ab9a70026edc8d715ece9b4be9a1653075c658ee6f271f394d/coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9", size = 219203, upload-time = "2025-10-15T15:12:59.902Z" }, - { url = "https://files.pythonhosted.org/packages/03/21/5ce8b3a0133179115af4c041abf2ee652395837cb896614beb8ce8ddcfd9/coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745", size = 217879, upload-time = "2025-10-15T15:13:01.35Z" }, - { url = "https://files.pythonhosted.org/packages/c4/db/86f6906a7c7edc1a52b2c6682d6dd9be775d73c0dfe2b84f8923dfea5784/coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1", size = 216098, upload-time = "2025-10-15T15:13:02.916Z" }, - { url = "https://files.pythonhosted.org/packages/21/54/e7b26157048c7ba555596aad8569ff903d6cd67867d41b75287323678ede/coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007", size = 216331, upload-time = "2025-10-15T15:13:04.403Z" }, - { url = "https://files.pythonhosted.org/packages/b9/19/1ce6bf444f858b83a733171306134a0544eaddf1ca8851ede6540a55b2ad/coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46", size = 247825, upload-time = "2025-10-15T15:13:05.92Z" }, - { url = "https://files.pythonhosted.org/packages/71/0b/d3bcbbc259fcced5fb67c5d78f6e7ee965f49760c14afd931e9e663a83b2/coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893", size = 250573, upload-time = "2025-10-15T15:13:07.471Z" }, - { url = "https://files.pythonhosted.org/packages/58/8d/b0ff3641a320abb047258d36ed1c21d16be33beed4152628331a1baf3365/coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115", size = 251706, upload-time = "2025-10-15T15:13:09.4Z" }, - { url = "https://files.pythonhosted.org/packages/59/c8/5a586fe8c7b0458053d9c687f5cff515a74b66c85931f7fe17a1c958b4ac/coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415", size = 248221, upload-time = "2025-10-15T15:13:10.964Z" }, - { url = "https://files.pythonhosted.org/packages/d0/ff/3a25e3132804ba44cfa9a778cdf2b73dbbe63ef4b0945e39602fc896ba52/coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186", size = 249624, upload-time = "2025-10-15T15:13:12.5Z" }, - { url = "https://files.pythonhosted.org/packages/c5/12/ff10c8ce3895e1b17a73485ea79ebc1896a9e466a9d0f4aef63e0d17b718/coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d", size = 247744, upload-time = "2025-10-15T15:13:14.554Z" }, - { url = "https://files.pythonhosted.org/packages/16/02/d500b91f5471b2975947e0629b8980e5e90786fe316b6d7299852c1d793d/coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d", size = 247325, upload-time = "2025-10-15T15:13:16.438Z" }, - { url = "https://files.pythonhosted.org/packages/77/11/dee0284fbbd9cd64cfce806b827452c6df3f100d9e66188e82dfe771d4af/coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2", size = 249180, upload-time = "2025-10-15T15:13:17.959Z" }, - { url = "https://files.pythonhosted.org/packages/59/1b/cdf1def928f0a150a057cab03286774e73e29c2395f0d30ce3d9e9f8e697/coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5", size = 218479, upload-time = "2025-10-15T15:13:19.608Z" }, - { url = "https://files.pythonhosted.org/packages/ff/55/e5884d55e031da9c15b94b90a23beccc9d6beee65e9835cd6da0a79e4f3a/coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0", size = 219290, upload-time = "2025-10-15T15:13:21.593Z" }, - { url = "https://files.pythonhosted.org/packages/23/a8/faa930cfc71c1d16bc78f9a19bb73700464f9c331d9e547bfbc1dbd3a108/coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad", size = 217924, upload-time = "2025-10-15T15:13:23.39Z" }, - { url = "https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1", size = 216129, upload-time = "2025-10-15T15:13:25.371Z" }, - { url = "https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be", size = 216380, upload-time = "2025-10-15T15:13:26.976Z" }, - { url = "https://files.pythonhosted.org/packages/b2/f5/3da9cc9596708273385189289c0e4d8197d37a386bdf17619013554b3447/coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d", size = 247375, upload-time = "2025-10-15T15:13:28.923Z" }, - { url = "https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82", size = 249978, upload-time = "2025-10-15T15:13:30.525Z" }, - { url = "https://files.pythonhosted.org/packages/e7/8c/042dede2e23525e863bf1ccd2b92689692a148d8b5fd37c37899ba882645/coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52", size = 251253, upload-time = "2025-10-15T15:13:32.174Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a9/3c58df67bfa809a7bddd786356d9c5283e45d693edb5f3f55d0986dd905a/coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b", size = 247591, upload-time = "2025-10-15T15:13:34.147Z" }, - { url = "https://files.pythonhosted.org/packages/26/5b/c7f32efd862ee0477a18c41e4761305de6ddd2d49cdeda0c1116227570fd/coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4", size = 249411, upload-time = "2025-10-15T15:13:38.425Z" }, - { url = "https://files.pythonhosted.org/packages/76/b5/78cb4f1e86c1611431c990423ec0768122905b03837e1b4c6a6f388a858b/coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd", size = 247303, upload-time = "2025-10-15T15:13:40.464Z" }, - { url = "https://files.pythonhosted.org/packages/87/c9/23c753a8641a330f45f221286e707c427e46d0ffd1719b080cedc984ec40/coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc", size = 247157, upload-time = "2025-10-15T15:13:42.087Z" }, - { url = "https://files.pythonhosted.org/packages/c5/42/6e0cc71dc8a464486e944a4fa0d85bdec031cc2969e98ed41532a98336b9/coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48", size = 248921, upload-time = "2025-10-15T15:13:43.715Z" }, - { url = "https://files.pythonhosted.org/packages/e8/1c/743c2ef665e6858cccb0f84377dfe3a4c25add51e8c7ef19249be92465b6/coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040", size = 218526, upload-time = "2025-10-15T15:13:45.336Z" }, - { url = "https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05", size = 219317, upload-time = "2025-10-15T15:13:47.401Z" }, - { url = "https://files.pythonhosted.org/packages/97/54/47db81dcbe571a48a298f206183ba8a7ba79200a37cd0d9f4788fcd2af4a/coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a", size = 217948, upload-time = "2025-10-15T15:13:49.096Z" }, - { url = "https://files.pythonhosted.org/packages/e5/8b/cb68425420154e7e2a82fd779a8cc01549b6fa83c2ad3679cd6c088ebd07/coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b", size = 216837, upload-time = "2025-10-15T15:13:51.09Z" }, - { url = "https://files.pythonhosted.org/packages/33/55/9d61b5765a025685e14659c8d07037247de6383c0385757544ffe4606475/coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37", size = 217061, upload-time = "2025-10-15T15:13:52.747Z" }, - { url = "https://files.pythonhosted.org/packages/52/85/292459c9186d70dcec6538f06ea251bc968046922497377bf4a1dc9a71de/coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de", size = 258398, upload-time = "2025-10-15T15:13:54.45Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e2/46edd73fb8bf51446c41148d81944c54ed224854812b6ca549be25113ee0/coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f", size = 260574, upload-time = "2025-10-15T15:13:56.145Z" }, - { url = "https://files.pythonhosted.org/packages/07/5e/1df469a19007ff82e2ca8fe509822820a31e251f80ee7344c34f6cd2ec43/coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c", size = 262797, upload-time = "2025-10-15T15:13:58.635Z" }, - { url = "https://files.pythonhosted.org/packages/f9/50/de216b31a1434b94d9b34a964c09943c6be45069ec704bfc379d8d89a649/coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa", size = 257361, upload-time = "2025-10-15T15:14:00.409Z" }, - { url = "https://files.pythonhosted.org/packages/82/1e/3f9f8344a48111e152e0fd495b6fff13cc743e771a6050abf1627a7ba918/coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740", size = 260349, upload-time = "2025-10-15T15:14:02.188Z" }, - { url = "https://files.pythonhosted.org/packages/65/9b/3f52741f9e7d82124272f3070bbe316006a7de1bad1093f88d59bfc6c548/coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef", size = 258114, upload-time = "2025-10-15T15:14:03.907Z" }, - { url = "https://files.pythonhosted.org/packages/0b/8b/918f0e15f0365d50d3986bbd3338ca01178717ac5678301f3f547b6619e6/coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0", size = 256723, upload-time = "2025-10-15T15:14:06.324Z" }, - { url = "https://files.pythonhosted.org/packages/44/9e/7776829f82d3cf630878a7965a7d70cc6ca94f22c7d20ec4944f7148cb46/coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca", size = 259238, upload-time = "2025-10-15T15:14:08.002Z" }, - { url = "https://files.pythonhosted.org/packages/9a/b8/49cf253e1e7a3bedb85199b201862dd7ca4859f75b6cf25ffa7298aa0760/coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2", size = 219180, upload-time = "2025-10-15T15:14:09.786Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e1/1a541703826be7ae2125a0fb7f821af5729d56bb71e946e7b933cc7a89a4/coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268", size = 220241, upload-time = "2025-10-15T15:14:11.471Z" }, - { url = "https://files.pythonhosted.org/packages/d5/d1/5ee0e0a08621140fd418ec4020f595b4d52d7eb429ae6a0c6542b4ba6f14/coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836", size = 218510, upload-time = "2025-10-15T15:14:13.46Z" }, - { url = "https://files.pythonhosted.org/packages/f4/06/e923830c1985ce808e40a3fa3eb46c13350b3224b7da59757d37b6ce12b8/coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497", size = 216110, upload-time = "2025-10-15T15:14:15.157Z" }, - { url = "https://files.pythonhosted.org/packages/42/82/cdeed03bfead45203fb651ed756dfb5266028f5f939e7f06efac4041dad5/coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e", size = 216395, upload-time = "2025-10-15T15:14:16.863Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ba/e1c80caffc3199aa699813f73ff097bc2df7b31642bdbc7493600a8f1de5/coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1", size = 247433, upload-time = "2025-10-15T15:14:18.589Z" }, - { url = "https://files.pythonhosted.org/packages/80/c0/5b259b029694ce0a5bbc1548834c7ba3db41d3efd3474489d7efce4ceb18/coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca", size = 249970, upload-time = "2025-10-15T15:14:20.307Z" }, - { url = "https://files.pythonhosted.org/packages/8c/86/171b2b5e1aac7e2fd9b43f7158b987dbeb95f06d1fbecad54ad8163ae3e8/coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd", size = 251324, upload-time = "2025-10-15T15:14:22.419Z" }, - { url = "https://files.pythonhosted.org/packages/1a/7e/7e10414d343385b92024af3932a27a1caf75c6e27ee88ba211221ff1a145/coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43", size = 247445, upload-time = "2025-10-15T15:14:24.205Z" }, - { url = "https://files.pythonhosted.org/packages/c4/3b/e4f966b21f5be8c4bf86ad75ae94efa0de4c99c7bbb8114476323102e345/coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777", size = 249324, upload-time = "2025-10-15T15:14:26.234Z" }, - { url = "https://files.pythonhosted.org/packages/00/a2/8479325576dfcd909244d0df215f077f47437ab852ab778cfa2f8bf4d954/coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2", size = 247261, upload-time = "2025-10-15T15:14:28.42Z" }, - { url = "https://files.pythonhosted.org/packages/7b/d8/3a9e2db19d94d65771d0f2e21a9ea587d11b831332a73622f901157cc24b/coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d", size = 247092, upload-time = "2025-10-15T15:14:30.784Z" }, - { url = "https://files.pythonhosted.org/packages/b3/b1/bbca3c472544f9e2ad2d5116b2379732957048be4b93a9c543fcd0207e5f/coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4", size = 248755, upload-time = "2025-10-15T15:14:32.585Z" }, - { url = "https://files.pythonhosted.org/packages/89/49/638d5a45a6a0f00af53d6b637c87007eb2297042186334e9923a61aa8854/coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721", size = 218793, upload-time = "2025-10-15T15:14:34.972Z" }, - { url = "https://files.pythonhosted.org/packages/30/cc/b675a51f2d068adb3cdf3799212c662239b0ca27f4691d1fff81b92ea850/coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad", size = 219587, upload-time = "2025-10-15T15:14:37.047Z" }, - { url = "https://files.pythonhosted.org/packages/93/98/5ac886876026de04f00820e5094fe22166b98dcb8b426bf6827aaf67048c/coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479", size = 218168, upload-time = "2025-10-15T15:14:38.861Z" }, - { url = "https://files.pythonhosted.org/packages/14/d1/b4145d35b3e3ecf4d917e97fc8895bcf027d854879ba401d9ff0f533f997/coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f", size = 216850, upload-time = "2025-10-15T15:14:40.651Z" }, - { url = "https://files.pythonhosted.org/packages/ca/d1/7f645fc2eccd318369a8a9948acc447bb7c1ade2911e31d3c5620544c22b/coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e", size = 217071, upload-time = "2025-10-15T15:14:42.755Z" }, - { url = "https://files.pythonhosted.org/packages/54/7d/64d124649db2737ceced1dfcbdcb79898d5868d311730f622f8ecae84250/coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44", size = 258570, upload-time = "2025-10-15T15:14:44.542Z" }, - { url = "https://files.pythonhosted.org/packages/6c/3f/6f5922f80dc6f2d8b2c6f974835c43f53eb4257a7797727e6ca5b7b2ec1f/coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3", size = 260738, upload-time = "2025-10-15T15:14:46.436Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5f/9e883523c4647c860b3812b417a2017e361eca5b635ee658387dc11b13c1/coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b", size = 262994, upload-time = "2025-10-15T15:14:48.3Z" }, - { url = "https://files.pythonhosted.org/packages/07/bb/43b5a8e94c09c8bf51743ffc65c4c841a4ca5d3ed191d0a6919c379a1b83/coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d", size = 257282, upload-time = "2025-10-15T15:14:50.236Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e5/0ead8af411411330b928733e1d201384b39251a5f043c1612970310e8283/coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2", size = 260430, upload-time = "2025-10-15T15:14:52.413Z" }, - { url = "https://files.pythonhosted.org/packages/ae/66/03dd8bb0ba5b971620dcaac145461950f6d8204953e535d2b20c6b65d729/coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e", size = 258190, upload-time = "2025-10-15T15:14:54.268Z" }, - { url = "https://files.pythonhosted.org/packages/45/ae/28a9cce40bf3174426cb2f7e71ee172d98e7f6446dff936a7ccecee34b14/coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996", size = 256658, upload-time = "2025-10-15T15:14:56.436Z" }, - { url = "https://files.pythonhosted.org/packages/5c/7c/3a44234a8599513684bfc8684878fd7b126c2760f79712bb78c56f19efc4/coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11", size = 259342, upload-time = "2025-10-15T15:14:58.538Z" }, - { url = "https://files.pythonhosted.org/packages/e1/e6/0108519cba871af0351725ebdb8660fd7a0fe2ba3850d56d32490c7d9b4b/coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73", size = 219568, upload-time = "2025-10-15T15:15:00.382Z" }, - { url = "https://files.pythonhosted.org/packages/c9/76/44ba876e0942b4e62fdde23ccb029ddb16d19ba1bef081edd00857ba0b16/coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547", size = 220687, upload-time = "2025-10-15T15:15:02.322Z" }, - { url = "https://files.pythonhosted.org/packages/b9/0c/0df55ecb20d0d0ed5c322e10a441775e1a3a5d78c60f0c4e1abfe6fcf949/coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3", size = 218711, upload-time = "2025-10-15T15:15:04.575Z" }, - { url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", size = 207761, upload-time = "2025-10-15T15:15:06.439Z" }, +version = "7.11.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/59/9698d57a3b11704c7b89b21d69e9d23ecf80d538cabb536c8b63f4a12322/coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b", size = 815210, upload-time = "2025-11-10T00:13:17.18Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/68/b53157115ef76d50d1d916d6240e5cd5b3c14dba8ba1b984632b8221fc2e/coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5", size = 216377, upload-time = "2025-11-10T00:10:27.317Z" }, + { url = "https://files.pythonhosted.org/packages/14/c1/d2f9d8e37123fe6e7ab8afcaab8195f13bc84a8b2f449a533fd4812ac724/coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7", size = 216892, upload-time = "2025-11-10T00:10:30.624Z" }, + { url = "https://files.pythonhosted.org/packages/83/73/18f05d8010149b650ed97ee5c9f7e4ae68c05c7d913391523281e41c2495/coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb", size = 243650, upload-time = "2025-11-10T00:10:32.392Z" }, + { url = "https://files.pythonhosted.org/packages/63/3c/c0cbb296c0ecc6dcbd70f4b473fcd7fe4517bbef8b09f4326d78f38adb87/coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1", size = 245478, upload-time = "2025-11-10T00:10:34.157Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9a/dad288cf9faa142a14e75e39dc646d968b93d74e15c83e9b13fd628f2cb3/coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c", size = 247337, upload-time = "2025-11-10T00:10:35.655Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ba/f6148ebf5547b3502013175e41bf3107a4e34b7dd19f9793a6ce0e1cd61f/coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31", size = 244328, upload-time = "2025-11-10T00:10:37.459Z" }, + { url = "https://files.pythonhosted.org/packages/e6/4d/b93784d0b593c5df89a0d48cbbd2d0963e0ca089eaf877405849792e46d3/coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2", size = 245381, upload-time = "2025-11-10T00:10:39.229Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/6735bfd4f0f736d457642ee056a570d704c9d57fdcd5c91ea5d6b15c944e/coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507", size = 243390, upload-time = "2025-11-10T00:10:40.984Z" }, + { url = "https://files.pythonhosted.org/packages/db/3d/7ba68ed52d1873d450aefd8d2f5a353e67b421915cb6c174e4222c7b918c/coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832", size = 243654, upload-time = "2025-11-10T00:10:42.496Z" }, + { url = "https://files.pythonhosted.org/packages/14/26/be2720c4c7bf73c6591ae4ab503a7b5a31c7a60ced6dba855cfcb4a5af7e/coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e", size = 244272, upload-time = "2025-11-10T00:10:44.39Z" }, + { url = "https://files.pythonhosted.org/packages/90/20/086f5697780df146dbc0df4ae9b6db2b23ddf5aa550f977b2825137728e9/coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb", size = 218969, upload-time = "2025-11-10T00:10:45.863Z" }, + { url = "https://files.pythonhosted.org/packages/98/5c/cc6faba945ede5088156da7770e30d06c38b8591785ac99bcfb2074f9ef6/coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8", size = 219903, upload-time = "2025-11-10T00:10:47.676Z" }, + { url = "https://files.pythonhosted.org/packages/92/92/43a961c0f57b666d01c92bcd960c7f93677de5e4ee7ca722564ad6dee0fa/coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1", size = 216504, upload-time = "2025-11-10T00:10:49.524Z" }, + { url = "https://files.pythonhosted.org/packages/5d/5c/dbfc73329726aef26dbf7fefef81b8a2afd1789343a579ea6d99bf15d26e/coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06", size = 217006, upload-time = "2025-11-10T00:10:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/a5/e0/878c84fb6661964bc435beb1e28c050650aa30e4c1cdc12341e298700bda/coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80", size = 247415, upload-time = "2025-11-10T00:10:52.805Z" }, + { url = "https://files.pythonhosted.org/packages/56/9e/0677e78b1e6a13527f39c4b39c767b351e256b333050539861c63f98bd61/coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa", size = 249332, upload-time = "2025-11-10T00:10:54.35Z" }, + { url = "https://files.pythonhosted.org/packages/54/90/25fc343e4ce35514262451456de0953bcae5b37dda248aed50ee51234cee/coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297", size = 251443, upload-time = "2025-11-10T00:10:55.832Z" }, + { url = "https://files.pythonhosted.org/packages/13/56/bc02bbc890fd8b155a64285c93e2ab38647486701ac9c980d457cdae857a/coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362", size = 247554, upload-time = "2025-11-10T00:10:57.829Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ab/0318888d091d799a82d788c1e8d8bd280f1d5c41662bbb6e11187efe33e8/coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87", size = 249139, upload-time = "2025-11-10T00:10:59.465Z" }, + { url = "https://files.pythonhosted.org/packages/79/d8/3ee50929c4cd36fcfcc0f45d753337001001116c8a5b8dd18d27ea645737/coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200", size = 247209, upload-time = "2025-11-10T00:11:01.432Z" }, + { url = "https://files.pythonhosted.org/packages/94/7c/3cf06e327401c293e60c962b4b8a2ceb7167c1a428a02be3adbd1d7c7e4c/coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4", size = 246936, upload-time = "2025-11-10T00:11:02.964Z" }, + { url = "https://files.pythonhosted.org/packages/99/0b/ffc03dc8f4083817900fd367110015ef4dd227b37284104a5eb5edc9c106/coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060", size = 247835, upload-time = "2025-11-10T00:11:04.405Z" }, + { url = "https://files.pythonhosted.org/packages/17/4d/dbe54609ee066553d0bcdcdf108b177c78dab836292bee43f96d6a5674d1/coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7", size = 218994, upload-time = "2025-11-10T00:11:05.966Z" }, + { url = "https://files.pythonhosted.org/packages/94/11/8e7155df53f99553ad8114054806c01a2c0b08f303ea7e38b9831652d83d/coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55", size = 219926, upload-time = "2025-11-10T00:11:07.936Z" }, + { url = "https://files.pythonhosted.org/packages/1f/93/bea91b6a9e35d89c89a1cd5824bc72e45151a9c2a9ca0b50d9e9a85e3ae3/coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc", size = 218599, upload-time = "2025-11-10T00:11:09.578Z" }, + { url = "https://files.pythonhosted.org/packages/c2/39/af056ec7a27c487e25c7f6b6e51d2ee9821dba1863173ddf4dc2eebef4f7/coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f", size = 216676, upload-time = "2025-11-10T00:11:11.566Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f8/21126d34b174d037b5d01bea39077725cbb9a0da94a95c5f96929c695433/coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e", size = 217034, upload-time = "2025-11-10T00:11:13.12Z" }, + { url = "https://files.pythonhosted.org/packages/d5/3f/0fd35f35658cdd11f7686303214bd5908225838f374db47f9e457c8d6df8/coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a", size = 248531, upload-time = "2025-11-10T00:11:15.023Z" }, + { url = "https://files.pythonhosted.org/packages/8f/59/0bfc5900fc15ce4fd186e092451de776bef244565c840c9c026fd50857e1/coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1", size = 251290, upload-time = "2025-11-10T00:11:16.628Z" }, + { url = "https://files.pythonhosted.org/packages/71/88/d5c184001fa2ac82edf1b8f2cd91894d2230d7c309e937c54c796176e35b/coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd", size = 252375, upload-time = "2025-11-10T00:11:18.249Z" }, + { url = "https://files.pythonhosted.org/packages/5c/29/f60af9f823bf62c7a00ce1ac88441b9a9a467e499493e5cc65028c8b8dd2/coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5", size = 248946, upload-time = "2025-11-10T00:11:20.202Z" }, + { url = "https://files.pythonhosted.org/packages/67/16/4662790f3b1e03fce5280cad93fd18711c35980beb3c6f28dca41b5230c6/coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e", size = 250310, upload-time = "2025-11-10T00:11:21.689Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/dd6c2e28308a83e5fc1ee602f8204bd3aa5af685c104cb54499230cf56db/coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044", size = 248461, upload-time = "2025-11-10T00:11:23.384Z" }, + { url = "https://files.pythonhosted.org/packages/16/fe/b71af12be9f59dc9eb060688fa19a95bf3223f56c5af1e9861dfa2275d2c/coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7", size = 248039, upload-time = "2025-11-10T00:11:25.07Z" }, + { url = "https://files.pythonhosted.org/packages/11/b8/023b2003a2cd96bdf607afe03d9b96c763cab6d76e024abe4473707c4eb8/coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405", size = 249903, upload-time = "2025-11-10T00:11:26.992Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ee/5f1076311aa67b1fa4687a724cc044346380e90ce7d94fec09fd384aa5fd/coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e", size = 219201, upload-time = "2025-11-10T00:11:28.619Z" }, + { url = "https://files.pythonhosted.org/packages/4f/24/d21688f48fe9fcc778956680fd5aaf69f4e23b245b7c7a4755cbd421d25b/coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055", size = 220012, upload-time = "2025-11-10T00:11:30.234Z" }, + { url = "https://files.pythonhosted.org/packages/4f/9e/d5eb508065f291456378aa9b16698b8417d87cb084c2b597f3beb00a8084/coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f", size = 218652, upload-time = "2025-11-10T00:11:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f6/d8572c058211c7d976f24dab71999a565501fb5b3cdcb59cf782f19c4acb/coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36", size = 216694, upload-time = "2025-11-10T00:11:34.296Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f6/b6f9764d90c0ce1bce8d995649fa307fff21f4727b8d950fa2843b7b0de5/coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e", size = 217065, upload-time = "2025-11-10T00:11:36.281Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8d/a12cb424063019fd077b5be474258a0ed8369b92b6d0058e673f0a945982/coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2", size = 248062, upload-time = "2025-11-10T00:11:37.903Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/dab1a4e8e75ce053d14259d3d7485d68528a662e286e184685ea49e71156/coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63", size = 250657, upload-time = "2025-11-10T00:11:39.509Z" }, + { url = "https://files.pythonhosted.org/packages/3f/89/a14f256438324f33bae36f9a1a7137729bf26b0a43f5eda60b147ec7c8c7/coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3", size = 251900, upload-time = "2025-11-10T00:11:41.372Z" }, + { url = "https://files.pythonhosted.org/packages/04/07/75b0d476eb349f1296486b1418b44f2d8780cc8db47493de3755e5340076/coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5", size = 248254, upload-time = "2025-11-10T00:11:43.27Z" }, + { url = "https://files.pythonhosted.org/packages/5a/4b/0c486581fa72873489ca092c52792d008a17954aa352809a7cbe6cf0bf07/coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5", size = 250041, upload-time = "2025-11-10T00:11:45.274Z" }, + { url = "https://files.pythonhosted.org/packages/af/a3/0059dafb240ae3e3291f81b8de00e9c511d3dd41d687a227dd4b529be591/coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7", size = 248004, upload-time = "2025-11-10T00:11:46.93Z" }, + { url = "https://files.pythonhosted.org/packages/83/93/967d9662b1eb8c7c46917dcc7e4c1875724ac3e73c3cb78e86d7a0ac719d/coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5", size = 247828, upload-time = "2025-11-10T00:11:48.563Z" }, + { url = "https://files.pythonhosted.org/packages/4c/1c/5077493c03215701e212767e470b794548d817dfc6247a4718832cc71fac/coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094", size = 249588, upload-time = "2025-11-10T00:11:50.581Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a5/77f64de461016e7da3e05d7d07975c89756fe672753e4cf74417fc9b9052/coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c", size = 219223, upload-time = "2025-11-10T00:11:52.184Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1c/ec51a3c1a59d225b44bdd3a4d463135b3159a535c2686fac965b698524f4/coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2", size = 220033, upload-time = "2025-11-10T00:11:53.871Z" }, + { url = "https://files.pythonhosted.org/packages/01/ec/e0ce39746ed558564c16f2cc25fa95ce6fc9fa8bfb3b9e62855d4386b886/coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944", size = 218661, upload-time = "2025-11-10T00:11:55.597Z" }, + { url = "https://files.pythonhosted.org/packages/46/cb/483f130bc56cbbad2638248915d97b185374d58b19e3cc3107359715949f/coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428", size = 217389, upload-time = "2025-11-10T00:11:57.59Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ae/81f89bae3afef75553cf10e62feb57551535d16fd5859b9ee5a2a97ddd27/coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a", size = 217742, upload-time = "2025-11-10T00:11:59.519Z" }, + { url = "https://files.pythonhosted.org/packages/db/6e/a0fb897041949888191a49c36afd5c6f5d9f5fd757e0b0cd99ec198a324b/coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655", size = 259049, upload-time = "2025-11-10T00:12:01.592Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/d13acc67eb402d91eb94b9bd60593411799aed09ce176ee8d8c0e39c94ca/coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7", size = 261113, upload-time = "2025-11-10T00:12:03.639Z" }, + { url = "https://files.pythonhosted.org/packages/ea/07/a6868893c48191d60406df4356aa7f0f74e6de34ef1f03af0d49183e0fa1/coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d", size = 263546, upload-time = "2025-11-10T00:12:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/24/e5/28598f70b2c1098332bac47925806353b3313511d984841111e6e760c016/coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f", size = 258260, upload-time = "2025-11-10T00:12:07.137Z" }, + { url = "https://files.pythonhosted.org/packages/0e/58/58e2d9e6455a4ed746a480c4b9cf96dc3cb2a6b8f3efbee5efd33ae24b06/coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0", size = 261121, upload-time = "2025-11-10T00:12:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/17/57/38803eefb9b0409934cbc5a14e3978f0c85cb251d2b6f6a369067a7105a0/coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739", size = 258736, upload-time = "2025-11-10T00:12:11.195Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/f94683167156e93677b3442be1d4ca70cb33718df32a2eea44a5898f04f6/coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71", size = 257625, upload-time = "2025-11-10T00:12:12.843Z" }, + { url = "https://files.pythonhosted.org/packages/87/ed/42d0bf1bc6bfa7d65f52299a31daaa866b4c11000855d753857fe78260ac/coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76", size = 259827, upload-time = "2025-11-10T00:12:15.128Z" }, + { url = "https://files.pythonhosted.org/packages/d3/76/5682719f5d5fbedb0c624c9851ef847407cae23362deb941f185f489c54e/coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c", size = 219897, upload-time = "2025-11-10T00:12:17.274Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/1da511d0ac3d39e6676fa6cc5ec35320bbf1cebb9b24e9ee7548ee4e931a/coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac", size = 220959, upload-time = "2025-11-10T00:12:19.292Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9d/e255da6a04e9ec5f7b633c54c0fdfa221a9e03550b67a9c83217de12e96c/coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc", size = 219234, upload-time = "2025-11-10T00:12:21.251Z" }, + { url = "https://files.pythonhosted.org/packages/84/d6/634ec396e45aded1772dccf6c236e3e7c9604bc47b816e928f32ce7987d1/coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c", size = 216746, upload-time = "2025-11-10T00:12:23.089Z" }, + { url = "https://files.pythonhosted.org/packages/28/76/1079547f9d46f9c7c7d0dad35b6873c98bc5aa721eeabceafabd722cd5e7/coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203", size = 217077, upload-time = "2025-11-10T00:12:24.863Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/6ad80d6ae0d7cb743b9a98df8bb88b1ff3dc54491508a4a97549c2b83400/coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240", size = 248122, upload-time = "2025-11-10T00:12:26.553Z" }, + { url = "https://files.pythonhosted.org/packages/20/1d/784b87270784b0b88e4beec9d028e8d58f73ae248032579c63ad2ac6f69a/coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83", size = 250638, upload-time = "2025-11-10T00:12:28.555Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/b6dd31e23e004e9de84d1a8672cd3d73e50f5dae65dbd0f03fa2cdde6100/coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902", size = 251972, upload-time = "2025-11-10T00:12:30.246Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ef/f9c64d76faac56b82daa036b34d4fe9ab55eb37f22062e68e9470583e688/coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428", size = 248147, upload-time = "2025-11-10T00:12:32.195Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/5b666f90a8f8053bd264a1ce693d2edef2368e518afe70680070fca13ecd/coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75", size = 249995, upload-time = "2025-11-10T00:12:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/eb/7b/871e991ffb5d067f8e67ffb635dabba65b231d6e0eb724a4a558f4a702a5/coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704", size = 247948, upload-time = "2025-11-10T00:12:36.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/8b/ce454f0af9609431b06dbe5485fc9d1c35ddc387e32ae8e374f49005748b/coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b", size = 247770, upload-time = "2025-11-10T00:12:38.167Z" }, + { url = "https://files.pythonhosted.org/packages/61/8f/79002cb58a61dfbd2085de7d0a46311ef2476823e7938db80284cedd2428/coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131", size = 249431, upload-time = "2025-11-10T00:12:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/58/cc/d06685dae97468ed22999440f2f2f5060940ab0e7952a7295f236d98cce7/coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a", size = 219508, upload-time = "2025-11-10T00:12:42.231Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ed/770cd07706a3598c545f62d75adf2e5bd3791bffccdcf708ec383ad42559/coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86", size = 220325, upload-time = "2025-11-10T00:12:44.065Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ac/6a1c507899b6fb1b9a56069954365f655956bcc648e150ce64c2b0ecbed8/coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e", size = 218899, upload-time = "2025-11-10T00:12:46.18Z" }, + { url = "https://files.pythonhosted.org/packages/9a/58/142cd838d960cd740654d094f7b0300d7b81534bb7304437d2439fb685fb/coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df", size = 217471, upload-time = "2025-11-10T00:12:48.392Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2c/2f44d39eb33e41ab3aba80571daad32e0f67076afcf27cb443f9e5b5a3ee/coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001", size = 217742, upload-time = "2025-11-10T00:12:50.182Z" }, + { url = "https://files.pythonhosted.org/packages/32/76/8ebc66c3c699f4de3174a43424c34c086323cd93c4930ab0f835731c443a/coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de", size = 259120, upload-time = "2025-11-10T00:12:52.451Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/78a3302b9595f331b86e4f12dfbd9252c8e93d97b8631500888f9a3a2af7/coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926", size = 261229, upload-time = "2025-11-10T00:12:54.667Z" }, + { url = "https://files.pythonhosted.org/packages/07/59/1a9c0844dadef2a6efac07316d9781e6c5a3f3ea7e5e701411e99d619bfd/coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd", size = 263642, upload-time = "2025-11-10T00:12:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/37/86/66c15d190a8e82eee777793cabde730640f555db3c020a179625a2ad5320/coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac", size = 258193, upload-time = "2025-11-10T00:12:58.687Z" }, + { url = "https://files.pythonhosted.org/packages/c7/c7/4a4aeb25cb6f83c3ec4763e5f7cc78da1c6d4ef9e22128562204b7f39390/coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46", size = 261107, upload-time = "2025-11-10T00:13:00.502Z" }, + { url = "https://files.pythonhosted.org/packages/ed/91/b986b5035f23cf0272446298967ecdd2c3c0105ee31f66f7e6b6948fd7f8/coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64", size = 258717, upload-time = "2025-11-10T00:13:02.747Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c7/6c084997f5a04d050c513545d3344bfa17bd3b67f143f388b5757d762b0b/coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f", size = 257541, upload-time = "2025-11-10T00:13:04.689Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c5/38e642917e406930cb67941210a366ccffa767365c8f8d9ec0f465a8b218/coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820", size = 259872, upload-time = "2025-11-10T00:13:06.559Z" }, + { url = "https://files.pythonhosted.org/packages/b7/67/5e812979d20c167f81dbf9374048e0193ebe64c59a3d93d7d947b07865fa/coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237", size = 220289, upload-time = "2025-11-10T00:13:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/24/3a/b72573802672b680703e0df071faadfab7dcd4d659aaaffc4626bc8bbde8/coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9", size = 221398, upload-time = "2025-11-10T00:13:10.734Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4e/649628f28d38bad81e4e8eb3f78759d20ac173e3c456ac629123815feb40/coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd", size = 219435, upload-time = "2025-11-10T00:13:12.712Z" }, + { url = "https://files.pythonhosted.org/packages/19/8f/92bdd27b067204b99f396a1414d6342122f3e2663459baf787108a6b8b84/coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe", size = 208478, upload-time = "2025-11-10T00:13:14.908Z" }, ] [package.optional-dependencies] @@ -344,6 +426,71 @@ toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] +[[package]] +name = "cryptography" +version = "46.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, + { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, + { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, + { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, + { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, + { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, + { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, + { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, + { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, + { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, + { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, + { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, + { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, + { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, + { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, + { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, + { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, + { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, + { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cd/1a8633802d766a0fa46f382a77e096d7e209e0817892929655fe0586ae32/cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32", size = 3689163, upload-time = "2025-10-15T23:18:13.821Z" }, + { url = "https://files.pythonhosted.org/packages/4c/59/6b26512964ace6480c3e54681a9859c974172fb141c38df11eadd8416947/cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c", size = 3429474, upload-time = "2025-10-15T23:18:15.477Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" }, + { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" }, + { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" }, + { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" }, +] + [[package]] name = "decorator" version = "5.2.1" @@ -388,14 +535,14 @@ wheels = [ [[package]] name = "griffe" -version = "1.14.0" +version = "1.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/d7/6c09dd7ce4c7837e4cdb11dce980cb45ae3cd87677298dc3b781b6bce7d3/griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13", size = 424684, upload-time = "2025-09-05T15:02:29.167Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/0c/3a471b6e31951dce2360477420d0a8d1e00dea6cf33b70f3e8c3ab6e28e1/griffe-1.15.0.tar.gz", hash = "sha256:7726e3afd6f298fbc3696e67958803e7ac843c1cfe59734b6251a40cdbfb5eea", size = 424112, upload-time = "2025-11-10T15:03:15.52Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" }, + { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" }, ] [[package]] @@ -489,7 +636,7 @@ wheels = [ [[package]] name = "ipython" -version = "9.6.0" +version = "9.7.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", @@ -507,9 +654,9 @@ dependencies = [ { name = "traitlets", marker = "python_full_version >= '3.11'" }, { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/34/29b18c62e39ee2f7a6a3bba7efd952729d8aadd45ca17efc34453b717665/ipython-9.6.0.tar.gz", hash = "sha256:5603d6d5d356378be5043e69441a072b50a5b33b4503428c77b04cb8ce7bc731", size = 4396932, upload-time = "2025-09-29T10:55:53.948Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/e6/48c74d54039241a456add616464ea28c6ebf782e4110d419411b83dae06f/ipython-9.7.0.tar.gz", hash = "sha256:5f6de88c905a566c6a9d6c400a8fed54a638e1f7543d17aae2551133216b1e4e", size = 4422115, upload-time = "2025-11-05T12:18:54.646Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl", hash = "sha256:5f77efafc886d2f023442479b8149e7d86547ad0a979e9da9f045d252f648196", size = 616170, upload-time = "2025-09-29T10:55:47.676Z" }, + { url = "https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl", hash = "sha256:bce8ac85eb9521adc94e1845b4c03d88365fd6ac2f4908ec4ed1eb1b0a065f9f", size = 618911, upload-time = "2025-11-05T12:18:52.484Z" }, ] [[package]] @@ -577,11 +724,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.9" +version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, + { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, ] [[package]] @@ -671,19 +818,19 @@ wheels = [ [[package]] name = "matplotlib-inline" -version = "0.1.7" +version = "0.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159, upload-time = "2024-04-15T13:44:44.803Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" }, + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, ] [[package]] name = "mcp" -version = "1.18.0" +version = "1.21.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -692,15 +839,18 @@ dependencies = [ { name = "jsonschema" }, { name = "pydantic" }, { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, { name = "python-multipart" }, { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "sse-starlette" }, { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1a/e0/fe34ce16ea2bacce489ab859abd1b47ae28b438c3ef60b9c5eee6c02592f/mcp-1.18.0.tar.gz", hash = "sha256:aa278c44b1efc0a297f53b68df865b988e52dd08182d702019edcf33a8e109f6", size = 482926, upload-time = "2025-10-16T19:19:55.125Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/bb/147bf5d47b8d6f3fb315204cd2f567b1ad85511acd8d59e85cccf7cce65c/mcp-1.21.2.tar.gz", hash = "sha256:b6497b7f7a77aabe263931a3335956dfd45fe8b1e2168931e455914ba1a7f4c9", size = 470799, upload-time = "2025-11-17T13:56:06.397Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/44/f5970e3e899803823826283a70b6003afd46f28e082544407e24575eccd3/mcp-1.18.0-py3-none-any.whl", hash = "sha256:42f10c270de18e7892fdf9da259029120b1ea23964ff688248c69db9d72b1d0a", size = 168762, upload-time = "2025-10-16T19:19:53.2Z" }, + { url = "https://files.pythonhosted.org/packages/67/0f/669ecbe78a0ba192afcc0b026ae62d1005779e91bad27ab9d703401510bf/mcp-1.21.2-py3-none-any.whl", hash = "sha256:59413ef15db757a785e3859548c1a7ffc7be57bf162c3c24afc0e04fd9f4181c", size = 174854, upload-time = "2025-11-17T13:56:04.987Z" }, ] [[package]] @@ -766,7 +916,7 @@ wheels = [ [[package]] name = "mkdocs-material" -version = "9.6.22" +version = "9.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, @@ -781,9 +931,9 @@ dependencies = [ { name = "pymdown-extensions" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/5d/317e37b6c43325cb376a1d6439df9cc743b8ee41c84603c2faf7286afc82/mkdocs_material-9.6.22.tar.gz", hash = "sha256:87c158b0642e1ada6da0cbd798a3389b0bc5516b90e5ece4a0fb939f00bacd1c", size = 4044968, upload-time = "2025-10-15T09:21:15.409Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/3b/111b84cd6ff28d9e955b5f799ef217a17bc1684ac346af333e6100e413cb/mkdocs_material-9.7.0.tar.gz", hash = "sha256:602b359844e906ee402b7ed9640340cf8a474420d02d8891451733b6b02314ec", size = 4094546, upload-time = "2025-11-11T08:49:09.73Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl", hash = "sha256:14ac5f72d38898b2f98ac75a5531aaca9366eaa427b0f49fc2ecf04d99b7ad84", size = 9206252, upload-time = "2025-10-15T09:21:12.175Z" }, + { url = "https://files.pythonhosted.org/packages/04/87/eefe8d5e764f4cf50ed91b943f8e8f96b5efd65489d8303b7a36e2e79834/mkdocs_material-9.7.0-py3-none-any.whl", hash = "sha256:da2866ea53601125ff5baa8aa06404c6e07af3c5ce3d5de95e3b52b80b442887", size = 9283770, upload-time = "2025-11-11T08:49:06.26Z" }, ] [[package]] @@ -819,7 +969,7 @@ python = [ [[package]] name = "mkdocstrings-python" -version = "1.18.2" +version = "1.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -827,9 +977,9 @@ dependencies = [ { name = "mkdocstrings" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/ae/58ab2bfbee2792e92a98b97e872f7c003deb903071f75d8d83aa55db28fa/mkdocstrings_python-1.18.2.tar.gz", hash = "sha256:4ad536920a07b6336f50d4c6d5603316fafb1172c5c882370cbbc954770ad323", size = 207972, upload-time = "2025-08-28T16:11:19.847Z" } +sdist = { url = "https://files.pythonhosted.org/packages/75/1c/3af8413919b0839b96a78f60e8bd0dfd26c844d3717eeb77f80b43f5be1c/mkdocstrings_python-1.19.0.tar.gz", hash = "sha256:917aac66cf121243c11db5b89f66b0ded6c53ec0de5318ff5e22424eb2f2e57c", size = 204010, upload-time = "2025-11-10T13:30:55.915Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/8f/ce008599d9adebf33ed144e7736914385e8537f5fc686fdb7cceb8c22431/mkdocstrings_python-1.18.2-py3-none-any.whl", hash = "sha256:944fe6deb8f08f33fa936d538233c4036e9f53e840994f6146e8e94eb71b600d", size = 138215, upload-time = "2025-08-28T16:11:18.176Z" }, + { url = "https://files.pythonhosted.org/packages/98/5c/2597cef67b6947b15c47f8dba967a0baf19fbdfdc86f6e4a8ba7af8b581a/mkdocstrings_python-1.19.0-py3-none-any.whl", hash = "sha256:395c1032af8f005234170575cc0c5d4d20980846623b623b35594281be4a3059", size = 143417, upload-time = "2025-11-10T13:30:54.164Z" }, ] [[package]] @@ -984,8 +1134,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/df/16848771155e7c419c60afeb24950b8aaa3ab09c0a091ec3ccca26a574d0/psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c47676e5b485393f069b4d7a811267d3168ce46f988fa602658b8bb901e9e64d", size = 4410873, upload-time = "2025-10-10T11:10:38.951Z" }, { url = "https://files.pythonhosted.org/packages/43/79/5ef5f32621abd5a541b89b04231fe959a9b327c874a1d41156041c75494b/psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a28d8c01a7b27a1e3265b11250ba7557e5f72b5ee9e5f3a2fa8d2949c29bf5d2", size = 4468016, upload-time = "2025-10-10T11:10:43.319Z" }, { url = "https://files.pythonhosted.org/packages/f0/9b/d7542d0f7ad78f57385971f426704776d7b310f5219ed58da5d605b1892e/psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f3f2732cf504a1aa9e9609d02f79bea1067d99edf844ab92c247bbca143303b", size = 4164996, upload-time = "2025-10-10T11:10:46.705Z" }, + { url = "https://files.pythonhosted.org/packages/14/ed/e409388b537fa7414330687936917c522f6a77a13474e4238219fcfd9a84/psycopg2_binary-2.9.11-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:865f9945ed1b3950d968ec4690ce68c55019d79e4497366d36e090327ce7db14", size = 3981881, upload-time = "2025-10-30T02:54:57.182Z" }, { url = "https://files.pythonhosted.org/packages/bf/30/50e330e63bb05efc6fa7c1447df3e08954894025ca3dcb396ecc6739bc26/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91537a8df2bde69b1c1db01d6d944c831ca793952e4f57892600e96cee95f2cd", size = 3650857, upload-time = "2025-10-10T11:10:50.112Z" }, { url = "https://files.pythonhosted.org/packages/f0/e0/4026e4c12bb49dd028756c5b0bc4c572319f2d8f1c9008e0dad8cc9addd7/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4dca1f356a67ecb68c81a7bc7809f1569ad9e152ce7fd02c2f2036862ca9f66b", size = 3296063, upload-time = "2025-10-10T11:10:54.089Z" }, + { url = "https://files.pythonhosted.org/packages/2c/34/eb172be293c886fef5299fe5c3fcf180a05478be89856067881007934a7c/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0da4de5c1ac69d94ed4364b6cbe7190c1a70d325f112ba783d83f8440285f152", size = 3043464, upload-time = "2025-10-30T02:55:02.483Z" }, { url = "https://files.pythonhosted.org/packages/18/1c/532c5d2cb11986372f14b798a95f2eaafe5779334f6a80589a68b5fcf769/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37d8412565a7267f7d79e29ab66876e55cb5e8e7b3bbf94f8206f6795f8f7e7e", size = 3345378, upload-time = "2025-10-10T11:11:01.039Z" }, { url = "https://files.pythonhosted.org/packages/70/e7/de420e1cf16f838e1fa17b1120e83afff374c7c0130d088dba6286fcf8ea/psycopg2_binary-2.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:c665f01ec8ab273a61c62beeb8cce3014c214429ced8a308ca1fc410ecac3a39", size = 2713904, upload-time = "2025-10-10T11:11:04.81Z" }, { url = "https://files.pythonhosted.org/packages/c7/ae/8d8266f6dd183ab4d48b95b9674034e1b482a3f8619b33a0d86438694577/psycopg2_binary-2.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0e8480afd62362d0a6a27dd09e4ca2def6fa50ed3a4e7c09165266106b2ffa10", size = 3756452, upload-time = "2025-10-10T11:11:11.583Z" }, @@ -993,8 +1145,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/89/3fdb5902bdab8868bbedc1c6e6023a4e08112ceac5db97fc2012060e0c9a/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e164359396576a3cc701ba8af4751ae68a07235d7a380c631184a611220d9a4", size = 4410955, upload-time = "2025-10-10T11:11:21.21Z" }, { url = "https://files.pythonhosted.org/packages/ce/24/e18339c407a13c72b336e0d9013fbbbde77b6fd13e853979019a1269519c/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d57c9c387660b8893093459738b6abddbb30a7eab058b77b0d0d1c7d521ddfd7", size = 4468007, upload-time = "2025-10-10T11:11:24.831Z" }, { url = "https://files.pythonhosted.org/packages/91/7e/b8441e831a0f16c159b5381698f9f7f7ed54b77d57bc9c5f99144cc78232/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2c226ef95eb2250974bf6fa7a842082b31f68385c4f3268370e3f3870e7859ee", size = 4165012, upload-time = "2025-10-10T11:11:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/0d/61/4aa89eeb6d751f05178a13da95516c036e27468c5d4d2509bb1e15341c81/psycopg2_binary-2.9.11-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a311f1edc9967723d3511ea7d2708e2c3592e3405677bf53d5c7246753591fbb", size = 3981881, upload-time = "2025-10-30T02:55:07.332Z" }, { url = "https://files.pythonhosted.org/packages/76/a1/2f5841cae4c635a9459fe7aca8ed771336e9383b6429e05c01267b0774cf/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb415404821b6d1c47353ebe9c8645967a5235e6d88f914147e7fd411419e6f", size = 3650985, upload-time = "2025-10-10T11:11:34.975Z" }, { url = "https://files.pythonhosted.org/packages/84/74/4defcac9d002bca5709951b975173c8c2fa968e1a95dc713f61b3a8d3b6a/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f07c9c4a5093258a03b28fab9b4f151aa376989e7f35f855088234e656ee6a94", size = 3296039, upload-time = "2025-10-10T11:11:40.432Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c2/782a3c64403d8ce35b5c50e1b684412cf94f171dc18111be8c976abd2de1/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:00ce1830d971f43b667abe4a56e42c1e2d594b32da4802e44a73bacacb25535f", size = 3043477, upload-time = "2025-10-30T02:55:11.182Z" }, { url = "https://files.pythonhosted.org/packages/c8/31/36a1d8e702aa35c38fc117c2b8be3f182613faa25d794b8aeaab948d4c03/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cffe9d7697ae7456649617e8bb8d7a45afb71cd13f7ab22af3e5c61f04840908", size = 3345842, upload-time = "2025-10-10T11:11:45.366Z" }, { url = "https://files.pythonhosted.org/packages/6e/b4/a5375cda5b54cb95ee9b836930fea30ae5a8f14aa97da7821722323d979b/psycopg2_binary-2.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:304fd7b7f97eef30e91b8f7e720b3db75fee010b520e434ea35ed1ff22501d03", size = 2713894, upload-time = "2025-10-10T11:11:48.775Z" }, { url = "https://files.pythonhosted.org/packages/d8/91/f870a02f51be4a65987b45a7de4c2e1897dd0d01051e2b559a38fa634e3e/psycopg2_binary-2.9.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be9b840ac0525a283a96b556616f5b4820e0526addb8dcf6525a0fa162730be4", size = 3756603, upload-time = "2025-10-10T11:11:52.213Z" }, @@ -1002,8 +1156,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2d/75/364847b879eb630b3ac8293798e380e441a957c53657995053c5ec39a316/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a", size = 4411159, upload-time = "2025-10-10T11:12:00.49Z" }, { url = "https://files.pythonhosted.org/packages/6f/a0/567f7ea38b6e1c62aafd58375665a547c00c608a471620c0edc364733e13/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e", size = 4468234, upload-time = "2025-10-10T11:12:04.892Z" }, { url = "https://files.pythonhosted.org/packages/30/da/4e42788fb811bbbfd7b7f045570c062f49e350e1d1f3df056c3fb5763353/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db", size = 4166236, upload-time = "2025-10-10T11:12:11.674Z" }, + { url = "https://files.pythonhosted.org/packages/3c/94/c1777c355bc560992af848d98216148be5f1be001af06e06fc49cbded578/psycopg2_binary-2.9.11-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1cf393f1cdaf6a9b57c0a719a1068ba1069f022a59b8b1fe44b006745b59757", size = 3983083, upload-time = "2025-10-30T02:55:15.73Z" }, { url = "https://files.pythonhosted.org/packages/bd/42/c9a21edf0e3daa7825ed04a4a8588686c6c14904344344a039556d78aa58/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3", size = 3652281, upload-time = "2025-10-10T11:12:17.713Z" }, { url = "https://files.pythonhosted.org/packages/12/22/dedfbcfa97917982301496b6b5e5e6c5531d1f35dd2b488b08d1ebc52482/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a", size = 3298010, upload-time = "2025-10-10T11:12:22.671Z" }, + { url = "https://files.pythonhosted.org/packages/66/ea/d3390e6696276078bd01b2ece417deac954dfdd552d2edc3d03204416c0c/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:edcb3aeb11cb4bf13a2af3c53a15b3d612edeb6409047ea0b5d6a21a9d744b34", size = 3044641, upload-time = "2025-10-30T02:55:19.929Z" }, { url = "https://files.pythonhosted.org/packages/12/9a/0402ded6cbd321da0c0ba7d34dc12b29b14f5764c2fc10750daa38e825fc/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d", size = 3347940, upload-time = "2025-10-10T11:12:26.529Z" }, { url = "https://files.pythonhosted.org/packages/b1/d2/99b55e85832ccde77b211738ff3925a5d73ad183c0b37bcbbe5a8ff04978/psycopg2_binary-2.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:b33fabeb1fde21180479b2d4667e994de7bbf0eec22832ba5d9b5e4cf65b6c6d", size = 2714147, upload-time = "2025-10-10T11:12:29.535Z" }, { url = "https://files.pythonhosted.org/packages/ff/a8/a2709681b3ac11b0b1786def10006b8995125ba268c9a54bea6f5ae8bd3e/psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c", size = 3756572, upload-time = "2025-10-10T11:12:32.873Z" }, @@ -1011,8 +1167,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0", size = 4411242, upload-time = "2025-10-10T11:12:42.388Z" }, { url = "https://files.pythonhosted.org/packages/10/04/6ca7477e6160ae258dc96f67c371157776564679aefd247b66f4661501a2/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766", size = 4468258, upload-time = "2025-10-10T11:12:48.654Z" }, { url = "https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3", size = 4166295, upload-time = "2025-10-10T11:12:52.525Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7d/c07374c501b45f3579a9eb761cbf2604ddef3d96ad48679112c2c5aa9c25/psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f", size = 3983133, upload-time = "2025-10-30T02:55:24.329Z" }, { url = "https://files.pythonhosted.org/packages/82/56/993b7104cb8345ad7d4516538ccf8f0d0ac640b1ebd8c754a7b024e76878/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4", size = 3652383, upload-time = "2025-10-10T11:12:56.387Z" }, { url = "https://files.pythonhosted.org/packages/2d/ac/eaeb6029362fd8d454a27374d84c6866c82c33bfc24587b4face5a8e43ef/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c", size = 3298168, upload-time = "2025-10-10T11:13:00.403Z" }, + { url = "https://files.pythonhosted.org/packages/2b/39/50c3facc66bded9ada5cbc0de867499a703dc6bca6be03070b4e3b65da6c/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60", size = 3044712, upload-time = "2025-10-30T02:55:27.975Z" }, { url = "https://files.pythonhosted.org/packages/9c/8e/b7de019a1f562f72ada81081a12823d3c1590bedc48d7d2559410a2763fe/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1", size = 3347549, upload-time = "2025-10-10T11:13:03.971Z" }, { url = "https://files.pythonhosted.org/packages/80/2d/1bb683f64737bbb1f86c82b7359db1eb2be4e2c0c13b947f80efefa7d3e5/psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa", size = 2714215, upload-time = "2025-10-10T11:13:07.14Z" }, { url = "https://files.pythonhosted.org/packages/64/12/93ef0098590cf51d9732b4f139533732565704f45bdc1ffa741b7c95fb54/psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1", size = 3756567, upload-time = "2025-10-10T11:13:11.885Z" }, @@ -1020,8 +1178,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/1e/98874ce72fd29cbde93209977b196a2edae03f8490d1bd8158e7f1daf3a0/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5", size = 4411646, upload-time = "2025-10-10T11:13:24.432Z" }, { url = "https://files.pythonhosted.org/packages/5a/bd/a335ce6645334fb8d758cc358810defca14a1d19ffbc8a10bd38a2328565/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8", size = 4468701, upload-time = "2025-10-10T11:13:29.266Z" }, { url = "https://files.pythonhosted.org/packages/44/d6/c8b4f53f34e295e45709b7568bf9b9407a612ea30387d35eb9fa84f269b4/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c", size = 4166293, upload-time = "2025-10-10T11:13:33.336Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e0/f8cc36eadd1b716ab36bb290618a3292e009867e5c97ce4aba908cb99644/psycopg2_binary-2.9.11-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e35b7abae2b0adab776add56111df1735ccc71406e56203515e228a8dc07089f", size = 3983184, upload-time = "2025-10-30T02:55:32.483Z" }, { url = "https://files.pythonhosted.org/packages/53/3e/2a8fe18a4e61cfb3417da67b6318e12691772c0696d79434184a511906dc/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747", size = 3652650, upload-time = "2025-10-10T11:13:38.181Z" }, { url = "https://files.pythonhosted.org/packages/76/36/03801461b31b29fe58d228c24388f999fe814dfc302856e0d17f97d7c54d/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f", size = 3298663, upload-time = "2025-10-10T11:13:44.878Z" }, + { url = "https://files.pythonhosted.org/packages/97/77/21b0ea2e1a73aa5fa9222b2a6b8ba325c43c3a8d54272839c991f2345656/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:32770a4d666fbdafab017086655bcddab791d7cb260a16679cc5a7338b64343b", size = 3044737, upload-time = "2025-10-30T02:55:35.69Z" }, { url = "https://files.pythonhosted.org/packages/67/69/f36abe5f118c1dca6d3726ceae164b9356985805480731ac6712a63f24f0/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d", size = 3347643, upload-time = "2025-10-10T11:13:53.499Z" }, { url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913, upload-time = "2025-10-10T11:13:57.058Z" }, ] @@ -1044,9 +1204,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, ] +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + [[package]] name = "pydantic" -version = "2.12.3" +version = "2.12.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1054,137 +1223,141 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038, upload-time = "2025-11-05T10:50:08.59Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, + { url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400, upload-time = "2025-11-05T10:50:06.732Z" }, ] [[package]] name = "pydantic-core" -version = "2.41.4" +version = "2.41.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/3d/9b8ca77b0f76fcdbf8bc6b72474e264283f461284ca84ac3fde570c6c49a/pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e", size = 2111197, upload-time = "2025-10-14T10:19:43.303Z" }, - { url = "https://files.pythonhosted.org/packages/59/92/b7b0fe6ed4781642232755cb7e56a86e2041e1292f16d9ae410a0ccee5ac/pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b", size = 1917909, upload-time = "2025-10-14T10:19:45.194Z" }, - { url = "https://files.pythonhosted.org/packages/52/8c/3eb872009274ffa4fb6a9585114e161aa1a0915af2896e2d441642929fe4/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd", size = 1969905, upload-time = "2025-10-14T10:19:46.567Z" }, - { url = "https://files.pythonhosted.org/packages/f4/21/35adf4a753bcfaea22d925214a0c5b880792e3244731b3f3e6fec0d124f7/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945", size = 2051938, upload-time = "2025-10-14T10:19:48.237Z" }, - { url = "https://files.pythonhosted.org/packages/7d/d0/cdf7d126825e36d6e3f1eccf257da8954452934ede275a8f390eac775e89/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706", size = 2250710, upload-time = "2025-10-14T10:19:49.619Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1c/af1e6fd5ea596327308f9c8d1654e1285cc3d8de0d584a3c9d7705bf8a7c/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba", size = 2367445, upload-time = "2025-10-14T10:19:51.269Z" }, - { url = "https://files.pythonhosted.org/packages/d3/81/8cece29a6ef1b3a92f956ea6da6250d5b2d2e7e4d513dd3b4f0c7a83dfea/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b", size = 2072875, upload-time = "2025-10-14T10:19:52.671Z" }, - { url = "https://files.pythonhosted.org/packages/e3/37/a6a579f5fc2cd4d5521284a0ab6a426cc6463a7b3897aeb95b12f1ba607b/pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d", size = 2191329, upload-time = "2025-10-14T10:19:54.214Z" }, - { url = "https://files.pythonhosted.org/packages/ae/03/505020dc5c54ec75ecba9f41119fd1e48f9e41e4629942494c4a8734ded1/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700", size = 2151658, upload-time = "2025-10-14T10:19:55.843Z" }, - { url = "https://files.pythonhosted.org/packages/cb/5d/2c0d09fb53aa03bbd2a214d89ebfa6304be7df9ed86ee3dc7770257f41ee/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6", size = 2316777, upload-time = "2025-10-14T10:19:57.607Z" }, - { url = "https://files.pythonhosted.org/packages/ea/4b/c2c9c8f5e1f9c864b57d08539d9d3db160e00491c9f5ee90e1bfd905e644/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9", size = 2320705, upload-time = "2025-10-14T10:19:59.016Z" }, - { url = "https://files.pythonhosted.org/packages/28/c3/a74c1c37f49c0a02c89c7340fafc0ba816b29bd495d1a31ce1bdeacc6085/pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57", size = 1975464, upload-time = "2025-10-14T10:20:00.581Z" }, - { url = "https://files.pythonhosted.org/packages/d6/23/5dd5c1324ba80303368f7569e2e2e1a721c7d9eb16acb7eb7b7f85cb1be2/pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc", size = 2024497, upload-time = "2025-10-14T10:20:03.018Z" }, - { url = "https://files.pythonhosted.org/packages/62/4c/f6cbfa1e8efacd00b846764e8484fe173d25b8dab881e277a619177f3384/pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80", size = 2109062, upload-time = "2025-10-14T10:20:04.486Z" }, - { url = "https://files.pythonhosted.org/packages/21/f8/40b72d3868896bfcd410e1bd7e516e762d326201c48e5b4a06446f6cf9e8/pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae", size = 1916301, upload-time = "2025-10-14T10:20:06.857Z" }, - { url = "https://files.pythonhosted.org/packages/94/4d/d203dce8bee7faeca791671c88519969d98d3b4e8f225da5b96dad226fc8/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827", size = 1968728, upload-time = "2025-10-14T10:20:08.353Z" }, - { url = "https://files.pythonhosted.org/packages/65/f5/6a66187775df87c24d526985b3a5d78d861580ca466fbd9d4d0e792fcf6c/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f", size = 2050238, upload-time = "2025-10-14T10:20:09.766Z" }, - { url = "https://files.pythonhosted.org/packages/5e/b9/78336345de97298cf53236b2f271912ce11f32c1e59de25a374ce12f9cce/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def", size = 2249424, upload-time = "2025-10-14T10:20:11.732Z" }, - { url = "https://files.pythonhosted.org/packages/99/bb/a4584888b70ee594c3d374a71af5075a68654d6c780369df269118af7402/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2", size = 2366047, upload-time = "2025-10-14T10:20:13.647Z" }, - { url = "https://files.pythonhosted.org/packages/5f/8d/17fc5de9d6418e4d2ae8c675f905cdafdc59d3bf3bf9c946b7ab796a992a/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8", size = 2071163, upload-time = "2025-10-14T10:20:15.307Z" }, - { url = "https://files.pythonhosted.org/packages/54/e7/03d2c5c0b8ed37a4617430db68ec5e7dbba66358b629cd69e11b4d564367/pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265", size = 2190585, upload-time = "2025-10-14T10:20:17.3Z" }, - { url = "https://files.pythonhosted.org/packages/be/fc/15d1c9fe5ad9266a5897d9b932b7f53d7e5cfc800573917a2c5d6eea56ec/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c", size = 2150109, upload-time = "2025-10-14T10:20:19.143Z" }, - { url = "https://files.pythonhosted.org/packages/26/ef/e735dd008808226c83ba56972566138665b71477ad580fa5a21f0851df48/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a", size = 2315078, upload-time = "2025-10-14T10:20:20.742Z" }, - { url = "https://files.pythonhosted.org/packages/90/00/806efdcf35ff2ac0f938362350cd9827b8afb116cc814b6b75cf23738c7c/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e", size = 2318737, upload-time = "2025-10-14T10:20:22.306Z" }, - { url = "https://files.pythonhosted.org/packages/41/7e/6ac90673fe6cb36621a2283552897838c020db343fa86e513d3f563b196f/pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03", size = 1974160, upload-time = "2025-10-14T10:20:23.817Z" }, - { url = "https://files.pythonhosted.org/packages/e0/9d/7c5e24ee585c1f8b6356e1d11d40ab807ffde44d2db3b7dfd6d20b09720e/pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e", size = 2021883, upload-time = "2025-10-14T10:20:25.48Z" }, - { url = "https://files.pythonhosted.org/packages/33/90/5c172357460fc28b2871eb4a0fb3843b136b429c6fa827e4b588877bf115/pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db", size = 1968026, upload-time = "2025-10-14T10:20:27.039Z" }, - { url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887", size = 2099043, upload-time = "2025-10-14T10:20:28.561Z" }, - { url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2", size = 1910699, upload-time = "2025-10-14T10:20:30.217Z" }, - { url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999", size = 1952121, upload-time = "2025-10-14T10:20:32.246Z" }, - { url = "https://files.pythonhosted.org/packages/b2/a7/e5fc60a6f781fc634ecaa9ecc3c20171d238794cef69ae0af79ac11b89d7/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4", size = 2041590, upload-time = "2025-10-14T10:20:34.332Z" }, - { url = "https://files.pythonhosted.org/packages/70/69/dce747b1d21d59e85af433428978a1893c6f8a7068fa2bb4a927fba7a5ff/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f", size = 2219869, upload-time = "2025-10-14T10:20:35.965Z" }, - { url = "https://files.pythonhosted.org/packages/83/6a/c070e30e295403bf29c4df1cb781317b6a9bac7cd07b8d3acc94d501a63c/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b", size = 2345169, upload-time = "2025-10-14T10:20:37.627Z" }, - { url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47", size = 2070165, upload-time = "2025-10-14T10:20:39.246Z" }, - { url = "https://files.pythonhosted.org/packages/14/0a/e567c2883588dd12bcbc110232d892cf385356f7c8a9910311ac997ab715/pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970", size = 2189067, upload-time = "2025-10-14T10:20:41.015Z" }, - { url = "https://files.pythonhosted.org/packages/f4/1d/3d9fca34273ba03c9b1c5289f7618bc4bd09c3ad2289b5420481aa051a99/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed", size = 2132997, upload-time = "2025-10-14T10:20:43.106Z" }, - { url = "https://files.pythonhosted.org/packages/52/70/d702ef7a6cd41a8afc61f3554922b3ed8d19dd54c3bd4bdbfe332e610827/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8", size = 2307187, upload-time = "2025-10-14T10:20:44.849Z" }, - { url = "https://files.pythonhosted.org/packages/68/4c/c06be6e27545d08b802127914156f38d10ca287a9e8489342793de8aae3c/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431", size = 2305204, upload-time = "2025-10-14T10:20:46.781Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e5/35ae4919bcd9f18603419e23c5eaf32750224a89d41a8df1a3704b69f77e/pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd", size = 1972536, upload-time = "2025-10-14T10:20:48.39Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c2/49c5bb6d2a49eb2ee3647a93e3dae7080c6409a8a7558b075027644e879c/pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff", size = 2031132, upload-time = "2025-10-14T10:20:50.421Z" }, - { url = "https://files.pythonhosted.org/packages/06/23/936343dbcba6eec93f73e95eb346810fc732f71ba27967b287b66f7b7097/pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8", size = 1969483, upload-time = "2025-10-14T10:20:52.35Z" }, - { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" }, - { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" }, - { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" }, - { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" }, - { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" }, - { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" }, - { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" }, - { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" }, - { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" }, - { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" }, - { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" }, - { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" }, - { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" }, - { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" }, - { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" }, - { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" }, - { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" }, - { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" }, - { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" }, - { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" }, - { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" }, - { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" }, - { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" }, - { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" }, - { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" }, - { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" }, - { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" }, - { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" }, - { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" }, - { url = "https://files.pythonhosted.org/packages/b0/12/5ba58daa7f453454464f92b3ca7b9d7c657d8641c48e370c3ebc9a82dd78/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b", size = 2122139, upload-time = "2025-10-14T10:22:47.288Z" }, - { url = "https://files.pythonhosted.org/packages/21/fb/6860126a77725c3108baecd10fd3d75fec25191d6381b6eb2ac660228eac/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42", size = 1936674, upload-time = "2025-10-14T10:22:49.555Z" }, - { url = "https://files.pythonhosted.org/packages/de/be/57dcaa3ed595d81f8757e2b44a38240ac5d37628bce25fb20d02c7018776/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee", size = 1956398, upload-time = "2025-10-14T10:22:52.19Z" }, - { url = "https://files.pythonhosted.org/packages/2f/1d/679a344fadb9695f1a6a294d739fbd21d71fa023286daeea8c0ed49e7c2b/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c", size = 2138674, upload-time = "2025-10-14T10:22:54.499Z" }, - { url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537", size = 2112087, upload-time = "2025-10-14T10:22:56.818Z" }, - { url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94", size = 1920387, upload-time = "2025-10-14T10:22:59.342Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c", size = 1951495, upload-time = "2025-10-14T10:23:02.089Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008, upload-time = "2025-10-14T10:23:04.539Z" }, - { url = "https://files.pythonhosted.org/packages/5d/d4/912e976a2dd0b49f31c98a060ca90b353f3b73ee3ea2fd0030412f6ac5ec/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00", size = 2106739, upload-time = "2025-10-14T10:23:06.934Z" }, - { url = "https://files.pythonhosted.org/packages/71/f0/66ec5a626c81eba326072d6ee2b127f8c139543f1bf609b4842978d37833/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9", size = 1932549, upload-time = "2025-10-14T10:23:09.24Z" }, - { url = "https://files.pythonhosted.org/packages/c4/af/625626278ca801ea0a658c2dcf290dc9f21bb383098e99e7c6a029fccfc0/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2", size = 2135093, upload-time = "2025-10-14T10:23:11.626Z" }, - { url = "https://files.pythonhosted.org/packages/20/f6/2fba049f54e0f4975fef66be654c597a1d005320fa141863699180c7697d/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258", size = 2187971, upload-time = "2025-10-14T10:23:14.437Z" }, - { url = "https://files.pythonhosted.org/packages/0e/80/65ab839a2dfcd3b949202f9d920c34f9de5a537c3646662bdf2f7d999680/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347", size = 2147939, upload-time = "2025-10-14T10:23:16.831Z" }, - { url = "https://files.pythonhosted.org/packages/44/58/627565d3d182ce6dfda18b8e1c841eede3629d59c9d7cbc1e12a03aeb328/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa", size = 2311400, upload-time = "2025-10-14T10:23:19.234Z" }, - { url = "https://files.pythonhosted.org/packages/24/06/8a84711162ad5a5f19a88cead37cca81b4b1f294f46260ef7334ae4f24d3/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a", size = 2316840, upload-time = "2025-10-14T10:23:21.738Z" }, - { url = "https://files.pythonhosted.org/packages/aa/8b/b7bb512a4682a2f7fbfae152a755d37351743900226d29bd953aaf870eaa/pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d", size = 2149135, upload-time = "2025-10-14T10:23:24.379Z" }, - { url = "https://files.pythonhosted.org/packages/7e/7d/138e902ed6399b866f7cfe4435d22445e16fff888a1c00560d9dc79a780f/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5", size = 2104721, upload-time = "2025-10-14T10:23:26.906Z" }, - { url = "https://files.pythonhosted.org/packages/47/13/0525623cf94627f7b53b4c2034c81edc8491cbfc7c28d5447fa318791479/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2", size = 1931608, upload-time = "2025-10-14T10:23:29.306Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f9/744bc98137d6ef0a233f808bfc9b18cf94624bf30836a18d3b05d08bf418/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd", size = 2132986, upload-time = "2025-10-14T10:23:32.057Z" }, - { url = "https://files.pythonhosted.org/packages/17/c8/629e88920171173f6049386cc71f893dff03209a9ef32b4d2f7e7c264bcf/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c", size = 2187516, upload-time = "2025-10-14T10:23:34.871Z" }, - { url = "https://files.pythonhosted.org/packages/2e/0f/4f2734688d98488782218ca61bcc118329bf5de05bb7fe3adc7dd79b0b86/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405", size = 2146146, upload-time = "2025-10-14T10:23:37.342Z" }, - { url = "https://files.pythonhosted.org/packages/ed/f2/ab385dbd94a052c62224b99cf99002eee99dbec40e10006c78575aead256/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8", size = 2311296, upload-time = "2025-10-14T10:23:40.145Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8e/e4f12afe1beeb9823bba5375f8f258df0cc61b056b0195fb1cf9f62a1a58/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308", size = 2315386, upload-time = "2025-10-14T10:23:42.624Z" }, - { url = "https://files.pythonhosted.org/packages/48/f7/925f65d930802e3ea2eb4d5afa4cb8730c8dc0d2cb89a59dc4ed2fcb2d74/pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f", size = 2147775, upload-time = "2025-10-14T10:23:45.406Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, + { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, + { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, + { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, + { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, + { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, + { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, + { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, + { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, + { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, ] [[package]] name = "pydantic-settings" -version = "2.11.0" +version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/c5/dbbc27b814c71676593d1c3f718e6cd7d4f00652cefa24b75f7aa3efb25e/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180", size = 188394, upload-time = "2025-09-24T14:19:11.764Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c", size = 48608, upload-time = "2025-09-24T14:19:10.015Z" }, + { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, ] [[package]] @@ -1196,35 +1369,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + [[package]] name = "pymdown-extensions" -version = "10.16.1" +version = "10.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/b3/6d2b3f149bc5413b0a29761c2c5832d8ce904a1d7f621e86616d96f505cc/pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91", size = 853277, upload-time = "2025-07-28T16:19:34.167Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/d9/a987e4d549c6c82353fce5fa5f650229bb60ea4c0d1684a2714a509aef58/pymdown_extensions-10.17.1.tar.gz", hash = "sha256:60d05fe55e7fb5a1e4740fc575facad20dc6ee3a748e8d3d36ba44142e75ce03", size = 845207, upload-time = "2025-11-11T21:44:58.815Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d", size = 266178, upload-time = "2025-07-28T16:19:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/81/40/b2d7b9fdccc63e48ae4dbd363b6b89eb7ac346ea49ed667bb71f92af3021/pymdown_extensions-10.17.1-py3-none-any.whl", hash = "sha256:1f160209c82eecbb5d8a0d8f89a4d9bd6bdcbde9a8537761844cfc57ad5cd8a6", size = 266310, upload-time = "2025-11-11T21:44:56.809Z" }, ] [[package]] name = "pyright" -version = "1.1.406" +version = "1.1.407" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/16/6b4fbdd1fef59a0292cbb99f790b44983e390321eccbc5921b4d161da5d1/pyright-1.1.406.tar.gz", hash = "sha256:c4872bc58c9643dac09e8a2e74d472c62036910b3bd37a32813989ef7576ea2c", size = 4113151, upload-time = "2025-10-02T01:04:45.488Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/1b/0aa08ee42948b61745ac5b5b5ccaec4669e8884b53d31c8ec20b2fcd6b6f/pyright-1.1.407.tar.gz", hash = "sha256:099674dba5c10489832d4a4b2d302636152a9a42d317986c38474c76fe562262", size = 4122872, upload-time = "2025-10-24T23:17:15.145Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/a2/e309afbb459f50507103793aaef85ca4348b66814c86bc73908bdeb66d12/pyright-1.1.406-py3-none-any.whl", hash = "sha256:1d81fb43c2407bf566e97e57abb01c811973fdb21b2df8df59f870f688bdca71", size = 5980982, upload-time = "2025-10-02T01:04:43.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/93/b69052907d032b00c40cb656d21438ec00b3a471733de137a3f65a49a0a0/pyright-1.1.407-py3-none-any.whl", hash = "sha256:6dd419f54fcc13f03b52285796d65e639786373f433e243f8b94cf93a7444d21", size = 5997008, upload-time = "2025-10-24T23:17:13.159Z" }, ] [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1235,9 +1422,9 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" }, ] [[package]] @@ -1268,11 +1455,11 @@ wheels = [ [[package]] name = "python-dotenv" -version = "1.1.1" +version = "1.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, ] [[package]] @@ -1413,163 +1600,150 @@ wheels = [ [[package]] name = "rpds-py" -version = "0.27.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/ed/3aef893e2dd30e77e35d20d4ddb45ca459db59cead748cad9796ad479411/rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef", size = 371606, upload-time = "2025-08-27T12:12:25.189Z" }, - { url = "https://files.pythonhosted.org/packages/6d/82/9818b443e5d3eb4c83c3994561387f116aae9833b35c484474769c4a8faf/rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be", size = 353452, upload-time = "2025-08-27T12:12:27.433Z" }, - { url = "https://files.pythonhosted.org/packages/99/c7/d2a110ffaaa397fc6793a83c7bd3545d9ab22658b7cdff05a24a4535cc45/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61", size = 381519, upload-time = "2025-08-27T12:12:28.719Z" }, - { url = "https://files.pythonhosted.org/packages/5a/bc/e89581d1f9d1be7d0247eaef602566869fdc0d084008ba139e27e775366c/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb", size = 394424, upload-time = "2025-08-27T12:12:30.207Z" }, - { url = "https://files.pythonhosted.org/packages/ac/2e/36a6861f797530e74bb6ed53495f8741f1ef95939eed01d761e73d559067/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657", size = 523467, upload-time = "2025-08-27T12:12:31.808Z" }, - { url = "https://files.pythonhosted.org/packages/c4/59/c1bc2be32564fa499f988f0a5c6505c2f4746ef96e58e4d7de5cf923d77e/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013", size = 402660, upload-time = "2025-08-27T12:12:33.444Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ec/ef8bf895f0628dd0a59e54d81caed6891663cb9c54a0f4bb7da918cb88cf/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a", size = 384062, upload-time = "2025-08-27T12:12:34.857Z" }, - { url = "https://files.pythonhosted.org/packages/69/f7/f47ff154be8d9a5e691c083a920bba89cef88d5247c241c10b9898f595a1/rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1", size = 401289, upload-time = "2025-08-27T12:12:36.085Z" }, - { url = "https://files.pythonhosted.org/packages/3b/d9/ca410363efd0615814ae579f6829cafb39225cd63e5ea5ed1404cb345293/rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10", size = 417718, upload-time = "2025-08-27T12:12:37.401Z" }, - { url = "https://files.pythonhosted.org/packages/e3/a0/8cb5c2ff38340f221cc067cc093d1270e10658ba4e8d263df923daa18e86/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808", size = 558333, upload-time = "2025-08-27T12:12:38.672Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8c/1b0de79177c5d5103843774ce12b84caa7164dfc6cd66378768d37db11bf/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8", size = 589127, upload-time = "2025-08-27T12:12:41.48Z" }, - { url = "https://files.pythonhosted.org/packages/c8/5e/26abb098d5e01266b0f3a2488d299d19ccc26849735d9d2b95c39397e945/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9", size = 554899, upload-time = "2025-08-27T12:12:42.925Z" }, - { url = "https://files.pythonhosted.org/packages/de/41/905cc90ced13550db017f8f20c6d8e8470066c5738ba480d7ba63e3d136b/rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4", size = 217450, upload-time = "2025-08-27T12:12:44.813Z" }, - { url = "https://files.pythonhosted.org/packages/75/3d/6bef47b0e253616ccdf67c283e25f2d16e18ccddd38f92af81d5a3420206/rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1", size = 228447, upload-time = "2025-08-27T12:12:46.204Z" }, - { url = "https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881", size = 371063, upload-time = "2025-08-27T12:12:47.856Z" }, - { url = "https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5", size = 353210, upload-time = "2025-08-27T12:12:49.187Z" }, - { url = "https://files.pythonhosted.org/packages/3a/57/f5eb3ecf434342f4f1a46009530e93fd201a0b5b83379034ebdb1d7c1a58/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e", size = 381636, upload-time = "2025-08-27T12:12:50.492Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f4/ef95c5945e2ceb5119571b184dd5a1cc4b8541bbdf67461998cfeac9cb1e/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c", size = 394341, upload-time = "2025-08-27T12:12:52.024Z" }, - { url = "https://files.pythonhosted.org/packages/5a/7e/4bd610754bf492d398b61725eb9598ddd5eb86b07d7d9483dbcd810e20bc/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195", size = 523428, upload-time = "2025-08-27T12:12:53.779Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e5/059b9f65a8c9149361a8b75094864ab83b94718344db511fd6117936ed2a/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52", size = 402923, upload-time = "2025-08-27T12:12:55.15Z" }, - { url = "https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed", size = 384094, upload-time = "2025-08-27T12:12:57.194Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e1/dc9094d6ff566bff87add8a510c89b9e158ad2ecd97ee26e677da29a9e1b/rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a", size = 401093, upload-time = "2025-08-27T12:12:58.985Z" }, - { url = "https://files.pythonhosted.org/packages/37/8e/ac8577e3ecdd5593e283d46907d7011618994e1d7ab992711ae0f78b9937/rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde", size = 417969, upload-time = "2025-08-27T12:13:00.367Z" }, - { url = "https://files.pythonhosted.org/packages/66/6d/87507430a8f74a93556fe55c6485ba9c259949a853ce407b1e23fea5ba31/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21", size = 558302, upload-time = "2025-08-27T12:13:01.737Z" }, - { url = "https://files.pythonhosted.org/packages/3a/bb/1db4781ce1dda3eecc735e3152659a27b90a02ca62bfeea17aee45cc0fbc/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9", size = 589259, upload-time = "2025-08-27T12:13:03.127Z" }, - { url = "https://files.pythonhosted.org/packages/7b/0e/ae1c8943d11a814d01b482e1f8da903f88047a962dff9bbdadf3bd6e6fd1/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948", size = 554983, upload-time = "2025-08-27T12:13:04.516Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d5/0b2a55415931db4f112bdab072443ff76131b5ac4f4dc98d10d2d357eb03/rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39", size = 217154, upload-time = "2025-08-27T12:13:06.278Z" }, - { url = "https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15", size = 228627, upload-time = "2025-08-27T12:13:07.625Z" }, - { url = "https://files.pythonhosted.org/packages/8d/3f/4fd04c32abc02c710f09a72a30c9a55ea3cc154ef8099078fd50a0596f8e/rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746", size = 220998, upload-time = "2025-08-27T12:13:08.972Z" }, - { url = "https://files.pythonhosted.org/packages/bd/fe/38de28dee5df58b8198c743fe2bea0c785c6d40941b9950bac4cdb71a014/rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", size = 361887, upload-time = "2025-08-27T12:13:10.233Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/4b6c7eedc7dd90986bf0fab6ea2a091ec11c01b15f8ba0a14d3f80450468/rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", size = 345795, upload-time = "2025-08-27T12:13:11.65Z" }, - { url = "https://files.pythonhosted.org/packages/6f/0e/e650e1b81922847a09cca820237b0edee69416a01268b7754d506ade11ad/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", size = 385121, upload-time = "2025-08-27T12:13:13.008Z" }, - { url = "https://files.pythonhosted.org/packages/1b/ea/b306067a712988e2bff00dcc7c8f31d26c29b6d5931b461aa4b60a013e33/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", size = 398976, upload-time = "2025-08-27T12:13:14.368Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0a/26dc43c8840cb8fe239fe12dbc8d8de40f2365e838f3d395835dde72f0e5/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", size = 525953, upload-time = "2025-08-27T12:13:15.774Z" }, - { url = "https://files.pythonhosted.org/packages/22/14/c85e8127b573aaf3a0cbd7fbb8c9c99e735a4a02180c84da2a463b766e9e/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", size = 407915, upload-time = "2025-08-27T12:13:17.379Z" }, - { url = "https://files.pythonhosted.org/packages/ed/7b/8f4fee9ba1fb5ec856eb22d725a4efa3deb47f769597c809e03578b0f9d9/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", size = 386883, upload-time = "2025-08-27T12:13:18.704Z" }, - { url = "https://files.pythonhosted.org/packages/86/47/28fa6d60f8b74fcdceba81b272f8d9836ac0340570f68f5df6b41838547b/rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", size = 405699, upload-time = "2025-08-27T12:13:20.089Z" }, - { url = "https://files.pythonhosted.org/packages/d0/fd/c5987b5e054548df56953a21fe2ebed51fc1ec7c8f24fd41c067b68c4a0a/rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", size = 423713, upload-time = "2025-08-27T12:13:21.436Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ba/3c4978b54a73ed19a7d74531be37a8bcc542d917c770e14d372b8daea186/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", size = 562324, upload-time = "2025-08-27T12:13:22.789Z" }, - { url = "https://files.pythonhosted.org/packages/b5/6c/6943a91768fec16db09a42b08644b960cff540c66aab89b74be6d4a144ba/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", size = 593646, upload-time = "2025-08-27T12:13:24.122Z" }, - { url = "https://files.pythonhosted.org/packages/11/73/9d7a8f4be5f4396f011a6bb7a19fe26303a0dac9064462f5651ced2f572f/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", size = 558137, upload-time = "2025-08-27T12:13:25.557Z" }, - { url = "https://files.pythonhosted.org/packages/6e/96/6772cbfa0e2485bcceef8071de7821f81aeac8bb45fbfd5542a3e8108165/rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", size = 221343, upload-time = "2025-08-27T12:13:26.967Z" }, - { url = "https://files.pythonhosted.org/packages/67/b6/c82f0faa9af1c6a64669f73a17ee0eeef25aff30bb9a1c318509efe45d84/rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", size = 232497, upload-time = "2025-08-27T12:13:28.326Z" }, - { url = "https://files.pythonhosted.org/packages/e1/96/2817b44bd2ed11aebacc9251da03689d56109b9aba5e311297b6902136e2/rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", size = 222790, upload-time = "2025-08-27T12:13:29.71Z" }, - { url = "https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", size = 361741, upload-time = "2025-08-27T12:13:31.039Z" }, - { url = "https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", size = 345574, upload-time = "2025-08-27T12:13:32.902Z" }, - { url = "https://files.pythonhosted.org/packages/20/42/ee2b2ca114294cd9847d0ef9c26d2b0851b2e7e00bf14cc4c0b581df0fc3/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", size = 385051, upload-time = "2025-08-27T12:13:34.228Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e8/1e430fe311e4799e02e2d1af7c765f024e95e17d651612425b226705f910/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", size = 398395, upload-time = "2025-08-27T12:13:36.132Z" }, - { url = "https://files.pythonhosted.org/packages/82/95/9dc227d441ff2670651c27a739acb2535ccaf8b351a88d78c088965e5996/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", size = 524334, upload-time = "2025-08-27T12:13:37.562Z" }, - { url = "https://files.pythonhosted.org/packages/87/01/a670c232f401d9ad461d9a332aa4080cd3cb1d1df18213dbd0d2a6a7ab51/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", size = 407691, upload-time = "2025-08-27T12:13:38.94Z" }, - { url = "https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", size = 386868, upload-time = "2025-08-27T12:13:40.192Z" }, - { url = "https://files.pythonhosted.org/packages/3b/03/8c897fb8b5347ff6c1cc31239b9611c5bf79d78c984430887a353e1409a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", size = 405469, upload-time = "2025-08-27T12:13:41.496Z" }, - { url = "https://files.pythonhosted.org/packages/da/07/88c60edc2df74850d496d78a1fdcdc7b54360a7f610a4d50008309d41b94/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", size = 422125, upload-time = "2025-08-27T12:13:42.802Z" }, - { url = "https://files.pythonhosted.org/packages/6b/86/5f4c707603e41b05f191a749984f390dabcbc467cf833769b47bf14ba04f/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", size = 562341, upload-time = "2025-08-27T12:13:44.472Z" }, - { url = "https://files.pythonhosted.org/packages/b2/92/3c0cb2492094e3cd9baf9e49bbb7befeceb584ea0c1a8b5939dca4da12e5/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", size = 592511, upload-time = "2025-08-27T12:13:45.898Z" }, - { url = "https://files.pythonhosted.org/packages/10/bb/82e64fbb0047c46a168faa28d0d45a7851cd0582f850b966811d30f67ad8/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", size = 557736, upload-time = "2025-08-27T12:13:47.408Z" }, - { url = "https://files.pythonhosted.org/packages/00/95/3c863973d409210da7fb41958172c6b7dbe7fc34e04d3cc1f10bb85e979f/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", size = 221462, upload-time = "2025-08-27T12:13:48.742Z" }, - { url = "https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", size = 232034, upload-time = "2025-08-27T12:13:50.11Z" }, - { url = "https://files.pythonhosted.org/packages/c7/78/3958f3f018c01923823f1e47f1cc338e398814b92d83cd278364446fac66/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", size = 222392, upload-time = "2025-08-27T12:13:52.587Z" }, - { url = "https://files.pythonhosted.org/packages/01/76/1cdf1f91aed5c3a7bf2eba1f1c4e4d6f57832d73003919a20118870ea659/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", size = 358355, upload-time = "2025-08-27T12:13:54.012Z" }, - { url = "https://files.pythonhosted.org/packages/c3/6f/bf142541229374287604caf3bb2a4ae17f0a580798fd72d3b009b532db4e/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", size = 342138, upload-time = "2025-08-27T12:13:55.791Z" }, - { url = "https://files.pythonhosted.org/packages/1a/77/355b1c041d6be40886c44ff5e798b4e2769e497b790f0f7fd1e78d17e9a8/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", size = 380247, upload-time = "2025-08-27T12:13:57.683Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a4/d9cef5c3946ea271ce2243c51481971cd6e34f21925af2783dd17b26e815/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", size = 390699, upload-time = "2025-08-27T12:13:59.137Z" }, - { url = "https://files.pythonhosted.org/packages/3a/06/005106a7b8c6c1a7e91b73169e49870f4af5256119d34a361ae5240a0c1d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", size = 521852, upload-time = "2025-08-27T12:14:00.583Z" }, - { url = "https://files.pythonhosted.org/packages/e5/3e/50fb1dac0948e17a02eb05c24510a8fe12d5ce8561c6b7b7d1339ab7ab9c/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", size = 402582, upload-time = "2025-08-27T12:14:02.034Z" }, - { url = "https://files.pythonhosted.org/packages/cb/b0/f4e224090dc5b0ec15f31a02d746ab24101dd430847c4d99123798661bfc/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", size = 384126, upload-time = "2025-08-27T12:14:03.437Z" }, - { url = "https://files.pythonhosted.org/packages/54/77/ac339d5f82b6afff1df8f0fe0d2145cc827992cb5f8eeb90fc9f31ef7a63/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", size = 399486, upload-time = "2025-08-27T12:14:05.443Z" }, - { url = "https://files.pythonhosted.org/packages/d6/29/3e1c255eee6ac358c056a57d6d6869baa00a62fa32eea5ee0632039c50a3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", size = 414832, upload-time = "2025-08-27T12:14:06.902Z" }, - { url = "https://files.pythonhosted.org/packages/3f/db/6d498b844342deb3fa1d030598db93937a9964fcf5cb4da4feb5f17be34b/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", size = 557249, upload-time = "2025-08-27T12:14:08.37Z" }, - { url = "https://files.pythonhosted.org/packages/60/f3/690dd38e2310b6f68858a331399b4d6dbb9132c3e8ef8b4333b96caf403d/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", size = 587356, upload-time = "2025-08-27T12:14:10.034Z" }, - { url = "https://files.pythonhosted.org/packages/86/e3/84507781cccd0145f35b1dc32c72675200c5ce8d5b30f813e49424ef68fc/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", size = 555300, upload-time = "2025-08-27T12:14:11.783Z" }, - { url = "https://files.pythonhosted.org/packages/e5/ee/375469849e6b429b3516206b4580a79e9ef3eb12920ddbd4492b56eaacbe/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", size = 216714, upload-time = "2025-08-27T12:14:13.629Z" }, - { url = "https://files.pythonhosted.org/packages/21/87/3fc94e47c9bd0742660e84706c311a860dcae4374cf4a03c477e23ce605a/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", size = 228943, upload-time = "2025-08-27T12:14:14.937Z" }, - { url = "https://files.pythonhosted.org/packages/70/36/b6e6066520a07cf029d385de869729a895917b411e777ab1cde878100a1d/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", size = 362472, upload-time = "2025-08-27T12:14:16.333Z" }, - { url = "https://files.pythonhosted.org/packages/af/07/b4646032e0dcec0df9c73a3bd52f63bc6c5f9cda992f06bd0e73fe3fbebd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", size = 345676, upload-time = "2025-08-27T12:14:17.764Z" }, - { url = "https://files.pythonhosted.org/packages/b0/16/2f1003ee5d0af4bcb13c0cf894957984c32a6751ed7206db2aee7379a55e/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", size = 385313, upload-time = "2025-08-27T12:14:19.829Z" }, - { url = "https://files.pythonhosted.org/packages/05/cd/7eb6dd7b232e7f2654d03fa07f1414d7dfc980e82ba71e40a7c46fd95484/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", size = 399080, upload-time = "2025-08-27T12:14:21.531Z" }, - { url = "https://files.pythonhosted.org/packages/20/51/5829afd5000ec1cb60f304711f02572d619040aa3ec033d8226817d1e571/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", size = 523868, upload-time = "2025-08-27T12:14:23.485Z" }, - { url = "https://files.pythonhosted.org/packages/05/2c/30eebca20d5db95720ab4d2faec1b5e4c1025c473f703738c371241476a2/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", size = 408750, upload-time = "2025-08-27T12:14:24.924Z" }, - { url = "https://files.pythonhosted.org/packages/90/1a/cdb5083f043597c4d4276eae4e4c70c55ab5accec078da8611f24575a367/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", size = 387688, upload-time = "2025-08-27T12:14:27.537Z" }, - { url = "https://files.pythonhosted.org/packages/7c/92/cf786a15320e173f945d205ab31585cc43969743bb1a48b6888f7a2b0a2d/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", size = 407225, upload-time = "2025-08-27T12:14:28.981Z" }, - { url = "https://files.pythonhosted.org/packages/33/5c/85ee16df5b65063ef26017bef33096557a4c83fbe56218ac7cd8c235f16d/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", size = 423361, upload-time = "2025-08-27T12:14:30.469Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8e/1c2741307fcabd1a334ecf008e92c4f47bb6f848712cf15c923becfe82bb/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", size = 562493, upload-time = "2025-08-27T12:14:31.987Z" }, - { url = "https://files.pythonhosted.org/packages/04/03/5159321baae9b2222442a70c1f988cbbd66b9be0675dd3936461269be360/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", size = 592623, upload-time = "2025-08-27T12:14:33.543Z" }, - { url = "https://files.pythonhosted.org/packages/ff/39/c09fd1ad28b85bc1d4554a8710233c9f4cefd03d7717a1b8fbfd171d1167/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", size = 558800, upload-time = "2025-08-27T12:14:35.436Z" }, - { url = "https://files.pythonhosted.org/packages/c5/d6/99228e6bbcf4baa764b18258f519a9035131d91b538d4e0e294313462a98/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", size = 221943, upload-time = "2025-08-27T12:14:36.898Z" }, - { url = "https://files.pythonhosted.org/packages/be/07/c802bc6b8e95be83b79bdf23d1aa61d68324cb1006e245d6c58e959e314d/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", size = 233739, upload-time = "2025-08-27T12:14:38.386Z" }, - { url = "https://files.pythonhosted.org/packages/c8/89/3e1b1c16d4c2d547c5717377a8df99aee8099ff050f87c45cb4d5fa70891/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", size = 223120, upload-time = "2025-08-27T12:14:39.82Z" }, - { url = "https://files.pythonhosted.org/packages/62/7e/dc7931dc2fa4a6e46b2a4fa744a9fe5c548efd70e0ba74f40b39fa4a8c10/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", size = 358944, upload-time = "2025-08-27T12:14:41.199Z" }, - { url = "https://files.pythonhosted.org/packages/e6/22/4af76ac4e9f336bfb1a5f240d18a33c6b2fcaadb7472ac7680576512b49a/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", size = 342283, upload-time = "2025-08-27T12:14:42.699Z" }, - { url = "https://files.pythonhosted.org/packages/1c/15/2a7c619b3c2272ea9feb9ade67a45c40b3eeb500d503ad4c28c395dc51b4/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", size = 380320, upload-time = "2025-08-27T12:14:44.157Z" }, - { url = "https://files.pythonhosted.org/packages/a2/7d/4c6d243ba4a3057e994bb5bedd01b5c963c12fe38dde707a52acdb3849e7/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", size = 391760, upload-time = "2025-08-27T12:14:45.845Z" }, - { url = "https://files.pythonhosted.org/packages/b4/71/b19401a909b83bcd67f90221330bc1ef11bc486fe4e04c24388d28a618ae/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", size = 522476, upload-time = "2025-08-27T12:14:47.364Z" }, - { url = "https://files.pythonhosted.org/packages/e4/44/1a3b9715c0455d2e2f0f6df5ee6d6f5afdc423d0773a8a682ed2b43c566c/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", size = 403418, upload-time = "2025-08-27T12:14:49.991Z" }, - { url = "https://files.pythonhosted.org/packages/1c/4b/fb6c4f14984eb56673bc868a66536f53417ddb13ed44b391998100a06a96/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", size = 384771, upload-time = "2025-08-27T12:14:52.159Z" }, - { url = "https://files.pythonhosted.org/packages/c0/56/d5265d2d28b7420d7b4d4d85cad8ef891760f5135102e60d5c970b976e41/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", size = 400022, upload-time = "2025-08-27T12:14:53.859Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e9/9f5fc70164a569bdd6ed9046486c3568d6926e3a49bdefeeccfb18655875/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", size = 416787, upload-time = "2025-08-27T12:14:55.673Z" }, - { url = "https://files.pythonhosted.org/packages/d4/64/56dd03430ba491db943a81dcdef115a985aac5f44f565cd39a00c766d45c/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", size = 557538, upload-time = "2025-08-27T12:14:57.245Z" }, - { url = "https://files.pythonhosted.org/packages/3f/36/92cc885a3129993b1d963a2a42ecf64e6a8e129d2c7cc980dbeba84e55fb/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", size = 588512, upload-time = "2025-08-27T12:14:58.728Z" }, - { url = "https://files.pythonhosted.org/packages/dd/10/6b283707780a81919f71625351182b4f98932ac89a09023cb61865136244/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", size = 555813, upload-time = "2025-08-27T12:15:00.334Z" }, - { url = "https://files.pythonhosted.org/packages/04/2e/30b5ea18c01379da6272a92825dd7e53dc9d15c88a19e97932d35d430ef7/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", size = 217385, upload-time = "2025-08-27T12:15:01.937Z" }, - { url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" }, - { url = "https://files.pythonhosted.org/packages/d5/63/b7cc415c345625d5e62f694ea356c58fb964861409008118f1245f8c3347/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf", size = 371360, upload-time = "2025-08-27T12:15:29.218Z" }, - { url = "https://files.pythonhosted.org/packages/e5/8c/12e1b24b560cf378b8ffbdb9dc73abd529e1adcfcf82727dfd29c4a7b88d/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3", size = 353933, upload-time = "2025-08-27T12:15:30.837Z" }, - { url = "https://files.pythonhosted.org/packages/9b/85/1bb2210c1f7a1b99e91fea486b9f0f894aa5da3a5ec7097cbad7dec6d40f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636", size = 382962, upload-time = "2025-08-27T12:15:32.348Z" }, - { url = "https://files.pythonhosted.org/packages/cc/c9/a839b9f219cf80ed65f27a7f5ddbb2809c1b85c966020ae2dff490e0b18e/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8", size = 394412, upload-time = "2025-08-27T12:15:33.839Z" }, - { url = "https://files.pythonhosted.org/packages/02/2d/b1d7f928b0b1f4fc2e0133e8051d199b01d7384875adc63b6ddadf3de7e5/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc", size = 523972, upload-time = "2025-08-27T12:15:35.377Z" }, - { url = "https://files.pythonhosted.org/packages/a9/af/2cbf56edd2d07716df1aec8a726b3159deb47cb5c27e1e42b71d705a7c2f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8", size = 403273, upload-time = "2025-08-27T12:15:37.051Z" }, - { url = "https://files.pythonhosted.org/packages/c0/93/425e32200158d44ff01da5d9612c3b6711fe69f606f06e3895511f17473b/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc", size = 385278, upload-time = "2025-08-27T12:15:38.571Z" }, - { url = "https://files.pythonhosted.org/packages/eb/1a/1a04a915ecd0551bfa9e77b7672d1937b4b72a0fc204a17deef76001cfb2/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71", size = 402084, upload-time = "2025-08-27T12:15:40.529Z" }, - { url = "https://files.pythonhosted.org/packages/51/f7/66585c0fe5714368b62951d2513b684e5215beaceab2c6629549ddb15036/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad", size = 419041, upload-time = "2025-08-27T12:15:42.191Z" }, - { url = "https://files.pythonhosted.org/packages/8e/7e/83a508f6b8e219bba2d4af077c35ba0e0cdd35a751a3be6a7cba5a55ad71/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab", size = 560084, upload-time = "2025-08-27T12:15:43.839Z" }, - { url = "https://files.pythonhosted.org/packages/66/66/bb945683b958a1b19eb0fe715594630d0f36396ebdef4d9b89c2fa09aa56/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059", size = 590115, upload-time = "2025-08-27T12:15:46.647Z" }, - { url = "https://files.pythonhosted.org/packages/12/00/ccfaafaf7db7e7adace915e5c2f2c2410e16402561801e9c7f96683002d3/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b", size = 556561, upload-time = "2025-08-27T12:15:48.219Z" }, - { url = "https://files.pythonhosted.org/packages/e1/b7/92b6ed9aad103bfe1c45df98453dfae40969eef2cb6c6239c58d7e96f1b3/rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819", size = 229125, upload-time = "2025-08-27T12:15:49.956Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ed/e1fba02de17f4f76318b834425257c8ea297e415e12c68b4361f63e8ae92/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df", size = 371402, upload-time = "2025-08-27T12:15:51.561Z" }, - { url = "https://files.pythonhosted.org/packages/af/7c/e16b959b316048b55585a697e94add55a4ae0d984434d279ea83442e460d/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3", size = 354084, upload-time = "2025-08-27T12:15:53.219Z" }, - { url = "https://files.pythonhosted.org/packages/de/c1/ade645f55de76799fdd08682d51ae6724cb46f318573f18be49b1e040428/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9", size = 383090, upload-time = "2025-08-27T12:15:55.158Z" }, - { url = "https://files.pythonhosted.org/packages/1f/27/89070ca9b856e52960da1472efcb6c20ba27cfe902f4f23ed095b9cfc61d/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc", size = 394519, upload-time = "2025-08-27T12:15:57.238Z" }, - { url = "https://files.pythonhosted.org/packages/b3/28/be120586874ef906aa5aeeae95ae8df4184bc757e5b6bd1c729ccff45ed5/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4", size = 523817, upload-time = "2025-08-27T12:15:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/70cc197bc11cfcde02a86f36ac1eed15c56667c2ebddbdb76a47e90306da/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66", size = 403240, upload-time = "2025-08-27T12:16:00.923Z" }, - { url = "https://files.pythonhosted.org/packages/cf/35/46936cca449f7f518f2f4996e0e8344db4b57e2081e752441154089d2a5f/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e", size = 385194, upload-time = "2025-08-27T12:16:02.802Z" }, - { url = "https://files.pythonhosted.org/packages/e1/62/29c0d3e5125c3270b51415af7cbff1ec587379c84f55a5761cc9efa8cd06/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c", size = 402086, upload-time = "2025-08-27T12:16:04.806Z" }, - { url = "https://files.pythonhosted.org/packages/8f/66/03e1087679227785474466fdd04157fb793b3b76e3fcf01cbf4c693c1949/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf", size = 419272, upload-time = "2025-08-27T12:16:06.471Z" }, - { url = "https://files.pythonhosted.org/packages/6a/24/e3e72d265121e00b063aef3e3501e5b2473cf1b23511d56e529531acf01e/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf", size = 560003, upload-time = "2025-08-27T12:16:08.06Z" }, - { url = "https://files.pythonhosted.org/packages/26/ca/f5a344c534214cc2d41118c0699fffbdc2c1bc7046f2a2b9609765ab9c92/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6", size = 590482, upload-time = "2025-08-27T12:16:10.137Z" }, - { url = "https://files.pythonhosted.org/packages/ce/08/4349bdd5c64d9d193c360aa9db89adeee6f6682ab8825dca0a3f535f434f/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", size = 556523, upload-time = "2025-08-27T12:16:12.188Z" }, +version = "0.29.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/33/23b3b3419b6a3e0f559c7c0d2ca8fc1b9448382b25245033788785921332/rpds_py-0.29.0.tar.gz", hash = "sha256:fe55fe686908f50154d1dc599232016e50c243b438c3b7432f24e2895b0e5359", size = 69359, upload-time = "2025-11-16T14:50:39.532Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/7a/c5b2ff381b74bc742768e8d870f26babac4ef256ba160bdbf8d57af56461/rpds_py-0.29.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4ae4b88c6617e1b9e5038ab3fccd7bac0842fdda2b703117b2aa99bc85379113", size = 372385, upload-time = "2025-11-16T14:47:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/28/36/531f1eb4d5bed4a9c150f363a7ec4a98d2dc746151bba5473bc38ee85dec/rpds_py-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7d9128ec9d8cecda6f044001fde4fb71ea7c24325336612ef8179091eb9596b9", size = 362869, upload-time = "2025-11-16T14:47:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/54/df/7e9c0493a2015d9c82807a2d5f023ea9774e27a4c15b33ef1cdb7456138d/rpds_py-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37812c3da8e06f2bb35b3cf10e4a7b68e776a706c13058997238762b4e07f4f", size = 391582, upload-time = "2025-11-16T14:47:39.746Z" }, + { url = "https://files.pythonhosted.org/packages/15/38/42a981c3592ef46fbd7e17adbf8730cc5ec87e6aa1770c658c44bbb52960/rpds_py-0.29.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:66786c3fb1d8de416a7fa8e1cb1ec6ba0a745b2b0eee42f9b7daa26f1a495545", size = 405685, upload-time = "2025-11-16T14:47:41.472Z" }, + { url = "https://files.pythonhosted.org/packages/12/45/628b8c15856c3849c3f52ec6dac93c046ed5faeed4a435af03b70525fd29/rpds_py-0.29.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58f5c77f1af888b5fd1876c9a0d9858f6f88a39c9dd7c073a88e57e577da66d", size = 527067, upload-time = "2025-11-16T14:47:43.036Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ba/6b56d09badeabd95098016d72a437d4a0fd82d4672ce92a7607df5d70a42/rpds_py-0.29.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:799156ef1f3529ed82c36eb012b5d7a4cf4b6ef556dd7cc192148991d07206ae", size = 412532, upload-time = "2025-11-16T14:47:44.484Z" }, + { url = "https://files.pythonhosted.org/packages/f1/39/2f1f3db92888314b50b8f9641f679188bd24b3665a8cb9923b7201ae8011/rpds_py-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:453783477aa4f2d9104c4b59b08c871431647cb7af51b549bbf2d9eb9c827756", size = 392736, upload-time = "2025-11-16T14:47:46.053Z" }, + { url = "https://files.pythonhosted.org/packages/60/43/3c3b1dcd827e50f2ae28786d846b8a351080d8a69a3b49bc10ae44cc39b1/rpds_py-0.29.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:24a7231493e3c4a4b30138b50cca089a598e52c34cf60b2f35cebf62f274fdea", size = 406300, upload-time = "2025-11-16T14:47:47.268Z" }, + { url = "https://files.pythonhosted.org/packages/da/02/bc96021b67f8525e6bcdd68935c4543ada61e1f3dcb067ed037d68b8c6d2/rpds_py-0.29.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7033c1010b1f57bb44d8067e8c25aa6fa2e944dbf46ccc8c92b25043839c3fd2", size = 423641, upload-time = "2025-11-16T14:47:48.878Z" }, + { url = "https://files.pythonhosted.org/packages/38/e9/c435ddb602ced19a80b8277a41371734f33ad3f91cc4ceb4d82596800a3c/rpds_py-0.29.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0248b19405422573621172ab8e3a1f29141362d13d9f72bafa2e28ea0cdca5a2", size = 574153, upload-time = "2025-11-16T14:47:50.435Z" }, + { url = "https://files.pythonhosted.org/packages/84/82/dc3c32e1f89ecba8a59600d4cd65fe0ad81b6c636ccdbf6cd177fd6a7bac/rpds_py-0.29.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f9f436aee28d13b9ad2c764fc273e0457e37c2e61529a07b928346b219fcde3b", size = 600304, upload-time = "2025-11-16T14:47:51.599Z" }, + { url = "https://files.pythonhosted.org/packages/35/98/785290e0b7142470735dc1b1f68fb33aae29e5296f062c88396eedf796c8/rpds_py-0.29.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24a16cb7163933906c62c272de20ea3c228e4542c8c45c1d7dc2b9913e17369a", size = 562211, upload-time = "2025-11-16T14:47:53.094Z" }, + { url = "https://files.pythonhosted.org/packages/30/58/4eeddcb0737c6875f3e30c65dc9d7e7a10dfd5779646a990fa602c6d56c5/rpds_py-0.29.0-cp310-cp310-win32.whl", hash = "sha256:1a409b0310a566bfd1be82119891fefbdce615ccc8aa558aff7835c27988cbef", size = 221803, upload-time = "2025-11-16T14:47:54.404Z" }, + { url = "https://files.pythonhosted.org/packages/54/77/b35a8dbdcbeb32505500547cdafaa9f8863e85f8faac50ef34464ec5a256/rpds_py-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5523b0009e7c3c1263471b69d8da1c7d41b3ecb4cb62ef72be206b92040a950", size = 235530, upload-time = "2025-11-16T14:47:56.061Z" }, + { url = "https://files.pythonhosted.org/packages/36/ab/7fb95163a53ab122c74a7c42d2d2f012819af2cf3deb43fb0d5acf45cc1a/rpds_py-0.29.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9b9c764a11fd637e0322a488560533112837f5334ffeb48b1be20f6d98a7b437", size = 372344, upload-time = "2025-11-16T14:47:57.279Z" }, + { url = "https://files.pythonhosted.org/packages/b3/45/f3c30084c03b0d0f918cb4c5ae2c20b0a148b51ba2b3f6456765b629bedd/rpds_py-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fd2164d73812026ce970d44c3ebd51e019d2a26a4425a5dcbdfa93a34abc383", size = 363041, upload-time = "2025-11-16T14:47:58.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e9/4d044a1662608c47a87cbb37b999d4d5af54c6d6ebdda93a4d8bbf8b2a10/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a097b7f7f7274164566ae90a221fd725363c0e9d243e2e9ed43d195ccc5495c", size = 391775, upload-time = "2025-11-16T14:48:00.197Z" }, + { url = "https://files.pythonhosted.org/packages/50/c9/7616d3ace4e6731aeb6e3cd85123e03aec58e439044e214b9c5c60fd8eb1/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cdc0490374e31cedefefaa1520d5fe38e82fde8748cbc926e7284574c714d6b", size = 405624, upload-time = "2025-11-16T14:48:01.496Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e2/6d7d6941ca0843609fd2d72c966a438d6f22617baf22d46c3d2156c31350/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89ca2e673ddd5bde9b386da9a0aac0cab0e76f40c8f0aaf0d6311b6bbf2aa311", size = 527894, upload-time = "2025-11-16T14:48:03.167Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f7/aee14dc2db61bb2ae1e3068f134ca9da5f28c586120889a70ff504bb026f/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5d9da3ff5af1ca1249b1adb8ef0573b94c76e6ae880ba1852f033bf429d4588", size = 412720, upload-time = "2025-11-16T14:48:04.413Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e2/2293f236e887c0360c2723d90c00d48dee296406994d6271faf1712e94ec/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8238d1d310283e87376c12f658b61e1ee23a14c0e54c7c0ce953efdbdc72deed", size = 392945, upload-time = "2025-11-16T14:48:06.252Z" }, + { url = "https://files.pythonhosted.org/packages/14/cd/ceea6147acd3bd1fd028d1975228f08ff19d62098078d5ec3eed49703797/rpds_py-0.29.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:2d6fb2ad1c36f91c4646989811e84b1ea5e0c3cf9690b826b6e32b7965853a63", size = 406385, upload-time = "2025-11-16T14:48:07.575Z" }, + { url = "https://files.pythonhosted.org/packages/52/36/fe4dead19e45eb77a0524acfdbf51e6cda597b26fc5b6dddbff55fbbb1a5/rpds_py-0.29.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:534dc9df211387547267ccdb42253aa30527482acb38dd9b21c5c115d66a96d2", size = 423943, upload-time = "2025-11-16T14:48:10.175Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7b/4551510803b582fa4abbc8645441a2d15aa0c962c3b21ebb380b7e74f6a1/rpds_py-0.29.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d456e64724a075441e4ed648d7f154dc62e9aabff29bcdf723d0c00e9e1d352f", size = 574204, upload-time = "2025-11-16T14:48:11.499Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/071ccdd7b171e727a6ae079f02c26f75790b41555f12ca8f1151336d2124/rpds_py-0.29.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a738f2da2f565989401bd6fd0b15990a4d1523c6d7fe83f300b7e7d17212feca", size = 600587, upload-time = "2025-11-16T14:48:12.822Z" }, + { url = "https://files.pythonhosted.org/packages/03/09/96983d48c8cf5a1e03c7d9cc1f4b48266adfb858ae48c7c2ce978dbba349/rpds_py-0.29.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a110e14508fd26fd2e472bb541f37c209409876ba601cf57e739e87d8a53cf95", size = 562287, upload-time = "2025-11-16T14:48:14.108Z" }, + { url = "https://files.pythonhosted.org/packages/40/f0/8c01aaedc0fa92156f0391f39ea93b5952bc0ec56b897763858f95da8168/rpds_py-0.29.0-cp311-cp311-win32.whl", hash = "sha256:923248a56dd8d158389a28934f6f69ebf89f218ef96a6b216a9be6861804d3f4", size = 221394, upload-time = "2025-11-16T14:48:15.374Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a5/a8b21c54c7d234efdc83dc034a4d7cd9668e3613b6316876a29b49dece71/rpds_py-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:539eb77eb043afcc45314d1be09ea6d6cafb3addc73e0547c171c6d636957f60", size = 235713, upload-time = "2025-11-16T14:48:16.636Z" }, + { url = "https://files.pythonhosted.org/packages/a7/1f/df3c56219523947b1be402fa12e6323fe6d61d883cf35d6cb5d5bb6db9d9/rpds_py-0.29.0-cp311-cp311-win_arm64.whl", hash = "sha256:bdb67151ea81fcf02d8f494703fb728d4d34d24556cbff5f417d74f6f5792e7c", size = 229157, upload-time = "2025-11-16T14:48:17.891Z" }, + { url = "https://files.pythonhosted.org/packages/3c/50/bc0e6e736d94e420df79be4deb5c9476b63165c87bb8f19ef75d100d21b3/rpds_py-0.29.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0891cfd8db43e085c0ab93ab7e9b0c8fee84780d436d3b266b113e51e79f954", size = 376000, upload-time = "2025-11-16T14:48:19.141Z" }, + { url = "https://files.pythonhosted.org/packages/3e/3a/46676277160f014ae95f24de53bed0e3b7ea66c235e7de0b9df7bd5d68ba/rpds_py-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3897924d3f9a0361472d884051f9a2460358f9a45b1d85a39a158d2f8f1ad71c", size = 360575, upload-time = "2025-11-16T14:48:20.443Z" }, + { url = "https://files.pythonhosted.org/packages/75/ba/411d414ed99ea1afdd185bbabeeaac00624bd1e4b22840b5e9967ade6337/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21deb8e0d1571508c6491ce5ea5e25669b1dd4adf1c9d64b6314842f708b5d", size = 392159, upload-time = "2025-11-16T14:48:22.12Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b1/e18aa3a331f705467a48d0296778dc1fea9d7f6cf675bd261f9a846c7e90/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9efe71687d6427737a0a2de9ca1c0a216510e6cd08925c44162be23ed7bed2d5", size = 410602, upload-time = "2025-11-16T14:48:23.563Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6c/04f27f0c9f2299274c76612ac9d2c36c5048bb2c6c2e52c38c60bf3868d9/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40f65470919dc189c833e86b2c4bd21bd355f98436a2cef9e0a9a92aebc8e57e", size = 515808, upload-time = "2025-11-16T14:48:24.949Z" }, + { url = "https://files.pythonhosted.org/packages/83/56/a8412aa464fb151f8bc0d91fb0bb888adc9039bd41c1c6ba8d94990d8cf8/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:def48ff59f181130f1a2cb7c517d16328efac3ec03951cca40c1dc2049747e83", size = 416015, upload-time = "2025-11-16T14:48:26.782Z" }, + { url = "https://files.pythonhosted.org/packages/04/4c/f9b8a05faca3d9e0a6397c90d13acb9307c9792b2bff621430c58b1d6e76/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad7bd570be92695d89285a4b373006930715b78d96449f686af422debb4d3949", size = 395325, upload-time = "2025-11-16T14:48:28.055Z" }, + { url = "https://files.pythonhosted.org/packages/34/60/869f3bfbf8ed7b54f1ad9a5543e0fdffdd40b5a8f587fe300ee7b4f19340/rpds_py-0.29.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:5a572911cd053137bbff8e3a52d31c5d2dba51d3a67ad902629c70185f3f2181", size = 410160, upload-time = "2025-11-16T14:48:29.338Z" }, + { url = "https://files.pythonhosted.org/packages/91/aa/e5b496334e3aba4fe4c8a80187b89f3c1294c5c36f2a926da74338fa5a73/rpds_py-0.29.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d583d4403bcbf10cffc3ab5cee23d7643fcc960dff85973fd3c2d6c86e8dbb0c", size = 425309, upload-time = "2025-11-16T14:48:30.691Z" }, + { url = "https://files.pythonhosted.org/packages/85/68/4e24a34189751ceb6d66b28f18159922828dd84155876551f7ca5b25f14f/rpds_py-0.29.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:070befbb868f257d24c3bb350dbd6e2f645e83731f31264b19d7231dd5c396c7", size = 574644, upload-time = "2025-11-16T14:48:31.964Z" }, + { url = "https://files.pythonhosted.org/packages/8c/cf/474a005ea4ea9c3b4f17b6108b6b13cebfc98ebaff11d6e1b193204b3a93/rpds_py-0.29.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fc935f6b20b0c9f919a8ff024739174522abd331978f750a74bb68abd117bd19", size = 601605, upload-time = "2025-11-16T14:48:33.252Z" }, + { url = "https://files.pythonhosted.org/packages/f4/b1/c56f6a9ab8c5f6bb5c65c4b5f8229167a3a525245b0773f2c0896686b64e/rpds_py-0.29.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c5a8ecaa44ce2d8d9d20a68a2483a74c07f05d72e94a4dff88906c8807e77b0", size = 564593, upload-time = "2025-11-16T14:48:34.643Z" }, + { url = "https://files.pythonhosted.org/packages/b3/13/0494cecce4848f68501e0a229432620b4b57022388b071eeff95f3e1e75b/rpds_py-0.29.0-cp312-cp312-win32.whl", hash = "sha256:ba5e1aeaf8dd6d8f6caba1f5539cddda87d511331714b7b5fc908b6cfc3636b7", size = 223853, upload-time = "2025-11-16T14:48:36.419Z" }, + { url = "https://files.pythonhosted.org/packages/1f/6a/51e9aeb444a00cdc520b032a28b07e5f8dc7bc328b57760c53e7f96997b4/rpds_py-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:b5f6134faf54b3cb83375db0f113506f8b7770785be1f95a631e7e2892101977", size = 239895, upload-time = "2025-11-16T14:48:37.956Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d4/8bce56cdad1ab873e3f27cb31c6a51d8f384d66b022b820525b879f8bed1/rpds_py-0.29.0-cp312-cp312-win_arm64.whl", hash = "sha256:b016eddf00dca7944721bf0cd85b6af7f6c4efaf83ee0b37c4133bd39757a8c7", size = 230321, upload-time = "2025-11-16T14:48:39.71Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d9/c5de60d9d371bbb186c3e9bf75f4fc5665e11117a25a06a6b2e0afb7380e/rpds_py-0.29.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1585648d0760b88292eecab5181f5651111a69d90eff35d6b78aa32998886a61", size = 375710, upload-time = "2025-11-16T14:48:41.063Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b3/0860cdd012291dc21272895ce107f1e98e335509ba986dd83d72658b82b9/rpds_py-0.29.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:521807963971a23996ddaf764c682b3e46459b3c58ccd79fefbe16718db43154", size = 360582, upload-time = "2025-11-16T14:48:42.423Z" }, + { url = "https://files.pythonhosted.org/packages/92/8a/a18c2f4a61b3407e56175f6aab6deacdf9d360191a3d6f38566e1eaf7266/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a8896986efaa243ab713c69e6491a4138410f0fe36f2f4c71e18bd5501e8014", size = 391172, upload-time = "2025-11-16T14:48:43.75Z" }, + { url = "https://files.pythonhosted.org/packages/fd/49/e93354258508c50abc15cdcd5fcf7ac4117f67bb6233ad7859f75e7372a0/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d24564a700ef41480a984c5ebed62b74e6ce5860429b98b1fede76049e953e6", size = 409586, upload-time = "2025-11-16T14:48:45.498Z" }, + { url = "https://files.pythonhosted.org/packages/5a/8d/a27860dae1c19a6bdc901f90c81f0d581df1943355802961a57cdb5b6cd1/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6596b93c010d386ae46c9fba9bfc9fc5965fa8228edeac51576299182c2e31c", size = 516339, upload-time = "2025-11-16T14:48:47.308Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ad/a75e603161e79b7110c647163d130872b271c6b28712c803c65d492100f7/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5cc58aac218826d054c7da7f95821eba94125d88be673ff44267bb89d12a5866", size = 416201, upload-time = "2025-11-16T14:48:48.615Z" }, + { url = "https://files.pythonhosted.org/packages/b9/42/555b4ee17508beafac135c8b450816ace5a96194ce97fefc49d58e5652ea/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de73e40ebc04dd5d9556f50180395322193a78ec247e637e741c1b954810f295", size = 395095, upload-time = "2025-11-16T14:48:50.027Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f0/c90b671b9031e800ec45112be42ea9f027f94f9ac25faaac8770596a16a1/rpds_py-0.29.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:295ce5ac7f0cf69a651ea75c8f76d02a31f98e5698e82a50a5f4d4982fbbae3b", size = 410077, upload-time = "2025-11-16T14:48:51.515Z" }, + { url = "https://files.pythonhosted.org/packages/3d/80/9af8b640b81fe21e6f718e9dec36c0b5f670332747243130a5490f292245/rpds_py-0.29.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1ea59b23ea931d494459c8338056fe7d93458c0bf3ecc061cd03916505369d55", size = 424548, upload-time = "2025-11-16T14:48:53.237Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0b/b5647446e991736e6a495ef510e6710df91e880575a586e763baeb0aa770/rpds_py-0.29.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f49d41559cebd608042fdcf54ba597a4a7555b49ad5c1c0c03e0af82692661cd", size = 573661, upload-time = "2025-11-16T14:48:54.769Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b3/1b1c9576839ff583d1428efbf59f9ee70498d8ce6c0b328ac02f1e470879/rpds_py-0.29.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:05a2bd42768ea988294ca328206efbcc66e220d2d9b7836ee5712c07ad6340ea", size = 600937, upload-time = "2025-11-16T14:48:56.247Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7b/b6cfca2f9fee4c4494ce54f7fb1b9f578867495a9aa9fc0d44f5f735c8e0/rpds_py-0.29.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33ca7bdfedd83339ca55da3a5e1527ee5870d4b8369456b5777b197756f3ca22", size = 564496, upload-time = "2025-11-16T14:48:57.691Z" }, + { url = "https://files.pythonhosted.org/packages/b9/fb/ba29ec7f0f06eb801bac5a23057a9ff7670623b5e8013bd59bec4aa09de8/rpds_py-0.29.0-cp313-cp313-win32.whl", hash = "sha256:20c51ae86a0bb9accc9ad4e6cdeec58d5ebb7f1b09dd4466331fc65e1766aae7", size = 223126, upload-time = "2025-11-16T14:48:59.058Z" }, + { url = "https://files.pythonhosted.org/packages/3c/6b/0229d3bed4ddaa409e6d90b0ae967ed4380e4bdd0dad6e59b92c17d42457/rpds_py-0.29.0-cp313-cp313-win_amd64.whl", hash = "sha256:6410e66f02803600edb0b1889541f4b5cc298a5ccda0ad789cc50ef23b54813e", size = 239771, upload-time = "2025-11-16T14:49:00.872Z" }, + { url = "https://files.pythonhosted.org/packages/e4/38/d2868f058b164f8efd89754d85d7b1c08b454f5c07ac2e6cc2e9bd4bd05b/rpds_py-0.29.0-cp313-cp313-win_arm64.whl", hash = "sha256:56838e1cd9174dc23c5691ee29f1d1be9eab357f27efef6bded1328b23e1ced2", size = 229994, upload-time = "2025-11-16T14:49:02.673Z" }, + { url = "https://files.pythonhosted.org/packages/52/91/5de91c5ec7d41759beec9b251630824dbb8e32d20c3756da1a9a9d309709/rpds_py-0.29.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:37d94eadf764d16b9a04307f2ab1d7af6dc28774bbe0535c9323101e14877b4c", size = 365886, upload-time = "2025-11-16T14:49:04.133Z" }, + { url = "https://files.pythonhosted.org/packages/85/7c/415d8c1b016d5f47ecec5145d9d6d21002d39dce8761b30f6c88810b455a/rpds_py-0.29.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d472cf73efe5726a067dce63eebe8215b14beabea7c12606fd9994267b3cfe2b", size = 355262, upload-time = "2025-11-16T14:49:05.543Z" }, + { url = "https://files.pythonhosted.org/packages/3d/14/bf83e2daa4f980e4dc848aed9299792a8b84af95e12541d9e7562f84a6ef/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72fdfd5ff8992e4636621826371e3ac5f3e3b8323e9d0e48378e9c13c3dac9d0", size = 384826, upload-time = "2025-11-16T14:49:07.301Z" }, + { url = "https://files.pythonhosted.org/packages/33/b8/53330c50a810ae22b4fbba5e6cf961b68b9d72d9bd6780a7c0a79b070857/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2549d833abdf8275c901313b9e8ff8fba57e50f6a495035a2a4e30621a2f7cc4", size = 394234, upload-time = "2025-11-16T14:49:08.782Z" }, + { url = "https://files.pythonhosted.org/packages/cc/32/01e2e9645cef0e584f518cfde4567563e57db2257244632b603f61b40e50/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4448dad428f28a6a767c3e3b80cde3446a22a0efbddaa2360f4bb4dc836d0688", size = 520008, upload-time = "2025-11-16T14:49:10.253Z" }, + { url = "https://files.pythonhosted.org/packages/98/c3/0d1b95a81affae2b10f950782e33a1fd2edd6ce2a479966cac98c9a66f57/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:115f48170fd4296a33938d8c11f697f5f26e0472e43d28f35624764173a60e4d", size = 409569, upload-time = "2025-11-16T14:49:12.478Z" }, + { url = "https://files.pythonhosted.org/packages/fa/60/aa3b8678f3f009f675b99174fa2754302a7fbfe749162e8043d111de2d88/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e5bb73ffc029820f4348e9b66b3027493ae00bca6629129cd433fd7a76308ee", size = 385188, upload-time = "2025-11-16T14:49:13.88Z" }, + { url = "https://files.pythonhosted.org/packages/92/02/5546c1c8aa89c18d40c1fcffdcc957ba730dee53fb7c3ca3a46f114761d2/rpds_py-0.29.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b1581fcde18fcdf42ea2403a16a6b646f8eb1e58d7f90a0ce693da441f76942e", size = 398587, upload-time = "2025-11-16T14:49:15.339Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e0/ad6eeaf47e236eba052fa34c4073078b9e092bd44da6bbb35aaae9580669/rpds_py-0.29.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16e9da2bda9eb17ea318b4c335ec9ac1818e88922cbe03a5743ea0da9ecf74fb", size = 416641, upload-time = "2025-11-16T14:49:16.832Z" }, + { url = "https://files.pythonhosted.org/packages/1a/93/0acedfd50ad9cdd3879c615a6dc8c5f1ce78d2fdf8b87727468bb5bb4077/rpds_py-0.29.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:28fd300326dd21198f311534bdb6d7e989dd09b3418b3a91d54a0f384c700967", size = 566683, upload-time = "2025-11-16T14:49:18.342Z" }, + { url = "https://files.pythonhosted.org/packages/62/53/8c64e0f340a9e801459fc6456821abc15b3582cb5dc3932d48705a9d9ac7/rpds_py-0.29.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2aba991e041d031c7939e1358f583ae405a7bf04804ca806b97a5c0e0af1ea5e", size = 592730, upload-time = "2025-11-16T14:49:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/85/ef/3109b6584f8c4b0d2490747c916df833c127ecfa82be04d9a40a376f2090/rpds_py-0.29.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f437026dbbc3f08c99cc41a5b2570c6e1a1ddbe48ab19a9b814254128d4ea7a", size = 557361, upload-time = "2025-11-16T14:49:21.574Z" }, + { url = "https://files.pythonhosted.org/packages/ff/3b/61586475e82d57f01da2c16edb9115a618afe00ce86fe1b58936880b15af/rpds_py-0.29.0-cp313-cp313t-win32.whl", hash = "sha256:6e97846e9800a5d0fe7be4d008f0c93d0feeb2700da7b1f7528dabafb31dfadb", size = 211227, upload-time = "2025-11-16T14:49:23.03Z" }, + { url = "https://files.pythonhosted.org/packages/3b/3a/12dc43f13594a54ea0c9d7e9d43002116557330e3ad45bc56097ddf266e2/rpds_py-0.29.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f49196aec7c4b406495f60e6f947ad71f317a765f956d74bbd83996b9edc0352", size = 225248, upload-time = "2025-11-16T14:49:24.841Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/0b1474e7899371d9540d3bbb2a499a3427ae1fc39c998563fe9035a1073b/rpds_py-0.29.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:394d27e4453d3b4d82bb85665dc1fcf4b0badc30fc84282defed71643b50e1a1", size = 363731, upload-time = "2025-11-16T14:49:26.683Z" }, + { url = "https://files.pythonhosted.org/packages/28/12/3b7cf2068d0a334ed1d7b385a9c3c8509f4c2bcba3d4648ea71369de0881/rpds_py-0.29.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55d827b2ae95425d3be9bc9a5838b6c29d664924f98146557f7715e331d06df8", size = 354343, upload-time = "2025-11-16T14:49:28.24Z" }, + { url = "https://files.pythonhosted.org/packages/eb/73/5afcf8924bc02a749416eda64e17ac9c9b28f825f4737385295a0e99b0c1/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc31a07ed352e5462d3ee1b22e89285f4ce97d5266f6d1169da1142e78045626", size = 385406, upload-time = "2025-11-16T14:49:29.943Z" }, + { url = "https://files.pythonhosted.org/packages/c8/37/5db736730662508535221737a21563591b6f43c77f2e388951c42f143242/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c4695dd224212f6105db7ea62197144230b808d6b2bba52238906a2762f1d1e7", size = 396162, upload-time = "2025-11-16T14:49:31.833Z" }, + { url = "https://files.pythonhosted.org/packages/70/0d/491c1017d14f62ce7bac07c32768d209a50ec567d76d9f383b4cfad19b80/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcae1770b401167f8b9e1e3f566562e6966ffa9ce63639916248a9e25fa8a244", size = 517719, upload-time = "2025-11-16T14:49:33.804Z" }, + { url = "https://files.pythonhosted.org/packages/d7/25/b11132afcb17cd5d82db173f0c8dab270ffdfaba43e5ce7a591837ae9649/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:90f30d15f45048448b8da21c41703b31c61119c06c216a1bf8c245812a0f0c17", size = 409498, upload-time = "2025-11-16T14:49:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7d/e6543cedfb2e6403a1845710a5ab0e0ccf8fc288e0b5af9a70bfe2c12053/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a91e0ab77bdc0004b43261a4b8cd6d6b451e8d443754cfda830002b5745b32", size = 382743, upload-time = "2025-11-16T14:49:36.704Z" }, + { url = "https://files.pythonhosted.org/packages/75/11/a4ebc9f654293ae9fefb83b2b6be7f3253e85ea42a5db2f77d50ad19aaeb/rpds_py-0.29.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:4aa195e5804d32c682e453b34474f411ca108e4291c6a0f824ebdc30a91c973c", size = 400317, upload-time = "2025-11-16T14:49:39.132Z" }, + { url = "https://files.pythonhosted.org/packages/52/18/97677a60a81c7f0e5f64e51fb3f8271c5c8fcabf3a2df18e97af53d7c2bf/rpds_py-0.29.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7971bdb7bf4ee0f7e6f67fa4c7fbc6019d9850cc977d126904392d363f6f8318", size = 416979, upload-time = "2025-11-16T14:49:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/f0/69/28ab391a9968f6c746b2a2db181eaa4d16afaa859fedc9c2f682d19f7e18/rpds_py-0.29.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8ae33ad9ce580c7a47452c3b3f7d8a9095ef6208e0a0c7e4e2384f9fc5bf8212", size = 567288, upload-time = "2025-11-16T14:49:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d3/0c7afdcdb830eee94f5611b64e71354ffe6ac8df82d00c2faf2bfffd1d4e/rpds_py-0.29.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c661132ab2fb4eeede2ef69670fd60da5235209874d001a98f1542f31f2a8a94", size = 593157, upload-time = "2025-11-16T14:49:43.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ac/a0fcbc2feed4241cf26d32268c195eb88ddd4bd862adfc9d4b25edfba535/rpds_py-0.29.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bb78b3a0d31ac1bde132c67015a809948db751cb4e92cdb3f0b242e430b6ed0d", size = 554741, upload-time = "2025-11-16T14:49:45.557Z" }, + { url = "https://files.pythonhosted.org/packages/0f/f1/fcc24137c470df8588674a677f33719d5800ec053aaacd1de8a5d5d84d9e/rpds_py-0.29.0-cp314-cp314-win32.whl", hash = "sha256:f475f103488312e9bd4000bc890a95955a07b2d0b6e8884aef4be56132adbbf1", size = 215508, upload-time = "2025-11-16T14:49:47.562Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c7/1d169b2045512eac019918fc1021ea07c30e84a4343f9f344e3e0aa8c788/rpds_py-0.29.0-cp314-cp314-win_amd64.whl", hash = "sha256:b9cf2359a4fca87cfb6801fae83a76aedf66ee1254a7a151f1341632acf67f1b", size = 228125, upload-time = "2025-11-16T14:49:49.064Z" }, + { url = "https://files.pythonhosted.org/packages/be/36/0cec88aaba70ec4a6e381c444b0d916738497d27f0c30406e3d9fcbd3bc2/rpds_py-0.29.0-cp314-cp314-win_arm64.whl", hash = "sha256:9ba8028597e824854f0f1733d8b964e914ae3003b22a10c2c664cb6927e0feb9", size = 221992, upload-time = "2025-11-16T14:49:50.777Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fa/a2e524631717c9c0eb5d90d30f648cfba6b731047821c994acacb618406c/rpds_py-0.29.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:e71136fd0612556b35c575dc2726ae04a1669e6a6c378f2240312cf5d1a2ab10", size = 366425, upload-time = "2025-11-16T14:49:52.691Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a4/6d43ebe0746ff694a30233f63f454aed1677bd50ab7a59ff6b2bb5ac61f2/rpds_py-0.29.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:76fe96632d53f3bf0ea31ede2f53bbe3540cc2736d4aec3b3801b0458499ef3a", size = 355282, upload-time = "2025-11-16T14:49:54.292Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a7/52fd8270e0320b09eaf295766ae81dd175f65394687906709b3e75c71d06/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9459a33f077130dbb2c7c3cea72ee9932271fb3126404ba2a2661e4fe9eb7b79", size = 384968, upload-time = "2025-11-16T14:49:55.857Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7d/e6bc526b7a14e1ef80579a52c1d4ad39260a058a51d66c6039035d14db9d/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5c9546cfdd5d45e562cc0444b6dddc191e625c62e866bf567a2c69487c7ad28a", size = 394714, upload-time = "2025-11-16T14:49:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3f/f0ade3954e7db95c791e7eaf978aa7e08a756d2046e8bdd04d08146ed188/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12597d11d97b8f7e376c88929a6e17acb980e234547c92992f9f7c058f1a7310", size = 520136, upload-time = "2025-11-16T14:49:59.162Z" }, + { url = "https://files.pythonhosted.org/packages/87/b3/07122ead1b97009715ab9d4082be6d9bd9546099b2b03fae37c3116f72be/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28de03cf48b8a9e6ec10318f2197b83946ed91e2891f651a109611be4106ac4b", size = 409250, upload-time = "2025-11-16T14:50:00.698Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/dcbee61fd1dc892aedcb1b489ba661313101aa82ec84b1a015d4c63ebfda/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd7951c964069039acc9d67a8ff1f0a7f34845ae180ca542b17dc1456b1f1808", size = 384940, upload-time = "2025-11-16T14:50:02.312Z" }, + { url = "https://files.pythonhosted.org/packages/47/11/914ecb6f3574cf9bf8b38aced4063e0f787d6e1eb30b181a7efbc6c1da9a/rpds_py-0.29.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:c07d107b7316088f1ac0177a7661ca0c6670d443f6fe72e836069025e6266761", size = 399392, upload-time = "2025-11-16T14:50:03.829Z" }, + { url = "https://files.pythonhosted.org/packages/f5/fd/2f4bd9433f58f816434bb934313584caa47dbc6f03ce5484df8ac8980561/rpds_py-0.29.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1de2345af363d25696969befc0c1688a6cb5e8b1d32b515ef84fc245c6cddba3", size = 416796, upload-time = "2025-11-16T14:50:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/79/a5/449f0281af33efa29d5c71014399d74842342ae908d8cd38260320167692/rpds_py-0.29.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:00e56b12d2199ca96068057e1ae7f9998ab6e99cda82431afafd32f3ec98cca9", size = 566843, upload-time = "2025-11-16T14:50:07.243Z" }, + { url = "https://files.pythonhosted.org/packages/ab/32/0a6a1ccee2e37fcb1b7ba9afde762b77182dbb57937352a729c6cd3cf2bb/rpds_py-0.29.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3919a3bbecee589300ed25000b6944174e07cd20db70552159207b3f4bbb45b8", size = 593956, upload-time = "2025-11-16T14:50:09.029Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3d/eb820f95dce4306f07a495ede02fb61bef36ea201d9137d4fcd5ab94ec1e/rpds_py-0.29.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7fa2ccc312bbd91e43aa5e0869e46bc03278a3dddb8d58833150a18b0f0283a", size = 557288, upload-time = "2025-11-16T14:50:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/b8ff786f40470462a252918e0836e0db903c28e88e3eec66bc4a7856ee5d/rpds_py-0.29.0-cp314-cp314t-win32.whl", hash = "sha256:97c817863ffc397f1e6a6e9d2d89fe5408c0a9922dac0329672fb0f35c867ea5", size = 211382, upload-time = "2025-11-16T14:50:12.827Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7f/1a65ae870bc9d0576aebb0c501ea5dccf1ae2178fe2821042150ebd2e707/rpds_py-0.29.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2023473f444752f0f82a58dfcbee040d0a1b3d1b3c2ec40e884bd25db6d117d2", size = 225919, upload-time = "2025-11-16T14:50:14.734Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ac/b97e80bf107159e5b9ba9c91df1ab95f69e5e41b435f27bdd737f0d583ac/rpds_py-0.29.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:acd82a9e39082dc5f4492d15a6b6c8599aa21db5c35aaf7d6889aea16502c07d", size = 373963, upload-time = "2025-11-16T14:50:16.205Z" }, + { url = "https://files.pythonhosted.org/packages/40/5a/55e72962d5d29bd912f40c594e68880d3c7a52774b0f75542775f9250712/rpds_py-0.29.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:715b67eac317bf1c7657508170a3e011a1ea6ccb1c9d5f296e20ba14196be6b3", size = 364644, upload-time = "2025-11-16T14:50:18.22Z" }, + { url = "https://files.pythonhosted.org/packages/99/2a/6b6524d0191b7fc1351c3c0840baac42250515afb48ae40c7ed15499a6a2/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3b1b87a237cb2dba4db18bcfaaa44ba4cd5936b91121b62292ff21df577fc43", size = 393847, upload-time = "2025-11-16T14:50:20.012Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b8/c5692a7df577b3c0c7faed7ac01ee3c608b81750fc5d89f84529229b6873/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c3c3e8101bb06e337c88eb0c0ede3187131f19d97d43ea0e1c5407ea74c0cbf", size = 407281, upload-time = "2025-11-16T14:50:21.64Z" }, + { url = "https://files.pythonhosted.org/packages/f0/57/0546c6f84031b7ea08b76646a8e33e45607cc6bd879ff1917dc077bb881e/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8e54d6e61f3ecd3abe032065ce83ea63417a24f437e4a3d73d2f85ce7b7cfe", size = 529213, upload-time = "2025-11-16T14:50:23.219Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c1/01dd5f444233605555bc11fe5fed6a5c18f379f02013870c176c8e630a23/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fbd4e9aebf110473a420dea85a238b254cf8a15acb04b22a5a6b5ce8925b760", size = 413808, upload-time = "2025-11-16T14:50:25.262Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0a/60f98b06156ea2a7af849fb148e00fbcfdb540909a5174a5ed10c93745c7/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fdf53d36e6c72819993e35d1ebeeb8e8fc688d0c6c2b391b55e335b3afba5a", size = 394600, upload-time = "2025-11-16T14:50:26.956Z" }, + { url = "https://files.pythonhosted.org/packages/37/f1/dc9312fc9bec040ece08396429f2bd9e0977924ba7a11c5ad7056428465e/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:ea7173df5d86f625f8dde6d5929629ad811ed8decda3b60ae603903839ac9ac0", size = 408634, upload-time = "2025-11-16T14:50:28.989Z" }, + { url = "https://files.pythonhosted.org/packages/ed/41/65024c9fd40c89bb7d604cf73beda4cbdbcebe92d8765345dd65855b6449/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:76054d540061eda273274f3d13a21a4abdde90e13eaefdc205db37c05230efce", size = 426064, upload-time = "2025-11-16T14:50:30.674Z" }, + { url = "https://files.pythonhosted.org/packages/a2/e0/cf95478881fc88ca2fdbf56381d7df36567cccc39a05394beac72182cd62/rpds_py-0.29.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:9f84c549746a5be3bc7415830747a3a0312573afc9f95785eb35228bb17742ec", size = 575871, upload-time = "2025-11-16T14:50:33.428Z" }, + { url = "https://files.pythonhosted.org/packages/ea/c0/df88097e64339a0218b57bd5f9ca49898e4c394db756c67fccc64add850a/rpds_py-0.29.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:0ea962671af5cb9a260489e311fa22b2e97103e3f9f0caaea6f81390af96a9ed", size = 601702, upload-time = "2025-11-16T14:50:36.051Z" }, + { url = "https://files.pythonhosted.org/packages/87/f4/09ffb3ebd0cbb9e2c7c9b84d252557ecf434cd71584ee1e32f66013824df/rpds_py-0.29.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:f7728653900035fb7b8d06e1e5900545d8088efc9d5d4545782da7df03ec803f", size = 564054, upload-time = "2025-11-16T14:50:37.733Z" }, ] [[package]] name = "ruff" -version = "0.14.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/58/6ca66896635352812de66f71cdf9ff86b3a4f79071ca5730088c0cd0fc8d/ruff-0.14.1.tar.gz", hash = "sha256:1dd86253060c4772867c61791588627320abcb6ed1577a90ef432ee319729b69", size = 5513429, upload-time = "2025-10-16T18:05:41.766Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/39/9cc5ab181478d7a18adc1c1e051a84ee02bec94eb9bdfd35643d7c74ca31/ruff-0.14.1-py3-none-linux_armv6l.whl", hash = "sha256:083bfc1f30f4a391ae09c6f4f99d83074416b471775b59288956f5bc18e82f8b", size = 12445415, upload-time = "2025-10-16T18:04:48.227Z" }, - { url = "https://files.pythonhosted.org/packages/ef/2e/1226961855ccd697255988f5a2474890ac7c5863b080b15bd038df820818/ruff-0.14.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f6fa757cd717f791009f7669fefb09121cc5f7d9bd0ef211371fad68c2b8b224", size = 12784267, upload-time = "2025-10-16T18:04:52.515Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ea/fd9e95863124ed159cd0667ec98449ae461de94acda7101f1acb6066da00/ruff-0.14.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6191903d39ac156921398e9c86b7354d15e3c93772e7dbf26c9fcae59ceccd5", size = 11781872, upload-time = "2025-10-16T18:04:55.396Z" }, - { url = "https://files.pythonhosted.org/packages/1e/5a/e890f7338ff537dba4589a5e02c51baa63020acfb7c8cbbaea4831562c96/ruff-0.14.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed04f0e04f7a4587244e5c9d7df50e6b5bf2705d75059f409a6421c593a35896", size = 12226558, upload-time = "2025-10-16T18:04:58.166Z" }, - { url = "https://files.pythonhosted.org/packages/a6/7a/8ab5c3377f5bf31e167b73651841217542bcc7aa1c19e83030835cc25204/ruff-0.14.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5c9e6cf6cd4acae0febbce29497accd3632fe2025c0c583c8b87e8dbdeae5f61", size = 12187898, upload-time = "2025-10-16T18:05:01.455Z" }, - { url = "https://files.pythonhosted.org/packages/48/8d/ba7c33aa55406955fc124e62c8259791c3d42e3075a71710fdff9375134f/ruff-0.14.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fa2458527794ecdfbe45f654e42c61f2503a230545a91af839653a0a93dbc6", size = 12939168, upload-time = "2025-10-16T18:05:04.397Z" }, - { url = "https://files.pythonhosted.org/packages/b4/c2/70783f612b50f66d083380e68cbd1696739d88e9b4f6164230375532c637/ruff-0.14.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:39f1c392244e338b21d42ab29b8a6392a722c5090032eb49bb4d6defcdb34345", size = 14386942, upload-time = "2025-10-16T18:05:07.102Z" }, - { url = "https://files.pythonhosted.org/packages/48/44/cd7abb9c776b66d332119d67f96acf15830d120f5b884598a36d9d3f4d83/ruff-0.14.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7382fa12a26cce1f95070ce450946bec357727aaa428983036362579eadcc5cf", size = 13990622, upload-time = "2025-10-16T18:05:09.882Z" }, - { url = "https://files.pythonhosted.org/packages/eb/56/4259b696db12ac152fe472764b4f78bbdd9b477afd9bc3a6d53c01300b37/ruff-0.14.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0bf2be3ae8521e1093a487c4aa3b455882f139787770698530d28ed3fbb37c", size = 13431143, upload-time = "2025-10-16T18:05:13.46Z" }, - { url = "https://files.pythonhosted.org/packages/e0/35/266a80d0eb97bd224b3265b9437bd89dde0dcf4faf299db1212e81824e7e/ruff-0.14.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabcaa9ccf8089fb4fdb78d17cc0e28241520f50f4c2e88cb6261ed083d85151", size = 13132844, upload-time = "2025-10-16T18:05:16.1Z" }, - { url = "https://files.pythonhosted.org/packages/65/6e/d31ce218acc11a8d91ef208e002a31acf315061a85132f94f3df7a252b18/ruff-0.14.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:747d583400f6125ec11a4c14d1c8474bf75d8b419ad22a111a537ec1a952d192", size = 13401241, upload-time = "2025-10-16T18:05:19.395Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b5/dbc4221bf0b03774b3b2f0d47f39e848d30664157c15b965a14d890637d2/ruff-0.14.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5a6e74c0efd78515a1d13acbfe6c90f0f5bd822aa56b4a6d43a9ffb2ae6e56cd", size = 12132476, upload-time = "2025-10-16T18:05:22.163Z" }, - { url = "https://files.pythonhosted.org/packages/98/4b/ac99194e790ccd092d6a8b5f341f34b6e597d698e3077c032c502d75ea84/ruff-0.14.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0ea6a864d2fb41a4b6d5b456ed164302a0d96f4daac630aeba829abfb059d020", size = 12139749, upload-time = "2025-10-16T18:05:25.162Z" }, - { url = "https://files.pythonhosted.org/packages/47/26/7df917462c3bb5004e6fdfcc505a49e90bcd8a34c54a051953118c00b53a/ruff-0.14.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0826b8764f94229604fa255918d1cc45e583e38c21c203248b0bfc9a0e930be5", size = 12544758, upload-time = "2025-10-16T18:05:28.018Z" }, - { url = "https://files.pythonhosted.org/packages/64/d0/81e7f0648e9764ad9b51dd4be5e5dac3fcfff9602428ccbae288a39c2c22/ruff-0.14.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cbc52160465913a1a3f424c81c62ac8096b6a491468e7d872cb9444a860bc33d", size = 13221811, upload-time = "2025-10-16T18:05:30.707Z" }, - { url = "https://files.pythonhosted.org/packages/c3/07/3c45562c67933cc35f6d5df4ca77dabbcd88fddaca0d6b8371693d29fd56/ruff-0.14.1-py3-none-win32.whl", hash = "sha256:e037ea374aaaff4103240ae79168c0945ae3d5ae8db190603de3b4012bd1def6", size = 12319467, upload-time = "2025-10-16T18:05:33.261Z" }, - { url = "https://files.pythonhosted.org/packages/02/88/0ee4ca507d4aa05f67e292d2e5eb0b3e358fbcfe527554a2eda9ac422d6b/ruff-0.14.1-py3-none-win_amd64.whl", hash = "sha256:59d599cdff9c7f925a017f6f2c256c908b094e55967f93f2821b1439928746a1", size = 13401123, upload-time = "2025-10-16T18:05:35.984Z" }, - { url = "https://files.pythonhosted.org/packages/b8/81/4b6387be7014858d924b843530e1b2a8e531846807516e9bea2ee0936bf7/ruff-0.14.1-py3-none-win_arm64.whl", hash = "sha256:e3b443c4c9f16ae850906b8d0a707b2a4c16f8d2f0a7fe65c475c5886665ce44", size = 12436636, upload-time = "2025-10-16T18:05:38.995Z" }, +version = "0.14.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/fa/fbb67a5780ae0f704876cb8ac92d6d76da41da4dc72b7ed3565ab18f2f52/ruff-0.14.5.tar.gz", hash = "sha256:8d3b48d7d8aad423d3137af7ab6c8b1e38e4de104800f0d596990f6ada1a9fc1", size = 5615944, upload-time = "2025-11-13T19:58:51.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/31/c07e9c535248d10836a94e4f4e8c5a31a1beed6f169b31405b227872d4f4/ruff-0.14.5-py3-none-linux_armv6l.whl", hash = "sha256:f3b8248123b586de44a8018bcc9fefe31d23dda57a34e6f0e1e53bd51fd63594", size = 13171630, upload-time = "2025-11-13T19:57:54.894Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/283c62516dca697cd604c2796d1487396b7a436b2f0ecc3fd412aca470e0/ruff-0.14.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72", size = 13413925, upload-time = "2025-11-13T19:57:59.181Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/aa319f4afc22cb6fcba2b9cdfc0f03bbf747e59ab7a8c5e90173857a1361/ruff-0.14.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d146132d1ee115f8802356a2dc9a634dbf58184c51bff21f313e8cd1c74899a", size = 12574040, upload-time = "2025-11-13T19:58:02.056Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7f/cb5845fcc7c7e88ed57f58670189fc2ff517fe2134c3821e77e29fd3b0c8/ruff-0.14.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2380596653dcd20b057794d55681571a257a42327da8894b93bbd6111aa801f", size = 13009755, upload-time = "2025-11-13T19:58:05.172Z" }, + { url = "https://files.pythonhosted.org/packages/21/d2/bcbedbb6bcb9253085981730687ddc0cc7b2e18e8dc13cf4453de905d7a0/ruff-0.14.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68", size = 12937641, upload-time = "2025-11-13T19:58:08.345Z" }, + { url = "https://files.pythonhosted.org/packages/a4/58/e25de28a572bdd60ffc6bb71fc7fd25a94ec6a076942e372437649cbb02a/ruff-0.14.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88f0770d42b7fa02bbefddde15d235ca3aa24e2f0137388cc15b2dcbb1f7c7a7", size = 13610854, upload-time = "2025-11-13T19:58:11.419Z" }, + { url = "https://files.pythonhosted.org/packages/7d/24/43bb3fd23ecee9861970978ea1a7a63e12a204d319248a7e8af539984280/ruff-0.14.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3676cb02b9061fee7294661071c4709fa21419ea9176087cb77e64410926eb78", size = 15061088, upload-time = "2025-11-13T19:58:14.551Z" }, + { url = "https://files.pythonhosted.org/packages/23/44/a022f288d61c2f8c8645b24c364b719aee293ffc7d633a2ca4d116b9c716/ruff-0.14.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b595bedf6bc9cab647c4a173a61acf4f1ac5f2b545203ba82f30fcb10b0318fb", size = 14734717, upload-time = "2025-11-13T19:58:17.518Z" }, + { url = "https://files.pythonhosted.org/packages/58/81/5c6ba44de7e44c91f68073e0658109d8373b0590940efe5bd7753a2585a3/ruff-0.14.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2", size = 14028812, upload-time = "2025-11-13T19:58:20.533Z" }, + { url = "https://files.pythonhosted.org/packages/ad/ef/41a8b60f8462cb320f68615b00299ebb12660097c952c600c762078420f8/ruff-0.14.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7497d19dce23976bdaca24345ae131a1d38dcfe1b0850ad8e9e6e4fa321a6e19", size = 13825656, upload-time = "2025-11-13T19:58:23.345Z" }, + { url = "https://files.pythonhosted.org/packages/7c/00/207e5de737fdb59b39eb1fac806904fe05681981b46d6a6db9468501062e/ruff-0.14.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:410e781f1122d6be4f446981dd479470af86537fb0b8857f27a6e872f65a38e4", size = 13959922, upload-time = "2025-11-13T19:58:26.537Z" }, + { url = "https://files.pythonhosted.org/packages/bc/7e/fa1f5c2776db4be405040293618846a2dece5c70b050874c2d1f10f24776/ruff-0.14.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c01be527ef4c91a6d55e53b337bfe2c0f82af024cc1a33c44792d6844e2331e1", size = 12932501, upload-time = "2025-11-13T19:58:29.822Z" }, + { url = "https://files.pythonhosted.org/packages/67/d8/d86bf784d693a764b59479a6bbdc9515ae42c340a5dc5ab1dabef847bfaa/ruff-0.14.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151", size = 12927319, upload-time = "2025-11-13T19:58:32.923Z" }, + { url = "https://files.pythonhosted.org/packages/ac/de/ee0b304d450ae007ce0cb3e455fe24fbcaaedae4ebaad6c23831c6663651/ruff-0.14.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d93be8f1fa01022337f1f8f3bcaa7ffee2d0b03f00922c45c2207954f351f465", size = 13206209, upload-time = "2025-11-13T19:58:35.952Z" }, + { url = "https://files.pythonhosted.org/packages/33/aa/193ca7e3a92d74f17d9d5771a765965d2cf42c86e6f0fd95b13969115723/ruff-0.14.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c135d4b681f7401fe0e7312017e41aba9b3160861105726b76cfa14bc25aa367", size = 13953709, upload-time = "2025-11-13T19:58:39.002Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f1/7119e42aa1d3bf036ffc9478885c2e248812b7de9abea4eae89163d2929d/ruff-0.14.5-py3-none-win32.whl", hash = "sha256:c83642e6fccfb6dea8b785eb9f456800dcd6a63f362238af5fc0c83d027dd08b", size = 12925808, upload-time = "2025-11-13T19:58:42.779Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9d/7c0a255d21e0912114784e4a96bf62af0618e2190cae468cd82b13625ad2/ruff-0.14.5-py3-none-win_amd64.whl", hash = "sha256:9d55d7af7166f143c94eae1db3312f9ea8f95a4defef1979ed516dbb38c27621", size = 14331546, upload-time = "2025-11-13T19:58:45.691Z" }, + { url = "https://files.pythonhosted.org/packages/e5/80/69756670caedcf3b9be597a6e12276a6cf6197076eb62aad0c608f8efce0/ruff-0.14.5-py3-none-win_arm64.whl", hash = "sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4", size = 13433331, upload-time = "2025-11-13T19:58:48.434Z" }, ] [[package]] @@ -1592,14 +1766,14 @@ wheels = [ [[package]] name = "sse-starlette" -version = "3.0.2" +version = "3.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/6f/22ed6e33f8a9e76ca0a412405f31abb844b779d52c5f96660766edcd737c/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a", size = 20985, upload-time = "2025-07-27T09:07:44.565Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/3c/fa6517610dc641262b77cc7bf994ecd17465812c1b0585fe33e11be758ab/sse_starlette-3.0.3.tar.gz", hash = "sha256:88cfb08747e16200ea990c8ca876b03910a23b547ab3bd764c0d8eb81019b971", size = 21943, upload-time = "2025-10-30T18:44:20.117Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/10/c78f463b4ef22eef8491f218f692be838282cd65480f6e423d7730dfd1fb/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a", size = 11297, upload-time = "2025-07-27T09:07:43.268Z" }, + { url = "https://files.pythonhosted.org/packages/23/a0/984525d19ca5c8a6c33911a0c164b11490dd0f90ff7fd689f704f84e9a11/sse_starlette-3.0.3-py3-none-any.whl", hash = "sha256:af5bf5a6f3933df1d9c7f8539633dc8444ca6a97ab2e2a7cd3b6e431ac03a431", size = 11765, upload-time = "2025-10-30T18:44:18.834Z" }, ] [[package]] @@ -1618,15 +1792,15 @@ wheels = [ [[package]] name = "starlette" -version = "0.48.0" +version = "0.50.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/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46", size = 2652949, upload-time = "2025-09-13T08:41:05.699Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" }, + { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, ] [[package]] From 5434d8dba8c30cc123f6f8ca95097abe4c127a86 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Tue, 18 Nov 2025 17:03:48 +0100 Subject: [PATCH 24/58] cocalc-api: tweak test config --- src/python/cocalc-api/tests/conftest.py | 185 ++++++++++++++++++-- src/python/cocalc-api/tests/test_jupyter.py | 87 ++++----- 2 files changed, 203 insertions(+), 69 deletions(-) diff --git a/src/python/cocalc-api/tests/conftest.py b/src/python/cocalc-api/tests/conftest.py index 9fa1eede30d..473e15afde3 100644 --- a/src/python/cocalc-api/tests/conftest.py +++ b/src/python/cocalc-api/tests/conftest.py @@ -10,11 +10,54 @@ from cocalc_api import Hub, Project from psycopg2 import pool as pg_pool +from typing import Callable, TypeVar, Any # Database configuration examples (DRY principle) PGHOST_SOCKET_EXAMPLE = "/path/to/cocalc-data/socket" PGHOST_NETWORK_EXAMPLE = "localhost" +T = TypeVar('T') + + +def retry_with_backoff( + func: Callable[[], T], + max_retries: int = 3, + retry_delay: int = 5, + error_condition: Callable[[RuntimeError], bool] = lambda e: any( + keyword in str(e).lower() for keyword in ["timeout", "closed", "connection", "reset", "broken"] + ), +) -> T: + """ + Retry a function call with exponential backoff for timeout and connection errors. + + This helper is useful for operations that may timeout or fail on first attempt due to + cold starts (e.g., kernel launches) or transient connection issues. + + Args: + func: Callable that performs the operation + max_retries: Maximum number of attempts (default: 3) + retry_delay: Delay in seconds between retries (default: 5) + error_condition: Function to determine if an error should trigger retry. + Defaults to checking for timeout/connection-related keywords. + + Returns: + The result of the function call + + Raises: + RuntimeError: If all retries fail or error condition doesn't match + """ + for attempt in range(max_retries): + try: + return func() + except RuntimeError as e: + error_msg = str(e).lower() + is_retryable = error_condition(e) + if is_retryable and attempt < max_retries - 1: + print(f"Attempt {attempt + 1} failed ({error_msg[:50]}...), retrying in {retry_delay}s...") + time.sleep(retry_delay) + else: + raise + def assert_valid_uuid(value, description="value"): """ @@ -125,6 +168,62 @@ def project_client(temporary_project, api_key, cocalc_host): return Project(project_id=temporary_project['project_id'], api_key=api_key, host=cocalc_host) +@pytest.fixture(autouse=True) +def cleanup_kernels_after_test(request, project_client): + """ + Clean up excess Jupyter kernels after test classes that use them. + + Kernel accumulation happens because the kernel pool reuses kernels, but under + heavy test load, old kernels aren't always properly cleaned up by the pool. + This fixture cleans up accumulated kernels BETWEEN test classes (not between + individual tests) to avoid interfering with the pool's reuse strategy. + + The fixture only runs for tests in classes that deal with Jupyter kernels + (TestJupyterExecuteViaHub, TestJupyterExecuteViaProject, TestJupyterKernelManagement) + to avoid interfering with other tests. + """ + yield # Allow test to run + + # Only cleanup for Jupyter-related tests + test_class = request.cls + if test_class is None: + return + + jupyter_test_classes = { + 'TestJupyterExecuteViaHub', + 'TestJupyterExecuteViaProject', + 'TestJupyterKernelManagement', + } + + if test_class.__name__ not in jupyter_test_classes: + return + + # Clean up accumulated kernels carefully + # Only cleanup if we have more kernels than the pool can manage (> 3) + # This gives some buffer to the pool's reuse mechanism + try: + import time + kernels = project_client.system.list_jupyter_kernels() + + # Only cleanup if significantly over pool size (pool size is 2) + # We use threshold of 3 to trigger cleanup + if len(kernels) > 3: + # Keep the 2 most recent kernels (higher PIDs), stop older ones + kernels_sorted = sorted(kernels, key=lambda k: k.get("pid", 0)) + kernels_to_stop = kernels_sorted[:-2] # All but the 2 newest + + for kernel in kernels_to_stop: + try: + project_client.system.stop_jupyter_kernel(pid=kernel["pid"]) + time.sleep(0.1) # Small delay between kills + except Exception: + # Silently ignore individual kernel failures + pass + except Exception: + # If listing kernels fails, just continue + pass + + def ensure_python3_kernel(project_client: Project): """ Ensure the default python3 Jupyter kernel is installed in the project. @@ -132,32 +231,47 @@ def ensure_python3_kernel(project_client: Project): If not available, install ipykernel and register the kernelspec. """ - def has_python_kernel() -> bool: + def try_exec(command: list[str], timeout: int = 60, capture_stdout: bool = False): try: result = project_client.system.exec( - command="python3", - args=["-m", "jupyter", "kernelspec", "list", "--json"], - timeout=60, + command=command[0], + args=command[1:], + timeout=timeout, ) - data = json.loads(result["stdout"]) - kernelspecs = data.get("kernelspecs", {}) - return "python3" in kernelspecs + return (True, result["stdout"] if capture_stdout else None) + except Exception as err: + print(f"Warning: command {command} failed: {err}") + return (False, None) + + def has_python_kernel() -> bool: + ok, stdout = try_exec( + ["python3", "-m", "jupyter", "kernelspec", "list", "--json"], + capture_stdout=True, + ) + if not ok or stdout is None: + return False + try: + data = json.loads(stdout) + return "python3" in data.get("kernelspecs", {}) except Exception as err: - print(f"Warning: Failed to list kernelspecs: {err}") + print(f"Warning: Failed to parse kernelspec list: {err}") return False if has_python_kernel(): return print("Installing python3 kernelspec in project...") - project_client.system.exec( - command="python3", - args=["-m", "pip", "install", "--user", "ipykernel"], - timeout=300, - ) - project_client.system.exec( - command="python3", - args=[ + # Install pip if needed + try_exec(["python3", "-m", "ensurepip", "--user"], timeout=120) + # Upgrade pip but ignore errors (not fatal) + try_exec(["python3", "-m", "pip", "install", "--user", "--upgrade", "pip"], timeout=120) + + if not try_exec(["python3", "-m", "pip", "install", "--user", "ipykernel"], timeout=300): + raise RuntimeError("Failed to install ipykernel via pip") + + if not try_exec( + [ + "python3", "-m", "ipykernel", "install", @@ -166,7 +280,8 @@ def has_python_kernel() -> bool: "--display-name=Python 3", ], timeout=120, - ) + ): + raise RuntimeError("Failed to install python3 kernelspec") if not has_python_kernel(): raise RuntimeError("Failed to ensure python3 kernelspec is installed in project") @@ -500,3 +615,39 @@ def cleanup(): request.addfinalizer(cleanup) yield + + +@pytest.fixture(scope="session", autouse=True) +def cleanup_jupyter_kernels_session(project_client): + """ + Clean up all Jupyter kernels created during the test session. + + This session-scoped fixture ensures that all kernels spawned during testing + are properly terminated at the end of the test session. This prevents + orphaned processes from accumulating in the system. + + The fixture runs AFTER all tests complete (via yield), ensuring no + interference with test execution while still guaranteeing cleanup. + """ + yield # Allow all tests to run first + + # After all tests complete, clean up all remaining kernels + try: + kernels = project_client.system.list_jupyter_kernels() + if kernels: + print(f"\n{'='*70}") + print(f"CLEANING UP {len(kernels)} JUPYTER KERNELS FROM TEST SESSION") + print(f"{'='*70}") + for kernel in kernels: + try: + pid = kernel.get("pid") + result = project_client.system.stop_jupyter_kernel(pid=pid) + if result.get("success"): + print(f"✓ Stopped kernel PID {pid}") + else: + print(f"✗ Failed to stop kernel PID {pid}") + except Exception as e: + print(f"✗ Error stopping kernel: {e}") + print(f"{'='*70}\n") + except Exception as e: + print(f"Warning: Failed to clean up jupyter kernels: {e}") diff --git a/src/python/cocalc-api/tests/test_jupyter.py b/src/python/cocalc-api/tests/test_jupyter.py index 01b03e33e01..ecc6c587067 100644 --- a/src/python/cocalc-api/tests/test_jupyter.py +++ b/src/python/cocalc-api/tests/test_jupyter.py @@ -2,8 +2,10 @@ Tests for Jupyter kernel functionality. """ -import time -from typing import Optional +import pytest + +# Import helper from conftest +from tests.conftest import retry_with_backoff class TestJupyterKernelSetup: @@ -68,10 +70,15 @@ class TestJupyterExecuteViaHub: """Tests for executing code via hub.jupyter.execute().""" def test_execute_simple_sum(self, hub, temporary_project): - """Test executing a simple sum using the python3 kernel.""" + """Test executing a simple sum using the python3 kernel. + + Note: First execution may take longer as kernel needs to start up (30+ seconds). + """ project_id = temporary_project["project_id"] - result = hub.jupyter.execute(input="sum(range(100))", kernel="python3", project_id=project_id) + result = retry_with_backoff( + lambda: hub.jupyter.execute(input="sum(range(100))", kernel="python3", project_id=project_id) + ) # Check the result structure assert isinstance(result, dict) @@ -92,7 +99,9 @@ def test_execute_with_history(self, hub, temporary_project): """Test executing code with history context.""" project_id = temporary_project["project_id"] - result = hub.jupyter.execute(history=["a = 100"], input="sum(range(a + 1))", kernel="python3", project_id=project_id) + result = retry_with_backoff( + lambda: hub.jupyter.execute(history=["a = 100"], input="sum(range(a + 1))", kernel="python3", project_id=project_id) + ) # Check the result (sum of 0..100 = 5050) assert isinstance(result, dict) @@ -107,10 +116,15 @@ def test_execute_with_history(self, hub, temporary_project): assert first_output["data"]["text/plain"] == "5050" def test_execute_print_statement(self, hub, temporary_project): - """Test executing code that prints output.""" + """Test executing code that prints output. + + Note: First execution may take longer as kernel needs to start up (30+ seconds). + """ project_id = temporary_project["project_id"] - result = hub.jupyter.execute(input='print("Hello from Jupyter")', kernel="python3", project_id=project_id) + result = retry_with_backoff( + lambda: hub.jupyter.execute(input='print("Hello from Jupyter")', kernel="python3", project_id=project_id) + ) # Check that we got output assert isinstance(result, dict) @@ -138,21 +152,9 @@ def test_jupyter_execute_simple_sum(self, project_client): Note: First execution may take longer as kernel needs to start up (30+ seconds). """ - # Retry logic for first kernel startup - max_retries = 3 - retry_delay = 15 - result: Optional[list] = None - - for attempt in range(max_retries): - try: - result = project_client.system.jupyter_execute(input="sum(range(100))", kernel="python3") - break - except RuntimeError as e: - if "timeout" in str(e).lower() and attempt < max_retries - 1: - print(f"Attempt {attempt + 1} timed out, retrying in {retry_delay}s...") - time.sleep(retry_delay) - else: - raise + result = retry_with_backoff( + lambda: project_client.system.jupyter_execute(input="sum(range(100))", kernel="python3") + ) # Result is a list, not a dict with 'output' key assert isinstance(result, list) @@ -169,8 +171,12 @@ def test_jupyter_execute_with_history(self, project_client): Test executing code with history via project API. The result is a list of output items directly. + + Note: First execution may take longer as kernel needs to start up (30+ seconds). """ - result = project_client.system.jupyter_execute(history=["b = 50"], input="b * 2", kernel="python3") + result = retry_with_backoff( + lambda: project_client.system.jupyter_execute(history=["b = 50"], input="b * 2", kernel="python3") + ) # Result is a list assert isinstance(result, list) @@ -188,21 +194,9 @@ def test_jupyter_execute_list_operation(self, project_client): The result is a list of output items directly. """ - # Retry logic for kernel startup - max_retries = 3 - retry_delay = 15 - result: Optional[list] = None - - for attempt in range(max_retries): - try: - result = project_client.system.jupyter_execute(input="[x**2 for x in range(5)]", kernel="python3") - break - except RuntimeError as e: - if "timeout" in str(e).lower() and attempt < max_retries - 1: - print(f"Attempt {attempt + 1} timed out, retrying in {retry_delay}s...") - time.sleep(retry_delay) - else: - raise + result = retry_with_backoff( + lambda: project_client.system.jupyter_execute(input="[x**2 for x in range(5)]", kernel="python3") + ) # Result is a list assert isinstance(result, list) @@ -221,20 +215,9 @@ class TestJupyterKernelManagement: def test_list_jupyter_kernels(self, project_client): """Test listing running Jupyter kernels.""" # First execute some code to ensure a kernel is running - # Retry logic for first kernel startup (may take longer in CI) - max_retries = 3 - retry_delay = 15 - - for attempt in range(max_retries): - try: - project_client.system.jupyter_execute(input="1+1", kernel="python3") - break - except RuntimeError as e: - if "timeout" in str(e).lower() and attempt < max_retries - 1: - print(f"Attempt {attempt + 1} timed out, retrying in {retry_delay}s...") - time.sleep(retry_delay) - else: - raise + retry_with_backoff( + lambda: project_client.system.jupyter_execute(input="1+1", kernel="python3") + ) # List kernels kernels = project_client.system.list_jupyter_kernels() From 7d39dcb8a1be4990ca8d5938102848d1694688e9 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Wed, 19 Nov 2025 19:02:04 +0100 Subject: [PATCH 25/58] cocalc-api/mcp: jupyter exec and more test fixes --- src/cocalc.code-workspace | 3 + src/packages/server/api/project-bridge.ts | 7 + .../src/cocalc_api/mcp/DEVELOPMENT.md | 36 +++++ .../cocalc-api/src/cocalc_api/mcp/README.md | 23 ++- .../src/cocalc_api/mcp/mcp_debug.py | 1 - .../src/cocalc_api/mcp/mcp_server.py | 89 +++++++---- .../src/cocalc_api/mcp/tools/__init__.py | 3 + .../src/cocalc_api/mcp/tools/exec.py | 19 ++- .../src/cocalc_api/mcp/tools/jupyter.py | 145 ++++++++++++++++++ src/python/cocalc-api/tests/conftest.py | 5 +- src/python/cocalc-api/tests/test_jupyter.py | 6 +- 11 files changed, 295 insertions(+), 42 deletions(-) create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/tools/jupyter.py diff --git a/src/cocalc.code-workspace b/src/cocalc.code-workspace index 5b0008c220a..535fcc22f83 100644 --- a/src/cocalc.code-workspace +++ b/src/cocalc.code-workspace @@ -3,6 +3,9 @@ { "name": "cocalc", "path": "." + }, + { + "path": "../.github" } ], "settings": { diff --git a/src/packages/server/api/project-bridge.ts b/src/packages/server/api/project-bridge.ts index 2939a3fbbcc..5cb101f987f 100644 --- a/src/packages/server/api/project-bridge.ts +++ b/src/packages/server/api/project-bridge.ts @@ -3,6 +3,7 @@ import { projectSubject } from "@cocalc/conat/names"; import { conat } from "@cocalc/backend/conat"; import { type Client as ConatClient } from "@cocalc/conat/core/client"; +import { getProject } from "@cocalc/server/projects/control"; const DEFAULT_TIMEOUT = 15000; let client: ConatClient | null = null; @@ -51,6 +52,12 @@ async function callProject({ service: "api", }); try { + // Ensure the project is running before making the API call + const project = getProject(project_id); + if (project) { + await project.start(); + } + // For system.test(), inject project_id into args[0] if not already present let finalArgs = args; if (name === "system.test" && (!args || args.length === 0)) { diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md b/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md index 682bc6ae502..52d5b1a63c7 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md +++ b/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md @@ -123,6 +123,7 @@ The MCP server exposes high-level metadata that clients can query to understand **Protocol Version** - MCP 2025-06-18 Clients can retrieve this metadata using the MCP `initialize` call, which returns: + - `serverInfo.name` - "cocalc-api" - `serverInfo.version` - Version from package metadata - `instructions` - High-level guide (see above) @@ -192,6 +193,41 @@ Execute arbitrary shell commands in the CoCalc project. {"command": "echo 'Hello'"} {"command": "python", "args": ["script.py", "--verbose"]} {"command": "for i in {1..3}; do echo $i; done", "bash": true} +{"command": "jupyter kernelspec list"} +{"command": "python3", "args": ["-m", "pip", "install", "--user", "ipykernel"]} +``` + +#### `jupyter_execute` - Execute Code in Jupyter Kernels + +Execute code using Jupyter kernels with rich output, preserved state, and support for multiple languages. + +**Parameters:** + +- `input` (string, required): Code to execute +- `kernel` (string, optional): Kernel name (default: "python3") + - Common kernels: `python3`, `ir` (R), `julia-1.9` + - Use `exec` tool with `jupyter kernelspec list` to discover available kernels +- `history` (list, optional): Previous code inputs to establish context + - Executed without capturing output, allows setting up variables and imports + +**Returns:** Formatted execution output (text, plots, dataframes, images, errors, etc.) + +**Examples:** + +```json +{"input": "2 + 2", "kernel": "python3"} +{"input": "import pandas as pd\ndf = pd.DataFrame({'a': [1,2,3]})\ndf", "kernel": "python3"} +{"input": "import matplotlib.pyplot as plt\nplt.plot([1,2,3])\nplt.show()", "kernel": "python3"} +{"input": "summary(cars)", "kernel": "ir"} +``` + +**Setup Instructions:** + +Before using Jupyter kernels, set them up with the `exec` tool: + +```json +{"command": "python3", "args": ["-m", "pip", "install", "--user", "ipykernel"], "timeout": 300} +{"command": "python3", "args": ["-m", "ipykernel", "install", "--user", "--name=python3", "--display-name=Python 3"], "timeout": 120} ``` ### Resources diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/README.md b/src/python/cocalc-api/src/cocalc_api/mcp/README.md index 104b8c3da87..4dc0594ae3d 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/README.md +++ b/src/python/cocalc-api/src/cocalc_api/mcp/README.md @@ -55,6 +55,7 @@ claude mcp add-json cocalc '{ ``` **Important:** + - Replace `/path/to/cocalc/src/python/cocalc-api` with the absolute path to your cocalc-api directory. - Replace `http://localhost:5000` with your CoCalc instance URL (defaults to `https://cocalc.com` if not set). @@ -67,7 +68,12 @@ Add to `~/.config/Claude/claude_desktop_config.json`: "mcpServers": { "cocalc": { "command": "uv", - "args": ["--directory", "/path/to/cocalc/src/python/cocalc-api", "run", "cocalc-mcp-server"], + "args": [ + "--directory", + "/path/to/cocalc/src/python/cocalc-api", + "run", + "cocalc-mcp-server" + ], "env": { "COCALC_API_KEY": "sk-your-api-key-here", "COCALC_PROJECT_ID": "[UUID]", @@ -79,6 +85,7 @@ Add to `~/.config/Claude/claude_desktop_config.json`: ``` **Important:** + - Replace `/path/to/cocalc/src/python/cocalc-api` with the absolute path to your cocalc-api directory. - Replace `http://localhost:5000` with your CoCalc instance URL (defaults to `https://cocalc.com` if not set). @@ -88,13 +95,12 @@ To automatically allow all CoCalc MCP tools without prompts, add this to `.claud ```json { - "allowedTools": [ - "mcp__cocalc__*" - ] + "allowedTools": ["mcp__cocalc__*"] } ``` This wildcard pattern (`mcp__cocalc__*`) automatically allows: + - `mcp__cocalc__exec` - Execute shell commands - `mcp__cocalc__project_files` - Browse project files - Any future tools added to the MCP server @@ -104,12 +110,20 @@ This wildcard pattern (`mcp__cocalc__*`) automatically allows: ### Tools - **`exec`** - Execute shell commands in the project + ``` Tool: exec Params: command (required), args, bash, timeout, cwd Returns: {stdout, stderr, exit_code} ``` +- **`jupyter_execute`** - Execute code using Jupyter kernels + ``` + Tool: jupyter_execute + Params: input (required), kernel (default: "python3"), history + Returns: Formatted execution output (text, plots, errors, etc.) + ``` + ### Resources - **`project-files`** - Browse project files with filtering and pagination @@ -121,6 +135,7 @@ This wildcard pattern (`mcp__cocalc__*`) automatically allows: ## Documentation See [DEVELOPMENT.md](./DEVELOPMENT.md) for: + - Architecture and design principles - Detailed API specifications - Configuration options diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_debug.py b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_debug.py index a0fdce5547c..262a28f7cd7 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_debug.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_debug.py @@ -19,7 +19,6 @@ """ import asyncio -import json import os import sys diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py index ef219ac5769..d6b4cc1ce31 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py @@ -6,6 +6,8 @@ AVAILABLE TOOLS (actions you can perform): - exec: Run shell commands, scripts, and programs in the project Use for: running code, data processing, build/test commands, git operations, etc. +- jupyter_execute: Execute code using Jupyter kernels (Python, R, Julia, etc.) + Use for: interactive code execution, data analysis, visualization, scientific computing AVAILABLE RESOURCES (information you can read): - project-files: Browse the project file structure @@ -33,6 +35,7 @@ import os import sys +import time from typing import Optional from mcp.server.fastmcp import FastMCP @@ -40,6 +43,32 @@ from cocalc_api import Project, Hub +def _retry_with_backoff(func, max_retries: int = 3, retry_delay: int = 2): + """ + Retry a function with exponential backoff for transient failures. + + Used during server initialization for operations that may timeout on cold starts. + """ + for attempt in range(max_retries): + try: + return func() + except Exception as e: + error_msg = str(e).lower() + is_retryable = any( + keyword in error_msg + for keyword in ["timeout", "closed", "connection", "reset", "broken"] + ) + if is_retryable and attempt < max_retries - 1: + print( + f"Initialization attempt {attempt + 1} failed ({error_msg[:50]}...), " + f"retrying in {retry_delay}s...", + file=sys.stderr, + ) + time.sleep(retry_delay) + else: + raise + + def get_config() -> tuple[str, str, Optional[str]]: """ Get and validate MCP server configuration. @@ -106,6 +135,7 @@ def check_api_key_scope(api_key: str, host: str) -> dict[str, str]: This server gives you direct access to a CoCalc project or account environment through the Model Context Protocol (MCP). WHAT YOU CAN DO: +- Execute code using Jupyter kernels (Python, R, Julia, etc.) with rich output and visualization support - Execute arbitrary shell commands in a Linux environment with Python, Node.js, R, Julia, and 100+ tools - Browse and explore project files to understand structure and contents - Run code, scripts, and build/test commands @@ -114,11 +144,14 @@ def check_api_key_scope(api_key: str, host: str) -> dict[str, str]: HOW TO USE: 1. Start by exploring the project structure using the project-files resource -2. Use the exec tool to run commands, scripts, or programs -3. Combine multiple commands to accomplish complex workflows +2. Use jupyter_execute for interactive code execution with rich output (plots, tables, etc.) +3. Use exec tool to run shell commands, scripts, or programs +4. Combine multiple commands to accomplish complex workflows EXAMPLES: -- Execute Python: exec with command="python3 script.py --verbose" +- Execute Python interactively: jupyter_execute with input="import pandas as pd; df = pd.read_csv('data.csv'); df.describe()" +- Data visualization: jupyter_execute with input="import matplotlib.pyplot as plt; plt.plot([1,2,3]); plt.show()" +- Execute shell command: exec with command="python3 script.py --verbose" - List files: use project-files resource or exec with command="ls -la" - Run tests: exec with command="pytest tests/" bash=true - Git operations: exec with command="git log --oneline" in your repository @@ -157,22 +190,12 @@ def _initialize_config() -> None: try: _api_key_scope = check_api_key_scope(_api_key, _host) except RuntimeError as check_error: - # If it's a project-scoped key error, try the project API to discover the project_id + # If it's a project-scoped key error, use a placeholder project_id + # Project-scoped keys have the project_id embedded in the key itself if "project-scoped" in str(check_error): - try: - # Try with empty project_id - project-scoped keys will use their own - project = Project(api_key=_api_key, project_id="", host=_host) - result = project.system.ping() - # Check if the response includes project_id (it shouldn't from ping, but try anyway) - if isinstance(result, dict) and "project_id" in result: - _api_key_scope = {"project_id": result["project_id"]} - else: - # If we still don't have it, this is an error - raise RuntimeError("Could not determine project_id from project-scoped API key. " - "Please restart with COCALC_PROJECT_ID environment variable.") - except Exception as project_error: - raise RuntimeError(f"Project-scoped API key detected but could not determine project_id. " - f"Error: {project_error}") from project_error + # Use empty string as project_id - the Project client will extract it from the API key + _api_key_scope = {"project_id": ""} + print("✓ Connected with project-scoped API key", file=sys.stderr) else: raise @@ -181,12 +204,15 @@ def _initialize_config() -> None: print(f"✓ Connected with account-scoped API key (account: {account_id})", file=sys.stderr) elif "project_id" in _api_key_scope: project_id = _api_key_scope["project_id"] - if not project_id: - raise RuntimeError("Project ID not found for project-scoped API key") - print(f"✓ Connected with project-scoped API key (project: {project_id})", file=sys.stderr) - # For project-scoped keys, eagerly create the project client - client = Project(api_key=_api_key, project_id=project_id, host=_host) - _project_clients[project_id] = client + # For project-scoped keys with empty/None project_id, the Project client will extract it from the API key + if project_id: + print(f"✓ Connected with project-scoped API key (project: {project_id})", file=sys.stderr) + # For project-scoped keys, eagerly create the project client + client = Project(api_key=_api_key, project_id=project_id, host=_host) + _project_clients[project_id] = client + else: + # Project-scoped key with empty project_id - will be discovered on first use + print("✓ Connected with project-scoped API key (project ID will be discovered on first use)", file=sys.stderr) else: # If we got here with no project_id but it might be project-scoped, check if COCALC_PROJECT_ID was provided if project_id_config: @@ -232,19 +258,24 @@ def get_project_client(project_id: Optional[str] = None) -> Project: raise RuntimeError("Account-scoped API key requires an explicit project_id argument. " "No project_id provided to get_project_client().") - if not project_id: - raise RuntimeError("Project ID cannot be empty") + # For project-scoped keys with None/empty project_id, the Project client will extract it from the API key + # For account-scoped keys, project_id must be non-empty + if not project_id and _api_key_scope and "account_id" in _api_key_scope: + raise RuntimeError("Account-scoped API key requires a non-empty project_id") + + # Use a cache key that handles None/empty project_id for project-scoped keys + cache_key = project_id if project_id else "_default_project" # Return cached client if available - if project_id in _project_clients: - return _project_clients[project_id] + if cache_key in _project_clients: + return _project_clients[cache_key] # Create new project client # At this point, _api_key and _host are guaranteed to be non-None (set in _initialize_config) assert _api_key is not None assert _host is not None client = Project(api_key=_api_key, project_id=project_id, host=_host) - _project_clients[project_id] = client + _project_clients[cache_key] = client return client diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py index d626c5c105b..a224db05353 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py @@ -5,6 +5,7 @@ Available Tools: - exec: Execute shell commands, scripts, and programs in the project environment +- jupyter_execute: Execute code using Jupyter kernels with rich output and interactive state See mcp_server.py for overview of all available tools and resources, and guidance on when to use each one. @@ -14,5 +15,7 @@ def register_tools(mcp) -> None: """Register all tools with the given FastMCP instance.""" from .exec import register_exec_tool + from .jupyter import register_jupyter_tool register_exec_tool(mcp) + register_jupyter_tool(mcp) diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py index 2eae9174e41..4ddaac34728 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py @@ -26,17 +26,28 @@ async def exec( Execute shell commands in a CoCalc project environment. A CoCalc project is a containerized Linux environment (Ubuntu-based) where you can run - arbitrary command-line tools and scripts. This tool allows you to execute any shell - command available in that environment, including: - - Programming language interpreters (Python, Node.js, R, Julia, etc.) - - System utilities (grep, awk, sed, find, etc.) + arbitrary command-line tools and scripts. This tool is best for system operations, + installing packages, and running compiled programs. For executing Python, R, or Julia code + with rich output (plots, dataframes, etc.), use the jupyter_execute tool instead. + + This tool allows you to execute any shell command available in that environment, including: + - System utilities (grep, awk, sed, find, ls, etc.) - Development tools (git, npm, pip, cargo, etc.) - Data processing tools (bc, jq, imagemagick, etc.) - Custom scripts and compiled programs + - Package management and environment setup The command executes in the project's Linux shell environment with access to the project's file system and all installed packages/tools. + Common use cases: + - List available Jupyter kernels: exec(command="jupyter kernelspec list") + - Install packages: exec(command="pip", args=["install", "pandas"]) + - Setup Jupyter kernel: exec(command="python3", args=["-m", "ipykernel", "install", "--user", "--name=python3", "--display-name=Python 3"]) + - Git operations: exec(command="git", args=["status"]) + - File operations: exec(command="find", args=[".", "-name", "*.py"]) + - Execute complex pipelines: exec(command="cat data.txt | grep pattern | wc -l", bash=True) + Args: command: The command to execute (e.g., 'ls -la', 'python script.py', 'echo 2 + 3 | bc') args: Optional list of arguments to pass to the command diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/jupyter.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/jupyter.py new file mode 100644 index 00000000000..e61a8310c76 --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/jupyter.py @@ -0,0 +1,145 @@ +""" +Jupyter kernel code execution in CoCalc project. + +Provides the 'jupyter_execute' tool that allows running code using Jupyter kernels +in the target CoCalc project environment. +""" + +import time +from typing import Optional, TYPE_CHECKING, Callable, TypeVar + +if TYPE_CHECKING: + pass + +T = TypeVar("T") + + +def _retry_with_backoff( + func: Callable[[], T], + max_retries: int = 3, + retry_delay: int = 5, + error_condition: Callable[[Exception], bool] = lambda e: any( + keyword in str(e).lower() + for keyword in ["timeout", "closed", "connection", "reset", "broken"] + ), +) -> T: + """ + Retry a function call with exponential backoff for transient failures. + + Useful for operations that may timeout on cold starts (e.g., kernel launches) + or fail due to transient connection issues. + """ + for attempt in range(max_retries): + try: + return func() + except Exception as e: + is_retryable = error_condition(e) + if is_retryable and attempt < max_retries - 1: + time.sleep(retry_delay) + else: + raise + + # This should never be reached due to the loop, but mypy needs this + raise RuntimeError("Retry loop exhausted without returning") + + +def register_jupyter_tool(mcp) -> None: + """Register the jupyter_execute tool with the given FastMCP instance.""" + + @mcp.tool() + async def jupyter_execute( + input: str, + kernel: str = "python3", + history: Optional[list[str]] = None, + ) -> str: + """ + Execute code using a Jupyter kernel in the CoCalc project. + + This is the primary tool for executing Python, R, Julia, or other code with rich output. + Use this instead of 'exec' for any code that needs rich formatting or visualization. + + Jupyter kernels provide rich, interactive code execution with support for multiple + programming languages and integrated visualization. Key features: + - Interactive code execution with preserved state between calls + - Rich output formatting (plots, dataframes, formatted tables, etc.) + - Access to installed data science libraries (NumPy, Pandas, Matplotlib, etc.) + - Display of images, HTML, LaTeX, and other media types + - Support for different languages (Python, R, Julia, etc.) + + The kernel maintains state across multiple execute calls, so variables defined in + one call are available in subsequent calls. + + Common use cases: + - Computations: jupyter_execute(input="sum(range(10, 1001))", kernel="python3") + - Data analysis: jupyter_execute(input="import pandas as pd; df = pd.read_csv('data.csv'); df.head()", kernel="python3") + - Visualization: jupyter_execute(input="import matplotlib.pyplot as plt; plt.plot([1,2,3]); plt.show()", kernel="python3") + - Statistical computing: jupyter_execute(input="summary(data)", kernel="ir") # R kernel + + To set up Jupyter kernels, use the 'exec' tool: + 1. Install ipykernel: exec(command="python3", args=["-m", "pip", "install", "--user", "ipykernel"]) + 2. Register Python kernel: exec(command="python3", args=["-m", "ipykernel", "install", "--user", "--name=python3", "--display-name=Python 3"]) + 3. List available kernels: exec(command="jupyter kernelspec list") + + Args: + input: Code to execute in the kernel (required) + kernel: Name of the kernel to use (default: "python3"). Use 'exec' tool with + "jupyter kernelspec list" to discover available kernels. + history: Optional list of previous code inputs to establish context. These are + executed without capturing output, allowing you to set up variables + and imports before the main input. + + Returns: + A string containing the execution output (stdout, plots, results, errors, etc.) + """ + from ..mcp_server import get_project_client + + try: + project = get_project_client() + + # Use retry logic to handle cold starts and transient connection failures + # The jupyter_execute call may timeout if the project is not running, + # so we retry multiple times with delays to allow the project to start. + result = _retry_with_backoff( + lambda: project.system.jupyter_execute( + input=input, + kernel=kernel, + history=history, + timeout=30, + ), + max_retries=3, + retry_delay=5, + ) + + # Format output items into readable text + output_lines = [] + for item in result: + if isinstance(item, dict): + if "data" in item: + # Rich output (result of expression) + data = item["data"] + if "text/plain" in data: + output_lines.append(data["text/plain"]) + elif "text/html" in data: + output_lines.append(f"[HTML]\n{data['text/html']}") + elif "text/latex" in data: + output_lines.append(f"[LaTeX]\n{data['text/latex']}") + elif "image/png" in data: + output_lines.append("[Image: PNG]") + elif "image/jpeg" in data: + output_lines.append("[Image: JPEG]") + elif "name" in item and "text" in item: + # Stream output (print, stderr, etc.) + output_lines.append(f"[{item['name']}] {item['text'].rstrip()}") + elif "ename" in item: + # Error output + output_lines.append(f"[Error: {item.get('ename', 'Exception')}] {item.get('evalue', '')}") + if "traceback" in item: + output_lines.append("\n".join(item["traceback"])) + + if not output_lines: + return "No output from kernel execution" + + return "\n".join(output_lines) + + except Exception as e: + return f"Error executing code in kernel: {str(e)}" diff --git a/src/python/cocalc-api/tests/conftest.py b/src/python/cocalc-api/tests/conftest.py index 473e15afde3..bef833838f7 100644 --- a/src/python/cocalc-api/tests/conftest.py +++ b/src/python/cocalc-api/tests/conftest.py @@ -10,7 +10,7 @@ from cocalc_api import Hub, Project from psycopg2 import pool as pg_pool -from typing import Callable, TypeVar, Any +from typing import Callable, TypeVar # Database configuration examples (DRY principle) PGHOST_SOCKET_EXAMPLE = "/path/to/cocalc-data/socket" @@ -58,6 +58,9 @@ def retry_with_backoff( else: raise + # This should never be reached due to the loop, but mypy needs this + raise RuntimeError("Retry loop exhausted without returning") + def assert_valid_uuid(value, description="value"): """ diff --git a/src/python/cocalc-api/tests/test_jupyter.py b/src/python/cocalc-api/tests/test_jupyter.py index ecc6c587067..57164cc1a79 100644 --- a/src/python/cocalc-api/tests/test_jupyter.py +++ b/src/python/cocalc-api/tests/test_jupyter.py @@ -2,8 +2,6 @@ Tests for Jupyter kernel functionality. """ -import pytest - # Import helper from conftest from tests.conftest import retry_with_backoff @@ -238,7 +236,9 @@ def test_list_jupyter_kernels(self, project_client): def test_stop_jupyter_kernel(self, project_client): """Test stopping a specific Jupyter kernel.""" # Execute code to start a kernel - project_client.system.jupyter_execute(input="1+1", kernel="python3") + retry_with_backoff( + lambda: project_client.system.jupyter_execute(input="1+1", kernel="python3") + ) # List kernels kernels = project_client.system.list_jupyter_kernels() From 3d2ba2fb1bf47e70add4864813f9a76f480a4e3b Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Thu, 20 Nov 2025 10:49:54 +0100 Subject: [PATCH 26/58] cocalc-api/mcp: dynamic tool and resource registration based on api key type --- .../src/cocalc_api/mcp/DEVELOPMENT.md | 394 +++++++----------- .../cocalc-api/src/cocalc_api/mcp/README.md | 159 ++----- .../src/cocalc_api/mcp/mcp_server.py | 66 ++- .../src/cocalc_api/mcp/resources/__init__.py | 19 +- .../mcp/resources/account_profile.py | 138 ++++++ .../src/cocalc_api/mcp/tools/__init__.py | 21 +- .../src/cocalc_api/mcp/tools/jupyter.py | 17 +- .../cocalc_api/mcp/tools/projects_search.py | 154 +++++++ 8 files changed, 557 insertions(+), 411 deletions(-) create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/resources/account_profile.py create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/tools/projects_search.py diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md b/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md index 52d5b1a63c7..4c973265c8f 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md +++ b/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md @@ -1,135 +1,10 @@ -# CoCalc API MCP Server - Development Guide +# CoCalc MCP Server - Development Guide ## Overview -This directory contains the **Model Context Protocol (MCP) server** for CoCalc API. It allows LLMs (via Claude Code, Claude Desktop) to interact with CoCalc projects through a standardized protocol. +This is the Model Context Protocol (MCP) server for CoCalc, allowing LLMs to interact with CoCalc accounts and projects through a standardized interface. -Learn more: https://modelcontextprotocol.io/docs/getting-started/intro - -## Configuration - -### Required Environment Variables - -- **`COCALC_API_KEY`** - API key for CoCalc authentication (format: `sk-...`, can be account-scoped or project-scoped) -- **`COCALC_HOST`** (optional) - CoCalc instance URL (default: `https://cocalc.com`) - -### Setup Examples - -**Local Development (Recommended: Project-Scoped API Key):** - -Create a project-scoped API key in your CoCalc project settings: - -```bash -export COCALC_API_KEY="sk-your-project-api-key-here" -export COCALC_HOST="http://localhost:5000" # For local CoCalc, or omit for cocalc.com -uv run cocalc-mcp-server -``` - -When started, the server will report: - -``` -✓ Connected with project-scoped API key (project: 6e75dbf1-0342-4249-9dce-6b21648656e9) -``` - -**Alternative: Account-Scoped API Key:** - -Create an account-scoped API key in your CoCalc account settings (Settings → API keys): - -```bash -export COCALC_API_KEY="sk-your-account-api-key-here" -uv run cocalc-mcp-server -``` - -When started, the server will report: - -``` -✓ Connected with account-scoped API key (account: d0bdabfd-850e-4c8d-8510-f6f1ecb9a5eb) -``` - -**Claude Code CLI (Project-Scoped Key - Recommended):** - -```bash -claude mcp add \ - --transport stdio \ - cocalc \ - --env COCALC_API_KEY="sk-your-project-api-key-here" \ - -- uv --directory /path/to/cocalc-api run cocalc-mcp-server -``` - -**Claude Code CLI (Account-Scoped Key):** - -```bash -claude mcp add \ - --transport stdio \ - cocalc \ - --env COCALC_API_KEY="sk-your-account-api-key-here" \ - -- uv --directory /path/to/cocalc-api run cocalc-mcp-server -``` - -**Claude Desktop (Project-Scoped Key - Recommended):** - -Add to `~/.config/Claude/claude_desktop_config.json`: - -```json -{ - "mcpServers": { - "cocalc": { - "command": "uv", - "args": [ - "--directory", - "/path/to/cocalc-api", - "run", - "cocalc-mcp-server" - ], - "env": { - "COCALC_API_KEY": "sk-your-project-api-key-here" - } - } - } -} -``` - -**Claude Desktop (Account-Scoped Key):** - -Add to `~/.config/Claude/claude_desktop_config.json`: - -```json -{ - "mcpServers": { - "cocalc": { - "command": "uv", - "args": [ - "--directory", - "/path/to/cocalc-api", - "run", - "cocalc-mcp-server" - ], - "env": { - "COCALC_API_KEY": "sk-your-account-api-key-here" - } - } - } -} -``` - -## Server Metadata - -The MCP server exposes high-level metadata that clients can query to understand what the server provides: - -**Server Instructions** - A comprehensive guide to the server's capabilities, usage, and examples. Sent to LLMs to provide context about what tools and resources are available. - -**Website URL** - Link to documentation (https://cocalc.com/api/python) - -**Protocol Version** - MCP 2025-06-18 - -Clients can retrieve this metadata using the MCP `initialize` call, which returns: - -- `serverInfo.name` - "cocalc-api" -- `serverInfo.version` - Version from package metadata -- `instructions` - High-level guide (see above) -- `capabilities` - What features the server supports (tools, resources, etc.) - -This allows LLM clients to understand the server's purpose and capabilities before making any requests. +Learn more about MCP: https://modelcontextprotocol.io/ ## Architecture @@ -137,183 +12,200 @@ This allows LLM clients to understand the server's purpose and capabilities befo ``` src/cocalc_api/mcp/ -├── server.py # Entry point, imports mcp_server -├── mcp_server.py # Central coordination hub +├── server.py # Entry point +├── mcp_server.py # Core initialization & dynamic registration ├── tools/ -│ ├── __init__.py # register_tools(mcp) -│ └── exec.py # register_exec_tool(mcp) +│ ├── __init__.py # Dynamic tool registration +│ ├── exec.py # Shell command tool (project-scoped) +│ ├── jupyter.py # Jupyter execution tool (project-scoped) +│ └── projects_search.py # Project search tool (account-scoped) └── resources/ - ├── __init__.py # register_resources(mcp) - └── file_listing.py # register_file_listing_resource(mcp) + ├── __init__.py # Dynamic resource registration + ├── file_listing.py # File browsing (project-scoped) + └── account_profile.py # Account info (account-scoped) ``` -### Initialization Flow +### Key Design: Dynamic Registration -1. **`server.py`** imports `mcp_server` module -2. **`mcp_server.py`** initializes at import time: - - Creates `FastMCP("cocalc-api")` instance with: - - `instructions` - High-level guide for LLM clients - - `website_url` - Link to documentation - - Initializes `Project` client (lazy, cached) - - Calls `tools.register_tools(mcp)` - - Calls `resources.register_resources(mcp)` -3. **`tools/` and `resources/`** register their handlers with the shared `mcp` object -4. **`server.py`** calls `mcp.run(transport="stdio")` -5. When clients connect, the server responds to `initialize` with metadata including instructions +The server detects your API key type at startup and registers **only the appropriate tools and resources**: -### Key Design Decisions +**Account-Scoped Keys** (can access multiple projects): +- Tool: `projects_search` - Find and list projects +- Resource: `account-profile` - View account settings -- **Single Project Client**: Initialized once, shared across all tools/resources -- **FastMCP Framework**: Automatic JSON-RPC handling, clean decorator pattern -- **No Wrapper Functions**: Tools/resources decorated directly in their modules -- **Dependency Injection**: mcp object passed to registration functions -- **Easy Extension**: Add new tool by creating `tools/my_tool.py` with `register_my_tool(mcp)` function +**Project-Scoped Keys** (limited to one project): +- Tools: `exec`, `jupyter_execute` - Run code in project +- Resource: `project-files` - Browse project files -## Available Tools & Resources +This design prevents exposing tools that cannot work with a given API key type. -### Tools +### Initialization Flow + +1. `server.py` calls `mcp.run()` which imports `mcp_server` +2. `mcp_server.py` initializes at import time: + - Creates `FastMCP` instance with instructions and metadata + - Calls `_initialize_config()` to validate API key and determine scope + - Calls `_register_tools_and_resources()` which: + - Checks if key is account-scoped or project-scoped + - Imports and registers only matching tools/resources +3. Server is ready to handle client requests + +### Key Components -#### `exec` - Execute Shell Commands +**`mcp_server.py`:** +- `_initialize_config()` - Validates API key and detects scope +- `_register_tools_and_resources()` - Registers tools based on scope +- `get_project_client()` - Lazy-loads Project client (for project-scoped keys) +- Global state: `_api_key`, `_host`, `_api_key_scope` -Execute arbitrary shell commands in the CoCalc project. +**Tool/Resource Functions:** +- Must be **synchronous** (not async) for FastMCP +- Decorated with `@mcp.tool()` or `@mcp.resource()` +- Access configuration via: `from ..mcp_server import _api_key, _host, _api_key_scope` -**Parameters:** +## Adding a New Tool -- `command` (string, required): Command to execute -- `args` (list, optional): Command arguments -- `bash` (boolean, optional): Interpret as bash script -- `timeout` (integer, optional): Timeout in seconds -- `cwd` (string, optional): Working directory +### For Project-Scoped Keys -**Returns:** stdout, stderr, and exit_code +Create `tools/my_tool.py`: -**Examples:** +```python +def register_my_tool(mcp) -> None: + """Register my tool with FastMCP.""" -```json -{"command": "echo 'Hello'"} -{"command": "python", "args": ["script.py", "--verbose"]} -{"command": "for i in {1..3}; do echo $i; done", "bash": true} -{"command": "jupyter kernelspec list"} -{"command": "python3", "args": ["-m", "pip", "install", "--user", "ipykernel"]} + @mcp.tool() + def my_tool(param: str) -> str: + """Tool description.""" + from ..mcp_server import get_project_client + project = get_project_client() + # Use project.system.exec(), project.system.jupyter_execute(), etc. + return result ``` -#### `jupyter_execute` - Execute Code in Jupyter Kernels +### For Account-Scoped Keys -Execute code using Jupyter kernels with rich output, preserved state, and support for multiple languages. +Create `tools/my_account_tool.py`: -**Parameters:** +```python +def register_my_account_tool(mcp) -> None: + """Register my account tool with FastMCP.""" -- `input` (string, required): Code to execute -- `kernel` (string, optional): Kernel name (default: "python3") - - Common kernels: `python3`, `ir` (R), `julia-1.9` - - Use `exec` tool with `jupyter kernelspec list` to discover available kernels -- `history` (list, optional): Previous code inputs to establish context - - Executed without capturing output, allows setting up variables and imports + @mcp.tool() + def my_account_tool(param: str) -> str: + """Tool description.""" + from ..mcp_server import _api_key, _host + from cocalc_api import Hub + + hub = Hub(api_key=_api_key, host=_host) + # Use hub.projects.get(), hub.system.user_search(), etc. + return result +``` -**Returns:** Formatted execution output (text, plots, dataframes, images, errors, etc.) +### Register the Tool -**Examples:** +Update `tools/__init__.py` to import and register in `_register_tools_and_resources()` in `mcp_server.py`: -```json -{"input": "2 + 2", "kernel": "python3"} -{"input": "import pandas as pd\ndf = pd.DataFrame({'a': [1,2,3]})\ndf", "kernel": "python3"} -{"input": "import matplotlib.pyplot as plt\nplt.plot([1,2,3])\nplt.show()", "kernel": "python3"} -{"input": "summary(cars)", "kernel": "ir"} +```python +# In mcp_server.py _register_tools_and_resources() +if account_scoped: + from .tools.my_account_tool import register_my_account_tool + register_my_account_tool(mcp) ``` -**Setup Instructions:** +## Testing -Before using Jupyter kernels, set them up with the `exec` tool: +### Run Tests -```json -{"command": "python3", "args": ["-m", "pip", "install", "--user", "ipykernel"], "timeout": 300} -{"command": "python3", "args": ["-m", "ipykernel", "install", "--user", "--name=python3", "--display-name=Python 3"], "timeout": 120} +```bash +make test # Run pytest +make check # Run ruff, mypy, pyright ``` -### Resources +### Manual Testing -#### `project-files` - List Files +```bash +# Terminal 1: Start the server +export COCALC_API_KEY="sk-your-key" +uv run cocalc-mcp-server -Browse project directory structure. +# Terminal 2: Debug the server +uv run cocalc-mcp-debug +``` -**URI:** `cocalc://project-files/{path}` +## Implementation Notes -**Parameters:** +### Important: Sync, Not Async -- `path` (string, optional): Directory path (default: `.`) +MCP tools and resources in FastMCP must be **synchronous functions**, not async: -**Returns:** Formatted list of files and directories +```python +# ✅ Correct +@mcp.tool() +def my_tool(param: str) -> str: + return "result" -## Development Workflow +# ❌ Wrong - will not be callable +@mcp.tool() +async def my_tool(param: str) -> str: + return "result" +``` -### Adding a New Tool +### Error Handling -1. Create `tools/my_tool.py`: +Tools should return error messages as strings, not raise exceptions: ```python -def register_my_tool(mcp) -> None: - """Register my tool with FastMCP instance.""" - @mcp.tool() - async def my_tool(param: str) -> str: - """Tool description.""" - from ..mcp_server import get_project_client - project = get_project_client() - # Implementation using project client - return result +try: + # Do something + return result +except Exception as e: + return f"Error: {str(e)}" ``` -2. Update `tools/__init__.py`: +### Type Annotations -```python -def register_tools(mcp) -> None: - from .exec import register_exec_tool - from .my_tool import register_my_tool # Add this +- All function parameters and return types must be fully typed +- Use `Optional[str]` or `str | None` for optional parameters +- Avoid `Any` type where possible - register_exec_tool(mcp) - register_my_tool(mcp) # Add this -``` +## Configuration -3. Done! The tool is automatically registered when `mcp_server` imports tools. +### Environment Variables -### Testing +- `COCALC_API_KEY` - API key (required) +- `COCALC_HOST` - CoCalc instance URL (optional, defaults to `https://cocalc.com`) +- `COCALC_PROJECT_ID` - Project ID for project-scoped keys (optional, embedded in key) -```bash -# Run MCP server in one terminal -make mcp - -# Test with another terminal (example) -python3 << 'EOF' -import json, subprocess -proc = subprocess.Popen(['uv', 'run', 'cocalc-mcp-server'], ...) -init = {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {...}} -proc.stdin.write(json.dumps(init) + '\n') -# ... test tool calls -EOF -``` +### API Key Scope Detection + +The server calls `hub.system.test()` to determine scope: +- If returns `account_id` → Account-scoped key +- If returns `project_id` → Project-scoped key -## Error Handling +## Available CoCalc APIs -- Configuration errors → Exit with error message -- Project authentication errors → Connection failure -- Tool runtime errors → Returned as error in response +See `../../hub.py` for full API reference: -## Security Notes +**Account-Scoped:** +- `hub.projects.get()` - List projects +- `hub.system.user_search()` - Search users +- `hub.db.query()` - Query account data +- `hub.messages.get()` - Get messages -1. **API Keys** - Never commit to version control; use environment variables -2. **Project Isolation** - Each server instance is bound to one project -3. **Command Execution** - `exec` tool runs arbitrary commands; verify API key permissions -4. **File Access** - File listing respects project filesystem permissions +**Project-Scoped:** +- `project.system.exec()` - Run shell commands +- `project.system.jupyter_execute()` - Run code in Jupyter -## Future Enhancements +## Security -- File read/write operations -- Jupyter code execution -- Git repository operations -- Directory caching and recursion -- Rate limiting +1. **API Keys** - Never hardcode; use environment variables +2. **Input Validation** - Validate all user inputs +3. **Error Messages** - Don't leak sensitive info in errors +4. **Permissions** - Check API key has required permissions +5. **Timeouts** - Use reasonable timeouts for long operations ## References -- **MCP Spec**: https://modelcontextprotocol.io/ -- **FastMCP Docs**: https://github.com/modelcontextprotocol/python-sdk -- **CoCalc API**: See parent directory README +- [MCP Specification](https://modelcontextprotocol.io/) +- [FastMCP SDK](https://github.com/modelcontextprotocol/python-sdk) +- [CoCalc API](https://github.com/sagemathinc/cocalc) diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/README.md b/src/python/cocalc-api/src/cocalc_api/mcp/README.md index 4dc0594ae3d..bb0cbf82c00 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/README.md +++ b/src/python/cocalc-api/src/cocalc_api/mcp/README.md @@ -1,17 +1,14 @@ # CoCalc API MCP Server -A Model Context Protocol (MCP) server that allows LLMs to interact with CoCalc projects. +A Model Context Protocol (MCP) server that provides LLMs (Claude, etc.) with direct access to CoCalc accounts and projects. ## Quick Start -### 1. Configuration - -Set environment variables: +### 1. Set Environment Variables ```bash -export COCALC_API_KEY="sk-your-api-key" -export COCALC_PROJECT_ID="your-project-uuid" -export COCALC_HOST="https://cocalc.com" # optional, defaults to https://cocalc.com +export COCALC_API_KEY="sk-your-api-key" # Account or project-scoped +export COCALC_HOST="http://localhost:5000" # Optional, defaults to https://cocalc.com ``` ### 2. Run the Server @@ -20,46 +17,31 @@ export COCALC_HOST="https://cocalc.com" # optional, defaults to https://cocalc. uv run cocalc-mcp-server ``` -### 3. Setup with Claude Code CLI +The server will detect your API key type and automatically register the appropriate tools/resources. + +## Setup with Claude Code + +### Quick Registration ```bash -# Set your credentials -export COCALC_API_KEY="sk-your-api-key-here" -export COCALC_PROJECT_ID="[UUID]" -export COCALC_API_PATH="/path/to/cocalc/src/python/cocalc-api" -# OPTIONAL: set the host, defaults to cocalc.com. for development use localhost:5000 -export COCALC_HOST="http://localhost:5000" - -# Add the MCP server to Claude Code claude mcp add \ --transport stdio \ cocalc \ - --env COCALC_API_KEY="$COCALC_API_KEY" \ - --env COCALC_PROJECT_ID="$COCALC_PROJECT_ID" \ - --env COCALC_HOST="$COCALC_HOST" \ - -- uv --directory "$COCALC_API_PATH" run cocalc-mcp-server + --env COCALC_API_KEY="sk-your-api-key" \ + -- uv --directory /path/to/cocalc-api run cocalc-mcp-server ``` -Alternatively, using JSON configuration: +### Via JSON Config ```bash claude mcp add-json cocalc '{ "command": "uv", - "args": ["--directory", "/path/to/cocalc/src/python/cocalc-api", "run", "cocalc-mcp-server"], - "env": { - "COCALC_API_KEY": "sk-your-api-key-here", - "COCALC_PROJECT_ID": "[UUID]", - "COCALC_HOST": "http://localhost:5000" - } + "args": ["--directory", "/path/to/cocalc-api", "run", "cocalc-mcp-server"], + "env": {"COCALC_API_KEY": "sk-your-api-key"} }' ``` -**Important:** - -- Replace `/path/to/cocalc/src/python/cocalc-api` with the absolute path to your cocalc-api directory. -- Replace `http://localhost:5000` with your CoCalc instance URL (defaults to `https://cocalc.com` if not set). - -### 4. Setup with Claude Desktop +## Setup with Claude Desktop Add to `~/.config/Claude/claude_desktop_config.json`: @@ -68,30 +50,16 @@ Add to `~/.config/Claude/claude_desktop_config.json`: "mcpServers": { "cocalc": { "command": "uv", - "args": [ - "--directory", - "/path/to/cocalc/src/python/cocalc-api", - "run", - "cocalc-mcp-server" - ], - "env": { - "COCALC_API_KEY": "sk-your-api-key-here", - "COCALC_PROJECT_ID": "[UUID]", - "COCALC_HOST": "http://localhost:5000" - } + "args": ["--directory", "/path/to/cocalc-api", "run", "cocalc-mcp-server"], + "env": {"COCALC_API_KEY": "sk-your-api-key"} } } } ``` -**Important:** - -- Replace `/path/to/cocalc/src/python/cocalc-api` with the absolute path to your cocalc-api directory. -- Replace `http://localhost:5000` with your CoCalc instance URL (defaults to `https://cocalc.com` if not set). - -### 5. Allow MCP Tools in Claude Code Settings +## Allow Tools in Claude Code Settings -To automatically allow all CoCalc MCP tools without prompts, add this to `.claude/settings.json`: +Add to `.claude/settings.json`: ```json { @@ -99,80 +67,45 @@ To automatically allow all CoCalc MCP tools without prompts, add this to `.claud } ``` -This wildcard pattern (`mcp__cocalc__*`) automatically allows: +## Available Tools & Resources -- `mcp__cocalc__exec` - Execute shell commands -- `mcp__cocalc__project_files` - Browse project files -- Any future tools added to the MCP server +The server automatically provides different tools based on your API key type: -## Features +### Account-Scoped API Keys -### Tools +**Tools:** +- `projects_search(query="")` - Search and list your projects with collaborator info -- **`exec`** - Execute shell commands in the project +**Resources:** +- `cocalc://account-profile` - View your account info, settings, and preferences - ``` - Tool: exec - Params: command (required), args, bash, timeout, cwd - Returns: {stdout, stderr, exit_code} - ``` +### Project-Scoped API Keys -- **`jupyter_execute`** - Execute code using Jupyter kernels - ``` - Tool: jupyter_execute - Params: input (required), kernel (default: "python3"), history - Returns: Formatted execution output (text, plots, errors, etc.) - ``` +**Tools:** +- `exec(command)` - Execute shell commands in the project +- `jupyter_execute(input, kernel="python3")` - Run code using Jupyter kernels -### Resources +**Resources:** +- `cocalc://project-files` - Browse the project directory structure -- **`project-files`** - Browse project files with filtering and pagination - ``` - URI: cocalc://project-files/{path}?glob=*.py&limit=100&recurse=true - Returns: File listing with metadata - ``` +## API Keys -## Documentation +Create API keys at: +- **Account-scoped**: CoCalc Settings → API keys → Create API key +- **Project-scoped**: Project Settings → API keys → Create API key -See [DEVELOPMENT.md](./DEVELOPMENT.md) for: - -- Architecture and design principles -- Detailed API specifications -- Configuration options -- Testing strategy -- Future roadmap - -## Directory Structure - -``` -src/cocalc_api/mcp/ -├── README.md # This file -├── DEVELOPMENT.md # Architecture & design documentation -├── server.py # Main MCP server -├── mcp_server.py # MCP instance and project client coordination -├── __main__.py # Module entry point -├── tools/ -│ ├── exec.py # Shell command execution tool -│ └── __init__.py -└── resources/ - ├── file_listing.py # File listing resource - └── __init__.py -``` - -## Requirements +## Security -- Python 3.10+ -- mcp>=1.0 -- httpx -- pydantic (via mcp) +- Never commit API keys to version control +- Use restricted file permissions (600) on config files with API keys +- Each server instance is isolated to its scope (account or project) +- Commands execute with the permissions of your API key -## Security +## Development -- API keys should never be committed to version control -- Use restricted file permissions (600) on config files containing API keys -- Each server instance is scoped to a single project -- Commands execute with the permissions of the CoCalc project user +See [DEVELOPMENT.md](./DEVELOPMENT.md) for architecture, design patterns, and adding new tools. -## Next Steps +## References -See [DEVELOPMENT.md](./DEVELOPMENT.md#next-steps) for implementation roadmap and upcoming features. +- [MCP Specification](https://modelcontextprotocol.io/) +- [CoCalc API Documentation](https://github.com/sagemathinc/cocalc) diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py index d6b4cc1ce31..8f31ef22980 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py @@ -2,35 +2,39 @@ CoCalc MCP (Model Context Protocol) Server - Central Coordination Module This MCP server gives you direct access to a CoCalc project or account environment. +The available tools and resources depend on your API key type (account-scoped or project-scoped). -AVAILABLE TOOLS (actions you can perform): +AVAILABLE TOOLS - PROJECT-SCOPED KEYS: - exec: Run shell commands, scripts, and programs in the project Use for: running code, data processing, build/test commands, git operations, etc. - jupyter_execute: Execute code using Jupyter kernels (Python, R, Julia, etc.) Use for: interactive code execution, data analysis, visualization, scientific computing -AVAILABLE RESOURCES (information you can read): +AVAILABLE TOOLS - ACCOUNT-SCOPED KEYS: +- projects_search: Search for and list projects you have access to + Use for: discovering projects, seeing collaborators, checking project states + +AVAILABLE RESOURCES - PROJECT-SCOPED KEYS: - project-files: Browse the project file structure Use for: exploring what files exist, understanding project layout, locating files to work with -HOW IT WORKS: -- You can use these tools and resources to understand, modify, and manage files in the project -- The project runs in an Ubuntu Linux container with common development tools pre-installed -- Commands execute with the permissions of the CoCalc project user -- All operations are scoped to this single project +AVAILABLE RESOURCES - ACCOUNT-SCOPED KEYS: +- account-profile: View your account profile and settings + Use for: checking personal info, account settings, preferences -WHEN TO USE WHICH: -1. First, use project-files to explore and understand the project structure -2. Then, use exec to run commands, edit files, run tests, etc. -3. Use project-files again if you need to navigate to new directories -4. Use exec for anything the project-files resource can't show (recursive listings, complex queries, etc.) +HOW IT WORKS: +- Account-scoped keys: Access your account information, manage projects, view profile +- Project-scoped keys: Execute code, run commands, and manage files in a specific project +- All operations are secure and scoped to what your API key authorizes AUTHENTICATION & CONFIGURATION: Required environment variables (already set when this server is running): - COCALC_API_KEY: Your CoCalc API authentication token (account-scoped or project-scoped) - COCALC_HOST: (optional) Your CoCalc instance URL (defaults to https://cocalc.com) +- COCALC_PROJECT_ID: (optional) Project ID for project-scoped keys -The server will validate your API key on startup and report whether it's account-scoped or project-scoped. +The server will validate your API key on startup and automatically register the appropriate +tools and resources based on whether it's account-scoped or project-scoped. """ import os @@ -279,13 +283,33 @@ def get_project_client(project_id: Optional[str] = None) -> Project: return client -# Register tools and resources -# This happens at module import time, auto-registering with the mcp instance -from . import tools as tools_module # noqa: E402 -from . import resources as resources_module # noqa: E402 +def _register_tools_and_resources() -> None: + """Register tools and resources based on API key scope.""" + global _api_key_scope + + _initialize_config() + + # Determine which tools/resources to register based on API key scope + if _api_key_scope and "account_id" in _api_key_scope: + # Account-scoped key: register account-scoped tools/resources + print("Registering account-scoped tools and resources...", file=sys.stderr) + from .tools.projects_search import register_projects_search_tool + from .resources.account_profile import register_account_profile_resource + + register_projects_search_tool(mcp) + register_account_profile_resource(mcp) + + elif _api_key_scope and "project_id" in _api_key_scope: + # Project-scoped key: register project-scoped tools/resources + print("Registering project-scoped tools and resources...", file=sys.stderr) + from .tools.exec import register_exec_tool + from .tools.jupyter import register_jupyter_tool + from .resources.file_listing import register_file_listing_resource + + register_exec_tool(mcp) + register_jupyter_tool(mcp) + register_file_listing_resource(mcp) -tools_module.register_tools(mcp) -resources_module.register_resources(mcp) -# Initialize configuration and validate API key at startup -_initialize_config() +# Initialize configuration and validate API key at startup, then register tools/resources +_register_tools_and_resources() diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/resources/__init__.py b/src/python/cocalc-api/src/cocalc_api/mcp/resources/__init__.py index da1cbd8a12e..f0d838c28a1 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/resources/__init__.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/resources/__init__.py @@ -1,18 +1,17 @@ """ CoCalc MCP Resources - Available Information -Resources are read-only information you can access about the CoCalc project. +Resources are read-only information you can access about the CoCalc project or account. -Available Resources: -- project-files: Browse and list files in the project directory structure +Resources are dynamically registered based on API key scope: -See mcp_server.py for overview of all available tools and resources, and guidance -on when to use each one. -""" +PROJECT-SCOPED KEYS: +- project-files: Browse and list files in the project directory structure +ACCOUNT-SCOPED KEYS: +- account-profile: View account profile information -def register_resources(mcp) -> None: - """Register all resources with the given FastMCP instance.""" - from .file_listing import register_file_listing_resource +See mcp_server.py for overview of all available tools and resources. - register_file_listing_resource(mcp) +Note: Individual resource registration functions are imported directly by mcp_server.py. +""" diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/resources/account_profile.py b/src/python/cocalc-api/src/cocalc_api/mcp/resources/account_profile.py new file mode 100644 index 00000000000..8fa647e93ca --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/resources/account_profile.py @@ -0,0 +1,138 @@ +""" +Account profile resource for account-scoped API keys. + +Provides the 'account-profile' resource that returns read-only information +about the current user account, including name, email, settings, etc. +""" + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + pass + + +def register_account_profile_resource(mcp) -> None: + """Register the account profile resource with the given FastMCP instance.""" + + @mcp.resource("cocalc://account-profile") + def account_profile() -> str: + """ + Get read-only profile information about your CoCalc account. + + Use this resource to: + - View your profile, i.e. account name, email, and personal information + - Check your editor and terminal settings + - Understand your account configuration and preferences + - See when your account was created and last active + + Returns account information including: + - Personal info (first_name, last_name, email_address, account_id) + - Settings (editor_settings, other_settings, terminal preferences) + - Account metadata (created, last_active, balance, groups) + - Profile customizations + """ + try: + # Import configuration from mcp_server + from ..mcp_server import _api_key, _host, _api_key_scope + from cocalc_api import Hub + + if not _api_key_scope or "account_id" not in _api_key_scope: + return "Error: This resource requires an account-scoped API key" + + if not _api_key or not _host: + return "Error: API configuration not initialized" + + hub = Hub(api_key=_api_key, host=_host) + + # Query account information from the database + account_data = hub.db.query({ + "accounts": { + "account_id": None, + "first_name": None, + "last_name": None, + "email_address": None, + "name": None, + "created": None, + "last_active": None, + "balance": None, + "groups": None, + "editor_settings": None, + "other_settings": None, + "terminal": None, + "autosave": None, + "font_size": None, + "profile": None, + } + }) + + if not account_data or "accounts" not in account_data: + return "Error: Could not retrieve account information" + + account_info = account_data["accounts"] + + # Format the output nicely + output = [] + output.append("=" * 70) + output.append("ACCOUNT PROFILE") + output.append("=" * 70) + + # Personal Information + output.append("\nPERSONAL INFORMATION") + output.append("-" * 70) + output.append(f"Account ID: {account_info.get('account_id', 'N/A')}") + output.append(f"First Name: {account_info.get('first_name', '')}") + output.append(f"Last Name: {account_info.get('last_name', '')}") + output.append(f"Username: {account_info.get('name', 'Not set')}") + output.append(f"Email Address: {account_info.get('email_address', 'Not set')}") + + # Account Metadata + output.append("\nACCOUNT METADATA") + output.append("-" * 70) + created = account_info.get('created') + if created: + output.append(f"Created: {created}") + last_active = account_info.get('last_active') + if last_active: + output.append(f"Last Active: {last_active}") + balance = account_info.get('balance') + if balance is not None: + output.append(f"Account Balance: ${balance:.2f}") + groups = account_info.get('groups', []) + if groups: + output.append(f"Groups: {', '.join(groups)}") + + # Editor Settings + editor_settings = account_info.get('editor_settings', {}) + if editor_settings: + output.append("\nEDITOR SETTINGS") + output.append("-" * 70) + output.append(f"Theme: {editor_settings.get('theme', 'default')}") + output.append(f"Bindings: {editor_settings.get('bindings', 'standard')}") + output.append(f"Font Size: {editor_settings.get('font_size', 'default')}") + output.append(f"Line Numbers: {editor_settings.get('line_numbers', True)}") + output.append(f"Line Wrapping: {editor_settings.get('line_wrapping', True)}") + + # Other Settings + other_settings = account_info.get('other_settings', {}) + if other_settings: + output.append("\nOTHER SETTINGS") + output.append("-" * 70) + output.append(f"Dark Mode: {other_settings.get('dark_mode', False)}") + output.append(f"KaTeX Enabled: {other_settings.get('katex', True)}") + output.append(f"Language: {other_settings.get('i18n', 'en')}") + + # Terminal Settings + terminal = account_info.get('terminal', {}) + if terminal: + output.append("\nTERMINAL SETTINGS") + output.append("-" * 70) + output.append(f"Font Size: {terminal.get('font_size', 14)}") + output.append(f"Color Scheme: {terminal.get('color_scheme', 'default')}") + output.append(f"Font: {terminal.get('font', 'monospace')}") + + output.append("\n" + "=" * 70) + + return "\n".join(output) + + except Exception as e: + return f"Error retrieving account profile: {str(e)}" diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py index a224db05353..3a55c0ae278 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/__init__.py @@ -1,21 +1,18 @@ """ CoCalc MCP Tools - Available Actions -Tools are actions you can perform in the CoCalc project. +Tools are actions you can perform in the CoCalc project or account. -Available Tools: +Tools are dynamically registered based on API key scope: + +PROJECT-SCOPED KEYS: - exec: Execute shell commands, scripts, and programs in the project environment - jupyter_execute: Execute code using Jupyter kernels with rich output and interactive state -See mcp_server.py for overview of all available tools and resources, and guidance -on when to use each one. -""" - +ACCOUNT-SCOPED KEYS: +- projects_search: Search for and list projects you have access to -def register_tools(mcp) -> None: - """Register all tools with the given FastMCP instance.""" - from .exec import register_exec_tool - from .jupyter import register_jupyter_tool +See mcp_server.py for overview of all available tools and resources. - register_exec_tool(mcp) - register_jupyter_tool(mcp) +Note: Individual tool registration functions are imported directly by mcp_server.py. +""" diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/jupyter.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/jupyter.py index e61a8310c76..e42b9c80ffa 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/tools/jupyter.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/jupyter.py @@ -14,14 +14,20 @@ T = TypeVar("T") +def _is_retryable_error(error: Exception) -> bool: + """Check if an error is retryable (transient connection issue).""" + error_msg = str(error).lower() + return any( + keyword in error_msg + for keyword in ["timeout", "closed", "connection", "reset", "broken"] + ) + + def _retry_with_backoff( func: Callable[[], T], max_retries: int = 3, retry_delay: int = 5, - error_condition: Callable[[Exception], bool] = lambda e: any( - keyword in str(e).lower() - for keyword in ["timeout", "closed", "connection", "reset", "broken"] - ), + error_condition: Callable[[Exception], bool] | None = None, ) -> T: """ Retry a function call with exponential backoff for transient failures. @@ -29,6 +35,9 @@ def _retry_with_backoff( Useful for operations that may timeout on cold starts (e.g., kernel launches) or fail due to transient connection issues. """ + if error_condition is None: + error_condition = _is_retryable_error + for attempt in range(max_retries): try: return func() diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/projects_search.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/projects_search.py new file mode 100644 index 00000000000..95142523e6d --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/projects_search.py @@ -0,0 +1,154 @@ +""" +Projects search tool for account-scoped API keys. + +Provides the 'projects_search' tool that allows listing and searching +for projects that you have access to. +""" + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + pass + + +def register_projects_search_tool(mcp) -> None: + """Register the projects search tool with the given FastMCP instance.""" + + @mcp.tool() + def projects_search(query: str = "") -> str: + """ + Search for and list projects you have access to. + + Use this tool to: + - List all your projects (default when query is empty) + - Find projects by title or description + - See project collaborators and access information + - Check when projects were last accessed/modified + + Args: + query (str): Search string to filter projects by title. + Default "" lists all projects. + + Returns: + Formatted list of projects with: + - Project ID (UUID) + - Project Title + - Last Accessed timestamp + - List of collaborators (with resolved names) + - Project State (running, stopped, etc.) + """ + try: + # Import configuration from mcp_server + from ..mcp_server import _api_key, _host, _api_key_scope + from cocalc_api import Hub + + # Verify this is an account-scoped key + if not _api_key_scope or "account_id" not in _api_key_scope: + return "Error: This tool requires an account-scoped API key" + + if not _api_key or not _host: + return "Error: API configuration not initialized" + + hub = Hub(api_key=_api_key, host=_host) + + # Get all projects with full details + projects = hub.projects.get( + all=True, + fields=[ + "project_id", + "title", + "description", + "last_edited", + "state", + "deleted", + "users", # collaborators + ] + ) + + if not projects: + return "No projects found" + + # Filter by query if provided + if query: + projects = [ + p for p in projects + if query.lower() in (p.get("title", "") or "").lower() + ] + if not projects: + return f"No projects found matching query: '{query}'" + + # Get account IDs for collaborator name resolution + all_account_ids: set[str] = set() + for project in projects: + users = project.get("users", {}) + if isinstance(users, dict): + all_account_ids.update(users.keys()) + + # Batch fetch user names + account_names = {} + if all_account_ids: + try: + names_data = hub.system.get_names(list(all_account_ids)) + account_names = names_data if isinstance(names_data, dict) else {} + except Exception: + # If get_names fails, we'll just use account IDs + pass + + # Format output + output = [] + output.append("=" * 100) + output.append(f"PROJECTS ({len(projects)} found)") + output.append("=" * 100) + + for idx, project in enumerate(projects, 1): + project_id = project.get("project_id", "Unknown") + title = project.get("title") or "Untitled Project" + last_edited = project.get("last_edited", "Never") + state = project.get("state", "unknown") + deleted = project.get("deleted", False) + + output.append(f"\n[{idx}] {title}") + output.append(f" Project ID: {project_id}") + output.append(f" State: {state}") + output.append(f" Last Edited: {last_edited}") + + if deleted: + output.append(" Status: DELETED") + + # Format collaborators + users = project.get("users", {}) + if isinstance(users, dict): + collaborators = [] + for account_id, user_info in users.items(): + # Try to get the user's name + if account_id in account_names: + name_info = account_names[account_id] + if isinstance(name_info, dict): + first_name = name_info.get("first_name", "") + last_name = name_info.get("last_name", "") + user_name = f"{first_name} {last_name}".strip() + else: + user_name = str(name_info) + else: + user_name = account_id[:8] # Show first 8 chars of UUID + + # Get the role/access level if available + access_level = "" + if isinstance(user_info, dict): + if user_info.get("group") == "owner": + access_level = " (owner)" + elif user_info.get("group"): + access_level = f" ({user_info.get('group')})" + + collaborators.append(f"{user_name}{access_level}") + + if collaborators: + output.append(f" Collaborators: {', '.join(collaborators)}") + else: + output.append(f" Collaborators: {len(users)} users") + + output.append("\n" + "=" * 100) + return "\n".join(output) + + except Exception as e: + return f"Error searching projects: {str(e)}" From 7bb53dc895793f4652e81c62dc2b1782dfed6201 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Thu, 20 Nov 2025 11:11:19 +0100 Subject: [PATCH 27/58] frontend/project/disk warning: point to upgrades page instead of settings --- src/packages/frontend/project/warnings/disk-space.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/packages/frontend/project/warnings/disk-space.tsx b/src/packages/frontend/project/warnings/disk-space.tsx index b2156505428..dff981a03bc 100644 --- a/src/packages/frontend/project/warnings/disk-space.tsx +++ b/src/packages/frontend/project/warnings/disk-space.tsx @@ -3,15 +3,15 @@ * License: MS-RSL – see LICENSE.md for details */ -import { Alert } from "../../antd-bootstrap"; +import { Alert } from "@cocalc/frontend/antd-bootstrap"; import { React, useMemo, useRedux, useTypedRedux, useActions, -} from "../../app-framework"; -import { Icon } from "../../components"; +} from "@cocalc/frontend/app-framework"; +import { Icon } from "@cocalc/frontend/components"; import { ALERT_STYLE } from "./common"; export const DiskSpaceWarning: React.FC<{ project_id: string }> = ({ @@ -40,7 +40,7 @@ export const DiskSpaceWarning: React.FC<{ project_id: string }> = ({ return null; } - // the disk_usage comes from the project.status datatbase entry – not the "project-status" synctable + // the disk_usage comes from the project.status database entry – not the "project-status" synctable const project_status = project.get("status"); const disk_usage = project_status?.get("disk_MB"); if (disk_usage == null) return null; @@ -57,7 +57,7 @@ export const DiskSpaceWarning: React.FC<{ project_id: string }> = ({ WARNING: This project is running out of disk space: only {disk_free} MB out of {quotas.disk_quota} MB available.{" "} - actions?.set_active_tab("settings")}> + actions?.set_active_tab("upgrades")}> Increase the "Disk Space" quota {" or "} From efdadadec0713e61709a1b1c62b2b042f515b927 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Thu, 20 Nov 2025 17:14:54 +0100 Subject: [PATCH 28/58] =?UTF-8?q?cocalc-api/ci:=20increase=20timeout=2030?= =?UTF-8?q?=20=E2=86=92=2090=20secs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/python/cocalc-api/src/cocalc_api/hub.py | 8 ++++---- src/python/cocalc-api/src/cocalc_api/project.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/python/cocalc-api/src/cocalc_api/hub.py b/src/python/cocalc-api/src/cocalc_api/hub.py index 491bd2b1994..c1377c40f21 100644 --- a/src/python/cocalc-api/src/cocalc_api/hub.py +++ b/src/python/cocalc-api/src/cocalc_api/hub.py @@ -10,8 +10,8 @@ class Hub: def __init__(self, api_key: str, host: str = "https://cocalc.com"): self.api_key = api_key self.host = host - # Use longer timeout for API calls (90 seconds instead of default 5) to handle slow operations like Jupyter - self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}, timeout=90.0) + # Use longer timeout for API calls (120 seconds instead of default 5) to handle slow operations like Jupyter + self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}, timeout=120.0) def call(self, name: str, arguments: list[Any], timeout: Optional[int] = None) -> Any: """ @@ -334,7 +334,7 @@ def execute( history: Optional[list[str]] = None, project_id: Optional[str] = None, path: Optional[str] = None, - timeout: Optional[int] = 30, + timeout: Optional[int] = 90, ) -> dict[str, Any]: # type: ignore[empty-body] """ Execute code using a Jupyter kernel. @@ -345,7 +345,7 @@ def execute( history (Optional[list[str]]): Array of previous inputs (they get evaluated every time, but without output being captured). project_id (Optional[str]): Project in which to run the code -- if not given, global anonymous project is used, if available. path (Optional[str]): File path context for execution. - timeout (Optional[int]): Timeout in SECONDS for the execute call (defaults to 30 seconds). + timeout (Optional[int]): Timeout in SECONDS for the execute call (defaults to 90 seconds). Returns: dict[str, Any]: JSON response containing execution results. diff --git a/src/python/cocalc-api/src/cocalc_api/project.py b/src/python/cocalc-api/src/cocalc_api/project.py index 2cd59d1ce4b..dff4371f16a 100644 --- a/src/python/cocalc-api/src/cocalc_api/project.py +++ b/src/python/cocalc-api/src/cocalc_api/project.py @@ -10,8 +10,8 @@ def __init__(self, api_key: str, host: str = "https://cocalc.com", project_id: O self.project_id = project_id self.api_key = api_key self.host = host - # Use longer timeout for API calls (60 seconds to handle slow kernel startups in CI) - self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}, timeout=60.0) + # Use longer timeout for API calls (120 seconds to handle slow kernel startups in CI) + self.client = httpx.Client(auth=(api_key, ""), headers={"Content-Type": "application/json"}, timeout=120.0) def call(self, name: str, arguments: list[Any], timeout: Optional[int] = None) -> Any: """ @@ -131,7 +131,7 @@ def jupyter_execute( kernel: str, history: Optional[list[str]] = None, path: Optional[str] = None, - timeout: Optional[int] = 30, + timeout: Optional[int] = 90, ) -> list[dict[str, Any]]: # type: ignore[empty-body] """ Execute code using a Jupyter kernel. @@ -141,7 +141,7 @@ def jupyter_execute( kernel (str): Name of kernel to use. Get options using hub.jupyter.kernels(). history (Optional[list[str]]): Array of previous inputs (they get evaluated every time, but without output being captured). path (Optional[str]): File path context for execution. - timeout (Optional[int]): Timeout in SECONDS for the execute call (defaults to 30 seconds). + timeout (Optional[int]): Timeout in SECONDS for the execute call (defaults to 90 seconds). Returns: list[dict[str, Any]]: List of output items. Each output item contains From 747a07d85fc96fa645d031bc827a3d617100ff45 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Fri, 21 Nov 2025 11:40:08 +0100 Subject: [PATCH 29/58] cocalc-api: improve api key type discovery --- src/packages/conat/hub/api/system.ts | 4 +- src/packages/next/pages/api/conat/project.ts | 5 - src/packages/server/api/project-bridge.ts | 14 ++- src/packages/server/conat/api/system.ts | 16 +-- .../server/projects/control/single-user.ts | 11 +- .../src/cocalc_api/mcp/DEVELOPMENT.md | 10 +- .../cocalc-api/src/cocalc_api/mcp/README.md | 3 + .../src/cocalc_api/mcp/mcp_server.py | 107 +++++++++--------- 8 files changed, 85 insertions(+), 85 deletions(-) diff --git a/src/packages/conat/hub/api/system.ts b/src/packages/conat/hub/api/system.ts index d18c9da67d3..a2f144e5661 100644 --- a/src/packages/conat/hub/api/system.ts +++ b/src/packages/conat/hub/api/system.ts @@ -32,8 +32,8 @@ export interface System { getCustomize: (fields?: string[]) => Promise; // ping server and get back the current time ping: () => { now: number }; - // test API key and return scope information (account_id or project_id) and server time - test: () => Promise<{ account_id?: string; project_id?: string; server_time: number }>; + // test API key and return scope information (account_id) and server time + test: () => Promise<{ account_id: string; server_time: number }>; // terminate a service: // - only admin can do this. // - useful for development diff --git a/src/packages/next/pages/api/conat/project.ts b/src/packages/next/pages/api/conat/project.ts index ff07ff78ad5..8d6dd8fc124 100644 --- a/src/packages/next/pages/api/conat/project.ts +++ b/src/packages/next/pages/api/conat/project.ts @@ -58,11 +58,6 @@ export default async function handle(req, res) { args, timeout, }); - // For project-scoped API keys, include the project_id in the response - // so the client can discover it - if (project_id0 && !resp.project_id) { - resp.project_id = project_id0; - } res.json(resp); } catch (err) { res.json({ error: err.message }); diff --git a/src/packages/server/api/project-bridge.ts b/src/packages/server/api/project-bridge.ts index 5cb101f987f..4459d15a034 100644 --- a/src/packages/server/api/project-bridge.ts +++ b/src/packages/server/api/project-bridge.ts @@ -4,6 +4,7 @@ import { projectSubject } from "@cocalc/conat/names"; import { conat } from "@cocalc/backend/conat"; import { type Client as ConatClient } from "@cocalc/conat/core/client"; import { getProject } from "@cocalc/server/projects/control"; + const DEFAULT_TIMEOUT = 15000; let client: ConatClient | null = null; @@ -58,10 +59,17 @@ async function callProject({ await project.start(); } - // For system.test(), inject project_id into args[0] if not already present + // For discovery-style calls, inject identifiers so the project can report scope let finalArgs = args; - if (name === "system.test" && (!args || args.length === 0)) { - finalArgs = [{ project_id }]; + if (name === "system.test") { + if (!args || args.length === 0 || typeof args[0] !== "object") { + finalArgs = [{}]; + } + if (finalArgs[0] == null || typeof finalArgs[0] !== "object") { + finalArgs = [{ project_id }]; + } else { + finalArgs = [{ ...finalArgs[0], project_id }]; + } } const data = { name, args: finalArgs }; // we use waitForInterest because often the project hasn't diff --git a/src/packages/server/conat/api/system.ts b/src/packages/server/conat/api/system.ts index 79fdd2795d7..1dc800d7e2a 100644 --- a/src/packages/server/conat/api/system.ts +++ b/src/packages/server/conat/api/system.ts @@ -20,21 +20,13 @@ export function ping() { export async function test({ account_id, - project_id, -}: { account_id?: string; project_id?: string } = {}) { +}: { account_id?: string } = {}) { // Return API key scope information and server time - // The authFirst decorator determines the scope from the API key and injects - // either account_id (for account-scoped keys) or project_id (for project-scoped keys) - // into this parameter object. - const response: { account_id?: string; project_id?: string; server_time: number } = { + // The authFirst decorator determines the scope from the API key and injects account_id. + const response: { account_id: string; server_time: number } = { + account_id: account_id ?? "", server_time: Date.now(), }; - if (account_id) { - response.account_id = account_id; - } - if (project_id) { - response.project_id = project_id; - } return response; } diff --git a/src/packages/server/projects/control/single-user.ts b/src/packages/server/projects/control/single-user.ts index f61f444d9e9..acaa2f1159b 100644 --- a/src/packages/server/projects/control/single-user.ts +++ b/src/packages/server/projects/control/single-user.ts @@ -156,7 +156,9 @@ class Project extends BaseProject { // First attempt: graceful shutdown with SIGTERM // This allows the process to clean up child processes (e.g., Jupyter kernels) - let usedSigterm = false; + const stopStartedAt = Date.now(); + const SIGKILL_GRACE_MS = 5000; + let sigkillSent = false; const killProject = (signal: NodeJS.Signals = "SIGTERM") => { try { logger.debug(`stop: sending kill -${pid} with ${signal}`); @@ -169,16 +171,15 @@ class Project extends BaseProject { // Try SIGTERM first for graceful shutdown killProject("SIGTERM"); - usedSigterm = true; await this.wait({ until: async () => { if (await isProjectRunning(this.HOME)) { - // After 5 seconds, escalate to SIGKILL - if (usedSigterm) { + // After a grace period, escalate to SIGKILL + if (!sigkillSent && Date.now() - stopStartedAt >= SIGKILL_GRACE_MS) { logger.debug("stop: escalating to SIGKILL"); killProject("SIGKILL"); - usedSigterm = false; + sigkillSent = true; } return false; } else { diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md b/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md index 4c973265c8f..f5fc1062f43 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md +++ b/src/python/cocalc-api/src/cocalc_api/mcp/DEVELOPMENT.md @@ -174,13 +174,15 @@ except Exception as e: - `COCALC_API_KEY` - API key (required) - `COCALC_HOST` - CoCalc instance URL (optional, defaults to `https://cocalc.com`) -- `COCALC_PROJECT_ID` - Project ID for project-scoped keys (optional, embedded in key) +- `COCALC_PROJECT_ID` - Optional project ID used only with **account-scoped** keys to target a specific project. Ignored for project-scoped keys (project_id comes from the key itself). ### API Key Scope Detection -The server calls `hub.system.test()` to determine scope: -- If returns `account_id` → Account-scoped key -- If returns `project_id` → Project-scoped key +The server detects scope in two steps: +- Call `hub.system.test()` (account-scoped keys only) → returns `account_id`. +- If that fails, call `project.system.test()` → returns `project_id` for project-scoped keys. + +If `COCALC_PROJECT_ID` is provided with an account-scoped key, it is used as the default project target; it is ignored for project-scoped keys. ## Available CoCalc APIs diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/README.md b/src/python/cocalc-api/src/cocalc_api/mcp/README.md index bb0cbf82c00..f50ea7442d0 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/README.md +++ b/src/python/cocalc-api/src/cocalc_api/mcp/README.md @@ -9,6 +9,8 @@ A Model Context Protocol (MCP) server that provides LLMs (Claude, etc.) with dir ```bash export COCALC_API_KEY="sk-your-api-key" # Account or project-scoped export COCALC_HOST="http://localhost:5000" # Optional, defaults to https://cocalc.com +# Optional: only used with account-scoped keys to target a specific project +# export COCALC_PROJECT_ID="your-project-uuid" ``` ### 2. Run the Server @@ -18,6 +20,7 @@ uv run cocalc-mcp-server ``` The server will detect your API key type and automatically register the appropriate tools/resources. +If you supply `COCALC_PROJECT_ID` with an account-scoped key, the MCP server will also prepare a project client for that project. For project-scoped keys, `COCALC_PROJECT_ID` is ignored because the project is embedded in the key. ## Setup with Claude Code diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py index 8f31ef22980..53f4feedc4e 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py @@ -40,7 +40,7 @@ import os import sys import time -from typing import Optional +from typing import Optional, TypedDict, Union, cast from mcp.server.fastmcp import FastMCP @@ -93,7 +93,18 @@ def get_config() -> tuple[str, str, Optional[str]]: return api_key, host, project_id -def check_api_key_scope(api_key: str, host: str) -> dict[str, str]: +class AccountScope(TypedDict): + account_id: str + + +class ProjectScope(TypedDict): + project_id: str + + +Scope = Union[AccountScope, ProjectScope] + + +def check_api_key_scope(api_key: str, host: str) -> Scope: """ Check if the API key is account-scoped or project-scoped. @@ -107,29 +118,26 @@ def check_api_key_scope(api_key: str, host: str) -> dict[str, str]: Raises: RuntimeError: If the API key is invalid or scope cannot be determined """ + # Try account scope first; hub.system.test only works for account-scoped keys try: - hub = Hub(api_key=api_key, host=host) - - # Try the hub.system.test() method (only works for account-scoped keys) - result = hub.system.test() - - # Check which scope is returned - if "account_id" in result and result["account_id"]: - return {"account_id": result["account_id"]} - elif "project_id" in result and result["project_id"]: - return {"project_id": result["project_id"]} - else: - raise RuntimeError("API key test returned neither account_id nor project_id") - + result = Hub(api_key=api_key, host=host).system.test() + account_id = result.get("account_id") + if account_id: + return {"account_id": account_id} + except Exception: + pass + + # Fall back to project scope + try: + result = Project(api_key=api_key, host=host).system.test() + project_id = result.get("project_id") + if project_id: + return {"project_id": project_id} except Exception as e: - # Check if this looks like a project-scoped key error - error_msg = str(e) - if "must be signed in and MUST provide an api key" in error_msg: - raise RuntimeError("API key appears to be project-scoped. " - "Project-scoped keys require the project_id to be specified at the OS level. " - "Please set the COCALC_PROJECT_ID environment variable and try again.") from e raise RuntimeError(f"API key validation failed: {e}") from e + raise RuntimeError("API key test returned neither account_id nor project_id") + # Initialize FastMCP server with instructions and documentation mcp = FastMCP( @@ -168,7 +176,7 @@ def check_api_key_scope(api_key: str, host: str) -> dict[str, str]: # Configuration (initialized at startup) _api_key: Optional[str] = None _host: Optional[str] = None -_api_key_scope: Optional[dict[str, str]] = None # Either {"account_id": ...} or {"project_id": ...} +_api_key_scope: Optional[Scope] = None # Either {"account_id": ...} or {"project_id": ...} # Lazy-initialized project clients map: project_id -> Project _project_clients: dict[str, Project] = {} @@ -191,41 +199,31 @@ def _initialize_config() -> None: # Validate API key and determine scope try: - try: - _api_key_scope = check_api_key_scope(_api_key, _host) - except RuntimeError as check_error: - # If it's a project-scoped key error, use a placeholder project_id - # Project-scoped keys have the project_id embedded in the key itself - if "project-scoped" in str(check_error): - # Use empty string as project_id - the Project client will extract it from the API key - _api_key_scope = {"project_id": ""} - print("✓ Connected with project-scoped API key", file=sys.stderr) - else: - raise + _api_key_scope = check_api_key_scope(_api_key, _host) - if "account_id" in _api_key_scope: - account_id = _api_key_scope["account_id"] + scope = _api_key_scope + if scope is None: + raise RuntimeError("Could not determine API key scope") + + if "account_id" in scope: + account_id = cast(AccountScope, scope)["account_id"] print(f"✓ Connected with account-scoped API key (account: {account_id})", file=sys.stderr) - elif "project_id" in _api_key_scope: - project_id = _api_key_scope["project_id"] - # For project-scoped keys with empty/None project_id, the Project client will extract it from the API key - if project_id: - print(f"✓ Connected with project-scoped API key (project: {project_id})", file=sys.stderr) - # For project-scoped keys, eagerly create the project client - client = Project(api_key=_api_key, project_id=project_id, host=_host) - _project_clients[project_id] = client - else: - # Project-scoped key with empty project_id - will be discovered on first use - print("✓ Connected with project-scoped API key (project ID will be discovered on first use)", file=sys.stderr) - else: - # If we got here with no project_id but it might be project-scoped, check if COCALC_PROJECT_ID was provided + # If a project_id is explicitly provided via env, prepare a client for it if project_id_config: - _api_key_scope = {"project_id": project_id_config} - print(f"✓ Using project-scoped API key with explicitly provided project_id (project: {project_id_config})", file=sys.stderr) client = Project(api_key=_api_key, project_id=project_id_config, host=_host) _project_clients[project_id_config] = client - else: - raise RuntimeError("Could not determine API key scope") + print( + f"✓ Using account-scoped API key with explicitly provided project_id (project: {project_id_config})", + file=sys.stderr, + ) + elif "project_id" in scope: + project_id = cast(ProjectScope, scope)["project_id"] + print(f"✓ Connected with project-scoped API key (project: {project_id})", file=sys.stderr) + # For project-scoped keys, eagerly create the project client + client = Project(api_key=_api_key, project_id=project_id, host=_host) + _project_clients[project_id] = client + else: + raise RuntimeError("Could not determine API key scope") except RuntimeError as e: print(f"Error: {e}", file=sys.stderr) @@ -255,8 +253,9 @@ def get_project_client(project_id: Optional[str] = None) -> Project: # Determine which project_id to use if project_id is None: # If no project_id provided, try to use the one from project-scoped key - if _api_key_scope and "project_id" in _api_key_scope: - project_id = _api_key_scope["project_id"] + scope = _api_key_scope + if scope and "project_id" in scope: + project_id = cast(ProjectScope, scope)["project_id"] else: # Account-scoped key requires explicit project_id raise RuntimeError("Account-scoped API key requires an explicit project_id argument. " From 157721bff7d67e7390ec78b5986d34ce9e48323e Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Fri, 21 Nov 2025 14:08:56 +0100 Subject: [PATCH 30/58] cocalc-api/mcp: tool and resource descovery via api key or explicit project id --- .../cocalc-api/src/cocalc_api/mcp/README.md | 29 ++++++++++++- .../src/cocalc_api/mcp/mcp_server.py | 43 ++++++++++--------- 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/README.md b/src/python/cocalc-api/src/cocalc_api/mcp/README.md index f50ea7442d0..64801b71d79 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/README.md +++ b/src/python/cocalc-api/src/cocalc_api/mcp/README.md @@ -53,13 +53,33 @@ Add to `~/.config/Claude/claude_desktop_config.json`: "mcpServers": { "cocalc": { "command": "uv", - "args": ["--directory", "/path/to/cocalc-api", "run", "cocalc-mcp-server"], - "env": {"COCALC_API_KEY": "sk-your-api-key"} + "args": [ + "--directory", + "/path/to/cocalc-api", + "run", + "cocalc-mcp-server" + ], + "env": { "COCALC_API_KEY": "sk-your-api-key" } } } } ``` +## Setup with Codex CLI + +```bash +cd /path/to/cocalc-api +export COCALC_API_KEY="sk-your-api-key" +export COCALC_HOST="https://cocalc.com" # or your local hub, e.g. http://localhost:5000 +# optional (account keys only): export COCALC_PROJECT_ID="your-project-uuid" + +codex mcp add --env COCALC_API_KEY="$COCALC_API_KEY" \ + --env COCALC_HOST="$COCALC_HOST" \ + cocalc -- uv --directory "$(pwd)" run cocalc-mcp-server +``` + +The MCP name (`cocalc` above) is the first positional argument; use `--` before command flags like `--directory`. + ## Allow Tools in Claude Code Settings Add to `.claude/settings.json`: @@ -77,23 +97,28 @@ The server automatically provides different tools based on your API key type: ### Account-Scoped API Keys **Tools:** + - `projects_search(query="")` - Search and list your projects with collaborator info **Resources:** + - `cocalc://account-profile` - View your account info, settings, and preferences ### Project-Scoped API Keys **Tools:** + - `exec(command)` - Execute shell commands in the project - `jupyter_execute(input, kernel="python3")` - Run code using Jupyter kernels **Resources:** + - `cocalc://project-files` - Browse the project directory structure ## API Keys Create API keys at: + - **Account-scoped**: CoCalc Settings → API keys → Create API key - **Project-scoped**: Project Settings → API keys → Create API key diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py index 53f4feedc4e..94e5a32e10c 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py @@ -208,8 +208,11 @@ def _initialize_config() -> None: if "account_id" in scope: account_id = cast(AccountScope, scope)["account_id"] print(f"✓ Connected with account-scoped API key (account: {account_id})", file=sys.stderr) - # If a project_id is explicitly provided via env, prepare a client for it + # If a project_id is explicitly provided via env, add it to scope if project_id_config: + # Store project_id in scope so tools/resources can use it as fallback + scope["project_id"] = project_id_config # type: ignore + _api_key_scope = scope client = Project(api_key=_api_key, project_id=project_id_config, host=_host) _project_clients[project_id_config] = client print( @@ -234,17 +237,19 @@ def get_project_client(project_id: Optional[str] = None) -> Project: """ Get or create a Project client for the given project. - For project-scoped API keys, project_id is optional (uses the key's project). - For account-scoped API keys, project_id is required. + Project ID resolution (in order of priority): + 1. Explicit project_id parameter + 2. project_id from _api_key_scope (for project-scoped keys or account-scoped keys with COCALC_PROJECT_ID) + 3. Project client extracts it from the API key (for project-scoped keys) Args: - project_id: The project UUID. If None, uses the project-scoped key's project. + project_id: The project UUID. If None, uses the value from _api_key_scope or the API key itself. Returns: Project client for the specified project Raises: - RuntimeError: If project_id cannot be determined or account-scoped key without project_id + RuntimeError: If project_id cannot be determined """ global _project_clients @@ -252,19 +257,11 @@ def get_project_client(project_id: Optional[str] = None) -> Project: # Determine which project_id to use if project_id is None: - # If no project_id provided, try to use the one from project-scoped key + # Try to use project_id from scope (works for both project-scoped keys + # and account-scoped keys with explicit COCALC_PROJECT_ID) scope = _api_key_scope if scope and "project_id" in scope: project_id = cast(ProjectScope, scope)["project_id"] - else: - # Account-scoped key requires explicit project_id - raise RuntimeError("Account-scoped API key requires an explicit project_id argument. " - "No project_id provided to get_project_client().") - - # For project-scoped keys with None/empty project_id, the Project client will extract it from the API key - # For account-scoped keys, project_id must be non-empty - if not project_id and _api_key_scope and "account_id" in _api_key_scope: - raise RuntimeError("Account-scoped API key requires a non-empty project_id") # Use a cache key that handles None/empty project_id for project-scoped keys cache_key = project_id if project_id else "_default_project" @@ -288,9 +285,15 @@ def _register_tools_and_resources() -> None: _initialize_config() - # Determine which tools/resources to register based on API key scope - if _api_key_scope and "account_id" in _api_key_scope: - # Account-scoped key: register account-scoped tools/resources + # Determine what needs to be registered + register_account_tools = _api_key_scope and "account_id" in _api_key_scope + register_project_tools = _api_key_scope and "project_id" in _api_key_scope + # Also register project tools if account-scoped key has explicit project + if register_account_tools and _project_clients: + register_project_tools = True + + # Register account-scoped tools/resources + if register_account_tools: print("Registering account-scoped tools and resources...", file=sys.stderr) from .tools.projects_search import register_projects_search_tool from .resources.account_profile import register_account_profile_resource @@ -298,8 +301,8 @@ def _register_tools_and_resources() -> None: register_projects_search_tool(mcp) register_account_profile_resource(mcp) - elif _api_key_scope and "project_id" in _api_key_scope: - # Project-scoped key: register project-scoped tools/resources + # Register project-scoped tools/resources + if register_project_tools: print("Registering project-scoped tools and resources...", file=sys.stderr) from .tools.exec import register_exec_tool from .tools.jupyter import register_jupyter_tool From 10646eee144d38f62aa484444b07ea5894fd7a90 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Fri, 21 Nov 2025 14:28:19 +0100 Subject: [PATCH 31/58] cocalc-api/mcp: touch project when in use --- src/packages/next/pages/api/conat/project.ts | 1 + src/packages/server/api/project-bridge.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/packages/next/pages/api/conat/project.ts b/src/packages/next/pages/api/conat/project.ts index 8d6dd8fc124..80597933210 100644 --- a/src/packages/next/pages/api/conat/project.ts +++ b/src/packages/next/pages/api/conat/project.ts @@ -57,6 +57,7 @@ export default async function handle(req, res) { name, args, timeout, + account_id, }); res.json(resp); } catch (err) { diff --git a/src/packages/server/api/project-bridge.ts b/src/packages/server/api/project-bridge.ts index 4459d15a034..14739023e4b 100644 --- a/src/packages/server/api/project-bridge.ts +++ b/src/packages/server/api/project-bridge.ts @@ -14,12 +14,14 @@ export default async function projectBridge({ name, args, timeout, + account_id, }: { project_id: string; compute_server_id?: number; name: string; args?: any[]; timeout?: number; + account_id?: string; }) { client ??= conat(); return await callProject({ @@ -29,6 +31,7 @@ export default async function projectBridge({ name, args, timeout, + account_id, }); } @@ -39,6 +42,7 @@ async function callProject({ name, args = [], timeout = DEFAULT_TIMEOUT, + account_id, }: { client: ConatClient; project_id: string; @@ -46,6 +50,7 @@ async function callProject({ name: string; args?: any[]; timeout?: number; + account_id?: string; }) { const subject = projectSubject({ project_id, @@ -53,10 +58,10 @@ async function callProject({ service: "api", }); try { - // Ensure the project is running before making the API call + // Ensure the project is running and signal activity before making the API call const project = getProject(project_id); if (project) { - await project.start(); + await project.touch(account_id); } // For discovery-style calls, inject identifiers so the project can report scope From a8c4ee2c378d8fa66bf69e1b9d6b8a31c6c62a46 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Fri, 21 Nov 2025 17:18:57 +0100 Subject: [PATCH 32/58] cocalc-api/test: fix edgecases in api and improve testing --- .github/workflows/make-and-test.yml | 1 + src/packages/conat/hub/api/projects.ts | 22 ++- src/packages/server/accounts/get-name.ts | 4 +- src/packages/server/conat/api/projects.ts | 42 +++++ src/python/cocalc-api/Makefile | 7 +- src/python/cocalc-api/pyproject.toml | 1 + .../cocalc-api/scripts/ci-create-project.py | 65 ++++++++ src/python/cocalc-api/src/cocalc_api/hub.py | 67 ++++++-- .../cocalc-api/src/cocalc_api/mcp/README.md | 2 +- .../src/cocalc_api/mcp/mcp_debug.py | 12 +- .../src/cocalc_api/mcp/mcp_server.py | 142 +++++++++++++++-- .../cocalc_api/mcp/resources/file_listing.py | 5 + .../src/cocalc_api/mcp/tools/exec.py | 5 + .../src/cocalc_api/mcp/tools/jupyter.py | 10 +- .../src/cocalc_api/mcp/tools/project_state.py | 87 ++++++++++ .../cocalc_api/mcp/tools/project_status.py | 136 ++++++++++++++++ .../cocalc_api/mcp/tools/projects_search.py | 8 +- src/python/cocalc-api/src/cocalc_api/org.py | 28 ++-- .../cocalc-api/src/cocalc_api/project.py | 12 +- src/python/cocalc-api/tests/README.md | 16 +- src/python/cocalc-api/tests/conftest.py | 38 ++++- src/python/cocalc-api/tests/test_hub.py | 148 ++++++++++++++++++ src/python/cocalc-api/tests/test_jupyter.py | 44 +++--- src/python/cocalc-api/tests/test_project.py | 34 ++++ 24 files changed, 838 insertions(+), 98 deletions(-) create mode 100755 src/python/cocalc-api/scripts/ci-create-project.py create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/tools/project_state.py create mode 100644 src/python/cocalc-api/src/cocalc_api/mcp/tools/project_status.py diff --git a/.github/workflows/make-and-test.yml b/.github/workflows/make-and-test.yml index 4ef9c133398..03a3c4952fc 100644 --- a/.github/workflows/make-and-test.yml +++ b/.github/workflows/make-and-test.yml @@ -278,6 +278,7 @@ jobs: run: | export COCALC_API_KEY=$(cat src/api_key.txt) export COCALC_HOST=http://localhost:5000 + export CI=true cd src/python/cocalc-api && make ci env: PGDATABASE: smc diff --git a/src/packages/conat/hub/api/projects.ts b/src/packages/conat/hub/api/projects.ts index f5c65aa03f6..3414522c25b 100644 --- a/src/packages/conat/hub/api/projects.ts +++ b/src/packages/conat/hub/api/projects.ts @@ -1,6 +1,10 @@ import { authFirstRequireAccount } from "./util"; import { type CreateProjectOptions } from "@cocalc/util/db-schema/projects"; import { type UserCopyOptions } from "@cocalc/util/db-schema/projects"; +import { + type ProjectState, + type ProjectStatus, +} from "@cocalc/util/db-schema/projects"; export const projects = { createProject: authFirstRequireAccount, @@ -13,6 +17,9 @@ export const projects = { start: authFirstRequireAccount, stop: authFirstRequireAccount, deleteProject: authFirstRequireAccount, + touch: authFirstRequireAccount, + state: authFirstRequireAccount, + status: authFirstRequireAccount, }; export type AddCollaborator = @@ -104,5 +111,18 @@ export interface Projects { start: (opts: { account_id: string; project_id: string }) => Promise; stop: (opts: { account_id: string; project_id: string }) => Promise; - deleteProject: (opts: { account_id: string; project_id: string }) => Promise; + deleteProject: (opts: { + account_id: string; + project_id: string; + }) => Promise; + + touch: (opts: { account_id: string; project_id: string }) => Promise; + state: (opts: { + account_id: string; + project_id: string; + }) => Promise; + status: (opts: { + account_id: string; + project_id: string; + }) => Promise; } diff --git a/src/packages/server/accounts/get-name.ts b/src/packages/server/accounts/get-name.ts index 16fe3fd8f61..1004573a643 100644 --- a/src/packages/server/accounts/get-name.ts +++ b/src/packages/server/accounts/get-name.ts @@ -37,8 +37,8 @@ type Names = { function canonicalName(row) { // some accounts have these null for some reason sometimes, but it is nice if client code can assume not null. let { first_name = "", last_name = "", profile } = row; - first_name = first_name.trim(); - last_name = last_name.trim(); + first_name = (first_name ?? "").trim(); + last_name = (last_name ?? "").trim(); if (!first_name && !last_name) { // Also ensure both are not empty so you can always see something. I think the frontend and/or api doesn't // allow a user to make their name empty, but *just in case* we do this. diff --git a/src/packages/server/conat/api/projects.ts b/src/packages/server/conat/api/projects.ts index d840ab0372b..5a060e419bc 100644 --- a/src/packages/server/conat/api/projects.ts +++ b/src/packages/server/conat/api/projects.ts @@ -106,3 +106,45 @@ export async function stop({ import deleteProject from "@cocalc/server/projects/delete"; export { deleteProject }; + +export async function touch({ + account_id, + project_id, +}: { + account_id: string; + project_id: string; +}): Promise { + if (!(await isCollaborator({ account_id, project_id }))) { + throw Error("must be collaborator on project to touch it"); + } + const project = await getProject(project_id); + await project.touch(account_id); +} + +export async function state({ + account_id, + project_id, +}: { + account_id: string; + project_id: string; +}): Promise { + if (!(await isCollaborator({ account_id, project_id }))) { + throw Error("must be collaborator on project to get state"); + } + const project = await getProject(project_id); + return await project.state(); +} + +export async function status({ + account_id, + project_id, +}: { + account_id: string; + project_id: string; +}): Promise { + if (!(await isCollaborator({ account_id, project_id }))) { + throw Error("must be collaborator on project to get status"); + } + const project = await getProject(project_id); + return await project.status(); +} diff --git a/src/python/cocalc-api/Makefile b/src/python/cocalc-api/Makefile index 5cc0ed2170b..f2f16dc9b7d 100644 --- a/src/python/cocalc-api/Makefile +++ b/src/python/cocalc-api/Makefile @@ -37,8 +37,11 @@ test: test-verbose: uv run pytest -v -ci: - uv run pytest --junitxml=test-results.xml --cov=src --cov-report=term-missing --cov-report=html +ci-create-project: + @uv run python3 scripts/ci-create-project.py + +ci: ci-create-project + export COCALC_PROJECT_ID=$$($(MAKE) ci-create-project) && uv run pytest --junitxml=test-results.xml --cov=src --cov-report=term-missing --cov-report=html coverage: uv run pytest --cov=src --cov-report=term-missing --cov-report=html diff --git a/src/python/cocalc-api/pyproject.toml b/src/python/cocalc-api/pyproject.toml index eeaa6684f34..04218c400dd 100644 --- a/src/python/cocalc-api/pyproject.toml +++ b/src/python/cocalc-api/pyproject.toml @@ -51,6 +51,7 @@ omit = [ "*/__pycache__/*", "*/venv/*", "*/env/*", + "*/mcp/*", ] [tool.coverage.report] diff --git a/src/python/cocalc-api/scripts/ci-create-project.py b/src/python/cocalc-api/scripts/ci-create-project.py new file mode 100755 index 00000000000..878257af5ec --- /dev/null +++ b/src/python/cocalc-api/scripts/ci-create-project.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +Create a test project for CI tests using the cocalc-api Hub. + +This script creates a temporary project for running tests that require a project ID +(e.g., cocalc-api tests with account-scoped API keys). + +Outputs the project ID to stdout so it can be captured by the CI workflow. +""" + +import os +import sys +import httpx + + +def main(): + """Create a test project and output its ID.""" + # Get configuration from environment + api_key = os.environ.get("COCALC_API_KEY") + host = os.environ.get("COCALC_HOST", "http://localhost:5000") + + if not api_key: + print("Error: COCALC_API_KEY environment variable not set", file=sys.stderr) + sys.exit(1) + + try: + # Create HTTP client with API key authentication + client = httpx.Client( + auth=(api_key, ""), + headers={"Content-Type": "application/json"}, + timeout=120.0 + ) + + # Call the hub API to create a project + resp = client.post( + f"{host}/api/conat/hub", + json={ + "name": "projects.createProject", + "args": [{"title": "CI Test Project"}] + } + ) + + result = resp.json() + + # Check for errors in the response + if "error" in result: + print(f"Error creating project: {result['error']}", file=sys.stderr) + sys.exit(1) + + # The result should be the project ID + project_id = result + if not project_id or project_id == "None": + print(f"Error: Invalid project ID returned: {result}", file=sys.stderr) + sys.exit(1) + + # Output the project ID to stdout + print(project_id) + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/src/python/cocalc-api/src/cocalc_api/hub.py b/src/python/cocalc-api/src/cocalc_api/hub.py index c1377c40f21..c0cdafab709 100644 --- a/src/python/cocalc-api/src/cocalc_api/hub.py +++ b/src/python/cocalc-api/src/cocalc_api/hub.py @@ -147,7 +147,7 @@ def user_search(self, query: str) -> UserSearchResult: 'created': 1756056224470, 'email_address_verified': None}] """ - ... + ... # pragma: no cover class Projects: @@ -246,7 +246,7 @@ def add_collaborator(self, project_id: str | list[str], account_id: str | list[s Returns: dict[str, Any]: JSON response from the API. """ - ... + ... # pragma: no cover @api_method("projects.removeCollaborator", opts=True) def remove_collaborator(self, project_id: str, account_id: str) -> dict[str, Any]: @@ -260,7 +260,7 @@ def remove_collaborator(self, project_id: str, account_id: str) -> dict[str, Any Returns: dict[str, Any]: JSON response from the API. """ - ... + ... # pragma: no cover @api_method("projects.start") def start(self, project_id: str) -> dict[str, Any]: @@ -295,6 +295,55 @@ def delete(self, project_id: str) -> dict[str, Any]: """ ... + @api_method("projects.touch") + def touch(self, project_id: str) -> dict[str, Any]: + """ + Signal that the project is in use by updating its last_edited timestamp. + This also ensures the project is started. + + Args: + project_id (str): Project ID of the project to touch. + + Returns: + dict[str, Any]: API response indicating success. + """ + ... + + @api_method("projects.state") + def state(self, project_id: str) -> dict[str, Any]: + """ + Get the current state of a project (running, stopped, starting, etc.). + + Args: + project_id (str): Project ID of the project. + + Returns: + dict[str, Any]: Project state object containing: + - state: "running" | "stopped" | "starting" | "restarting" | "error" + - ip: IP address where project is running (if running) + - error: Error message (if in error state) + - time: Timestamp of last state change + """ + ... + + @api_method("projects.status") + def status(self, project_id: str) -> dict[str, Any]: + """ + Get detailed status information about a project. + + Args: + project_id (str): Project ID of the project. + + Returns: + dict[str, Any]: Project status object containing: + - project.pid: PID of project server process + - start_ts: Timestamp when project started + - version: Project code version + - disk_MB: Disk usage in MB + - memory: Memory usage information + """ + ... + class Jupyter: @@ -324,7 +373,7 @@ def kernels(self, project_id: Optional[str] = None) -> list[dict[str, Any]]: >>> 'python3' in kernel_names True """ - ... + ... # pragma: no cover @api_method("jupyter.execute", timeout_seconds=True) def execute( @@ -365,7 +414,7 @@ def execute( ... project_id='6e75dbf1-0342-4249-9dce-6b21648656e9') {'output': [{'data': {'text/plain': '3^4 * 5^2'}}], ...} """ - ... + ... # pragma: no cover class Sync: @@ -385,7 +434,7 @@ def history(self, project_id: str, path: str) -> list[dict[str, Any]]: # type: Returns: list[dict[str, Any]]: Array of patches in a compressed diff-match-patch format, along with time and user data. """ - ... + ... # pragma: no cover class Database: @@ -420,7 +469,7 @@ def query(self, query: dict[str, Any]) -> dict[str, Any]: >>> hub.db.query({"accounts":{"first_name":None}}) {'accounts': {'first_name': 'W'}} """ - ... + ... # pragma: no cover class Messages: @@ -442,7 +491,7 @@ def send(self, subject: str, body: str, to_ids: list[str], reply_id: Optional[in Returns: int: ID of the message. """ - ... + ... # pragma: no cover @api_method("messages.get") def get( @@ -462,7 +511,7 @@ def get( Returns: list[MessageType]: List of messages. """ - ... + ... # pragma: no cover """ diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/README.md b/src/python/cocalc-api/src/cocalc_api/mcp/README.md index 64801b71d79..830d99c2ead 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/README.md +++ b/src/python/cocalc-api/src/cocalc_api/mcp/README.md @@ -27,7 +27,7 @@ If you supply `COCALC_PROJECT_ID` with an account-scoped key, the MCP server wil ### Quick Registration ```bash -claude mcp add \ +claude mcp add [ --scope user ] \ --transport stdio \ cocalc \ --env COCALC_API_KEY="sk-your-api-key" \ diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_debug.py b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_debug.py index 262a28f7cd7..12cd7d76d1b 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_debug.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_debug.py @@ -43,8 +43,7 @@ async def debug_mcp_server() -> None: read_stream, write_stream = client async with ClientSession(read_stream, write_stream) as session: # Initialize the connection - print("\n=== MCP Server Debug Information ===\n", - file=sys.stderr) + print("\n=== MCP Server Debug Information ===\n", file=sys.stderr) # Get capabilities info = await session.initialize() @@ -105,16 +104,13 @@ async def debug_mcp_server() -> None: except Exception as e: error_str = str(e) if "COCALC_API_KEY" in error_str or "not set" in error_str: - print("Error: COCALC_API_KEY environment variable is not set", - file=sys.stderr) + print("Error: COCALC_API_KEY environment variable is not set", file=sys.stderr) print("\nUsage:", file=sys.stderr) print(" export COCALC_API_KEY='sk-...'", file=sys.stderr) print(" make mcp-debug", file=sys.stderr) elif "project_id" in error_str.lower(): - print("Error: Project-scoped API key requires COCALC_PROJECT_ID", - file=sys.stderr) - print("\nFor project-scoped API keys, provide the project ID:", - file=sys.stderr) + print("Error: Project-scoped API key requires COCALC_PROJECT_ID", file=sys.stderr) + print("\nFor project-scoped API keys, provide the project ID:", file=sys.stderr) print(" export COCALC_API_KEY='sk-...'", file=sys.stderr) print(" export COCALC_PROJECT_ID='uuid-...'", file=sys.stderr) print(" make mcp-debug", file=sys.stderr) diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py index 94e5a32e10c..d813b3d7845 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py @@ -40,7 +40,7 @@ import os import sys import time -from typing import Optional, TypedDict, Union, cast +from typing import Any, Optional, TypedDict, Union, cast from mcp.server.fastmcp import FastMCP @@ -58,10 +58,7 @@ def _retry_with_backoff(func, max_retries: int = 3, retry_delay: int = 2): return func() except Exception as e: error_msg = str(e).lower() - is_retryable = any( - keyword in error_msg - for keyword in ["timeout", "closed", "connection", "reset", "broken"] - ) + is_retryable = any(keyword in error_msg for keyword in ["timeout", "closed", "connection", "reset", "broken"]) if is_retryable and attempt < max_retries - 1: print( f"Initialization attempt {attempt + 1} failed ({error_msg[:50]}...), " @@ -181,10 +178,20 @@ def check_api_key_scope(api_key: str, host: str) -> Scope: # Lazy-initialized project clients map: project_id -> Project _project_clients: dict[str, Project] = {} +# Current project management (for account-scoped keys to switch between projects) +_current_project_id: Optional[str] = None + + +def _update_scope_with_current_project() -> None: + """Update _api_key_scope to include current_project_id for account-scoped keys.""" + global _api_key_scope, _current_project_id + if _api_key_scope and "account_id" in _api_key_scope and _current_project_id: + _api_key_scope["project_id"] = _current_project_id # type: ignore + def _initialize_config() -> None: """Initialize configuration and validate API key at startup.""" - global _api_key, _host, _api_key_scope, _project_clients + global _api_key, _host, _api_key_scope, _project_clients, _current_project_id if _api_key is not None: return # Already initialized @@ -213,6 +220,7 @@ def _initialize_config() -> None: # Store project_id in scope so tools/resources can use it as fallback scope["project_id"] = project_id_config # type: ignore _api_key_scope = scope + _current_project_id = project_id_config client = Project(api_key=_api_key, project_id=project_id_config, host=_host) _project_clients[project_id_config] = client print( @@ -279,37 +287,139 @@ def get_project_client(project_id: Optional[str] = None) -> Project: return client +def set_current_project(project_id: str) -> dict[str, Any]: + """ + Set the current project for an account-scoped API key. + This creates/caches a project client and updates the scope. + + Args: + project_id: The UUID of the project to set as current + + Returns: + dict with project info: project_id, title, status + + Raises: + RuntimeError: If API key is not account-scoped or project cannot be accessed + """ + global _current_project_id, _api_key_scope, _project_clients + + _initialize_config() + + # Only account-scoped keys can switch projects + if not _api_key_scope or "account_id" not in _api_key_scope: + raise RuntimeError("Only account-scoped API keys can switch projects") + + # Validate that user is collaborator on this project + assert _api_key is not None + assert _host is not None + from cocalc_api import Hub + + try: + hub = Hub(api_key=_api_key, host=_host) + projects = hub.projects.get(project_id=project_id) + if not projects: + raise RuntimeError(f"Project {project_id} not found or not accessible") + project_info = projects[0] + except Exception as e: + raise RuntimeError(f"Cannot access project {project_id}: {str(e)}") from e + + # Set as current project + _current_project_id = project_id + _update_scope_with_current_project() + + # Create/cache the project client for this project + assert _api_key is not None + assert _host is not None + client = Project(api_key=_api_key, project_id=project_id, host=_host) + _project_clients[project_id] = client + + # Return project info + return { + "project_id": project_info.get("project_id"), + "title": project_info.get("title", "Untitled"), + "state": project_info.get("state", {}).get("state", "unknown"), + } + + +def get_current_project() -> dict[str, Any]: + """ + Get information about the current project (if set). + + Returns: + dict with project info if a project is current, else empty dict + + Raises: + RuntimeError: If API key is not account-scoped + """ + global _current_project_id, _api_key_scope + + _initialize_config() + + # Only account-scoped keys have "current project" concept + if not _api_key_scope or "account_id" not in _api_key_scope: + raise RuntimeError("Only account-scoped API keys have a current project concept") + + if not _current_project_id: + return {} + + # Fetch current project info + assert _api_key is not None + assert _host is not None + from cocalc_api import Hub + + try: + hub = Hub(api_key=_api_key, host=_host) + projects = hub.projects.get(project_id=_current_project_id) + if not projects: + return {"error": f"Project {_current_project_id} no longer accessible"} + project_info = projects[0] + return { + "project_id": project_info.get("project_id"), + "title": project_info.get("title", "Untitled"), + "state": project_info.get("state", {}).get("state", "unknown"), + "last_edited": project_info.get("last_edited"), + } + except Exception as e: + return {"error": str(e)} + + def _register_tools_and_resources() -> None: """Register tools and resources based on API key scope.""" global _api_key_scope _initialize_config() - # Determine what needs to be registered - register_account_tools = _api_key_scope and "account_id" in _api_key_scope - register_project_tools = _api_key_scope and "project_id" in _api_key_scope - # Also register project tools if account-scoped key has explicit project - if register_account_tools and _project_clients: - register_project_tools = True + # Register tools based on API key type + is_account_scoped = _api_key_scope and "account_id" in _api_key_scope + is_project_scoped = _api_key_scope and "project_id" in _api_key_scope - # Register account-scoped tools/resources - if register_account_tools: + if is_account_scoped: print("Registering account-scoped tools and resources...", file=sys.stderr) from .tools.projects_search import register_projects_search_tool + from .tools.project_state import ( + register_set_current_project_tool, + register_get_current_project_tool, + ) from .resources.account_profile import register_account_profile_resource register_projects_search_tool(mcp) + register_set_current_project_tool(mcp) + register_get_current_project_tool(mcp) register_account_profile_resource(mcp) - # Register project-scoped tools/resources - if register_project_tools: + # Register project tools if: + # - It's a project-scoped key, OR + # - It's an account-scoped key (so users can switch projects after initial setup) + if is_project_scoped or is_account_scoped: print("Registering project-scoped tools and resources...", file=sys.stderr) from .tools.exec import register_exec_tool from .tools.jupyter import register_jupyter_tool + from .tools.project_status import register_project_status_tool from .resources.file_listing import register_file_listing_resource register_exec_tool(mcp) register_jupyter_tool(mcp) + register_project_status_tool(mcp) register_file_listing_resource(mcp) diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py b/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py index 1b2f61679c6..9273863c406 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/resources/file_listing.py @@ -72,5 +72,10 @@ async def project_files() -> str: header = "Files in project home directory:\n" + "=" * 60 + "\n" return header + "\n".join(formatted_lines) + except RuntimeError as e: + error_msg = str(e) + if "No current project set" in error_msg or "project_id" in error_msg: + return "Error: No project set. Use set_current_project(project_id) to select a project first." + return f"Error listing files: {error_msg}" except Exception as e: return f"Error listing files: {str(e)}" diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py index 4ddaac34728..894eb9b1079 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/exec.py @@ -73,5 +73,10 @@ async def exec( output = f"stdout:\n{result['stdout']}\n\nstderr:\n{result['stderr']}\n\nexit_code: {result['exit_code']}" return output + except RuntimeError as e: + error_msg = str(e) + if "No current project set" in error_msg or "project_id" in error_msg: + return "Error: No project set. Use set_current_project(project_id) to select a project first." + return f"Error executing command: {error_msg}" except Exception as e: return f"Error executing command: {str(e)}" diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/jupyter.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/jupyter.py index e42b9c80ffa..49585655a39 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/tools/jupyter.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/jupyter.py @@ -17,10 +17,7 @@ def _is_retryable_error(error: Exception) -> bool: """Check if an error is retryable (transient connection issue).""" error_msg = str(error).lower() - return any( - keyword in error_msg - for keyword in ["timeout", "closed", "connection", "reset", "broken"] - ) + return any(keyword in error_msg for keyword in ["timeout", "closed", "connection", "reset", "broken"]) def _retry_with_backoff( @@ -150,5 +147,10 @@ async def jupyter_execute( return "\n".join(output_lines) + except RuntimeError as e: + error_msg = str(e) + if "No current project set" in error_msg or "project_id" in error_msg: + return "Error: No project set. Use set_current_project(project_id) to select a project first." + return f"Error executing code in kernel: {error_msg}" except Exception as e: return f"Error executing code in kernel: {str(e)}" diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/project_state.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/project_state.py new file mode 100644 index 00000000000..b7a9e488469 --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/project_state.py @@ -0,0 +1,87 @@ +""" +Project state management tools for account-scoped keys. + +Provides tools to set and get the current project for an account-scoped API key, +allowing users to switch between projects and query current project information. +""" + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + pass + + +def register_set_current_project_tool(mcp) -> None: + """Register the set_current_project tool with the given FastMCP instance.""" + + @mcp.tool() + async def set_current_project(project_id: str) -> str: + """ + Set the current project for an account-scoped API key. + + After setting a project, all subsequent project-level operations (exec, jupyter_execute, file access) + will use this project by default. You only need to call this once to switch projects. + + Args: + project_id: The UUID of the project to work with + + Returns: + String with project information (title, state) or error message + + Examples: + Set project after searching: + + >>> set_current_project("c8787b71-a85f-437b-9d1b-29833c3a199e") + "✓ Project 'Test 01' is now active (state: running)" + + The project is now the default for all subsequent operations. + """ + from ..mcp_server import set_current_project as _set_current_project + + try: + result = _set_current_project(project_id) + state = result.get("state", "unknown") + title = result.get("title", "Untitled") + return f"✓ Project '{title}' is now active (state: {state})" + except Exception as e: + return f"Error setting project: {str(e)}" + + +def register_get_current_project_tool(mcp) -> None: + """Register the get_current_project tool with the given FastMCP instance.""" + + @mcp.tool() + async def get_current_project() -> str: + """ + Get information about the currently active project. + + Use this to check which project is active before running commands. + + Returns: + String with current project information or "no project set" + + Examples: + Check the active project: + + >>> get_current_project() + "Current project: 'Test 01' (c8787b71-a85f-437b-9d1b-29833c3a199e)" + "State: running | Last edited: 2025-11-21 13:35 UTC" + """ + from ..mcp_server import get_current_project as _get_current_project + + try: + result = _get_current_project() + if not result: + return "No project currently set. Use set_current_project(project_id) to select one." + if "error" in result: + return f"Error: {result['error']}" + + title = result.get("title", "Untitled") + project_id = result.get("project_id", "?") + state = result.get("state", "unknown") + last_edited = result.get("last_edited", "unknown") + + return f"""Current project: '{title}' ({project_id}) +State: {state} | Last edited: {last_edited}""" + except Exception as e: + return f"Error getting current project: {str(e)}" diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/project_status.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/project_status.py new file mode 100644 index 00000000000..cf8f763e5e2 --- /dev/null +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/project_status.py @@ -0,0 +1,136 @@ +""" +Project status tool for querying detailed project information. + +Provides a tool to get comprehensive status information about a specific project, +including state, running processes, disk usage, and memory information. +""" + +import json +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + pass + + +def register_project_status_tool(mcp) -> None: + """Register the project_status tool with the given FastMCP instance.""" + + @mcp.tool() + async def project_status(project_id: str) -> str: + """ + Get detailed status information about a specific project. + + This provides a comprehensive summary of what's happening in a project, + including its current state, running processes, and resource usage. + + Args: + project_id: The UUID of the project to query + + Returns: + Formatted string with detailed project status information + + Examples: + Get status of a project: + + >>> project_status("c8787b71-a85f-437b-9d1b-29833c3a199e") + "Project Status for 'main test project1' (c8787b71-a85f-437b-9d1b-29833c3a199e) + State: running + IP: 127.0.0.1 + Last changed: 2025-11-21T14:55:21.253Z + ..." + """ + from cocalc_api import Hub + from ..mcp_server import _api_key, _host + + try: + assert _api_key is not None + assert _host is not None + hub = Hub(api_key=_api_key, host=_host) + + # Get project state and status via hub API + state_info = hub.projects.state(project_id) + status_info = hub.projects.status(project_id) + + # Build formatted output + lines = [ + f"Project Status for '{project_id}'", + "=" * 60, + "", + ] + + # State information + if state_info: + state = state_info.pop("state", "unknown") + ip = state_info.pop("ip", None) + error = state_info.pop("error", None) + time = state_info.pop("time", "unknown") + + # Add comment about the state for clarity + if state == "running": + state_comment = " (project is active)" + elif state in ("starting", "stopping"): + state_comment = f" (project is {state})" + else: + state_comment = " (project is stopped)" + + lines.append("STATE:") + lines.append(f" State: {state}{state_comment}") + if ip: + lines.append(f" IP Address: {ip}") + if error: + lines.append(f" Error: {error}") + lines.append(f" Last Changed: {time}") + lines.append("") + + # Detailed status information + if status_info: + lines.append("RESOURCES:") + + # Process information + project_dict = status_info.pop("project", {}) + if isinstance(project_dict, dict): + pid = project_dict.get("pid") + if pid: + lines.append(f" Process ID: {pid}") + + # Timing information + start_ts = status_info.pop("start_ts", None) + if start_ts: + lines.append(f" Started At: {start_ts}") + + version = status_info.pop("version", None) + if version: + lines.append(f" Version: {version}") + + # Disk usage + disk_mb = status_info.pop("disk_MB", None) + if disk_mb is not None: + lines.append(f" Disk Usage: {disk_mb} MB") + + # Memory information + memory = status_info.pop("memory", None) + if memory: + if isinstance(memory, dict): + for key, val in memory.items(): + lines.append(f" Memory {key}: {val}") + else: + lines.append(f" Memory: {memory}") + + # Add remaining fields as JSON if any + remaining: dict[str, Any] = {} + if state_info: + remaining["state_info"] = state_info + if status_info: + remaining["status_info"] = status_info + + if remaining: + lines.append("") + lines.append("ADDITIONAL INFORMATION:") + lines.append(json.dumps(remaining, indent=2)) + + return "\n".join(lines) + + except RuntimeError as e: + return f"Error: {str(e)}" + except Exception as e: + return f"Error getting project status: {str(e)}" diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/projects_search.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/projects_search.py index 95142523e6d..c7784d8f96c 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/tools/projects_search.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/projects_search.py @@ -62,18 +62,14 @@ def projects_search(query: str = "") -> str: "state", "deleted", "users", # collaborators - ] - ) + ]) if not projects: return "No projects found" # Filter by query if provided if query: - projects = [ - p for p in projects - if query.lower() in (p.get("title", "") or "").lower() - ] + projects = [p for p in projects if query.lower() in (p.get("title", "") or "").lower()] if not projects: return f"No projects found matching query: '{query}'" diff --git a/src/python/cocalc-api/src/cocalc_api/org.py b/src/python/cocalc-api/src/cocalc_api/org.py index 8f6af5ef3fb..d6fceca4430 100644 --- a/src/python/cocalc-api/src/cocalc_api/org.py +++ b/src/python/cocalc-api/src/cocalc_api/org.py @@ -2,7 +2,7 @@ from .util import api_method from .api_types import TokenType, OrganizationUser -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from .hub import Hub @@ -19,7 +19,7 @@ def get_all(self) -> dict[str, Any]: Returns: dict[str, Any]: Organization data. """ - ... + ... # pragma: no cover @api_method("org.create") def create(self, name: str) -> dict[str, Any]: @@ -33,7 +33,7 @@ def create(self, name: str) -> dict[str, Any]: Returns: dict[str, Any]: Organization data. """ - ... + ... # pragma: no cover @api_method("org.get") def get(self, name: str) -> dict[str, Any]: @@ -46,7 +46,7 @@ def get(self, name: str) -> dict[str, Any]: Returns: dict[str, Any]: Organization data. """ - ... + ... # pragma: no cover @api_method("org.set") def set(self, @@ -66,7 +66,7 @@ def set(self, (nothing to do with a cocalc account). link (Optional[str]): A website of the organization. """ - ... + ... # pragma: no cover @api_method("org.addAdmin") def add_admin(self, name: str, user: str) -> dict[str, Any]: @@ -78,7 +78,7 @@ def add_admin(self, name: str, user: str) -> dict[str, Any]: name (str): name of the organization user (str): email or account_id """ - ... + ... # pragma: no cover @api_method("org.addUser") def add_user(self, name: str, user: str) -> dict[str, Any]: @@ -92,7 +92,7 @@ def add_user(self, name: str, user: str) -> dict[str, Any]: name (str): name of the organization user (str): email or account_id """ - ... + ... # pragma: no cover @api_method("org.createUser") def create_user(self, @@ -117,7 +117,7 @@ def create_user(self, Returns: str: account_id of the new user """ - ... + ... # pragma: no cover @api_method("org.createToken") def create_token(self, user: str) -> TokenType: @@ -141,7 +141,7 @@ def create_token(self, user: str) -> TokenType: them. You can also test out this url, since the token works multiple times. """ - ... + ... # pragma: no cover @api_method("org.expireToken") def expire_token(self, token: str) -> dict[str, Any]: @@ -151,7 +151,7 @@ def expire_token(self, token: str) -> dict[str, Any]: Args: token (str): a token """ - ... + ... # pragma: no cover @api_method("org.getUsers") def get_users(self, name: str) -> list[OrganizationUser]: # type: ignore[empty-body] @@ -172,7 +172,7 @@ def get_users(self, name: str) -> list[OrganizationUser]: # type: ignore[empty- - `account_id` (str): a uuid - `email_address` (str) """ - ... + ... # pragma: no cover @api_method("org.removeUser") def remove_user(self, name: str, user: str) -> dict[str, Any]: @@ -183,7 +183,7 @@ def remove_user(self, name: str, user: str) -> dict[str, Any]: name (str): name of the organization user (str): email or account_id """ - ... + ... # pragma: no cover @api_method("org.removeAdmin") def remove_admin(self, name: str, user: str) -> dict[str, Any]: @@ -194,7 +194,7 @@ def remove_admin(self, name: str, user: str) -> dict[str, Any]: name (str): name of the organization user (str): email or account_id """ - ... + ... # pragma: no cover @api_method("org.message") def message(self, name: str, subject: str, body: str) -> dict[str, Any]: @@ -207,4 +207,4 @@ def message(self, name: str, subject: str, body: str) -> dict[str, Any]: subject (str): plain text subject of the message body (str): markdown body of the message (math typesetting works) """ - ... + ... # pragma: no cover diff --git a/src/python/cocalc-api/src/cocalc_api/project.py b/src/python/cocalc-api/src/cocalc_api/project.py index dff4371f16a..c44aaebf289 100644 --- a/src/python/cocalc-api/src/cocalc_api/project.py +++ b/src/python/cocalc-api/src/cocalc_api/project.py @@ -64,7 +64,7 @@ def ping(self) -> PingResponse: {'now': 1756489740133} """ - ... + ... # pragma: no cover @api_method("system.test") def test(self) -> dict[str, Any]: @@ -75,7 +75,7 @@ def test(self) -> dict[str, Any]: dict: JSON object containing project_id and server_time. """ - ... + ... # pragma: no cover @api_method("system.exec", timeout_seconds=True) def exec( @@ -122,7 +122,7 @@ def exec( >>> project.system.exec(command="echo 'hello from cocalc'") {'stdout': 'hello from cocalc\\n', 'stderr':'', 'exit_code': 0} """ - ... + ... # pragma: no cover @api_method("system.jupyterExecute", timeout_seconds=True) def jupyter_execute( @@ -172,7 +172,7 @@ def jupyter_execute( >>> result [{'name': 'stdout', 'text': 'Hello\\n'}] """ - ... + ... # pragma: no cover @api_method("system.listJupyterKernels") def list_jupyter_kernels(self) -> list[dict[str, Any]]: # type: ignore[empty-body] @@ -193,7 +193,7 @@ def list_jupyter_kernels(self) -> list[dict[str, Any]]: # type: ignore[empty-bo >>> kernels [{'pid': 12345, 'connectionFile': '/run/user/1000/jupyter/kernel-abc123.json', 'kernel_name': 'python3'}] """ - ... + ... # pragma: no cover @api_method("system.stopJupyterKernel") def stop_jupyter_kernel(self, pid: int) -> dict[str, bool]: # type: ignore[empty-body] @@ -213,4 +213,4 @@ def stop_jupyter_kernel(self, pid: int) -> dict[str, bool]: # type: ignore[empt >>> project.system.stop_jupyter_kernel(pid=12345) {'success': True} """ - ... + ... # pragma: no cover diff --git a/src/python/cocalc-api/tests/README.md b/src/python/cocalc-api/tests/README.md index b367d153838..55c0507973d 100644 --- a/src/python/cocalc-api/tests/README.md +++ b/src/python/cocalc-api/tests/README.md @@ -30,11 +30,24 @@ uv run pytest tests/test_jupyter.py -v - `conftest.py` - Pytest configuration and fixtures (includes resource tracking and cleanup) - `test_hub.py` - Tests for Hub client functionality (projects, database queries, messages) -- `test_project.py` - Tests for Project client functionality (ping, exec commands) +- `test_project.py` - Tests for Project client functionality (ping, exec commands, Jupyter kernel management) - `test_jupyter.py` - Tests for Jupyter kernel installation and code execution - `test_org.py` - Tests for organization management (create, users, licenses) - `test_org_basic.py` - Basic organization API tests +### Jupyter Tests and CI Environments + +The Jupyter-related tests are **skipped in CI environments** (e.g., GitHub Actions) due to resource constraints: + +- **`test_jupyter.py`**: All 12 Jupyter tests are skipped when `CI=true` +- **`test_project.py::TestProjectSystem::test_stop_jupyter_kernel`**: Skipped when `CI=true` + +This is because Jupyter kernel operations are resource-intensive and can fail in containerized CI environments. The tests run successfully in local development environments. + +**Test counts:** +- **Local development** (CI not set): 64 tests run +- **GitHub Actions CI** (CI=true): 52 tests run (12 Jupyter tests skipped) + ## Environment Variables ### Required @@ -45,6 +58,7 @@ uv run pytest tests/test_jupyter.py -v - `COCALC_HOST` - CoCalc server URL (default: `http://localhost:5000`) - `COCALC_TESTS_CLEANUP` - Enable/disable automatic cleanup (default: `true`) +- `CI` - Set to `true` to skip Jupyter tests in CI environments (e.g., GitHub Actions). When not set, all tests run locally. ### For Database Cleanup (Recommended) diff --git a/src/python/cocalc-api/tests/conftest.py b/src/python/cocalc-api/tests/conftest.py index bef833838f7..d19d5fdaaef 100644 --- a/src/python/cocalc-api/tests/conftest.py +++ b/src/python/cocalc-api/tests/conftest.py @@ -23,9 +23,8 @@ def retry_with_backoff( func: Callable[[], T], max_retries: int = 3, retry_delay: int = 5, - error_condition: Callable[[RuntimeError], bool] = lambda e: any( - keyword in str(e).lower() for keyword in ["timeout", "closed", "connection", "reset", "broken"] - ), + error_condition: Callable[[RuntimeError], + bool] = lambda e: any(keyword in str(e).lower() for keyword in ["timeout", "closed", "connection", "reset", "broken"]), ) -> T: """ Retry a function call with exponential backoff for timeout and connection errors. @@ -120,7 +119,36 @@ def hub(api_key, cocalc_host): @pytest.fixture(scope="session") -def temporary_project(hub, resource_tracker, request): +def validate_api_key_config(hub): + """ + Validate that the API key is properly configured for testing. + + For account-scoped keys, requires COCALC_PROJECT_ID to be set. + For project-scoped keys, no additional configuration needed. + """ + try: + scope = hub.system.test() + except Exception as e: + pytest.fail(f"Failed to determine API key scope: {e}") + + is_account_scoped = "account_id" in scope + is_project_scoped = "project_id" in scope + + if is_account_scoped: + # Account-scoped key requires COCALC_PROJECT_ID for project tests + project_id = os.environ.get("COCALC_PROJECT_ID") + if not project_id: + pytest.fail("Account-scoped API key detected, but COCALC_PROJECT_ID is not set.\n\n" + "For testing with an account-scoped key, you must provide a project ID:\n" + " export COCALC_PROJECT_ID=\n\n" + "Alternatively, use a project-scoped API key which has the project ID embedded.") + elif not is_project_scoped: + pytest.fail(f"Could not determine API key scope. Response: {scope}\n" + "Expected either 'account_id' (account-scoped) or 'project_id' (project-scoped).") + + +@pytest.fixture(scope="session") +def temporary_project(hub, resource_tracker, request, validate_api_key_config): """ Create a temporary project for testing and return project info. Uses a session-scoped fixture so only ONE project is created for the entire test suite. @@ -282,7 +310,7 @@ def has_python_kernel() -> bool: "--name=python3", "--display-name=Python 3", ], - timeout=120, + timeout=120, ): raise RuntimeError("Failed to install python3 kernelspec") diff --git a/src/python/cocalc-api/tests/test_hub.py b/src/python/cocalc-api/tests/test_hub.py index 675027a5ffb..5dc01de92e6 100644 --- a/src/python/cocalc-api/tests/test_hub.py +++ b/src/python/cocalc-api/tests/test_hub.py @@ -170,6 +170,39 @@ def test_delete_method_exists(self, hub): # Note: We don't actually delete anything in this test since # deletion is tested in the project lifecycle via temporary_project fixture + def test_project_state_and_status(self, hub, temporary_project, project_client): + """Test retrieving state and status information for a project.""" + project_id = temporary_project["project_id"] + + # Ensure project is responsive before checking its status + project_client.system.ping() + + state_info = hub.projects.state(project_id) + assert isinstance(state_info, dict) + state = state_info.get("state") + assert isinstance(state, str) + assert state, "Expected a non-empty state string" + + status_info = hub.projects.status(project_id) + assert isinstance(status_info, dict) + informative_keys = ("project", "start_ts", "version", "disk_MB", "memory") + assert any(key in status_info for key in informative_keys), "Status response should include resource information" + + project_status = status_info.get("project") + if isinstance(project_status, dict): + pid = project_status.get("pid") + if pid is not None: + assert isinstance(pid, int) + assert pid > 0 + + if "disk_MB" in status_info: + disk_usage = status_info["disk_MB"] + assert isinstance(disk_usage, (int, float)) + + memory_info = status_info.get("memory") + if memory_info is not None: + assert isinstance(memory_info, dict) + def test_project_lifecycle(self, hub, resource_tracker): """Test complete project lifecycle: create, wait for ready, run command, delete, verify deletion.""" @@ -345,3 +378,118 @@ def test_collaborator_management(self, hub, resource_tracker): print("\n✅ Collaborator management test completed successfully!") # Note: No cleanup needed - hard-delete happens automatically at session end + + def test_stop_project(self, hub, temporary_project): + """Test stopping a running project.""" + project_id = temporary_project["project_id"] + result = hub.projects.stop(project_id) + # Stop can return None or a dict, both are valid + assert result is None or isinstance(result, dict) + print(f"✓ Project stop request sent") + + def test_touch_project(self, hub, temporary_project): + """Test touching a project to signal it's in use.""" + project_id = temporary_project["project_id"] + result = hub.projects.touch(project_id) + # Touch can return None or a dict, both are valid + assert result is None or isinstance(result, dict) + print(f"✓ Project touched successfully") + + def test_get_names(self, hub, resource_tracker): + """Test getting account names.""" + import time + timestamp = int(time.time()) + org_name = f"names-test-org-{timestamp}" + + # Create a test user first + user_id = create_tracked_user(hub, resource_tracker, org_name, email=f"names-test-{timestamp}@test.local") + + # Get the name(s) - returns a dict mapping user_id to display name + result = hub.system.get_names([user_id]) + assert isinstance(result, dict) + # The result should have the user_id as a key + assert user_id in result or len(result) > 0 + print(f"✓ Got names for user: {result}") + + def test_copy_path_between_projects(self, hub, temporary_project, resource_tracker, project_client): + """Test copying paths between projects.""" + import time + import uuid + timestamp = int(time.time()) + + # Create a second project + project2_id = create_tracked_project(hub, resource_tracker, title=f"copy-target-{timestamp}") + project2_client = Project(project_id=project2_id, api_key=hub.api_key, host=hub.host) + + # Create a unique test string + test_string = str(uuid.uuid4()) + src_filename = f"testfile-copy-{timestamp}.txt" + dst_filename = f"testfile-copied-{timestamp}.txt" + + # Create a test file in the first project + project_client.system.exec(f"echo '{test_string}' > {src_filename}") + + # Copy the file to the second project + result = hub.projects.copy_path_between_projects(src_project_id=temporary_project["project_id"], + src_path=src_filename, + target_project_id=project2_id, + target_path=dst_filename) + # copy_path_between_projects can return None or a dict + assert result is None or isinstance(result, dict) + print(f"✓ File copy request sent") + + # Verify the file was copied by reading it + verify_result = project2_client.system.exec(f"cat {dst_filename}") + assert verify_result["exit_code"] == 0 + assert test_string in verify_result["stdout"] + print(f"✓ Verified copied file contains expected content") + + def test_sync_history(self, hub, temporary_project, project_client): + """Test getting sync history of a file.""" + import time + timestamp = int(time.time()) + filename = f"history-test-{timestamp}.txt" + + # Create a test file + project_client.system.exec(f"echo 'initial' > {filename}") + + result = hub.sync.history(project_id=temporary_project["project_id"], path=filename) + # Result can be a list or a dict with patches and info + if isinstance(result, dict): + patches = result.get('patches', []) + assert isinstance(patches, list) + else: + assert isinstance(result, list) + print(f"✓ Got sync history") + + def test_db_query(self, hub): + """Test database query for user info.""" + result = hub.db.query({"accounts": {"first_name": None}}) + assert isinstance(result, dict) + assert "accounts" in result + first_name = result["accounts"].get("first_name") + assert first_name is not None + print(f"✓ DB query successful, first_name: {first_name}") + + def test_messages_send(self, hub, resource_tracker): + """Test sending a message.""" + import time + timestamp = int(time.time()) + org_name = f"msg-test-org-{timestamp}" + + # Create a test user to send message to + user_id = create_tracked_user(hub, resource_tracker, org_name, email=f"msg-test-{timestamp}@test.local") + + result = hub.messages.send(subject="Test Message", body="This is a test message", to_ids=[user_id]) + assert isinstance(result, int) + assert result > 0 + print(f"✓ Message sent with ID: {result}") + + def test_jupyter_kernels(self, hub, temporary_project): + """Test getting available Jupyter kernels.""" + result = hub.jupyter.kernels(project_id=temporary_project["project_id"]) + assert isinstance(result, list) + # Should have at least python3 + kernel_names = [k.get("name") for k in result] + assert "python3" in kernel_names or len(result) > 0 + print(f"✓ Found {len(result)} Jupyter kernels: {kernel_names}") diff --git a/src/python/cocalc-api/tests/test_jupyter.py b/src/python/cocalc-api/tests/test_jupyter.py index 57164cc1a79..09bb5b731bf 100644 --- a/src/python/cocalc-api/tests/test_jupyter.py +++ b/src/python/cocalc-api/tests/test_jupyter.py @@ -1,10 +1,20 @@ """ Tests for Jupyter kernel functionality. + +Note: These tests are skipped in CI environments due to unreliable Jupyter setup +in containerized environments. They are thoroughly tested locally. """ +import os +import pytest + # Import helper from conftest from tests.conftest import retry_with_backoff +# Skip all Jupyter tests in CI (GitHub Actions) +# Jupyter kernel startup and execution is unreliable in containerized CI environments +pytestmark = pytest.mark.skipif(os.environ.get("CI") == "true", reason="Jupyter tests skipped in CI due to environment constraints") + class TestJupyterKernelSetup: """Tests for Jupyter kernel installation and availability.""" @@ -71,12 +81,13 @@ def test_execute_simple_sum(self, hub, temporary_project): """Test executing a simple sum using the python3 kernel. Note: First execution may take longer as kernel needs to start up (30+ seconds). + In CI environments, this can take even longer, so we use more retries. """ project_id = temporary_project["project_id"] - result = retry_with_backoff( - lambda: hub.jupyter.execute(input="sum(range(100))", kernel="python3", project_id=project_id) - ) + result = retry_with_backoff(lambda: hub.jupyter.execute(input="sum(range(100))", kernel="python3", project_id=project_id), + max_retries=5, + retry_delay=10) # Check the result structure assert isinstance(result, dict) @@ -98,8 +109,7 @@ def test_execute_with_history(self, hub, temporary_project): project_id = temporary_project["project_id"] result = retry_with_backoff( - lambda: hub.jupyter.execute(history=["a = 100"], input="sum(range(a + 1))", kernel="python3", project_id=project_id) - ) + lambda: hub.jupyter.execute(history=["a = 100"], input="sum(range(a + 1))", kernel="python3", project_id=project_id)) # Check the result (sum of 0..100 = 5050) assert isinstance(result, dict) @@ -120,9 +130,7 @@ def test_execute_print_statement(self, hub, temporary_project): """ project_id = temporary_project["project_id"] - result = retry_with_backoff( - lambda: hub.jupyter.execute(input='print("Hello from Jupyter")', kernel="python3", project_id=project_id) - ) + result = retry_with_backoff(lambda: hub.jupyter.execute(input='print("Hello from Jupyter")', kernel="python3", project_id=project_id)) # Check that we got output assert isinstance(result, dict) @@ -150,9 +158,7 @@ def test_jupyter_execute_simple_sum(self, project_client): Note: First execution may take longer as kernel needs to start up (30+ seconds). """ - result = retry_with_backoff( - lambda: project_client.system.jupyter_execute(input="sum(range(100))", kernel="python3") - ) + result = retry_with_backoff(lambda: project_client.system.jupyter_execute(input="sum(range(100))", kernel="python3")) # Result is a list, not a dict with 'output' key assert isinstance(result, list) @@ -172,9 +178,7 @@ def test_jupyter_execute_with_history(self, project_client): Note: First execution may take longer as kernel needs to start up (30+ seconds). """ - result = retry_with_backoff( - lambda: project_client.system.jupyter_execute(history=["b = 50"], input="b * 2", kernel="python3") - ) + result = retry_with_backoff(lambda: project_client.system.jupyter_execute(history=["b = 50"], input="b * 2", kernel="python3")) # Result is a list assert isinstance(result, list) @@ -192,9 +196,7 @@ def test_jupyter_execute_list_operation(self, project_client): The result is a list of output items directly. """ - result = retry_with_backoff( - lambda: project_client.system.jupyter_execute(input="[x**2 for x in range(5)]", kernel="python3") - ) + result = retry_with_backoff(lambda: project_client.system.jupyter_execute(input="[x**2 for x in range(5)]", kernel="python3")) # Result is a list assert isinstance(result, list) @@ -213,9 +215,7 @@ class TestJupyterKernelManagement: def test_list_jupyter_kernels(self, project_client): """Test listing running Jupyter kernels.""" # First execute some code to ensure a kernel is running - retry_with_backoff( - lambda: project_client.system.jupyter_execute(input="1+1", kernel="python3") - ) + retry_with_backoff(lambda: project_client.system.jupyter_execute(input="1+1", kernel="python3")) # List kernels kernels = project_client.system.list_jupyter_kernels() @@ -236,9 +236,7 @@ def test_list_jupyter_kernels(self, project_client): def test_stop_jupyter_kernel(self, project_client): """Test stopping a specific Jupyter kernel.""" # Execute code to start a kernel - retry_with_backoff( - lambda: project_client.system.jupyter_execute(input="1+1", kernel="python3") - ) + retry_with_backoff(lambda: project_client.system.jupyter_execute(input="1+1", kernel="python3")) # List kernels kernels = project_client.system.list_jupyter_kernels() diff --git a/src/python/cocalc-api/tests/test_project.py b/src/python/cocalc-api/tests/test_project.py index fefe182dbc0..9e500cc0736 100644 --- a/src/python/cocalc-api/tests/test_project.py +++ b/src/python/cocalc-api/tests/test_project.py @@ -1,6 +1,7 @@ """ Tests for Project client functionality. """ +import os import pytest from cocalc_api import Project @@ -115,3 +116,36 @@ def test_exec_stderr_and_exit_code(self, project_client): assert stderr_match is not None, "Could not find stderr in error message" stderr_content = stderr_match.group(1).strip() assert stderr_content == "test error message" + + def test_list_jupyter_kernels(self, project_client): + """Test listing Jupyter kernels in a project.""" + result = project_client.system.list_jupyter_kernels() + assert isinstance(result, list) + print(f"✓ Found {len(result)} Jupyter kernels") + # Each kernel should have basic properties + for kernel in result: + assert "pid" in kernel + assert isinstance(kernel["pid"], int) + assert kernel["pid"] > 0 + + @pytest.mark.skipif(os.environ.get("CI") == "true", reason="Jupyter tests skipped in CI due to environment constraints") + def test_stop_jupyter_kernel(self, project_client): + """Test stopping a Jupyter kernel. + + Note: Skipped in CI environments due to unreliable Jupyter setup. + """ + from tests.conftest import retry_with_backoff + + # First, execute code to ensure a kernel is running with retry + retry_with_backoff(lambda: project_client.system.jupyter_execute(input="1+1", kernel="python3")) + + # List kernels + kernels = project_client.system.list_jupyter_kernels() + assert len(kernels) > 0, "Expected at least one kernel to be running" + + # Stop the first kernel + pid = kernels[0]["pid"] + result = project_client.system.stop_jupyter_kernel(pid=pid) + assert isinstance(result, dict) + assert "success" in result + print(f"✓ Stopped Jupyter kernel with PID {pid}: {result}") From ab4ab267f2b97cbbf537809882884cdaecc4b674 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Mon, 24 Nov 2025 11:20:26 +0100 Subject: [PATCH 33/58] cocalc-api/mcp: improve project.get (limit, filtering) --- src/python/cocalc-api/src/cocalc_api/hub.py | 87 +++++++++++++++++-- .../cocalc_api/mcp/tools/projects_search.py | 26 +++++- 2 files changed, 106 insertions(+), 7 deletions(-) diff --git a/src/python/cocalc-api/src/cocalc_api/hub.py b/src/python/cocalc-api/src/cocalc_api/hub.py index c0cdafab709..a594a4e2e4f 100644 --- a/src/python/cocalc-api/src/cocalc_api/hub.py +++ b/src/python/cocalc-api/src/cocalc_api/hub.py @@ -155,35 +155,112 @@ class Projects: def __init__(self, parent: "Hub"): self._parent = parent - def get(self, fields: Optional[list[str]] = None, all: Optional[bool] = False, project_id: Optional[str] = None) -> list[dict[str, Any]]: + def get( + self, + fields: Optional[list[str]] = None, + all: Optional[bool] = False, + project_id: Optional[str] = None, + limit: Optional[int] = None, + deleted: Optional[bool] = None, + hidden: Optional[bool] = None, + state: Optional[str] = None, + account_id_for_hidden: Optional[str] = None, + ) -> list[dict[str, Any]]: """ Get data about projects that you are a collaborator on. Only gets recent projects by default; set all=True to get all projects. Args: fields (Optional[list[str]]): The fields about the project to get. - Default: ['project_id', 'title', 'last_edited', 'state'], but see + Default: ['project_id', 'title', 'last_edited', 'created', 'state', 'deleted', 'users'], but see https://github.com/sagemathinc/cocalc/blob/master/src/packages/util/db-schema/projects.ts all (Optional[bool]): If True, return ALL your projects, not just the recent ones. False by default. project_id (Optional[str]): If given, gets just this one project (as a list of length 1). + limit (Optional[int]): Maximum number of projects to return after filtering. None means no limit. + deleted (Optional[bool]): If set, filter deleted status (True -> only deleted, False -> only not deleted). + hidden (Optional[bool]): If set, filter by collaborator-specific hidden flag. Default None (no filter). + state (Optional[str]): If set, only return projects whose state matches (e.g., 'opened', 'running'). + account_id_for_hidden (Optional[str]): Account ID used to evaluate the hidden flag in the users map. Returns: list[dict[str, Any]]: List of projects. """ + from datetime import datetime + + def _parse_ts(value: Any) -> float: + if value is None: + return 0.0 + if isinstance(value, (int, float)): + return float(value) + if isinstance(value, str): + try: + return datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp() + except ValueError: + try: + return float(value) + except Exception: + return 0.0 + return 0.0 + + def _state_str(val: Any) -> str: + if isinstance(val, dict): + return str(val.get("state") or val.get("status") or "") + if val is None: + return "" + return str(val) + if fields is None: - fields = ['project_id', 'title', 'last_edited', 'state'] + fields = ['project_id', 'title', 'last_edited', 'created', 'state', 'deleted', 'users'] v: list[dict[str, Any]] = [{}] for field in fields: v[0][field] = None if project_id: v[0]['project_id'] = project_id - query: dict[str, list[dict[str, None]]] = {} + query: dict[str, list[dict[str, Any]]] = {} table = 'projects_all' if all else 'projects' query[table] = v result = self._parent.db.query(query) - return result[table] + projects: list[dict[str, Any]] = result[table] + + filtered: list[dict[str, Any]] = [] + for project in projects: + if deleted is not None: + if bool(project.get("deleted")) != deleted: + continue + + if state: + project_state = _state_str(project.get("state")).lower() + if project_state != state.lower(): + continue + + if hidden is not None and account_id_for_hidden: + users = project.get("users") or {} + if isinstance(users, dict): + user_info = users.get(account_id_for_hidden, {}) + is_hidden = False + if isinstance(user_info, dict): + is_hidden = bool(user_info.get("hide")) + if is_hidden != hidden: + continue + + filtered.append(project) + + filtered.sort( + key=lambda p: ( + _parse_ts(p.get("last_edited")), + _parse_ts(p.get("created")), + (p.get("title") or "").lower(), + p.get("project_id") or "", + ), + reverse=True, + ) + + if limit is not None and limit >= 0: + filtered = filtered[:limit] + + return filtered @api_method("projects.copyPathBetweenProjects") def copy_path_between_projects( diff --git a/src/python/cocalc-api/src/cocalc_api/mcp/tools/projects_search.py b/src/python/cocalc-api/src/cocalc_api/mcp/tools/projects_search.py index c7784d8f96c..6e26997df6e 100644 --- a/src/python/cocalc-api/src/cocalc_api/mcp/tools/projects_search.py +++ b/src/python/cocalc-api/src/cocalc_api/mcp/tools/projects_search.py @@ -15,7 +15,13 @@ def register_projects_search_tool(mcp) -> None: """Register the projects search tool with the given FastMCP instance.""" @mcp.tool() - def projects_search(query: str = "") -> str: + def projects_search( + query: str = "", + limit: int = 100, + deleted: bool = False, + hidden: bool = False, + state: str | None = None, + ) -> str: """ Search for and list projects you have access to. @@ -28,6 +34,10 @@ def projects_search(query: str = "") -> str: Args: query (str): Search string to filter projects by title. Default "" lists all projects. + limit (int): Maximum number of projects to return. Default 100. + deleted (bool): If True, only show deleted projects. Default False. + hidden (bool): If True, only show hidden projects; if False, exclude them. Default False. + state (Optional[str]): Filter by state (e.g., "opened" or "running"). Default None (all states). Returns: Formatted list of projects with: @@ -51,6 +61,11 @@ def projects_search(query: str = "") -> str: hub = Hub(api_key=_api_key, host=_host) + # Normalize limit + limit = max(0, limit if limit is not None else 100) + + account_id = _api_key_scope.get("account_id") + # Get all projects with full details projects = hub.projects.get( all=True, @@ -59,10 +74,17 @@ def projects_search(query: str = "") -> str: "title", "description", "last_edited", + "created", "state", "deleted", "users", # collaborators - ]) + ], + limit=limit, + deleted=deleted, + hidden=hidden, + state=state, + account_id_for_hidden=account_id, + ) if not projects: return "No projects found" From ef5d0caafebd02ad6466dc8596a7a04767641cd5 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Mon, 24 Nov 2025 11:47:25 +0100 Subject: [PATCH 34/58] cocalc-api/test: fix edge cases in api and improve testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit updates the conftest.py test fixtures to properly support both account-scoped and project-scoped API keys during testing. The validate_api_key_config fixture now: - First attempts to detect scope via hub.system.test() (account-scoped keys) - Falls back to project.system.test() if hub fails (project-scoped keys) - Reports both errors if both endpoints fail, for better debugging Also clarifies that the hub endpoint's system.test() is for account-scoped keys only, with clear comments directing project-scoped key users to the project endpoint. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/AGENTS.md | 49 +++++-------------------- src/packages/server/conat/api/system.ts | 6 +-- src/python/cocalc-api/tests/conftest.py | 22 ++++++++++- 3 files changed, 34 insertions(+), 43 deletions(-) diff --git a/src/AGENTS.md b/src/AGENTS.md index 1b12f60ef11..fab25974621 100644 --- a/src/AGENTS.md +++ b/src/AGENTS.md @@ -108,11 +108,13 @@ CoCalc is organized as a monorepo with key packages: #### CoCalc Conat Hub API Architecture **API Method Registration Pattern:** + - **Registry**: `packages/conat/hub/api/projects.ts` contains `export const projects = { methodName: authFirstRequireAccount }` - **Implementation**: `packages/server/conat/api/projects.ts` contains `export async function methodName() { ... }` - **Flow**: Python client `@api_method("projects.methodName")` → POST `/api/conat/hub` → `hubBridge()` → conat subject `hub.account.{account_id}.api` → registry lookup → implementation **Example - projects.createProject:** + 1. **Python**: `@api_method("projects.createProject")` decorator 2. **HTTP**: `POST /api/conat/hub {"name": "projects.createProject", "args": [...]}` 3. **Bridge**: `hubBridge()` routes to conat subject @@ -172,7 +174,6 @@ CoCalc is organized as a monorepo with key packages: - Prefix git commits with the package and general area. e.g. 'frontend/latex: ...' if it concerns latex editor changes in the packages/frontend/... code. - When pushing a new branch to Github, track it upstream. e.g. `git push --set-upstream origin feature-foo` for branch "feature-foo". - ## React-intl / Internationalization (i18n) CoCalc uses react-intl for internationalization with SimpleLocalize as the translation platform. @@ -234,7 +235,10 @@ Same flow as above, but **before 3. i18n:upload**, delete the key. Only new keys ## Overview -The `python/cocalc-api/` directory contains a Python client library for the CoCalc API, published as the `cocalc-api` package on PyPI. +The `python/cocalc-api/` directory contains a uv-based Python client library for the CoCalc API, published as the `cocalc-api` package on PyPI. + +It also contains a test framework (`python/cocalc-api/tests/README.md`) and an MCP client (`python/cocalc-api/src/cocalc_api/mcp/README.md`). +For convenience, a `python/cocalc-api/Makefile` exists. ## Client-Server Architecture Investigation @@ -248,54 +252,21 @@ The `python/cocalc-api/` directory contains a Python client library for the CoCa ### Endpoints Discovered #### Hub API: `POST /api/conat/hub` + - **Bridge**: `packages/next/pages/api/conat/hub.ts` → `hubBridge()` → conat subject `hub.account.{account_id}.api` - **Implementation**: `packages/conat/hub/api/projects.ts` - **Available Methods**: `createProject`, `start`, `stop`, `setQuotas`, `addCollaborator`, `removeCollaborator`, etc. -- **Missing**: ❌ **No `delete` method implemented in conat hub API** #### Project API: `POST /api/conat/project` + - **Bridge**: `packages/next/pages/api/conat/project.ts` → `projectBridge()` → conat project subjects - **Implementation**: `packages/conat/project/api/` (system.ping, system.exec, system.jupyterExecute) -### Project Deletion Investigation - -#### ✅ Next.js v2 API Route Available -- **Endpoint**: `packages/next/pages/api/v2/projects/delete.ts` -- **Functionality**: Sets deleted=true, removes licenses, stops project -- **Authentication**: Requires collaborator access or admin - -#### ❌ Missing Conat Hub API Method -- **Current Methods**: Only CRUD operations (create, start, stop, quotas, collaborators) -- **Gap**: No `delete` method exposed through conat hub API used by cocalc-api - -#### Frontend Implementation -- **Location**: `packages/frontend/projects/actions.ts:delete_project()` -- **Method**: Direct database table update via `projects_table_set({deleted: true})` - -## Implementation - -### Solution Implemented: Direct v2 API Call -- **Added**: `hub.projects.delete(project_id)` method to cocalc-api Python client -- **Implementation**: Direct HTTP POST to `/api/v2/projects/delete` endpoint -- **Reasoning**: Fastest path to complete project lifecycle without requiring conat hub API changes -- **Consistency**: Uses same authentication and error handling patterns as other methods - -### Code Changes -1. **`src/cocalc_api/hub.py`**: Added `delete()` method to Projects class -2. **`tests/conftest.py`**: Updated cleanup to use new delete method -3. **`tests/test_hub.py`**: Added test for delete method availability - -## Current Status -- ✅ pytest test framework established with automatic project lifecycle -- ✅ Project creation/start/stop working via conat hub API -- ✅ Project deletion implemented by calling v2 API route directly -- ✅ Complete project lifecycle management: create → start → test → stop → delete -- ✅ All 14 tests passing with proper resource cleanup - # important-instruction-reminders + - Do what has been asked; nothing more, nothing less. - NEVER create files unless they're absolutely necessary for achieving your goal. - ALWAYS prefer editing an existing file to creating a new one. -- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User. +- NEVER proactively create documentation files (\*.md) or README files. Only create documentation files if explicitly requested by the User. - ALWAYS ask questions if something is unclear. Only proceed to the implementation step if you have no questions left. - When modifying a file with a copyright banner at the top, make sure to fix/add the current year to indicate the copyright year. diff --git a/src/packages/server/conat/api/system.ts b/src/packages/server/conat/api/system.ts index 1dc800d7e2a..37304c10a82 100644 --- a/src/packages/server/conat/api/system.ts +++ b/src/packages/server/conat/api/system.ts @@ -18,11 +18,11 @@ export function ping() { return { now: Date.now() }; } -export async function test({ - account_id, -}: { account_id?: string } = {}) { +export async function test({ account_id }: { account_id?: string } = {}) { // Return API key scope information and server time // The authFirst decorator determines the scope from the API key and injects account_id. + // Note: This endpoint only accepts account-scoped keys (see packages/next/pages/api/conat/hub.ts). + // For project-scoped keys, use the project endpoint (packages/next/pages/api/conat/project.ts). const response: { account_id: string; server_time: number } = { account_id: account_id ?? "", server_time: Date.now(), diff --git a/src/python/cocalc-api/tests/conftest.py b/src/python/cocalc-api/tests/conftest.py index d19d5fdaaef..91624b97222 100644 --- a/src/python/cocalc-api/tests/conftest.py +++ b/src/python/cocalc-api/tests/conftest.py @@ -126,10 +126,30 @@ def validate_api_key_config(hub): For account-scoped keys, requires COCALC_PROJECT_ID to be set. For project-scoped keys, no additional configuration needed. """ + scope = None + hub_error = None + + # First, try the hub endpoint (works only for account-scoped keys) try: scope = hub.system.test() except Exception as e: - pytest.fail(f"Failed to determine API key scope: {e}") + hub_error = e + + # If hub check failed, fall back to the project endpoint so project-scoped keys work + if scope is None: + try: + project_client = Project( + api_key=hub.api_key, + host=hub.host, + project_id=os.environ.get("COCALC_PROJECT_ID"), + ) + scope = project_client.system.test() + except Exception as project_error: + pytest.fail( + "Failed to determine API key scope using both hub and project endpoints:\n" + f" hub error: {hub_error}\n" + f" project error: {project_error}" + ) is_account_scoped = "account_id" in scope is_project_scoped = "project_id" in scope From 8cdfc42300e5088465be6d7e4aa14647ad602935 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Tue, 25 Nov 2025 10:55:42 +0100 Subject: [PATCH 35/58] frontend/projects/starred: recalc layout only when really necessary --- .../frontend/projects/projects-starred.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/packages/frontend/projects/projects-starred.tsx b/src/packages/frontend/projects/projects-starred.tsx index 3eba337478f..9d406c9b67b 100644 --- a/src/packages/frontend/projects/projects-starred.tsx +++ b/src/packages/frontend/projects/projects-starred.tsx @@ -21,7 +21,7 @@ import { import { CSS, useActions, useTypedRedux } from "@cocalc/frontend/app-framework"; import { Icon, TimeAgo } from "@cocalc/frontend/components"; -import { trunc } from "@cocalc/util/misc"; +import { sha1, trunc } from "@cocalc/util/misc"; import { COLORS } from "@cocalc/util/theme"; import { useBookmarkedProjects } from "./use-bookmarked-projects"; @@ -131,9 +131,7 @@ export function StarredProjectsBar() { title: project.get("title") ?? "Untitled", description: project.get("description") ?? "", last_edited: project.get("last_edited"), - state: project.get("state"), avatar_image_tiny: project.get("avatar_image_tiny"), - users: project.get("users"), color: project.get("color"), }; }) @@ -143,6 +141,24 @@ export function StarredProjectsBar() { return projects; }, [bookmarkedProjects, project_map]); + // Hash only the fields that impact layout so we can avoid unnecessary re-measurements. + const layoutKey = useMemo(() => { + if (starredProjects.length === 0) { + return ""; + } + const signature = starredProjects + .map((project) => + [ + project.project_id, + project.title, + project.color ?? "", + project.avatar_image_tiny ?? "", + ].join("|"), + ) + .join("::"); + return sha1(signature); + }, [starredProjects]); + // Drag and drop sensors const mouseSensor = useSensor(MouseSensor, { activationConstraint: { distance: 5 }, // 5px to activate drag @@ -215,7 +231,7 @@ export function StarredProjectsBar() { useLayoutEffect(() => { setMeasurementPhase(true); setVisibleCount(0); - }, [starredProjects]); + }, [layoutKey]); // Measure button widths from hidden container and calculate visible count useLayoutEffect(() => { From b754a2614bf559c603db34b2a7c59dcd16a153fb Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Tue, 25 Nov 2025 11:10:51 +0100 Subject: [PATCH 36/58] frontend/settings: translate Dim file extensions and delete/update all translations --- src/AGENTS.md | 4 ++-- .../frontend/account/other-settings.tsx | 6 ++++-- src/packages/frontend/i18n/bin/delete.sh | 6 +++--- src/packages/frontend/i18n/trans/ar_EG.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/de_DE.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/es_ES.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/es_PV.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/fr_FR.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/he_IL.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/hi_IN.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/hu_HU.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/it_IT.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/ja_JP.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/ko_KR.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/nl_NL.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/pl_PL.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/pt_BR.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/pt_PT.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/ru_RU.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/tr_TR.json | 18 ++++++++++-------- src/packages/frontend/i18n/trans/zh_CN.json | 18 ++++++++++-------- 21 files changed, 189 insertions(+), 151 deletions(-) diff --git a/src/AGENTS.md b/src/AGENTS.md index 0fa74f9444d..c204c91fdb2 100644 --- a/src/AGENTS.md +++ b/src/AGENTS.md @@ -176,7 +176,7 @@ Translation IDs follow a hierarchical pattern: `[directory].[subdir].[filename]. Examples: -- `labels.masked_files` - for common UI labels +- `labels.account` - for common UI labels - `account.sign-out.button.title` - for account sign-out dialog - `command.generic.force_build.label` - for command labels @@ -223,4 +223,4 @@ Same flow as above, but **before 3. i18n:upload**, delete the key. Only new keys - ALWAYS prefer editing an existing file to creating a new one - REFUSE to modify files when the git repository is on the `master` or `main` branch - NEVER proactively create documentation files (`*.md`) or README files. Only create documentation files if explicitly requested by the User -- when modifying a file with a copyright banner at the top, make sure to fix/add the current year to indicate the copyright year \ No newline at end of file +- when modifying a file with a copyright banner at the top, make sure to fix/add the current year to indicate the copyright year diff --git a/src/packages/frontend/account/other-settings.tsx b/src/packages/frontend/account/other-settings.tsx index 12fed33a974..e3449b3237b 100644 --- a/src/packages/frontend/account/other-settings.tsx +++ b/src/packages/frontend/account/other-settings.tsx @@ -216,8 +216,10 @@ export function OtherSettings(props: Readonly): React.JSX.Element { checked={!!props.other_settings.get("dim_file_extensions")} onChange={(e) => on_change("dim_file_extensions", e.target.checked)} > - Dim file extensions: gray out file extensions so their - names stand out. + Dim file extensions: gray out file extensions so their names stand out.`} + /> ); } diff --git a/src/packages/frontend/i18n/bin/delete.sh b/src/packages/frontend/i18n/bin/delete.sh index f741e1ca828..f21cd1fab56 100755 --- a/src/packages/frontend/i18n/bin/delete.sh +++ b/src/packages/frontend/i18n/bin/delete.sh @@ -7,8 +7,8 @@ if [ $# -eq 0 ]; then echo "Delete one or more translation keys from SimpleLocalize" echo "" echo "Example:" - echo " $0 labels.masked_files" - echo " $0 labels.masked_files account.sign-out.button.title" + echo " $0 labels.account" + echo " $0 labels.account account.sign-out.button.title" exit 1 fi @@ -34,4 +34,4 @@ echo echo "Done! Now you should run:" echo " pnpm i18n:upload (to re-upload the key with new content)" echo " pnpm i18n:download (to fetch updated translations)" -echo " pnpm i18n:compile (to compile translation files)" \ No newline at end of file +echo " pnpm i18n:compile (to compile translation files)" diff --git a/src/packages/frontend/i18n/trans/ar_EG.json b/src/packages/frontend/i18n/trans/ar_EG.json index 969c3308ec6..11c4ae251a9 100644 --- a/src/packages/frontend/i18n/trans/ar_EG.json +++ b/src/packages/frontend/i18n/trans/ar_EG.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "إزالة كلما تم حفظ الملف", "account.editor-settings-autosave-interval.label": "فترة الحفظ التلقائي", "account.editor-settings.basic.title": "الإعدادات الأساسية", - "account.editor-settings.color-schemes.label": "نظام ألوان المحرر", "account.editor-settings.color-schemes.panel_title": "نظام ألوان المحرر", "account.editor-settings.font-size.label": "حجم الخط العالمي الافتراضي", "account.editor-settings.indent-size.label": "حجم المسافة البادئة", "account.editor-settings.keyboard-bindings.label": "اختصارات لوحة المفاتيح للمحرر", "account.editor-settings.keyboard.title": "لوحة المفاتيح", - "account.editor-settings.title": "المحرر", "account.editor-settings.x11-keyboard-variant.label": "متغير لوحة المفاتيح (لجهاز سطح المكتب X11)", "account.editor-settings.x11-physical-keyboard.label": "تخطيط لوحة المفاتيح (لـ X11 Desktop)", "account.global-ssh-keys.help": "للدخول إلى مشروع باستخدام SSH، استخدم التالي username@host: [project-id-without-dashes]@ssh.cocalc.com يمكن العثور على معرف المشروع بدون فواصل في جزء إعدادات المشروع المتعلق بمفاتيح SSH. للدخول بين المشاريع باستخدام SSH، استخدم [project-id-without-dashes]@ssh.", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "عرض منقسم في ورقة عمل Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "تبديل تعليق التحديد", "account.other-settings._page_size.label": "عدد الملفات لكل صفحة", - "account.other-settings.browser_performance.title": "متصفح", + "account.other-settings.auto_focus": "إدخال نص التركيز التلقائي: التركيز تلقائيًا على حقول إدخال النص عند ظهورها (مثل مستكشف الملفات، المشاريع، ...)", "account.other-settings.button_tooltips": "إخفاء تلميحات الأزرار: يخفي بعض تلميحات الأزرار (هذا جزئي فقط)", "account.other-settings.confirm_close": "تأكيد الإغلاق: اطلب دائمًا التأكيد قبل إغلاق نافذة المتصفح", - "account.other-settings.content_display.title": "عرض المحتوى", "account.other-settings.default_file_sort.by_name": "الترتيب حسب الاسم", "account.other-settings.default_file_sort.by_time": "الترتيب حسب الوقت", "account.other-settings.default_file_sort.label": "ترتيب الملفات الافتراضي", + "account.other-settings.dim_file_extensions": "تعتيم امتدادات الملفات: تظليل امتدادات الملفات حتى تبرز أسماؤها.", "account.other-settings.file_popovers": "إخفاء النوافذ المنبثقة لعلامات تبويب الملفات: عدم عرض النوافذ المنبثقة فوق علامات تبويب الملفات", "account.other-settings.filename_generator.description": "اختر كيفية توليد أسماء الملفات التلقائية. بشكل خاص، لجعلها فريدة أو لتضمين الوقت الحالي.", "account.other-settings.filename_generator.label": "مولد اسم الملف", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "إعدادات AI", "account.other-settings.markdown_codebar": "تعطيل شريط كود العلامات في جميع مستندات العلامات. يؤدي تحديد هذا إلى إخفاء أزرار التشغيل والنسخ والشرح الإضافية في كتل الكود المسورة.", "account.other-settings.mask_files": "إخفاء الملفات: تظليل الملفات في عارض الملفات التي ربما لا تريد فتحها", - "account.other-settings.messages.title": "رسائل", "account.other-settings.project_popovers": "إخفاء النوافذ المنبثقة لعلامات التبويب في المشروع: لا تعرض النوافذ المنبثقة فوق علامات تبويب المشروع", - "account.other-settings.projects.title": "مشاريع", "account.other-settings.standby_timeout": "مهلة الانتظار", "account.other-settings.symbol_bar_labels": "إظهار تسميات شريط الرموز: إظهار التسميات في شريط رموز محرر الإطار", - "account.other-settings.theme": "السمة", "account.other-settings.theme.antd.animations": "الرسوم المتحركة: تحريك بعض العناصر بإيجاز، مثل الأزرار", "account.other-settings.theme.antd.color_scheme": "مخطط الألوان: استخدم ألوان العلامة التجارية بدلاً من الألوان الافتراضية", "account.other-settings.theme.antd.compact": "تصميم مضغوط: استخدم تصميمًا أكثر إحكامًا", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "إنشاء مستند {docName} باستخدام الذكاء الاصطناعي", "ai-generator.select_llm": "اختر نموذج اللغة", "app.fullscreen-button.tooltip": "وضع الشاشة الكاملة، يركز على المستند أو الصفحة الحالية.", + "app.hotkey.dialog.help_text": "انقر على الإطارات أعلاه • المفتاح 0 يبدل الدردشة • المفاتيح 1-9 تركز على الإطارات • اكتب للبحث • ↑↓ للتنقل • عودة لفتح • ESC للإغلاق", + "app.hotkey.dialog.search_placeholder": "البحث في الملفات والصفحات...", + "app.hotkey.dialog.title": "التنقل السريع", "app.verify-email-banner.edit": "إذا كانت عنوان البريد الإلكتروني خاطئة، يرجى تعديله في إعدادات الحساب.", "app.verify-email-banner.help.text": "من المهم أن يكون لديك عنوان بريد إلكتروني يعمل. نستخدمه لإعادة تعيين كلمة المرور، وإرسال الرسائل، وإشعارات الفوترة، والدعم. يرجى التأكد من صحة بريدك الإلكتروني للبقاء على اطلاع.", "app.verify-email-banner.text": "{sent, select, true {تم إرسال البريد الإلكتروني! يرجى التحقق من صندوق البريد الإلكتروني (وربما الرسائل غير المرغوب فيها) والنقر على رابط التأكيد.} other {يرجى التحقق من عنوان بريدك الإلكتروني وتأكيده:}}", @@ -988,6 +986,7 @@ "labels.config": "الإعدادات", "labels.configuration": "التكوين", "labels.configuration.short": "الإعدادات", + "labels.connected": "متصل", "labels.connecting": "الاتصال", "labels.connection": "الاتصال", "labels.copied": "تم النسخ", @@ -1016,6 +1015,7 @@ "labels.environment": "بيئة", "labels.explorer": "المستكشف", "labels.file_explorer": "مستكشف الملفات", + "labels.file_use_notifications": "إشعارات استخدام الملفات", "labels.files": "الملفات", "labels.folder": "مجلد", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {عام} read_only {للقراءة فقط} other {حفظ}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "إيقاف{short, select, true {} other { المشروع}}…", "labels.project.settings.stop-project.ok": "نعم، أوقف المشروع", "labels.projects": "المشاريع", + "labels.public_paths": "المسارات العامة", "labels.published_files": "ملفات منشورة", "labels.purchases": "المشتريات", "labels.ready": "جاهز", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "اختر عنوانًا. يمكنك تغييره بسهولة لاحقًا!", "projects.create-project.requireLicense": "مطلوب ترخيص لإنشاء مشاريع إضافية.", "projects.filename-search.placeholder": "ابحث عن أسماء الملفات التي قمت بتحريرها...", - "projects.list.no_starred_found": "لم يتم العثور على مشاريع مميزة. استخدم رمز النجمة بجانب عناوين المشاريع لوضع إشارة مرجعية على مشاريعك المفضلة.", "projects.load-all.label": "عرض كل المشاريع...", "projects.operations.clear-filter": "مسح الفلتر", "projects.operations.delete.button": "{deleted, select, true {استعادة الكل} other {حذف الكل}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "التصفية حسب الوسوم...", "projects.table-controls.hidden.label": "مخفي", "projects.table-controls.search.placeholder": "ابحث عن المشاريع...", + "projects.table.keyboard-row-hint": "المشروع {title}. استخدم الأسهم لأعلى ولأسفل للتحرك؛ اضغط على Enter أو Space للفتح.", "projects.table.last-edited": "آخر تعديل", + "projects.table.untitled": "بدون عنوان", "purchases.automatic-payments-warning.description": "المدفوعات التلقائية هي أكثر ملاءمة بكثير، وستوفر لك الوقت، وتضمن عدم إلغاء الاشتراكات عن طريق الخطأ.", "purchases.automatic-payments-warning.title": "الدفع التلقائي ليس مطلوبًا للاشتراك", "purchases.automatic-payments.are-enabled": "المدفوعات التلقائية مفعلة", diff --git a/src/packages/frontend/i18n/trans/de_DE.json b/src/packages/frontend/i18n/trans/de_DE.json index 0933088f29f..d535472836e 100644 --- a/src/packages/frontend/i18n/trans/de_DE.json +++ b/src/packages/frontend/i18n/trans/de_DE.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "entfernen, wenn Datei gespeichert wird", "account.editor-settings-autosave-interval.label": "Automatisches Speicherintervall", "account.editor-settings.basic.title": "Grundeinstellungen", - "account.editor-settings.color-schemes.label": "Editor-Farbschema", "account.editor-settings.color-schemes.panel_title": "Editor Farbschema", "account.editor-settings.font-size.label": "Standard globale Schriftgröße", "account.editor-settings.indent-size.label": "Einzugsgröße", "account.editor-settings.keyboard-bindings.label": "Editor-Tastenkombinationen", "account.editor-settings.keyboard.title": "Tastatur", - "account.editor-settings.title": "Editor", "account.editor-settings.x11-keyboard-variant.label": "Tastaturvariante (für X11-Desktop)", "account.editor-settings.x11-physical-keyboard.label": "Tastaturlayout (für X11-Desktop)", "account.global-ssh-keys.help": "Um über SSH in ein Projekt zu gelangen, verwenden Sie folgendes username@host: [project-id-without-dashes]@ssh.cocalc.com Die Projekt-ID ohne Bindestriche finden Sie im Teil der Projekteinstellungen über SSH-Schlüssel. Um zwischen Projekten über SSH zu wechseln, verwenden Sie [project-id-without-dashes]@ssh", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Geteilte Ansicht im Sage-Arbeitsblatt", "account.keyboard-shortcuts.shortcut.toggle-comment": "Auswahl kommentieren umschalten", "account.other-settings._page_size.label": "Anzahl der Dateien pro Seite", - "account.other-settings.browser_performance.title": "Browser", + "account.other-settings.auto_focus": "Textfelder automatisch fokussieren: Textfelder automatisch fokussieren, wenn sie erscheinen (z.B. Dateiexplorer, Projekte, ...)", "account.other-settings.button_tooltips": "Schaltflächen-Tooltips ausblenden: blendet einige Schaltflächen-Tooltips aus (dies ist nur teilweise)", "account.other-settings.confirm_close": "Schließen bestätigen: immer um Bestätigung bitten, bevor das Browserfenster geschlossen wird", - "account.other-settings.content_display.title": "Inhaltsanzeige", "account.other-settings.default_file_sort.by_name": "Nach Name sortieren", "account.other-settings.default_file_sort.by_time": "Nach Zeit sortieren", "account.other-settings.default_file_sort.label": "Standarddatei sortieren", + "account.other-settings.dim_file_extensions": "Dateierweiterungen abdunkeln: Dateierweiterungen ausgrauen, damit ihre Namen hervorstechen.", "account.other-settings.file_popovers": "Datei-Tab-Popovers ausblenden: Popovers über Dateitabs nicht anzeigen", "account.other-settings.filename_generator.description": "Wählen Sie aus, wie automatisch generierte Dateinamen erstellt werden. Insbesondere, um sie einzigartig zu machen oder die aktuelle Uhrzeit einzubeziehen.", "account.other-settings.filename_generator.label": "Dateinamengenerator", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "KI-Einstellungen", "account.other-settings.markdown_codebar": "Deaktiviere die Markdown-Codeleiste in allen Markdown-Dokumenten. Wenn Sie dies aktivieren, werden die zusätzlichen Ausführen-, Kopieren- und Erklären-Buttons in umrandeten Codeblöcken ausgeblendet.", "account.other-settings.mask_files": "Dateien maskieren: Dateien im Dateibetrachter ausgrauen, die Sie wahrscheinlich nicht öffnen möchten", - "account.other-settings.messages.title": "Nachrichten", "account.other-settings.project_popovers": "Projekt-Tab-Popovers ausblenden: die Popovers über den Projekt-Tabs nicht anzeigen", - "account.other-settings.projects.title": "Projekte", "account.other-settings.standby_timeout": "Standby-Timeout", "account.other-settings.symbol_bar_labels": "Symbolleistensymbol-Beschriftungen anzeigen: Beschriftungen in der Symbolleiste des Rahmeneditors anzeigen", - "account.other-settings.theme": "Thema", "account.other-settings.theme.antd.animations": "Animationen: einige Aspekte kurz animieren, z. B. Schaltflächen", "account.other-settings.theme.antd.color_scheme": "Farbschema: Verwenden Sie Markenfarben anstelle der Standardfarben", "account.other-settings.theme.antd.compact": "Kompaktes Design: ein kompakteres Design verwenden", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "Erzeuge ein {docName} Dokument mit KI", "ai-generator.select_llm": "Sprachmodell auswählen", "app.fullscreen-button.tooltip": "Vollbildmodus, fokussiert auf das aktuelle Dokument oder die aktuelle Seite", + "app.hotkey.dialog.help_text": "Klicke auf Rahmen oben • Taste 0 schaltet Chat um • Tasten 1–9 fokussieren Rahmen • Tippen, um zu suchen • ↑↓ navigieren • Eingabetaste zum Öffnen • ESC zum Schließen", + "app.hotkey.dialog.search_placeholder": "Dateien und Seiten durchsuchen...", + "app.hotkey.dialog.title": "Schnellnavigation", "app.verify-email-banner.edit": "Wenn die E-Mail-Adresse falsch ist, bitte bearbeiten Sie sie in den Kontoeinstellungen.", "app.verify-email-banner.help.text": "Es ist wichtig, eine funktionierende E-Mail-Adresse zu haben. Wir verwenden sie für das Zurücksetzen von Passwörtern, das Senden von Nachrichten, Abrechnungsbenachrichtigungen und Unterstützung. Bitte stellen Sie sicher, dass Ihre E-Mail korrekt ist, um informiert zu bleiben.", "app.verify-email-banner.text": "{sent, select, true {E-Mail gesendet! Bitte überprüfen Sie Ihren E-Mail-Posteingang (und vielleicht Spam) und klicken Sie auf den Bestätigungslink.} other {Bitte überprüfen und verifizieren Sie Ihre E-Mail-Adresse:}}", @@ -988,6 +986,7 @@ "labels.config": "Konfiguration", "labels.configuration": "Konfiguration", "labels.configuration.short": "Konfig", + "labels.connected": "Verbunden", "labels.connecting": "Verbinde", "labels.connection": "Verbindung", "labels.copied": "kopiert", @@ -1016,6 +1015,7 @@ "labels.environment": "Umgebung", "labels.explorer": "Explorer", "labels.file_explorer": "Dateimanager", + "labels.file_use_notifications": "Benachrichtigungen zur Dateiverwendung", "labels.files": "Dateien", "labels.folder": "Ordner", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Öffentlich} read_only {Schreibgeschützt} other {Speichern}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "Stop{short, select, true {} other { Projekt}}…", "labels.project.settings.stop-project.ok": "Ja, stoppe Projekt", "labels.projects": "Projekte", + "labels.public_paths": "Öffentliche Pfade", "labels.published_files": "Veröffentlichte Dateien", "labels.purchases": "Einkäufe", "labels.ready": "Bereit", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "Wählen Sie einen Titel. Sie können ihn später einfach ändern!", "projects.create-project.requireLicense": "Eine Lizenz ist erforderlich, um zusätzliche Projekte zu erstellen.", "projects.filename-search.placeholder": "Suche nach Dateien, die Sie bearbeitet haben...", - "projects.list.no_starred_found": "Keine mit Stern markierten Projekte gefunden. Verwenden Sie das Sternsymbol neben Projekttiteln, um Ihre Lieblingsprojekte zu speichern.", "projects.load-all.label": "Zeige alle Projekte...", "projects.operations.clear-filter": "Filter löschen", "projects.operations.delete.button": "{deleted, select, true {Alle wiederherstellen} other {Alle löschen}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "Nach Hashtags filtern...", "projects.table-controls.hidden.label": "Versteckt", "projects.table-controls.search.placeholder": "Projekte durchsuchen...", + "projects.table.keyboard-row-hint": "Projekt {title}. Verwenden Sie die Pfeiltasten nach oben und unten, um sich zu bewegen; drücken Sie Enter oder Leertaste, um zu öffnen.", "projects.table.last-edited": "Zuletzt bearbeitet", + "projects.table.untitled": "Unbenannt", "purchases.automatic-payments-warning.description": "Automatische Zahlungen sind viel bequemer, ersparen Ihnen Zeit und stellen sicher, dass Abonnements nicht versehentlich gekündigt werden.", "purchases.automatic-payments-warning.title": "Automatische Zahlungen sind NICHT erforderlich, um ein Abonnement zu haben", "purchases.automatic-payments.are-enabled": "Automatische Zahlungen sind aktiviert", diff --git a/src/packages/frontend/i18n/trans/es_ES.json b/src/packages/frontend/i18n/trans/es_ES.json index ff03e994cdc..cf0e4512f35 100644 --- a/src/packages/frontend/i18n/trans/es_ES.json +++ b/src/packages/frontend/i18n/trans/es_ES.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "eliminar siempre que se guarde el archivo", "account.editor-settings-autosave-interval.label": "Intervalo de guardado automático", "account.editor-settings.basic.title": "Configuración básica", - "account.editor-settings.color-schemes.label": "Esquema de color del editor", "account.editor-settings.color-schemes.panel_title": "Esquema de Color del Editor", "account.editor-settings.font-size.label": "Tamaño de fuente global predeterminado", "account.editor-settings.indent-size.label": "Tamaño de sangría", "account.editor-settings.keyboard-bindings.label": "Asignaciones de teclado del editor", "account.editor-settings.keyboard.title": "Teclado", - "account.editor-settings.title": "Editor", "account.editor-settings.x11-keyboard-variant.label": "Variante de teclado (para escritorio X11)", "account.editor-settings.x11-physical-keyboard.label": "Distribución del teclado (para el escritorio X11)", "account.global-ssh-keys.help": "Para hacer SSH en un proyecto, usa el siguiente username@host: [project-id-without-dashes]@ssh.cocalc.com El ID del proyecto sin guiones se puede encontrar en la parte de configuración del proyecto sobre claves SSH. Para hacer SSH entre proyectos, usa [project-id-without-dashes]@ssh", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Vista dividida en hoja de trabajo Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Alternar selección de comentarios", "account.other-settings._page_size.label": "Número de archivos por página", - "account.other-settings.browser_performance.title": "Navegador", + "account.other-settings.auto_focus": "Enfoque Automático en la Entrada de Texto: enfocar automáticamente los campos de entrada de texto cuando aparezcan (por ejemplo, explorador de archivos, proyectos, ...)", "account.other-settings.button_tooltips": "Ocultar descripciones emergentes de botones: oculta algunas descripciones emergentes de botones (esto es solo parcial)", "account.other-settings.confirm_close": "Confirmar cierre: siempre pedir confirmación antes de cerrar la ventana del navegador", - "account.other-settings.content_display.title": "Visualización de contenido", "account.other-settings.default_file_sort.by_name": "Ordenar por nombre", "account.other-settings.default_file_sort.by_time": "Ordenar por tiempo", "account.other-settings.default_file_sort.label": "Orden predeterminado de archivos", + "account.other-settings.dim_file_extensions": "Atenuar las extensiones de archivo: atenuar las extensiones de archivo para que sus nombres destaquen.", "account.other-settings.file_popovers": "Ocultar Popovers de Pestañas de Archivos: no mostrar los popovers sobre pestañas de archivos", "account.other-settings.filename_generator.description": "Seleccione cómo se generan los nombres de archivo automáticamente. En particular, para hacerlos únicos o incluir la hora actual.", "account.other-settings.filename_generator.label": "Generador de nombres de archivo", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "Configuración de IA", "account.other-settings.markdown_codebar": "Desactivar la barra de código markdown en todos los documentos markdown. Al marcar esto, se ocultan los botones adicionales de ejecutar, copiar y explicar en los bloques de código delimitados.", "account.other-settings.mask_files": "Archivos ocultos: atenuar archivos en el visor de archivos que probablemente no desees abrir", - "account.other-settings.messages.title": "Mensajes", "account.other-settings.project_popovers": "Ocultar los Popovers de Pestañas de Proyecto: no mostrar los popovers sobre las pestañas del proyecto", - "account.other-settings.projects.title": "Proyectos", "account.other-settings.standby_timeout": "Tiempo de espera en espera", "account.other-settings.symbol_bar_labels": "Mostrar etiquetas de la barra de símbolos: mostrar etiquetas en la barra de símbolos del editor de marcos", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animaciones: animar brevemente algunos aspectos, p. ej., botones", "account.other-settings.theme.antd.color_scheme": "Esquema de Color: usar colores de marca en lugar de colores predeterminados", "account.other-settings.theme.antd.compact": "Diseño Compacto: usar un diseño más compacto", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "Generar un documento {docName} usando IA", "ai-generator.select_llm": "Seleccionar modelo de idioma", "app.fullscreen-button.tooltip": "Modo de pantalla completa, centrado en el documento o página actual.", + "app.hotkey.dialog.help_text": "Haz clic en los marcos de arriba • La tecla 0 alterna el chat • Las teclas 1–9 enfocan los marcos • Escribe para buscar • ↑↓ navega • Return para abrir • ESC para cerrar", + "app.hotkey.dialog.search_placeholder": "Buscar archivos y páginas...", + "app.hotkey.dialog.title": "Navegación Rápida", "app.verify-email-banner.edit": "Si la dirección de correo electrónico es incorrecta, por favor edítala en la configuración de la cuenta.", "app.verify-email-banner.help.text": "Es importante tener una dirección de correo electrónico funcional. La usamos para restablecer contraseñas, enviar mensajes, notificaciones de facturación y soporte. Por favor, asegúrate de que tu correo electrónico sea correcto para mantenerte informado.", "app.verify-email-banner.text": "{sent, select, true {¡Correo electrónico enviado! Por favor, revisa tu bandeja de entrada de correo (y quizá el spam) y haz clic en el enlace de confirmación.} other {Por favor, revisa y verifica tu dirección de correo electrónico:}}", @@ -988,6 +986,7 @@ "labels.config": "Configurar", "labels.configuration": "Configuración", "labels.configuration.short": "Configurar", + "labels.connected": "Conectado", "labels.connecting": "Conectando", "labels.connection": "Conexión", "labels.copied": "copiado", @@ -1016,6 +1015,7 @@ "labels.environment": "Entorno", "labels.explorer": "Explorador", "labels.file_explorer": "Explorador de Archivos", + "labels.file_use_notifications": "Notificaciones de uso de archivos", "labels.files": "Archivos", "labels.folder": "carpeta", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Público} read_only {Solo lectura} other {Guardar}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "Detener{short, select, true {} other { Proyecto}}…", "labels.project.settings.stop-project.ok": "Sí, detener proyecto", "labels.projects": "Proyectos", + "labels.public_paths": "Rutas Públicas", "labels.published_files": "Archivos Publicados", "labels.purchases": "Compras", "labels.ready": "Listo", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "Elige un título. ¡Puedes cambiarlo fácilmente más tarde!", "projects.create-project.requireLicense": "Se requiere una licencia para crear proyectos adicionales.", "projects.filename-search.placeholder": "Buscar nombres de archivos que editaste", - "projects.list.no_starred_found": "No se encontraron proyectos destacados. Usa el icono de estrella junto a los títulos de los proyectos para marcar tus proyectos favoritos.", "projects.load-all.label": "Mostrar todos los proyectos...", "projects.operations.clear-filter": "Borrar filtro", "projects.operations.delete.button": "{deleted, select, true {Restaurar todo} other {Eliminar todo}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "Filtrar por hashtags...", "projects.table-controls.hidden.label": "Oculto", "projects.table-controls.search.placeholder": "Buscar proyectos...", + "projects.table.keyboard-row-hint": "Proyecto {title}. Usa las flechas Arriba y Abajo para moverte; presiona Enter o Espacio para abrir.", "projects.table.last-edited": "Última edición", + "projects.table.untitled": "Sin título", "purchases.automatic-payments-warning.description": "Los pagos automáticos son mucho más convenientes, te ahorrarán tiempo y asegurarán que las suscripciones no se cancelen por accidente.", "purchases.automatic-payments-warning.title": "Los pagos automáticos NO son necesarios para tener una suscripción", "purchases.automatic-payments.are-enabled": "Los pagos automáticos están habilitados", diff --git a/src/packages/frontend/i18n/trans/es_PV.json b/src/packages/frontend/i18n/trans/es_PV.json index dc59eb628b5..eaaec2a5ed3 100644 --- a/src/packages/frontend/i18n/trans/es_PV.json +++ b/src/packages/frontend/i18n/trans/es_PV.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "kendu, fitxategia gordetzen den bakoitzean", "account.editor-settings-autosave-interval.label": "Autogordetze tartea", "account.editor-settings.basic.title": "Oinarrizko Ezarpenak", - "account.editor-settings.color-schemes.label": "Editorearen kolore eskema", "account.editor-settings.color-schemes.panel_title": "Editorearen Kolore Eskema", "account.editor-settings.font-size.label": "Lehenetsitako letra-tamaina globala", "account.editor-settings.indent-size.label": "Indentazio tamaina", "account.editor-settings.keyboard-bindings.label": "Editorearen teklatu loturak", "account.editor-settings.keyboard.title": "Teklatua", - "account.editor-settings.title": "Editorearen Ezarpenak", "account.editor-settings.x11-keyboard-variant.label": "Teklatuaren aldaera (X11 Mahaigainarentzat)", "account.editor-settings.x11-physical-keyboard.label": "Teklatuaren diseinua (X11 Mahaigainarentzat)", "account.global-ssh-keys.help": "SSH bidez proiektu batean sartzeko, erabili honako hau username@host: [proiektuaren-id-gidoi-barik]@ssh.cocalc.com Proiektuaren IDa gidoirik gabe SSH gakoen inguruko proiektuaren ezarpenetan aurki daiteke. Proiektuen artean SSH egiteko, erabili [proiektuaren-id-gidoi-barik]@ssh", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Bista zatitua Sageko kalkulu orrian", "account.keyboard-shortcuts.shortcut.toggle-comment": "Gaitu hautaketa komentatzea", "account.other-settings._page_size.label": "Fitxategi kopurua orrialde bakoitzeko", - "account.other-settings.browser_performance.title": "Nabigatzailea", + "account.other-settings.auto_focus": "Testu Sarrera Automatikoki Fokatzea: automatikoki fokatu testu sarrera eremuak agertzen direnean (adibidez, fitxategi arakatzailea, proiektuak, ...)", "account.other-settings.button_tooltips": "Ezkutatu Botoiaren Tresna-aholkuak: botoi batzuen tresna-aholkuak ezkutatzen ditu (hau partziala da)", "account.other-settings.confirm_close": "Berrestu Ixtea: beti eskatu berrespena nabigatzailearen leihoa itxi aurretik", - "account.other-settings.content_display.title": "Edukien Erakusketa", "account.other-settings.default_file_sort.by_name": "Izenez ordenatu", "account.other-settings.default_file_sort.by_time": "Ordenatu denboraren arabera", "account.other-settings.default_file_sort.label": "Fitxategi ordenazio lehenetsia", + "account.other-settings.dim_file_extensions": "Fitxategiaren luzapenak ilundu: gristatu fitxategiaren luzapenak, haien izenak nabarmentzeko.", "account.other-settings.file_popovers": "Ezkutatu Fitxategi Fitxen Popoverrak: ez erakutsi popoverrak fitxategi fitxetan", "account.other-settings.filename_generator.description": "Hautatu automatikoki sortutako fitxategi-izenak nola sortzen diren. Bereziki, bakarrak bihurtzeko edo egungo denbora sartzeko.", "account.other-settings.filename_generator.label": "Fitxategi-izenen sortzailea", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "AI Ezarpenak", "account.other-settings.markdown_codebar": "Desgaitu markdown kode barra markdown dokumentu guztietan. Hau markatzeak \"exekutatu\", \"kopiatu\" eta \"azaldu\" botoi gehigarriak ezkutatzen ditu.", "account.other-settings.mask_files": "Fitxategiak Maskaratu: ziurrenik ireki nahi ez dituzun fitxategiak esploratzailean grisez apaldu", - "account.other-settings.messages.title": "Mezuak", "account.other-settings.project_popovers": "Proiektuaren fitxa popoverrak ezkutatu: ez erakutsi popoverrak proiektuaren fitxen gainean", - "account.other-settings.projects.title": "Proiektuak", "account.other-settings.standby_timeout": "Itxaronaldi epea", "account.other-settings.symbol_bar_labels": "Ikusi Sinbolo Barra Etiketak: erakutsi etiketak marko editorearen sinbolo barran", - "account.other-settings.theme": "Gaia", "account.other-settings.theme.antd.animations": "Animazioak: gauza batzuk labur animatu, adibidez, botoiak", "account.other-settings.theme.antd.color_scheme": "Kolore Eskema: erabili marka koloreak lehenetsitako koloreen ordez", "account.other-settings.theme.antd.compact": "Diseinu Trinkoa: Diseinu trinkoagoa erabili", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "Sortu {docName} Dokumentua AI erabiliz", "ai-generator.select_llm": "Aukeratu hizkuntza eredua", "app.fullscreen-button.tooltip": "Pantaila osoa modua, uneko dokumentuan edo orrian zentratuta.", + "app.hotkey.dialog.help_text": "Egin klik goiko markoetan • Tekla 0k txata aktibatu/desaktibatzen du • Teklak 1–9 markoetan zentratzen dira • Idatzi bilatzeko • ↑↓ nabigatzeko • Return irekitzeko • ESC ixteko", + "app.hotkey.dialog.search_placeholder": "Bilatu fitxategiak eta orriak...", + "app.hotkey.dialog.title": "Nabigazio Bizkorra", "app.verify-email-banner.edit": "Email helbidea ez bada zuzena editatu egizu kontu ezarpenetan.", "app.verify-email-banner.help.text": "Garrantzitsua da funtzionatzen duen helbide elektroniko bat izatea. Pasahitzak berrezartzeko, mezuak bidaltzeko, fakturazio jakinarazpenak egiteko eta laguntza emateko bidaltzen dugu. Mesedez, ziurtatu zure helbide elektronikoa zuzena dela informazioa jasotzeko.", "app.verify-email-banner.text": "{sent, select, true {Emaila Bidalita! Mesedez, begiratu zure email sarrera-ontzia (eta agian spam karpeta) eta klik egin berrespen estekan.} other {Mesedez, egiaztatu eta baieztatu zure helbide elektronikoa:}}", @@ -988,6 +986,7 @@ "labels.config": "Konfigurazio", "labels.configuration": "Konfigurazioa", "labels.configuration.short": "Konfigurazio", + "labels.connected": "Konektatuta", "labels.connecting": "Konexioa egiten", "labels.connection": "Konexioa", "labels.copied": "kopiatuta", @@ -1016,6 +1015,7 @@ "labels.environment": "Ingurunea", "labels.explorer": "Arakatzailea", "labels.file_explorer": "Fitxategi Arakatzailea", + "labels.file_use_notifications": "Fitxategi Erabilera Jakinarazpenak", "labels.files": "Fitxategiak", "labels.folder": "karpeta", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Publikoa} read_only {Iragargaitza} other {Gorde}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "Gelditu{short, select, true {} other { Proiektua}}…", "labels.project.settings.stop-project.ok": "Bai, gelditu proiektua", "labels.projects": "Proiektuak", + "labels.public_paths": "Bide Publikoak", "labels.published_files": "Argitaratua", "labels.purchases": "Erosketak", "labels.ready": "Prest", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "Hautatu izenburua. Erraz alda dezakezu geroago!", "projects.create-project.requireLicense": "Proiektu gehigarriak sortzeko lizentzia behar da.", "projects.filename-search.placeholder": "Bilatu editatu dituzun fitxategi izenak...", - "projects.list.no_starred_found": "Ez da izarrez markatutako proiekturik aurkitu. Erabili izar ikonoa proiektuen izenburuen ondoan zure proiektu gogokoenak laster-markan jartzeko.", "projects.load-all.label": "Erakutsi proiektu guztiak...", "projects.operations.clear-filter": "Garbitu Iragazkia", "projects.operations.delete.button": "{deleted, select, true {Berreskuratu Guztiak} other {Ezabatu Guztiak}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "Iragazi etiketen arabera...", "projects.table-controls.hidden.label": "Ezkutua", "projects.table-controls.search.placeholder": "Proiektuak bilatu...", + "projects.table.keyboard-row-hint": "Proiektua {title}. Erabili gora eta behera gezien tekla mugitzeko; sakatu Enter edo Space irekitzeko.", "projects.table.last-edited": "Azken aldiz editatua", + "projects.table.untitled": "Izenbururik gabe", "purchases.automatic-payments-warning.description": "Ordainketa automatikoak askoz erosoagoak dira, denbora aurreztuko dizute eta harpidetzak ez direla istripuz bertan behera geldituko ziurtatuko dute.", "purchases.automatic-payments-warning.title": "Ez da beharrezkoa ordainketa automatikoak izatea harpidetza izateko", "purchases.automatic-payments.are-enabled": "Ordainketa Automatikoak Gaituta Daude", diff --git a/src/packages/frontend/i18n/trans/fr_FR.json b/src/packages/frontend/i18n/trans/fr_FR.json index 29624a48bb1..820c1f2960d 100644 --- a/src/packages/frontend/i18n/trans/fr_FR.json +++ b/src/packages/frontend/i18n/trans/fr_FR.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "supprimer chaque fois que le fichier est enregistré", "account.editor-settings-autosave-interval.label": "Intervalle de sauvegarde automatique", "account.editor-settings.basic.title": "Paramètres de base", - "account.editor-settings.color-schemes.label": "Schéma de couleurs de l'éditeur", "account.editor-settings.color-schemes.panel_title": "Schéma de couleurs de l'éditeur", "account.editor-settings.font-size.label": "Taille de police globale par défaut", "account.editor-settings.indent-size.label": "Taille de l'indentation", "account.editor-settings.keyboard-bindings.label": "Raccourcis clavier de l'éditeur", "account.editor-settings.keyboard.title": "Clavier", - "account.editor-settings.title": "Éditeur", "account.editor-settings.x11-keyboard-variant.label": "Variante de clavier (pour le bureau X11)", "account.editor-settings.x11-physical-keyboard.label": "Disposition du clavier (pour le bureau X11)", "account.global-ssh-keys.help": "Pour SSH dans un projet, utilisez le format suivant username@host: [project-id-without-dashes]@ssh.cocalc.com L'identifiant du projet sans tirets se trouve dans la section des paramètres du projet concernant les clés SSH. Pour SSH entre projets, utilisez [project-id-without-dashes]@ssh", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Vue partagée dans la feuille de calcul Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Basculer le commentaire de la sélection", "account.other-settings._page_size.label": "Nombre de fichiers par page", - "account.other-settings.browser_performance.title": "Navigateur", + "account.other-settings.auto_focus": "Texte d'entrée de mise au point automatique : met automatiquement au point les champs de saisie de texte lorsqu'ils apparaissent (par exemple, explorateur de fichiers, projets, ...)", "account.other-settings.button_tooltips": "Masquer les info-bulles des boutons : masque certaines info-bulles des boutons (ceci est seulement partiel)", "account.other-settings.confirm_close": "Confirmer la fermeture : toujours demander confirmation avant de fermer la fenêtre du navigateur", - "account.other-settings.content_display.title": "Affichage du contenu", "account.other-settings.default_file_sort.by_name": "Trier par nom", "account.other-settings.default_file_sort.by_time": "Trier par temps", "account.other-settings.default_file_sort.label": "Tri par défaut des fichiers", + "account.other-settings.dim_file_extensions": "Extensions de fichier en gris : griser les extensions de fichier pour que leurs noms se démarquent.", "account.other-settings.file_popovers": "Cacher les info-bulles des onglets de fichiers : ne pas afficher les info-bulles sur les onglets de fichiers", "account.other-settings.filename_generator.description": "Sélectionnez comment les noms de fichiers générés automatiquement sont créés. En particulier, pour les rendre uniques ou inclure l'heure actuelle.", "account.other-settings.filename_generator.label": "Générateur de nom de fichier", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "Paramètres AI", "account.other-settings.markdown_codebar": "Désactiver la barre de code markdown dans tous les documents markdown. Cocher ceci masque les boutons supplémentaires d'exécution, de copie et d'explication dans les blocs de code délimités.", "account.other-settings.mask_files": "Masquer les fichiers : griser les fichiers dans le visualiseur de fichiers que vous ne souhaitez probablement pas ouvrir", - "account.other-settings.messages.title": "Messages", "account.other-settings.project_popovers": "Masquer les popovers des onglets de projet : ne pas afficher les popovers au-dessus des onglets de projet", - "account.other-settings.projects.title": "Projets", "account.other-settings.standby_timeout": "Délai d'attente en veille", "account.other-settings.symbol_bar_labels": "Afficher les étiquettes de la barre de symboles : afficher les étiquettes dans la barre de symboles de l'éditeur de cadre", - "account.other-settings.theme": "Thème", "account.other-settings.theme.antd.animations": "Animations : animer brièvement certains aspects, par exemple les boutons", "account.other-settings.theme.antd.color_scheme": "Schéma de couleurs : utiliser les couleurs de marque au lieu des couleurs par défaut", "account.other-settings.theme.antd.compact": "Conception compacte: utiliser une conception plus compacte", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "Générer un document {docName} en utilisant l'IA", "ai-generator.select_llm": "Sélectionner le modèle de langue", "app.fullscreen-button.tooltip": "Mode plein écran, concentré sur le document ou la page actuelle.", + "app.hotkey.dialog.help_text": "Cliquez sur les cadres ci-dessus • Touche 0 pour basculer le chat • Touches 1–9 pour focaliser les cadres • Tapez pour rechercher • ↑↓ naviguer • Entrée pour ouvrir • Échap pour fermer", + "app.hotkey.dialog.search_placeholder": "Rechercher des fichiers et des pages...", + "app.hotkey.dialog.title": "Navigation rapide", "app.verify-email-banner.edit": "Si l'adresse e-mail est incorrecte, veuillez la modifier dans les paramètres du compte.", "app.verify-email-banner.help.text": "Il est important d'avoir une adresse e-mail fonctionnelle. Nous l'utilisons pour les réinitialisations de mot de passe, l'envoi de messages, les notifications de facturation et le support. Veuillez vous assurer que votre e-mail est correct pour rester informé.", "app.verify-email-banner.text": "{sent, select, true {Email envoyé ! Veuillez vérifier votre boîte de réception (et peut-être les spams) et cliquez sur le lien de confirmation.} other {Veuillez vérifier et confirmer votre adresse e-mail :}}", @@ -988,6 +986,7 @@ "labels.config": "Config", "labels.configuration": "Configuration", "labels.configuration.short": "Config", + "labels.connected": "Connecté", "labels.connecting": "Connexion", "labels.connection": "Connexion", "labels.copied": "copié", @@ -1016,6 +1015,7 @@ "labels.environment": "Environnement", "labels.explorer": "Explorateur", "labels.file_explorer": "Explorateur de fichiers", + "labels.file_use_notifications": "Notifications d'utilisation de fichier", "labels.files": "Fichiers", "labels.folder": "dossier", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Public} read_only {Lecture seule} other {Enregistrer}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "Arrêter{short, select, true {} other { Projet}}…", "labels.project.settings.stop-project.ok": "Oui, arrêter le projet", "labels.projects": "Projets", + "labels.public_paths": "Chemins Publics", "labels.published_files": "Fichiers publiés", "labels.purchases": "Achats", "labels.ready": "Prêt", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "Choisissez un titre. Vous pouvez facilement le changer plus tard !", "projects.create-project.requireLicense": "Une licence est requise pour créer des projets supplémentaires.", "projects.filename-search.placeholder": "Rechercher les noms de fichiers que vous avez modifiés...", - "projects.list.no_starred_found": "Aucun projet étoilé trouvé. Utilisez l'icône en forme d'étoile à côté des titres de projet pour ajouter vos projets favoris en signet.", "projects.load-all.label": "Afficher tous les projets...", "projects.operations.clear-filter": "Effacer le filtre", "projects.operations.delete.button": "{deleted, select, true {Restaurer tout} other {Tout supprimer}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "Filtrer par hashtags...", "projects.table-controls.hidden.label": "Caché", "projects.table-controls.search.placeholder": "Rechercher des projets...", + "projects.table.keyboard-row-hint": "Projet {title}. Utilisez les flèches Haut et Bas pour vous déplacer ; appuyez sur Entrée ou Espace pour ouvrir.", "projects.table.last-edited": "Dernière modification", + "projects.table.untitled": "Sans titre", "purchases.automatic-payments-warning.description": "Les paiements automatiques sont beaucoup plus pratiques, vous feront gagner du temps, et assurent que les abonnements ne soient pas annulés par accident.", "purchases.automatic-payments-warning.title": "Les paiements automatiques NE sont PAS nécessaires pour avoir un abonnement", "purchases.automatic-payments.are-enabled": "Les paiements automatiques sont activés", diff --git a/src/packages/frontend/i18n/trans/he_IL.json b/src/packages/frontend/i18n/trans/he_IL.json index fe22315927b..5b1c82d83eb 100644 --- a/src/packages/frontend/i18n/trans/he_IL.json +++ b/src/packages/frontend/i18n/trans/he_IL.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "הסר בכל פעם שקובץ נשמר", "account.editor-settings-autosave-interval.label": "מרווח שמירה אוטומטית", "account.editor-settings.basic.title": "הגדרות בסיסיות", - "account.editor-settings.color-schemes.label": "ערכת צבעים של העורך", "account.editor-settings.color-schemes.panel_title": "ערכת צבעים לעורך", "account.editor-settings.font-size.label": "גודל גופן גלובלי ברירת מחדל", "account.editor-settings.indent-size.label": "גודל הזחה", "account.editor-settings.keyboard-bindings.label": "קיצורי מקלדת לעורך", "account.editor-settings.keyboard.title": "מקלדת", - "account.editor-settings.title": "עורך", "account.editor-settings.x11-keyboard-variant.label": "גרסת מקלדת (לשולחן עבודה X11)", "account.editor-settings.x11-physical-keyboard.label": "פריסת מקלדת (לשולחן העבודה X11)", "account.global-ssh-keys.help": "כדי להתחבר בפרוטוקול SSH לפרויקט, השתמש ב-username@host: [project-id-without-dashes]@ssh.cocalc.com ניתן למצוא את מזהה הפרויקט ללא מקפים בחלק של הגדרות הפרויקט לגבי מפתחות SSH. כדי להתחבר בפרוטוקול SSH בין פרויקטים, השתמש ב-[project-id-without-dashes]@ssh", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "תצוגה מפוצלת בגליון העבודה של Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "החלפת מצב הערות בחירה", "account.other-settings._page_size.label": "מספר הקבצים לכל עמוד", - "account.other-settings.browser_performance.title": "דפדפן", + "account.other-settings.auto_focus": "מיקוד אוטומטי על שדה טקסט: מיקוד אוטומטי על שדות טקסט כשאלה מופיעים (לדוגמה, סייר קבצים, פרויקטים, ...)", "account.other-settings.button_tooltips": "הסתרת תיאורי כלים של כפתורים: מסתיר חלק מתיאורי הכלים של הכפתורים (זה חלקי בלבד)", "account.other-settings.confirm_close": "אישור סגירה: תמיד לבקש אישור לפני סגירת חלון הדפדפן", - "account.other-settings.content_display.title": "תצוגת תוכן", "account.other-settings.default_file_sort.by_name": "מיין לפי שם", "account.other-settings.default_file_sort.by_time": "מיין לפי זמן", "account.other-settings.default_file_sort.label": "מיון קבצים ברירת מחדל", + "account.other-settings.dim_file_extensions": "סיומות קבצים בגוון אפור: צביעת סיומות קבצים באפור כדי ששמותיהם יבלטו.", "account.other-settings.file_popovers": "הסתרת פופאברים בכרטיסיות קבצים: אל תציג את הפופאברים מעל כרטיסיות הקבצים", "account.other-settings.filename_generator.description": "בחר כיצד שמות קבצים הנוצרים באופן אוטומטי נוצרים. במיוחד, כדי להפוך אותם לייחודיים או לכלול את הזמן הנוכחי.", "account.other-settings.filename_generator.label": "מחולל שמות קבצים", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "הגדרות AI", "account.other-settings.markdown_codebar": "השבת את סרגל הקוד של Markdown בכל מסמכי ה-Markdown. סימון זה מסתיר את כפתורי הריצה, ההעתקה וההסבר הנוספים בקטעי קוד מגודרים.", "account.other-settings.mask_files": "הסתר קבצים: האפור קבצים בצופה הקבצים שבדרך כלל לא תרצה לפתוח", - "account.other-settings.messages.title": "הודעות", "account.other-settings.project_popovers": "הסתרת פופאברים בכרטיסיות הפרויקט: אל תציג את הפופאברים מעל כרטיסיות הפרויקט", - "account.other-settings.projects.title": "פרויקטים", "account.other-settings.standby_timeout": "פסק זמן במצב המתנה", "account.other-settings.symbol_bar_labels": "הצג תוויות סרגל סמלים: הצג תוויות בסרגל הסמלים בעורך המסגרת", - "account.other-settings.theme": "ערכת נושא", "account.other-settings.theme.antd.animations": "אנימציות: הנפשה קצרה של חלקים מסוימים, למשל כפתורים", "account.other-settings.theme.antd.color_scheme": "ערכת צבעים: השתמש בצבעי המותג במקום בצבעי ברירת המחדל", "account.other-settings.theme.antd.compact": "עיצוב קומפקטי: השתמש בעיצוב יותר קומפקטי", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "צור מסמך {docName} באמצעות AI", "ai-generator.select_llm": "בחר מודל שפה", "app.fullscreen-button.tooltip": "מצב מסך מלא, ממוקד במסמך או בעמוד הנוכחי", + "app.hotkey.dialog.help_text": "לחץ על המסגרות למעלה • מקש 0 מחליף צ'אט • מקשים 1–9 ממקדים מסגרות • הקלד לחיפוש • ↑↓ ניווט • Enter לפתיחה • ESC לסגירה", + "app.hotkey.dialog.search_placeholder": "חפש קבצים ודפים...", + "app.hotkey.dialog.title": "ניווט מהיר", "app.verify-email-banner.edit": "אם כתובת הדוא\"ל שגויה, אנא ערוך אותה בהגדרות החשבון.", "app.verify-email-banner.help.text": "חשוב שתהיה כתובת דוא\"ל פעילה. אנו משתמשים בה לאיפוס סיסמאות, שליחת הודעות, התראות חיוב ותמיכה. אנא ודא שכתובת הדוא\"ל שלך נכונה כדי להישאר מעודכן.", "app.verify-email-banner.text": "{sent, select, true {האימייל נשלח! בדוק את תיבת הדואר הנכנס שלך (ואולי דואר זבל) ולחץ על קישור האישור.} other {אנא בדוק ואמת את כתובת האימייל שלך:}}", @@ -988,6 +986,7 @@ "labels.config": "הגדרות", "labels.configuration": "תצורה", "labels.configuration.short": "הגדרות", + "labels.connected": "מחובר", "labels.connecting": "מתחבר", "labels.connection": "חיבור", "labels.copied": "הועתק", @@ -1016,6 +1015,7 @@ "labels.environment": "סביבה", "labels.explorer": "סייר", "labels.file_explorer": "סייר קבצים", + "labels.file_use_notifications": "התראות שימוש בקבצים", "labels.files": "קבצים", "labels.folder": "תיקייה", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {ציבורי} read_only {לקריאה בלבד} other {שמור}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "עצור{short, select, true {} other { פרויקט}}…", "labels.project.settings.stop-project.ok": "כן, עצור פרויקט", "labels.projects": "פרויקטים", + "labels.public_paths": "נתיבים ציבוריים", "labels.published_files": "קבצים פורסמו", "labels.purchases": "רכישות", "labels.ready": "מוכן", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "בחר כותרת. תוכל לשנות אותה בקלות מאוחר יותר!", "projects.create-project.requireLicense": "נדרשת רישיון כדי ליצור פרויקטים נוספים.", "projects.filename-search.placeholder": "חפש קבצים שערכת...", - "projects.list.no_starred_found": "לא נמצאו פרויקטים מסומנים בכוכב. השתמש באייקון הכוכב ליד כותרות הפרויקטים כדי לסמן את הפרויקטים המועדפים עליך.", "projects.load-all.label": "הצג את כל הפרויקטים...", "projects.operations.clear-filter": "נקה מסנן", "projects.operations.delete.button": "{deleted, select, true {שחזר הכל} other {מחק הכל}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "סנן לפי האשטגים...", "projects.table-controls.hidden.label": "מוסתר", "projects.table-controls.search.placeholder": "חיפוש פרויקטים...", + "projects.table.keyboard-row-hint": "פרויקט {title}. השתמש בחצים למעלה ולמטה כדי לנוע; לחץ על Enter או על רווח כדי לפתוח.", "projects.table.last-edited": "נערך לאחרונה", + "projects.table.untitled": "ללא כותרת", "purchases.automatic-payments-warning.description": "תשלומים אוטומטיים הם הרבה יותר נוחים, יחסכו לך זמן, ויבטיחו שמנויים לא יבוטלו בטעות.", "purchases.automatic-payments-warning.title": "תשלומים אוטומטיים אינם נדרשים למנוי", "purchases.automatic-payments.are-enabled": "תשלומים אוטומטיים מופעלים", diff --git a/src/packages/frontend/i18n/trans/hi_IN.json b/src/packages/frontend/i18n/trans/hi_IN.json index 545a80e5d49..b54b986cdcb 100644 --- a/src/packages/frontend/i18n/trans/hi_IN.json +++ b/src/packages/frontend/i18n/trans/hi_IN.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "जब भी फ़ाइल सहेजी जाती है, हटाएं", "account.editor-settings-autosave-interval.label": "ऑटोसेव अंतराल", "account.editor-settings.basic.title": "मूलभूत सेटिंग्स", - "account.editor-settings.color-schemes.label": "एडिटर रंग योजना", "account.editor-settings.color-schemes.panel_title": "संपादक रंग योजना", "account.editor-settings.font-size.label": "डिफ़ॉल्ट वैश्विक फ़ॉन्ट आकार", "account.editor-settings.indent-size.label": "इंडेंट आकार", "account.editor-settings.keyboard-bindings.label": "संपादक कीबोर्ड बाइंडिंग्स", "account.editor-settings.keyboard.title": "कीबोर्ड", - "account.editor-settings.title": "संपादक", "account.editor-settings.x11-keyboard-variant.label": "कीबोर्ड प्रकार (X11 डेस्कटॉप के लिए)", "account.editor-settings.x11-physical-keyboard.label": "कीबोर्ड लेआउट (X11 डेस्कटॉप के लिए)", "account.global-ssh-keys.help": "एक प्रोजेक्ट में SSH करने के लिए, निम्नलिखित का उपयोग करें username@host: [project-id-without-dashes]@ssh.cocalc.com SSH कुंजियों के बारे में प्रोजेक्ट सेटिंग्स के भाग में प्रोजेक्ट आईडी बिना डैश के पाई जा सकती है। प्रोजेक्ट्स के बीच SSH करने के लिए, [project-id-without-dashes]@ssh का उपयोग करें।", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Sage कार्यपत्रक में दृश्य विभाजन", "account.keyboard-shortcuts.shortcut.toggle-comment": "चयन पर टिप्पणी टॉगल करें", "account.other-settings._page_size.label": "प्रति पृष्ठ फ़ाइलों की संख्या", - "account.other-settings.browser_performance.title": "ब्राउज़र", + "account.other-settings.auto_focus": "ऑटो फोकस टेक्स्ट इनपुट: जब वे प्रकट होते हैं तो टेक्स्ट इनपुट फ़ील्ड्स पर स्वचालित रूप से फोकस करें (जैसे, फाइल एक्सप्लोरर, प्रोजेक्ट्स, ...)", "account.other-settings.button_tooltips": "हाइड बटन टूलटिप्स: कुछ बटन टूलटिप्स को छुपाता है (यह केवल आंशिक है)", "account.other-settings.confirm_close": "बंद करने की पुष्टि करें: ब्राउज़र विंडो बंद करने से पहले हमेशा पुष्टि के लिए पूछें", - "account.other-settings.content_display.title": "सामग्री प्रदर्शन", "account.other-settings.default_file_sort.by_name": "नाम के अनुसार क्रमबद्ध करें", "account.other-settings.default_file_sort.by_time": "समय के अनुसार छाँटें", "account.other-settings.default_file_sort.label": "डिफ़ॉल्ट फ़ाइल क्रम", + "account.other-settings.dim_file_extensions": "फ़ाइल एक्सटेंशन को धुंधला करें: फ़ाइल एक्सटेंशन को ग्रे कर दें ताकि उनके नाम अलग दिखें।", "account.other-settings.file_popovers": "फाइल टैब पॉपओवर छुपाएं: फाइल टैब पर पॉपओवर न दिखाएं", "account.other-settings.filename_generator.description": "स्वचालित रूप से उत्पन्न फ़ाइल नाम कैसे उत्पन्न होते हैं, इसका चयन करें। विशेष रूप से, उन्हें अद्वितीय बनाने या वर्तमान समय शामिल करने के लिए।", "account.other-settings.filename_generator.label": "फाइलनाम जनरेटर", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "एआई सेटिंग्स", "account.other-settings.markdown_codebar": "सभी मार्कडाउन दस्तावेज़ों में मार्कडाउन कोड बार को अक्षम करें. इसे चेक करने से बाड़े गए कोड ब्लॉकों में अतिरिक्त चलाएं, कॉपी करें और समझाएं बटन छिप जाते हैं।", "account.other-settings.mask_files": "मास्क फाइल्स: उन फाइलों को ग्रे आउट करें जिन्हें आप शायद खोलना नहीं चाहते फाइल्स व्यूअर में", - "account.other-settings.messages.title": "संदेश", "account.other-settings.project_popovers": "प्रोजेक्ट टैब पॉपओवर छुपाएं: प्रोजेक्ट टैब पर पॉपओवर न दिखाएं", - "account.other-settings.projects.title": "प्रोजेक्ट्स", "account.other-settings.standby_timeout": "स्टैंडबाय टाइमआउट", "account.other-settings.symbol_bar_labels": "प्रतीक पट्टी लेबल दिखाएं: फ्रेम संपादक प्रतीक पट्टी में लेबल दिखाएं", - "account.other-settings.theme": "थीम", "account.other-settings.theme.antd.animations": "एनिमेशन: कुछ पहलुओं को संक्षेप में एनिमेट करें, जैसे बटन", "account.other-settings.theme.antd.color_scheme": "रंग योजना: डिफ़ॉल्ट रंगों के बजाय ब्रांड रंगों का उपयोग करें", "account.other-settings.theme.antd.compact": "कॉम्पैक्ट डिज़ाइन: अधिक कॉम्पैक्ट डिज़ाइन का उपयोग करें", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "AI का उपयोग करके {docName} दस्तावेज़ उत्पन्न करें", "ai-generator.select_llm": "भाषा मॉडल चुनें", "app.fullscreen-button.tooltip": "पूर्ण स्क्रीन मोड, वर्तमान दस्तावेज़ या पृष्ठ पर केंद्रित।", + "app.hotkey.dialog.help_text": "ऊपर फ्रेम्स पर क्लिक करें • कुंजी 0 चैट टॉगल करती है • कुंजियाँ 1–9 फ्रेम्स पर फोकस करती हैं • खोजने के लिए टाइप करें • ↑↓ नेविगेट करें • खोलने के लिए रिटर्न दबाएं • बंद करने के लिए ESC दबाएं", + "app.hotkey.dialog.search_placeholder": "फ़ाइलें और पृष्ठ खोजें...", + "app.hotkey.dialog.title": "त्वरित नेविगेशन", "app.verify-email-banner.edit": "यदि ईमेल पता गलत है, कृपया इसे खाता सेटिंग्स में संपादित करें।", "app.verify-email-banner.help.text": "यह महत्वपूर्ण है कि आपके पास एक कार्यशील ईमेल पता हो। हम इसे पासवर्ड रीसेट, संदेश भेजने, बिलिंग सूचनाएं और सहायता के लिए उपयोग करते हैं। कृपया सूचित रहने के लिए सुनिश्चित करें कि आपका ईमेल सही है।", "app.verify-email-banner.text": "{sent, select, true {ईमेल भेजा गया! कृपया अपने ईमेल इनबॉक्स (और शायद स्पैम) की जाँच करें और पुष्टि लिंक पर क्लिक करें।} other {कृपया अपना ईमेल पता जाँचें और सत्यापित करें:}}", @@ -988,6 +986,7 @@ "labels.config": "कॉन्फ़िग", "labels.configuration": "कॉन्फ़िगरेशन", "labels.configuration.short": "कॉन्फ़िग", + "labels.connected": "कनेक्टेड", "labels.connecting": "कनेक्टिंग", "labels.connection": "कनेक्शन", "labels.copied": "कॉपी किया गया", @@ -1016,6 +1015,7 @@ "labels.environment": "पर्यावरण", "labels.explorer": "एक्सप्लोरर", "labels.file_explorer": "फ़ाइल एक्सप्लोरर", + "labels.file_use_notifications": "फ़ाइल उपयोग सूचनाएँ", "labels.files": "फ़ाइलें", "labels.folder": "फ़ोल्डर", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {सार्वजनिक} read_only {केवल पढ़ने के लिए} other {सहेजें}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "रुकें {short, select, true {} other { प्रोजेक्ट}}…", "labels.project.settings.stop-project.ok": "हाँ, प्रोजेक्ट बंद करें", "labels.projects": "प्रोजेक्ट्स", + "labels.public_paths": "सार्वजनिक पथ", "labels.published_files": "प्रकाशित फ़ाइलें", "labels.purchases": "खरीदें", "labels.ready": "तैयार", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "शीर्षक चुनें। आप इसे बाद में आसानी से बदल सकते हैं!", "projects.create-project.requireLicense": "अतिरिक्त प्रोजेक्ट्स बनाने के लिए एक लाइसेंस आवश्यक है।", "projects.filename-search.placeholder": "संपादित की गई फ़ाइल नाम खोजें...", - "projects.list.no_starred_found": "कोई स्टार किए गए प्रोजेक्ट नहीं मिले। अपने पसंदीदा प्रोजेक्ट को बुकमार्क करने के लिए प्रोजेक्ट शीर्षकों के पास स्टार आइकन का उपयोग करें।", "projects.load-all.label": "सभी प्रोजेक्ट दिखाएँ...", "projects.operations.clear-filter": "फ़िल्टर साफ करें", "projects.operations.delete.button": "{deleted, select, true {सभी पुनःस्थापित करें} other {सभी हटाएं}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "हैशटैग द्वारा फ़िल्टर करें...", "projects.table-controls.hidden.label": "छिपा हुआ", "projects.table-controls.search.placeholder": "प्रोजेक्ट खोजें...", + "projects.table.keyboard-row-hint": "प्रोजेक्ट {title}. स्थानांतरित करने के लिए ऊपर और नीचे तीरों का उपयोग करें; खोलने के लिए Enter या Space दबाएं।", "projects.table.last-edited": "अंतिम संपादन", + "projects.table.untitled": "शीर्षकहीन", "purchases.automatic-payments-warning.description": "स्वचालित भुगतान बहुत अधिक सुविधाजनक हैं, आपका समय बचाएंगे, और सुनिश्चित करेंगे कि सदस्यताएँ गलती से रद्द न हों।", "purchases.automatic-payments-warning.title": "स्वत: भुगतान के लिए सदस्यता की आवश्यकता नहीं है", "purchases.automatic-payments.are-enabled": "स्वचालित भुगतान सक्षम हैं", diff --git a/src/packages/frontend/i18n/trans/hu_HU.json b/src/packages/frontend/i18n/trans/hu_HU.json index 196feacdd46..909cce90285 100644 --- a/src/packages/frontend/i18n/trans/hu_HU.json +++ b/src/packages/frontend/i18n/trans/hu_HU.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "távolítsa el, amikor a fájl mentésre kerül", "account.editor-settings-autosave-interval.label": "Automatikus mentés intervalluma", "account.editor-settings.basic.title": "Alapbeállítások", - "account.editor-settings.color-schemes.label": "Szerkesztő színséma", "account.editor-settings.color-schemes.panel_title": "Szerkesztő Színséma", "account.editor-settings.font-size.label": "Alapértelmezett globális betűméret", "account.editor-settings.indent-size.label": "Behúzás mérete", "account.editor-settings.keyboard-bindings.label": "Szerkesztő billentyűkötések", "account.editor-settings.keyboard.title": "Billentyűzet", - "account.editor-settings.title": "Szerkesztő", "account.editor-settings.x11-keyboard-variant.label": "Billentyűzet variáns (X11 asztali környezethez)", "account.editor-settings.x11-physical-keyboard.label": "Billentyűzetkiosztás (X11 asztalhoz)", "account.global-ssh-keys.help": "A projektbe való SSH belépéshez használja a következőt username@host: [project-id-without-dashes]@ssh.cocalc.com A kötőjelek nélküli projektazonosító az SSH kulcsok projektbeállításai részben található. Projektek közötti SSH-hoz használja [project-id-without-dashes]@ssh", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Megosztott nézet Sage munkalapban", "account.keyboard-shortcuts.shortcut.toggle-comment": "Kommentár kijelölésének váltása", "account.other-settings._page_size.label": "Fájlok száma oldalanként", - "account.other-settings.browser_performance.title": "Böngésző", + "account.other-settings.auto_focus": "Automatikus fókusz a szövegbeviteli mezőkre: a szövegbeviteli mezők automatikus fókuszálása, amikor megjelennek (pl. fájlkezelő, projektek, ...)", "account.other-settings.button_tooltips": "Gombok tippjeinek elrejtése: elrejti néhány gomb tippjét (ez csak részleges)", "account.other-settings.confirm_close": "Zárás megerősítése: mindig kérjen megerősítést, mielőtt bezárja a böngészőablakot", - "account.other-settings.content_display.title": "Tartalom megjelenítés", "account.other-settings.default_file_sort.by_name": "Rendezés név szerint", "account.other-settings.default_file_sort.by_time": "Idő szerinti rendezés", "account.other-settings.default_file_sort.label": "Alapértelmezett fájlrendezés", + "account.other-settings.dim_file_extensions": "Fájl kiterjesztések elhalványítása: a fájl kiterjesztések szürkévé tétele, hogy a nevük kiemelkedjen.", "account.other-settings.file_popovers": "Fájltáb Popoverek Elrejtése: ne jelenítse meg a fájltábok feletti popovereket", "account.other-settings.filename_generator.description": "Válassza ki, hogyan generálódjanak automatikusan a fájlnevek. Különösen, hogy egyediek legyenek vagy tartalmazzák az aktuális időt.", "account.other-settings.filename_generator.label": "Fájlnév generátor", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "AI beállítások", "account.other-settings.markdown_codebar": "Tiltsa le a markdown kódsávot minden markdown dokumentumban. Ennek bejelölése elrejti a további futtatás, másolás és magyarázat gombokat a keretezett kódrészletekben.", "account.other-settings.mask_files": "Fájlok elrejtése: szürkítse el a fájlokat a fájlnézegetőben, amelyeket valószínűleg nem akar megnyitni", - "account.other-settings.messages.title": "Üzenetek", "account.other-settings.project_popovers": "Projektfülek felugróinak elrejtése: ne jelenítse meg a felugrókat a projektfülek felett", - "account.other-settings.projects.title": "Projektek", "account.other-settings.standby_timeout": "Készenléti időkorlát", "account.other-settings.symbol_bar_labels": "Szimbólumsáv címkéinek megjelenítése: címkék megjelenítése a keret szerkesztő szimbólumsávjában", - "account.other-settings.theme": "Téma", "account.other-settings.theme.antd.animations": "Animációk: röviden animálni néhány elemet, pl. gombokat", "account.other-settings.theme.antd.color_scheme": "Színséma: márka színek használata az alapértelmezett színek helyett", "account.other-settings.theme.antd.compact": "Kompakt Dizájn: használjon kompaktabb dizájnt", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "Hozzon létre egy {docName} dokumentumot mesterséges intelligencia segítségével", "ai-generator.select_llm": "Nyelvi modell kiválasztása", "app.fullscreen-button.tooltip": "Teljes képernyős mód, a jelenlegi dokumentumra vagy oldalra összpontosítva.", + "app.hotkey.dialog.help_text": "Kattintson a keretekre fent • A 0 gomb váltja a csevegést • Az 1–9 gombok a keretekre fókuszálnak • Gépeljen a kereséshez • ↑↓ navigálás • Enter a megnyitáshoz • ESC a bezáráshoz", + "app.hotkey.dialog.search_placeholder": "Fájlok és oldalak keresése...", + "app.hotkey.dialog.title": "Gyors navigáció", "app.verify-email-banner.edit": "Ha az e-mail cím helytelen, kérjük, szerkessze azt a fiókbeállításokban.", "app.verify-email-banner.help.text": "Fontos, hogy legyen működő e-mail címe. Jelszó-visszaállításhoz, üzenetek küldéséhez, számlázási értesítésekhez és támogatáshoz használjuk. Kérjük, győződjön meg róla, hogy az e-mail címe helyes, hogy tájékozott maradhasson.", "app.verify-email-banner.text": "{sent, select, true {Email elküldve! Kérjük, ellenőrizze az e-mail fiókját (és esetleg a spam mappát), és kattintson a megerősítő linkre.} other {Kérjük, ellenőrizze és igazolja az e-mail címét:}}", @@ -988,6 +986,7 @@ "labels.config": "Beállítás", "labels.configuration": "Konfiguráció", "labels.configuration.short": "Beállítás", + "labels.connected": "Csatlakoztatva", "labels.connecting": "Csatlakozás", "labels.connection": "Csatlakozás", "labels.copied": "másolva", @@ -1016,6 +1015,7 @@ "labels.environment": "Környezet", "labels.explorer": "Felfedező", "labels.file_explorer": "Fájlkezelő", + "labels.file_use_notifications": "Fájlhasználati értesítések", "labels.files": "Fájlok", "labels.folder": "mappa", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Nyilvános} read_only {Csak olvasható} other {Mentés}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "Állítsa le{short, select, true {} other { Projekt}}…", "labels.project.settings.stop-project.ok": "Igen, állítsa le a projektet", "labels.projects": "Projektek", + "labels.public_paths": "Nyilvános útvonalak", "labels.published_files": "Közzétett fájlok", "labels.purchases": "Vásárlások", "labels.ready": "Kész", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "Válasszon egy címet. Ezt később könnyedén megváltoztathatja!", "projects.create-project.requireLicense": "További projektek létrehozásához licenc szükséges.", "projects.filename-search.placeholder": "Keressen a szerkesztett fájlnevek között...", - "projects.list.no_starred_found": "Nincsenek csillagozott projektek. A projektcímek melletti csillag ikon használatával jelölheti meg a kedvenc projektjeit.", "projects.load-all.label": "Összes projekt megjelenítése...", "projects.operations.clear-filter": "Szűrő törlése", "projects.operations.delete.button": "{deleted, select, true {Összes visszaállítása} other {Összes törlése}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "Szűrés hashtagek alapján...", "projects.table-controls.hidden.label": "Rejtett", "projects.table-controls.search.placeholder": "Projektek keresése...", + "projects.table.keyboard-row-hint": "Projekt {title}. Használja a Fel és Le nyilakat a mozgatáshoz; nyomja meg az Enter vagy a Space gombot a megnyitáshoz.", "projects.table.last-edited": "Utoljára szerkesztve", + "projects.table.untitled": "Cím nélküli", "purchases.automatic-payments-warning.description": "Az automatikus fizetések sokkal kényelmesebbek, időt takarítanak meg, és biztosítják, hogy az előfizetések ne szűnjenek meg véletlenül.", "purchases.automatic-payments-warning.title": "Az automatikus fizetések NEM szükségesek előfizetéshez", "purchases.automatic-payments.are-enabled": "Az automatikus fizetések engedélyezve vannak", diff --git a/src/packages/frontend/i18n/trans/it_IT.json b/src/packages/frontend/i18n/trans/it_IT.json index 0d20a28b245..e55d64a73be 100644 --- a/src/packages/frontend/i18n/trans/it_IT.json +++ b/src/packages/frontend/i18n/trans/it_IT.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "rimuovi ogni volta che il file viene salvato", "account.editor-settings-autosave-interval.label": "Intervallo di salvataggio automatico", "account.editor-settings.basic.title": "Impostazioni di base", - "account.editor-settings.color-schemes.label": "Schema di colori dell'editor", "account.editor-settings.color-schemes.panel_title": "Schema di colori dell'editor", "account.editor-settings.font-size.label": "Dimensione predefinita del carattere globale", "account.editor-settings.indent-size.label": "Dimensione rientro", "account.editor-settings.keyboard-bindings.label": "Associazioni tastiera dell'editor", "account.editor-settings.keyboard.title": "Tastiera", - "account.editor-settings.title": "Editor", "account.editor-settings.x11-keyboard-variant.label": "Variante tastiera (per Desktop X11)", "account.editor-settings.x11-physical-keyboard.label": "Layout tastiera (per desktop X11)", "account.global-ssh-keys.help": "Per SSH in un progetto, usa il seguente username@host: [project-id-without-dashes]@ssh.cocalc.com L'id del progetto senza trattini può essere trovato nella parte delle impostazioni del progetto riguardante le chiavi SSH. Per SSH tra progetti, usa [project-id-without-dashes]@ssh", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Visualizzazione divisa nel foglio di lavoro Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Attiva/disattiva commento selezione", "account.other-settings._page_size.label": "Numero di file per pagina", - "account.other-settings.browser_performance.title": "Browser", + "account.other-settings.auto_focus": "Auto Focus Text Input: concentra automaticamente i campi di inserimento testo quando appaiono (ad esempio, esploratore di file, progetti, ...)", "account.other-settings.button_tooltips": "Nascondi suggerimenti dei pulsanti: nasconde alcuni suggerimenti dei pulsanti (questo è solo parziale)", "account.other-settings.confirm_close": "Conferma Chiusura: chiedi sempre conferma prima di chiudere la finestra del browser", - "account.other-settings.content_display.title": "Visualizzazione contenuti", "account.other-settings.default_file_sort.by_name": "Ordina per nome", "account.other-settings.default_file_sort.by_time": "Ordina per tempo", "account.other-settings.default_file_sort.label": "Ordinamento file predefinito", + "account.other-settings.dim_file_extensions": "Estensioni file opache: rendi opache le estensioni dei file in modo che i loro nomi risaltino.", "account.other-settings.file_popovers": "Nascondi Popup Schede File: non mostrare i popup sulle schede dei file", "account.other-settings.filename_generator.description": "Seleziona come vengono generati automaticamente i nomi dei file. In particolare, per renderli unici o includere l'ora attuale.", "account.other-settings.filename_generator.label": "Generatore di nomi file", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "Impostazioni AI", "account.other-settings.markdown_codebar": "Disabilita la barra del codice markdown in tutti i documenti markdown. Se selezionato, questo nasconde i pulsanti aggiuntivi di esecuzione, copia e spiegazione nei blocchi di codice recintati.", "account.other-settings.mask_files": "Maschera file: disattiva i file nel visualizzatore di file che probabilmente non vuoi aprire", - "account.other-settings.messages.title": "Messaggi", "account.other-settings.project_popovers": "Nascondi i Popover delle Schede del Progetto: non mostrare i popover sulle schede del progetto", - "account.other-settings.projects.title": "Progetti", "account.other-settings.standby_timeout": "Timeout di attesa", "account.other-settings.symbol_bar_labels": "Mostra etichette barra simboli: mostra etichette nella barra simboli dell'editor di frame", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animazioni: animare brevemente alcuni aspetti, ad es. pulsanti", "account.other-settings.theme.antd.color_scheme": "Schema Colori: usa i colori del marchio invece dei colori predefiniti", "account.other-settings.theme.antd.compact": "Design compatto: usa un design più compatto", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "Genera un documento {docName} utilizzando l'IA", "ai-generator.select_llm": "Seleziona modello linguistico", "app.fullscreen-button.tooltip": "Modalità a schermo intero, focalizzata sul documento o pagina corrente", + "app.hotkey.dialog.help_text": "Clicca sui frame sopra • Tasto 0 attiva/disattiva chat • Tasti 1–9 mettono a fuoco i frame • Digita per cercare • ↑↓ naviga • Invio per aprire • ESC per chiudere", + "app.hotkey.dialog.search_placeholder": "Cerca file e pagine...", + "app.hotkey.dialog.title": "Navigazione Rapida", "app.verify-email-banner.edit": "Se l'indirizzo email è sbagliato, per favore modificalo nelle impostazioni dell'account.", "app.verify-email-banner.help.text": "È importante avere un indirizzo email funzionante. Lo utilizziamo per reimpostare le password, inviare messaggi, notifiche di fatturazione e supporto. Assicurati che la tua email sia corretta per rimanere informato.", "app.verify-email-banner.text": "{sent, select, true {Email inviata! Controlla la tua casella di posta elettronica (e magari spam) e clicca sul link di conferma.} other {Per favore controlla e verifica il tuo indirizzo email:}}", @@ -988,6 +986,7 @@ "labels.config": "Configura", "labels.configuration": "Configurazione", "labels.configuration.short": "Configura", + "labels.connected": "Connesso", "labels.connecting": "Connessione", "labels.connection": "Connessione", "labels.copied": "copiato", @@ -1016,6 +1015,7 @@ "labels.environment": "Ambiente", "labels.explorer": "Esploratore", "labels.file_explorer": "Esplora File", + "labels.file_use_notifications": "Notifiche di utilizzo file", "labels.files": "File", "labels.folder": "cartella", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Pubblico} read_only {Sola lettura} other {Salva}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "Stop{short, select, true {} other { Progetto}}…", "labels.project.settings.stop-project.ok": "Sì, ferma progetto", "labels.projects": "Progetti", + "labels.public_paths": "Percorsi Pubblici", "labels.published_files": "File Pubblicati", "labels.purchases": "Acquisti", "labels.ready": "Pronto", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "Scegli un titolo. Puoi cambiarlo facilmente in seguito!", "projects.create-project.requireLicense": "È necessaria una licenza per creare progetti aggiuntivi.", "projects.filename-search.placeholder": "Cerca i nomi dei file che hai modificato...", - "projects.list.no_starred_found": "Nessun progetto con stella trovato. Usa l'icona a stella accanto ai titoli dei progetti per aggiungere ai segnalibri i tuoi progetti preferiti.", "projects.load-all.label": "Mostra tutti i progetti...", "projects.operations.clear-filter": "Cancella filtro", "projects.operations.delete.button": "{deleted, select, true {Ripristina Tutto} other {Elimina Tutto}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "Filtra per hashtag...", "projects.table-controls.hidden.label": "Nascosto", "projects.table-controls.search.placeholder": "Cerca progetti...", + "projects.table.keyboard-row-hint": "Progetto {title}. Usa le frecce Su e Giù per spostarti; premi Invio o Spazio per aprire.", "projects.table.last-edited": "Ultima modifica", + "projects.table.untitled": "Senza titolo", "purchases.automatic-payments-warning.description": "I pagamenti automatici sono molto più convenienti, ti fanno risparmiare tempo e assicurano che gli abbonamenti non vengano annullati per errore.", "purchases.automatic-payments-warning.title": "I pagamenti automatici NON sono necessari per avere un abbonamento", "purchases.automatic-payments.are-enabled": "I pagamenti automatici sono abilitati", diff --git a/src/packages/frontend/i18n/trans/ja_JP.json b/src/packages/frontend/i18n/trans/ja_JP.json index 97112dca3e3..ba9efb4b99f 100644 --- a/src/packages/frontend/i18n/trans/ja_JP.json +++ b/src/packages/frontend/i18n/trans/ja_JP.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "ファイルが保存されるたびに削除", "account.editor-settings-autosave-interval.label": "自動保存間隔", "account.editor-settings.basic.title": "基本設定", - "account.editor-settings.color-schemes.label": "エディターのカラースキーム", "account.editor-settings.color-schemes.panel_title": "エディターのカラースキーム", "account.editor-settings.font-size.label": "デフォルトのグローバルフォントサイズ", "account.editor-settings.indent-size.label": "インデントサイズ", "account.editor-settings.keyboard-bindings.label": "エディターのキーボードバインディング", "account.editor-settings.keyboard.title": "キーボード", - "account.editor-settings.title": "エディター", "account.editor-settings.x11-keyboard-variant.label": "X11デスクトップ用のキーボードバリアント", "account.editor-settings.x11-physical-keyboard.label": "キーボードレイアウト(X11デスクトップ用)", "account.global-ssh-keys.help": "プロジェクトにSSHするには、次のusername@host: [project-id-without-dashes]@ssh.cocalc.comを使用します。ダッシュのないプロジェクトIDは、SSHキーに関するプロジェクト設定の部分で見つけることができます。プロジェクト間でSSHするには、[project-id-without-dashes]@sshを使用します。", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Sageワークシートで分割表示", "account.keyboard-shortcuts.shortcut.toggle-comment": "選択範囲のコメントを切り替え", "account.other-settings._page_size.label": "1ページあたりのファイル数", - "account.other-settings.browser_performance.title": "ブラウザー", + "account.other-settings.auto_focus": "自動フォーカステキスト入力: テキスト入力フィールドが表示されたときに自動的にフォーカスする (例: ファイルエクスプローラー、プロジェクト、...)", "account.other-settings.button_tooltips": "ボタンのツールチップを非表示: 一部のボタンのツールチップを非表示にします(これは部分的です)", "account.other-settings.confirm_close": "閉じる確認: ブラウザーウィンドウを閉じる前に必ず確認を求める", - "account.other-settings.content_display.title": "コンテンツ表示", "account.other-settings.default_file_sort.by_name": "名前で並べ替え", "account.other-settings.default_file_sort.by_time": "時間順に並べ替え", "account.other-settings.default_file_sort.label": "デフォルトファイルソート", + "account.other-settings.dim_file_extensions": "ファイル拡張子を薄く表示: ファイル名が目立つようにファイル拡張子をグレーアウトします。", "account.other-settings.file_popovers": "ファイルタブポップオーバーを非表示: ファイルタブ上にポップオーバーを表示しない", "account.other-settings.filename_generator.description": "自動生成されるファイル名の生成方法を選択します。特に、それらをユニークにするか、現在の時間を含めるかを選択します。", "account.other-settings.filename_generator.label": "ファイル名ジェネレーター", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "AI設定", "account.other-settings.markdown_codebar": "すべてのMarkdown文書でMarkdownコードバーを無効化します。これをチェックすると、囲まれたコードブロック内の追加の実行、コピー、説明ボタンが非表示になります。", "account.other-settings.mask_files": "マスクファイル: 開きたくないファイルをファイルビューアでグレーアウト", - "account.other-settings.messages.title": "メッセージ", "account.other-settings.project_popovers": "プロジェクトタブのポップオーバーを非表示: プロジェクトタブ上のポップオーバーを表示しない", - "account.other-settings.projects.title": "プロジェクト", "account.other-settings.standby_timeout": "待機タイムアウト", "account.other-settings.symbol_bar_labels": "シンボルバーラベルを表示:フレームエディタのシンボルバーにラベルを表示", - "account.other-settings.theme": "テーマ", "account.other-settings.theme.antd.animations": "アニメーション: ボタンなどの要素を簡潔にアニメーション化", "account.other-settings.theme.antd.color_scheme": "カラー スキーム: デフォルトの色ではなくブランドの色を使用", "account.other-settings.theme.antd.compact": "コンパクトデザイン: よりコンパクトなデザインを使用", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "AIを使用して{docName}ドキュメントを生成", "ai-generator.select_llm": "言語モデルを選択", "app.fullscreen-button.tooltip": "全画面モード、現在のドキュメントまたはページに集中。", + "app.hotkey.dialog.help_text": "上のフレームをクリック • キー0でチャットを切り替え • キー1–9でフレームにフォーカス • 検索するには入力 • ↑↓でナビゲート • Enterで開く • ESCで閉じる", + "app.hotkey.dialog.search_placeholder": "ファイルとページを検索...", + "app.hotkey.dialog.title": "クイックナビゲーション", "app.verify-email-banner.edit": "メールアドレスが間違っている場合は、アカウント設定で編集してください。", "app.verify-email-banner.help.text": "作業可能なメールアドレスを持つことが重要です。パスワードのリセット、メッセージの送信、請求通知、サポートのために使用します。情報を受け取るために、メールアドレスが正しいことを確認してください。", "app.verify-email-banner.text": "{sent, select, true {メールが送信されました!メールの受信トレイ(場合によってはスパムフォルダ)を確認し、確認リンクをクリックしてください。} other {メールアドレスを確認してください:}}", @@ -988,6 +986,7 @@ "labels.config": "設定", "labels.configuration": "設定", "labels.configuration.short": "設定", + "labels.connected": "接続済み", "labels.connecting": "接続中", "labels.connection": "接続", "labels.copied": "コピー済み", @@ -1016,6 +1015,7 @@ "labels.environment": "環境", "labels.explorer": "書類", "labels.file_explorer": "ファイルエクスプローラー", + "labels.file_use_notifications": "ファイル使用通知", "labels.files": "ファイル", "labels.folder": "フォルダー", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {公開} read_only {読み取り専用} other {保存}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "停止{short, select, true {} other {プロジェクト}}…", "labels.project.settings.stop-project.ok": "はい、プロジェクトを停止", "labels.projects": "プロジェクト", + "labels.public_paths": "公開パス", "labels.published_files": "公開されたファイル", "labels.purchases": "購入", "labels.ready": "準備完了", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "タイトルを選んでください。後で簡単に変更できます!", "projects.create-project.requireLicense": "追加のプロジェクトを作成するにはライセンスが必要です。", "projects.filename-search.placeholder": "編集したファイル名を検索...", - "projects.list.no_starred_found": "スター付きのプロジェクトが見つかりませんでした。お気に入りのプロジェクトをブックマークするには、プロジェクトタイトルの横にある星アイコンを使用してください。", "projects.load-all.label": "すべてのプロジェクトを表示...", "projects.operations.clear-filter": "フィルターをクリア", "projects.operations.delete.button": "{deleted, select, true {すべてを復元} other {すべてを削除}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "ハッシュタグでフィルター...", "projects.table-controls.hidden.label": "非表示", "projects.table-controls.search.placeholder": "プロジェクトを検索...", + "projects.table.keyboard-row-hint": "プロジェクト {title}。上下矢印キーで移動し、EnterキーまたはSpaceキーで開く。", "projects.table.last-edited": "最終編集", + "projects.table.untitled": "無題", "purchases.automatic-payments-warning.description": "自動支払いはずっと便利で、時間を節約し、サブスクリプションが誤ってキャンセルされないようにします。", "purchases.automatic-payments-warning.title": "自動支払いはサブスクリプションに必要ありません", "purchases.automatic-payments.are-enabled": "自動支払いが有効です", diff --git a/src/packages/frontend/i18n/trans/ko_KR.json b/src/packages/frontend/i18n/trans/ko_KR.json index 1234fd4c23d..9ee11835a0c 100644 --- a/src/packages/frontend/i18n/trans/ko_KR.json +++ b/src/packages/frontend/i18n/trans/ko_KR.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "파일이 저장될 때마다 제거", "account.editor-settings-autosave-interval.label": "자동 저장 간격", "account.editor-settings.basic.title": "기본 설정", - "account.editor-settings.color-schemes.label": "편집기 색상 테마", "account.editor-settings.color-schemes.panel_title": "편집기 색상 테마", "account.editor-settings.font-size.label": "기본 전역 글꼴 크기", "account.editor-settings.indent-size.label": "들여쓰기 크기", "account.editor-settings.keyboard-bindings.label": "편집기 키보드 바인딩", "account.editor-settings.keyboard.title": "키보드", - "account.editor-settings.title": "에디터", "account.editor-settings.x11-keyboard-variant.label": "키보드 변형 (X11 데스크탑용)", "account.editor-settings.x11-physical-keyboard.label": "키보드 레이아웃 (X11 데스크탑용)", "account.global-ssh-keys.help": "프로젝트에 SSH 접속을 하려면 다음을 사용하세요 username@host: [project-id-without-dashes]@ssh.cocalc.com 대시 없이 프로젝트 ID는 SSH 키와 관련된 프로젝트 설정 부분에서 찾을 수 있습니다. 프로젝트 간에 SSH 접속을 하려면 [project-id-without-dashes]@ssh를 사용하세요.", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Sage 워크시트에서 분할 보기", "account.keyboard-shortcuts.shortcut.toggle-comment": "선택 주석 전환", "account.other-settings._page_size.label": "페이지당 파일 수", - "account.other-settings.browser_performance.title": "브라우저", + "account.other-settings.auto_focus": "자동 포커스 텍스트 입력: 텍스트 입력 필드가 나타날 때 자동으로 포커스 (예: 파일 탐색기, 프로젝트, ...)", "account.other-settings.button_tooltips": "버튼 툴팁 숨기기: 일부 버튼 툴팁 숨기기 (부분적으로만 적용됨)", "account.other-settings.confirm_close": "종료 확인: 브라우저 창을 닫기 전에 항상 확인 요청", - "account.other-settings.content_display.title": "콘텐츠 표시", "account.other-settings.default_file_sort.by_name": "이름으로 정렬", "account.other-settings.default_file_sort.by_time": "시간순 정렬", "account.other-settings.default_file_sort.label": "기본 파일 정렬", + "account.other-settings.dim_file_extensions": "파일 확장자 흐리게: 파일 이름이 돋보이도록 확장자를 회색으로 표시합니다.", "account.other-settings.file_popovers": "파일 탭 팝오버 숨기기: 파일 탭 위에 팝오버를 표시하지 않음", "account.other-settings.filename_generator.description": "자동 생성된 파일 이름이 생성되는 방식을 선택하십시오. 특히, 고유하게 만들거나 현재 시간을 포함하도록 선택하십시오.", "account.other-settings.filename_generator.label": "파일명 생성기", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "AI 설정", "account.other-settings.markdown_codebar": "모든 마크다운 문서에서 마크다운 코드 바 비활성화. 이 옵션을 선택하면 코드 블록의 실행, 복사 및 설명 버튼이 숨겨집니다.", "account.other-settings.mask_files": "파일 숨기기: 파일 뷰어에서 열고 싶지 않은 파일을 회색으로 표시", - "account.other-settings.messages.title": "메시지", "account.other-settings.project_popovers": "프로젝트 탭 팝오버 숨기기: 프로젝트 탭 위에 팝오버를 표시하지 않음", - "account.other-settings.projects.title": "프로젝트", "account.other-settings.standby_timeout": "대기 시간 초과", "account.other-settings.symbol_bar_labels": "기호 막대 레이블 표시: 프레임 편집기 기호 막대에 레이블 표시", - "account.other-settings.theme": "테마", "account.other-settings.theme.antd.animations": "애니메이션: 버튼과 같은 일부 측면을 간단히 애니메이션화합니다", "account.other-settings.theme.antd.color_scheme": "색상 테마: 기본 색상 대신 브랜드 색상 사용", "account.other-settings.theme.antd.compact": "간결한 디자인: 더 간결한 디자인 사용", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "AI를 사용하여 {docName} 문서 생성", "ai-generator.select_llm": "언어 모델 선택", "app.fullscreen-button.tooltip": "풀스크린 모드, 현재 문서나 페이지에 집중.", + "app.hotkey.dialog.help_text": "위의 프레임을 클릭 • 0 키로 채팅 전환 • 1–9 키로 프레임 선택 • 검색하려면 입력 • ↑↓ 탐색 • Return으로 열기 • ESC로 닫기", + "app.hotkey.dialog.search_placeholder": "파일 및 페이지 검색...", + "app.hotkey.dialog.title": "빠른 탐색", "app.verify-email-banner.edit": "이메일 주소가 잘못된 경우 계정 설정에서 편집하십시오.", "app.verify-email-banner.help.text": "작동하는 이메일 주소를 가지고 있는 것이 중요합니다. 우리는 비밀번호 재설정, 메시지 전송, 청구 알림 및 지원을 위해 이메일을 사용합니다. 정보를 받으려면 이메일이 올바른지 확인하세요.", "app.verify-email-banner.text": "{sent, select, true {이메일이 전송되었습니다! 이메일 받은 편지함(그리고 스팸 폴더도)에서 확인하고 확인 링크를 클릭하세요.} other {이메일 주소를 확인하고 인증해 주세요:}}", @@ -988,6 +986,7 @@ "labels.config": "구성", "labels.configuration": "구성", "labels.configuration.short": "구성", + "labels.connected": "연결됨", "labels.connecting": "연결 중", "labels.connection": "연결", "labels.copied": "복사됨", @@ -1016,6 +1015,7 @@ "labels.environment": "환경", "labels.explorer": "탐색기", "labels.file_explorer": "파일 탐색기", + "labels.file_use_notifications": "파일 사용 알림", "labels.files": "파일", "labels.folder": "폴더", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {공개} read_only {읽기 전용} other {저장}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "중지{short, select, true {} other { 프로젝트}}…", "labels.project.settings.stop-project.ok": "예, 프로젝트 중지", "labels.projects": "프로젝트", + "labels.public_paths": "공개 경로", "labels.published_files": "게시된 파일", "labels.purchases": "구매", "labels.ready": "준비됨", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "제목을 선택하세요. 나중에 쉽게 변경할 수 있습니다!", "projects.create-project.requireLicense": "추가 프로젝트를 만들려면 라이선스가 필요합니다.", "projects.filename-search.placeholder": "편집한 파일 이름 검색...", - "projects.list.no_starred_found": "별표가 있는 프로젝트를 찾을 수 없습니다. 프로젝트 제목 옆의 별 아이콘을 사용하여 즐겨찾는 프로젝트를 북마크하세요.", "projects.load-all.label": "모든 프로젝트 보기...", "projects.operations.clear-filter": "필터 지우기", "projects.operations.delete.button": "{deleted, select, true {모두 복구} other {모두 삭제}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "해시태그로 필터링...", "projects.table-controls.hidden.label": "숨김", "projects.table-controls.search.placeholder": "프로젝트 검색...", + "projects.table.keyboard-row-hint": "프로젝트 {title}. 이동하려면 위아래 화살표를 사용하고, 열려면 Enter 또는 Space 키를 누르세요.", "projects.table.last-edited": "마지막 편집", + "projects.table.untitled": "제목 없음", "purchases.automatic-payments-warning.description": "자동 결제는 훨씬 더 편리하고, 시간을 절약하며, 구독이 실수로 취소되지 않도록 보장합니다.", "purchases.automatic-payments-warning.title": "자동 결제는 구독에 필수적이지 않습니다", "purchases.automatic-payments.are-enabled": "자동 결제가 활성화되었습니다", diff --git a/src/packages/frontend/i18n/trans/nl_NL.json b/src/packages/frontend/i18n/trans/nl_NL.json index 8ddfd4b7b54..8132b2198fb 100644 --- a/src/packages/frontend/i18n/trans/nl_NL.json +++ b/src/packages/frontend/i18n/trans/nl_NL.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "verwijderen wanneer bestand wordt opgeslagen", "account.editor-settings-autosave-interval.label": "Automatisch opslaan interval", "account.editor-settings.basic.title": "Basisinstellingen", - "account.editor-settings.color-schemes.label": "Editor kleurenschema", "account.editor-settings.color-schemes.panel_title": "Editor Kleurschema", "account.editor-settings.font-size.label": "Standaard globale lettergrootte", "account.editor-settings.indent-size.label": "Inspringingsgrootte", "account.editor-settings.keyboard-bindings.label": "Editor-toetsenbordkoppelingen", "account.editor-settings.keyboard.title": "Toetsenbord", - "account.editor-settings.title": "Editorinstellingen", "account.editor-settings.x11-keyboard-variant.label": "Toetsenbordvariant (voor X11 Desktop)", "account.editor-settings.x11-physical-keyboard.label": "Toetsenbordindeling (voor X11 Desktop)", "account.global-ssh-keys.help": "Om SSH in een project te gebruiken, gebruik de volgende gebruikersnaam@host: [project-id-zonder-streepjes]@ssh.cocalc.com De project-id zonder streepjes is te vinden in het gedeelte van de projectinstellingen over SSH-sleutels. Om SSH tussen projecten te gebruiken, gebruik [project-id-zonder-streepjes]@ssh", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Gesplitste weergave in Sage-werkblad", "account.keyboard-shortcuts.shortcut.toggle-comment": "Selectie van commentaar voorzien/ongedaan maken", "account.other-settings._page_size.label": "Aantal bestanden per pagina", - "account.other-settings.browser_performance.title": "Browser", + "account.other-settings.auto_focus": "Auto Focus Tekstinvoer: tekstinvoervelden automatisch focussen wanneer ze verschijnen (bijv. bestandsverkenner, projecten, ...)", "account.other-settings.button_tooltips": "Verberg knopinfo: verbergt enkele knopinfo (dit is slechts gedeeltelijk)", "account.other-settings.confirm_close": "Bevestig Sluiten: altijd om bevestiging vragen voordat het browservenster wordt gesloten", - "account.other-settings.content_display.title": "Inhoudweergave", "account.other-settings.default_file_sort.by_name": "Sorteren op naam", "account.other-settings.default_file_sort.by_time": "Sorteren op tijd", "account.other-settings.default_file_sort.label": "Standaard bestandssortering", + "account.other-settings.dim_file_extensions": "Bestandsextensies dempen: bestandsextensies grijs maken zodat hun namen opvallen.", "account.other-settings.file_popovers": "Bestandstab-Popovers verbergen: toon de popovers over bestandstabs niet", "account.other-settings.filename_generator.description": "Selecteer hoe automatisch gegenereerde bestandsnamen worden gegenereerd. Met name om ze uniek te maken of de huidige tijd op te nemen.", "account.other-settings.filename_generator.label": "Bestandsnaam generator", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "AI-instellingen", "account.other-settings.markdown_codebar": "Deactiveer de markdown codebalk in alle markdown-documenten. Als u dit aanvinkt, worden de extra uitvoer-, kopieer- en uitlegknoppen in omkaderde codeblokken verborgen.", "account.other-settings.mask_files": "Masker bestanden: grijs bestanden uit in de bestandsweergave die je waarschijnlijk niet wilt openen", - "account.other-settings.messages.title": "Berichten", "account.other-settings.project_popovers": "Verberg Projecttabblad Popovers: toon de popovers niet boven de projecttabbladen", - "account.other-settings.projects.title": "Projecten", "account.other-settings.standby_timeout": "Standby-timeout", "account.other-settings.symbol_bar_labels": "Toon Symbolenbalk Labels: toon labels in de frame-editor symbolenbalk", - "account.other-settings.theme": "Thema", "account.other-settings.theme.antd.animations": "Animaties: kort sommige aspecten animeren, bijv. knoppen", "account.other-settings.theme.antd.color_scheme": "Kleurschema: gebruik merkkleuren in plaats van standaardkleuren", "account.other-settings.theme.antd.compact": "Compact Design: gebruik een compacter ontwerp", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "Genereer een {docName} Document met AI", "ai-generator.select_llm": "Selecteer taalmodel", "app.fullscreen-button.tooltip": "Volledig schermmodus, gericht op het huidige document of de pagina.", + "app.hotkey.dialog.help_text": "Klik op frames hierboven • Toets 0 schakelt chat in/uit • Toetsen 1–9 focussen frames • Typ om te zoeken • ↑↓ navigeer • Return om te openen • ESC om te sluiten", + "app.hotkey.dialog.search_placeholder": "Bestanden en pagina's zoeken...", + "app.hotkey.dialog.title": "Snelle Navigatie", "app.verify-email-banner.edit": "Als het e-mailadres verkeerd is, pas het dan aan in de accountinstellingen.", "app.verify-email-banner.help.text": "Het is belangrijk om een werkend e-mailadres te hebben. We gebruiken het voor het opnieuw instellen van wachtwoorden, het verzenden van berichten, factureringsmeldingen en ondersteuning. Zorg ervoor dat uw e-mailadres correct is om op de hoogte te blijven.", "app.verify-email-banner.text": "{sent, select, true {E-mail verzonden! Controleer je e-mailinbox (en misschien spam) en klik op de bevestigingslink.} other {Controleer en verifieer je e-mailadres:}}", @@ -988,6 +986,7 @@ "labels.config": "Config", "labels.configuration": "Configuratie", "labels.configuration.short": "Config", + "labels.connected": "Verbonden", "labels.connecting": "Verbinden", "labels.connection": "Verbinding", "labels.copied": "gekopieerd", @@ -1016,6 +1015,7 @@ "labels.environment": "Omgeving", "labels.explorer": "Verkenner", "labels.file_explorer": "Bestandsverkenner", + "labels.file_use_notifications": "Meldingen Bestandsgebruik", "labels.files": "Bestanden", "labels.folder": "map", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Openbaar} read_only {Alleen lezen} other {Opslaan}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "Stop{short, select, true {} other { Project}}…", "labels.project.settings.stop-project.ok": "Ja, stop project", "labels.projects": "Projecten", + "labels.public_paths": "Openbare Paden", "labels.published_files": "Gepubliceerde bestanden", "labels.purchases": "Aankopen", "labels.ready": "Klaar", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "Kies een titel. Je kunt het later eenvoudig wijzigen!", "projects.create-project.requireLicense": "Er is een licentie vereist om extra projecten te maken.", "projects.filename-search.placeholder": "Zoek naar bestandsnamen die je hebt bewerkt...", - "projects.list.no_starred_found": "Geen gemarkeerde projecten gevonden. Gebruik het sterpictogram naast projecttitels om je favoriete projecten te markeren.", "projects.load-all.label": "Toon alle projecten...", "projects.operations.clear-filter": "Filter wissen", "projects.operations.delete.button": "{deleted, select, true {Alles Herstellen} other {Alles Verwijderen}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "Filteren op hashtags...", "projects.table-controls.hidden.label": "Verborgen", "projects.table-controls.search.placeholder": "Zoek projecten...", + "projects.table.keyboard-row-hint": "Project {title}. Gebruik de pijltjestoetsen omhoog en omlaag om te bewegen; druk op Enter of Spatiebalk om te openen.", "projects.table.last-edited": "Laatst bewerkt", + "projects.table.untitled": "Naamloos", "purchases.automatic-payments-warning.description": "Automatische betalingen zijn veel handiger, zullen je tijd besparen, en ervoor zorgen dat abonnementen niet per ongeluk worden opgezegd.", "purchases.automatic-payments-warning.title": "Automatische betalingen zijn NIET vereist om een abonnement te hebben", "purchases.automatic-payments.are-enabled": "Automatische Betalingen zijn Ingeschakeld", diff --git a/src/packages/frontend/i18n/trans/pl_PL.json b/src/packages/frontend/i18n/trans/pl_PL.json index 3ce0be42a5a..39c8705fb5d 100644 --- a/src/packages/frontend/i18n/trans/pl_PL.json +++ b/src/packages/frontend/i18n/trans/pl_PL.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "usuń za każdym razem, gdy plik jest zapisywany", "account.editor-settings-autosave-interval.label": "Interwał automatycznego zapisu", "account.editor-settings.basic.title": "Podstawowe ustawienia", - "account.editor-settings.color-schemes.label": "Schemat kolorów edytora", "account.editor-settings.color-schemes.panel_title": "Schemat kolorów edytora", "account.editor-settings.font-size.label": "Domyślny globalny rozmiar czcionki", "account.editor-settings.indent-size.label": "Rozmiar wcięcia", "account.editor-settings.keyboard-bindings.label": "Powiązania klawiatury edytora", "account.editor-settings.keyboard.title": "Klawiatura", - "account.editor-settings.title": "Edytor", "account.editor-settings.x11-keyboard-variant.label": "Odmiana klawiatury (dla pulpitu X11)", "account.editor-settings.x11-physical-keyboard.label": "Układ klawiatury (dla pulpitu X11)", "account.global-ssh-keys.help": "Aby zalogować się do projektu przez SSH, użyj następującego username@host: [project-id-without-dashes]@ssh.cocalc.com Identyfikator projektu bez myślników można znaleźć w części ustawień projektu dotyczącej kluczy SSH. Aby zalogować się przez SSH między projektami, użyj [project-id-without-dashes]@ssh", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Podziel widok w arkuszu Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Przełącz komentarzowanie zaznaczenia", "account.other-settings._page_size.label": "Liczba plików na stronę", - "account.other-settings.browser_performance.title": "Przeglądarka", + "account.other-settings.auto_focus": "Automatyczne ustawianie fokusu na polach tekstowych: automatycznie ustawiaj fokus na polach tekstowych, gdy się pojawiają (np. eksplorator plików, projekty, ...)", "account.other-settings.button_tooltips": "Ukryj podpowiedzi przycisków: ukrywa niektóre podpowiedzi przycisków (to jest tylko częściowe)", "account.other-settings.confirm_close": "Potwierdź zamknięcie: zawsze pytaj o potwierdzenie przed zamknięciem okna przeglądarki", - "account.other-settings.content_display.title": "Wyświetlanie treści", "account.other-settings.default_file_sort.by_name": "Sortuj według nazwy", "account.other-settings.default_file_sort.by_time": "Sortuj według czasu", "account.other-settings.default_file_sort.label": "Domyślne sortowanie plików", + "account.other-settings.dim_file_extensions": "Przyciemnij rozszerzenia plików: wyszarz rozszerzenia plików, aby ich nazwy się wyróżniały.", "account.other-settings.file_popovers": "Ukryj dymki zakładek plików: nie pokazuj dymków nad zakładkami plików", "account.other-settings.filename_generator.description": "Wybierz, jak automatycznie generowane nazwy plików są tworzone. W szczególności, aby były unikalne lub zawierały bieżący czas.", "account.other-settings.filename_generator.label": "Generator nazw plików", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "Ustawienia AI", "account.other-settings.markdown_codebar": "Wyłącz pasek kodu markdown we wszystkich dokumentach markdown. Zaznaczenie tego ukrywa dodatkowe przyciski uruchamiania, kopiowania i wyjaśniania w blokach kodu.", "account.other-settings.mask_files": "Maskuj pliki: wyszarz pliki w przeglądarce plików, których prawdopodobnie nie chcesz otworzyć", - "account.other-settings.messages.title": "Wiadomości", "account.other-settings.project_popovers": "Ukryj dymki kart projektu: nie pokazuj dymków nad kartami projektu", - "account.other-settings.projects.title": "Projekty", "account.other-settings.standby_timeout": "Limit czasu w trybie gotowości", "account.other-settings.symbol_bar_labels": "Pokaż etykiety paska symboli: pokaż etykiety na pasku symboli edytora ramki", - "account.other-settings.theme": "Motyw", "account.other-settings.theme.antd.animations": "Animacje: krótko animuj niektóre aspekty, np. przyciski", "account.other-settings.theme.antd.color_scheme": "Schemat kolorów: użyj kolorów marki zamiast domyślnych kolorów", "account.other-settings.theme.antd.compact": "Kompaktowy układ: użyj bardziej zwartego układu", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "Wygeneruj dokument {docName} za pomocą AI", "ai-generator.select_llm": "Wybierz model językowy", "app.fullscreen-button.tooltip": "Tryb pełnoekranowy, skupiony na bieżącym dokumencie lub stronie.", + "app.hotkey.dialog.help_text": "Kliknij ramki powyżej • Klawisz 0 przełącza czat • Klawisze 1–9 skupiają ramki • Wpisz, aby wyszukać • ↑↓ nawigacja • Return, aby otworzyć • ESC, aby zamknąć", + "app.hotkey.dialog.search_placeholder": "Szukaj plików i stron...", + "app.hotkey.dialog.title": "Szybka nawigacja", "app.verify-email-banner.edit": "Jeśli adres e-mail jest niepoprawny, proszę edytować go w ustawieniach konta.", "app.verify-email-banner.help.text": "Ważne jest posiadanie działającego adresu e-mail. Używamy go do resetowania haseł, wysyłania wiadomości, powiadomień o rozliczeniach i wsparcia. Upewnij się, że Twój e-mail jest poprawny, aby być na bieżąco.", "app.verify-email-banner.text": "{sent, select, true {Email wysłany! Sprawdź swoją skrzynkę odbiorczą (a może spam) i kliknij link potwierdzający.} other {Proszę sprawdzić i zweryfikować swój adres e-mail:}}", @@ -988,6 +986,7 @@ "labels.config": "Konfiguracja", "labels.configuration": "Konfiguracja", "labels.configuration.short": "Konfiguracja", + "labels.connected": "Połączono", "labels.connecting": "Łączenie", "labels.connection": "Połączenie", "labels.copied": "skopiowano", @@ -1016,6 +1015,7 @@ "labels.environment": "Środowisko", "labels.explorer": "Eksplorator", "labels.file_explorer": "Eksplorator plików", + "labels.file_use_notifications": "Powiadomienia o użyciu plików", "labels.files": "Pliki", "labels.folder": "folder", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Publiczny} read_only {Tylko do odczytu} other {Zapisz}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "Zatrzymaj{short, select, true {} other { Projekt}}…", "labels.project.settings.stop-project.ok": "Tak, zatrzymaj projekt", "labels.projects": "Projekty", + "labels.public_paths": "Ścieżki publiczne", "labels.published_files": "Opublikowane Pliki", "labels.purchases": "Zakupy", "labels.ready": "Gotowe", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "Wybierz tytuł. Możesz łatwo zmienić go później!", "projects.create-project.requireLicense": "Do utworzenia dodatkowych projektów wymagana jest licencja.", "projects.filename-search.placeholder": "Szukaj nazw plików, które edytowałeś...", - "projects.list.no_starred_found": "Nie znaleziono oznaczonych projektów. Użyj ikony gwiazdki obok tytułów projektów, aby dodać do zakładek swoje ulubione projekty.", "projects.load-all.label": "Pokaż wszystkie projekty...", "projects.operations.clear-filter": "Wyczyść filtr", "projects.operations.delete.button": "{deleted, select, true {Przywróć wszystko} other {Usuń wszystko}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "Filtruj według hashtagów...", "projects.table-controls.hidden.label": "Ukryty", "projects.table-controls.search.placeholder": "Szukaj projektów...", + "projects.table.keyboard-row-hint": "Projekt {title}. Użyj strzałek w górę i w dół, aby się poruszać; naciśnij Enter lub Spację, aby otworzyć.", "projects.table.last-edited": "Ostatnia edycja", + "projects.table.untitled": "Bez tytułu", "purchases.automatic-payments-warning.description": "Automatyczne płatności są znacznie wygodniejsze, zaoszczędzą Twój czas i zapewnią, że subskrypcje nie zostaną anulowane przez przypadek.", "purchases.automatic-payments-warning.title": "Automatyczne płatności NIE są wymagane do posiadania subskrypcji", "purchases.automatic-payments.are-enabled": "Automatyczne Płatności są Włączone", diff --git a/src/packages/frontend/i18n/trans/pt_BR.json b/src/packages/frontend/i18n/trans/pt_BR.json index ae7b547ba09..82978ccc48d 100644 --- a/src/packages/frontend/i18n/trans/pt_BR.json +++ b/src/packages/frontend/i18n/trans/pt_BR.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "remover sempre que o arquivo for salvo", "account.editor-settings-autosave-interval.label": "Intervalo de salvamento automático", "account.editor-settings.basic.title": "Configurações Básicas", - "account.editor-settings.color-schemes.label": "Esquema de cores do editor", "account.editor-settings.color-schemes.panel_title": "Esquema de Cores do Editor", "account.editor-settings.font-size.label": "Tamanho padrão da fonte global", "account.editor-settings.indent-size.label": "Tamanho da indentação", "account.editor-settings.keyboard-bindings.label": "Atalhos de teclado do editor", "account.editor-settings.keyboard.title": "Teclado", - "account.editor-settings.title": "Configurações do Editor", "account.editor-settings.x11-keyboard-variant.label": "Variante do teclado (para X11 Desktop)", "account.editor-settings.x11-physical-keyboard.label": "Layout de teclado (para X11 Desktop)", "account.global-ssh-keys.help": "Para acessar um projeto via SSH, use o seguinte username@host: [project-id-without-dashes]@ssh.cocalc.com O ID do projeto sem traços pode ser encontrado na parte das configurações do projeto sobre chaves SSH. Para acessar entre projetos via SSH, use [project-id-without-dashes]@ssh", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Divisão de visualização na planilha Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Alternar comentário da seleção", "account.other-settings._page_size.label": "Número de arquivos por página", - "account.other-settings.browser_performance.title": "Navegador", + "account.other-settings.auto_focus": "Focar Automaticamente no Campo de Texto: focar automaticamente nos campos de entrada de texto quando eles aparecem (por exemplo, explorador de arquivos, projetos, ...)", "account.other-settings.button_tooltips": "Ocultar Dicas de Botões: oculta algumas dicas de botões (isso é apenas parcial)", "account.other-settings.confirm_close": "Confirmar Fechamento: sempre pedir confirmação antes de fechar a janela do navegador", - "account.other-settings.content_display.title": "Exibição de Conteúdo", "account.other-settings.default_file_sort.by_name": "Ordenar por nome", "account.other-settings.default_file_sort.by_time": "Ordenar por tempo", "account.other-settings.default_file_sort.label": "Ordenação padrão de arquivos", + "account.other-settings.dim_file_extensions": "Extensões de arquivo em cinza: desbotar extensões de arquivo para que seus nomes se destaquem.", "account.other-settings.file_popovers": "Ocultar Popovers de Abas de Arquivos: não mostrar os popovers sobre as abas de arquivos", "account.other-settings.filename_generator.description": "Selecione como os nomes de arquivos gerados automaticamente são criados. Em particular, para torná-los únicos ou incluir a hora atual.", "account.other-settings.filename_generator.label": "Gerador de nomes de arquivo", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "Configurações de IA", "account.other-settings.markdown_codebar": "Desativar a barra de código markdown em todos os documentos markdown. Marcar isso oculta os botões extras de executar, copiar e explicar em blocos de código delimitados.", "account.other-settings.mask_files": "Mascarar Arquivos: escurecer arquivos no visualizador de arquivos que você provavelmente não deseja abrir", - "account.other-settings.messages.title": "Mensagens", "account.other-settings.project_popovers": "Ocultar Pop-ups das Abas do Projeto: não mostrar os pop-ups sobre as abas do projeto", - "account.other-settings.projects.title": "Projetos", "account.other-settings.standby_timeout": "Tempo de espera em espera", "account.other-settings.symbol_bar_labels": "Mostrar Rótulos da Barra de Símbolos: mostrar rótulos na barra de símbolos do editor de quadros", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animações: animar brevemente alguns aspectos, por exemplo, botões", "account.other-settings.theme.antd.color_scheme": "Esquema de Cores: use cores da marca em vez de cores padrão", "account.other-settings.theme.antd.compact": "Design Compacto: use um design mais compacto", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "Gerar um Documento {docName} usando IA", "ai-generator.select_llm": "Selecionar modelo de linguagem", "app.fullscreen-button.tooltip": "Modo de tela cheia, focado no documento ou página atual.", + "app.hotkey.dialog.help_text": "Clique nos quadros acima • Tecla 0 alterna chat • Teclas 1–9 focam quadros • Digite para pesquisar • ↑↓ navegar • Enter para abrir • ESC para fechar", + "app.hotkey.dialog.search_placeholder": "Pesquisar arquivos e páginas...", + "app.hotkey.dialog.title": "Navegação Rápida", "app.verify-email-banner.edit": "Se o endereço de e-mail estiver errado, por favor edite nas configurações da conta.", "app.verify-email-banner.help.text": "É importante ter um endereço de e-mail funcional. Nós o usamos para redefinir senhas, enviar mensagens, notificações de cobrança e suporte. Por favor, certifique-se de que seu e-mail está correto para se manter informado.", "app.verify-email-banner.text": "{sent, select, true {Email enviado! Por favor, verifique a caixa de entrada do seu email (e talvez o spam) e clique no link de confirmação.} other {Por favor, verifique e confirme seu endereço de email:}}", @@ -988,6 +986,7 @@ "labels.config": "Configuração", "labels.configuration": "Configuração", "labels.configuration.short": "Configuração", + "labels.connected": "Conectado", "labels.connecting": "Conectando", "labels.connection": "Conexão", "labels.copied": "copiado", @@ -1016,6 +1015,7 @@ "labels.environment": "Ambiente", "labels.explorer": "Explorador", "labels.file_explorer": "Explorador de Arquivos", + "labels.file_use_notifications": "Notificações de Uso de Arquivo", "labels.files": "Arquivos", "labels.folder": "pasta", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Público} read_only {Somente leitura} other {Salvar}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "Parar{short, select, true {} other { Projeto}}…", "labels.project.settings.stop-project.ok": "Sim, parar projeto", "labels.projects": "Projetos", + "labels.public_paths": "Caminhos Públicos", "labels.published_files": "Publicado", "labels.purchases": "Compras", "labels.ready": "Pronto", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "Escolha um título. Você pode alterá-lo facilmente depois!", "projects.create-project.requireLicense": "É necessária uma licença para criar projetos adicionais.", "projects.filename-search.placeholder": "Pesquisar por nomes de arquivos que você editou...", - "projects.list.no_starred_found": "Nenhum projeto marcado com estrela encontrado. Use o ícone de estrela ao lado dos títulos dos projetos para marcar seus projetos favoritos.", "projects.load-all.label": "Mostrar todos os projetos...", "projects.operations.clear-filter": "Limpar Filtro", "projects.operations.delete.button": "{deleted, select, true {Restaurar Tudo} other {Excluir Tudo}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "Filtrar por hashtags...", "projects.table-controls.hidden.label": "Oculto", "projects.table-controls.search.placeholder": "Buscar projetos...", + "projects.table.keyboard-row-hint": "Projeto {title}. Use as setas para cima e para baixo para mover; pressione Enter ou Espaço para abrir.", "projects.table.last-edited": "Última Edição", + "projects.table.untitled": "Sem título", "purchases.automatic-payments-warning.description": "Pagamentos automáticos são muito mais convenientes, vão economizar seu tempo e garantir que assinaturas não sejam canceladas por acidente.", "purchases.automatic-payments-warning.title": "Pagamentos automáticos NÃO são necessários para ter uma assinatura", "purchases.automatic-payments.are-enabled": "Pagamentos Automáticos estão Ativados", diff --git a/src/packages/frontend/i18n/trans/pt_PT.json b/src/packages/frontend/i18n/trans/pt_PT.json index e5c65f6b920..e202701c62c 100644 --- a/src/packages/frontend/i18n/trans/pt_PT.json +++ b/src/packages/frontend/i18n/trans/pt_PT.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "remover sempre que o ficheiro for guardado", "account.editor-settings-autosave-interval.label": "Intervalo de Autossalvamento", "account.editor-settings.basic.title": "Configurações Básicas", - "account.editor-settings.color-schemes.label": "Esquema de cores do editor", "account.editor-settings.color-schemes.panel_title": "Esquema de Cores do Editor", "account.editor-settings.font-size.label": "Tamanho de fonte global padrão", "account.editor-settings.indent-size.label": "Tamanho da indentação", "account.editor-settings.keyboard-bindings.label": "Ligações de teclado do editor", "account.editor-settings.keyboard.title": "Teclado", - "account.editor-settings.title": "Editor", "account.editor-settings.x11-keyboard-variant.label": "Variante de teclado (para Desktop X11)", "account.editor-settings.x11-physical-keyboard.label": "Distribuição do teclado (para X11 Desktop)", "account.global-ssh-keys.help": "Para SSH em um projeto, use o seguinte username@host: [project-id-without-dashes]@ssh.cocalc.com O ID do projeto sem traços pode ser encontrado na parte das configurações do projeto sobre chaves SSH. Para SSH entre projetos, use [project-id-without-dashes]@ssh", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Vista dividida na folha de cálculo Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Alternar comentário da seleção", "account.other-settings._page_size.label": "Número de ficheiros por página", - "account.other-settings.browser_performance.title": "Navegador", + "account.other-settings.auto_focus": "Focar Automaticamente no Campo de Texto: focar automaticamente nos campos de texto quando eles aparecem (por exemplo, explorador de ficheiros, projetos, ...)", "account.other-settings.button_tooltips": "Ocultar Dicas de Botão: oculta algumas dicas de botão (isto é apenas parcial)", "account.other-settings.confirm_close": "Confirmar Fecho: pedir sempre confirmação antes de fechar a janela do navegador", - "account.other-settings.content_display.title": "Exibição de Conteúdo", "account.other-settings.default_file_sort.by_name": "Ordenar por nome", "account.other-settings.default_file_sort.by_time": "Ordenar por tempo", "account.other-settings.default_file_sort.label": "Ordenação padrão de ficheiros", + "account.other-settings.dim_file_extensions": "Extensões de ficheiros em cinzento: atenuar as extensões de ficheiros para que os seus nomes se destaquem.", "account.other-settings.file_popovers": "Ocultar Popovers das Abas de Ficheiro: não mostrar os popovers sobre as abas de ficheiro", "account.other-settings.filename_generator.description": "Selecione como os nomes de ficheiros gerados automaticamente são criados. Em particular, para torná-los únicos ou incluir a hora atual.", "account.other-settings.filename_generator.label": "Gerador de nomes de ficheiros", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "Configurações de IA", "account.other-settings.markdown_codebar": "Desativar a barra de código markdown em todos os documentos markdown. Marcar esta opção oculta os botões extra de execução, cópia e explicação em blocos de código delimitados.", "account.other-settings.mask_files": "Mascarar ficheiros: esbater ficheiros no visualizador de ficheiros que provavelmente não quer abrir", - "account.other-settings.messages.title": "Mensagens", "account.other-settings.project_popovers": "Ocultar Pop-ups das Abas do Projeto: não mostrar os pop-ups sobre as abas do projeto", - "account.other-settings.projects.title": "Projetos", "account.other-settings.standby_timeout": "Tempo limite de espera", "account.other-settings.symbol_bar_labels": "Mostrar Etiquetas da Barra de Símbolos: mostrar etiquetas na barra de símbolos do editor de frames", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animações: animar brevemente alguns aspetos, por exemplo, botões", "account.other-settings.theme.antd.color_scheme": "Esquema de Cores: usar cores da marca em vez de cores padrão", "account.other-settings.theme.antd.compact": "Design Compacto: usar um design mais compacto", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "Gerar um Documento {docName} usando IA", "ai-generator.select_llm": "Selecionar modelo de linguagem", "app.fullscreen-button.tooltip": "Modo de ecrã completo, focado no documento ou página atual", + "app.hotkey.dialog.help_text": "Clique nas molduras acima • A tecla 0 alterna o chat • As teclas 1–9 focam nas molduras • Digite para pesquisar • ↑↓ navegue • Enter para abrir • ESC para fechar", + "app.hotkey.dialog.search_placeholder": "Procurar ficheiros e páginas...", + "app.hotkey.dialog.title": "Navegação Rápida", "app.verify-email-banner.edit": "Se o endereço de email estiver errado, por favor edite nas configurações da conta.", "app.verify-email-banner.help.text": "É importante ter um endereço de email funcional. Usamo-lo para redefinições de senha, envio de mensagens, notificações de faturação e suporte. Por favor, certifique-se de que o seu email está correto para se manter informado.", "app.verify-email-banner.text": "{sent, select, true {Email Enviado! Por favor, verifique a sua caixa de entrada de email (e talvez o spam) e clique no link de confirmação.} other {Por favor, verifique e confirme o seu endereço de email:}}", @@ -988,6 +986,7 @@ "labels.config": "Configuração", "labels.configuration": "Configuração", "labels.configuration.short": "Configurar", + "labels.connected": "Ligado", "labels.connecting": "A ligar", "labels.connection": "Ligação", "labels.copied": "copiado", @@ -1016,6 +1015,7 @@ "labels.environment": "Ambiente", "labels.explorer": "Explorador", "labels.file_explorer": "Explorador de Ficheiros", + "labels.file_use_notifications": "Notificações de Uso de Ficheiro", "labels.files": "Ficheiros", "labels.folder": "pasta", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Público} read_only {Só de leitura} other {Guardar}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "Parar{short, select, true {} other { Projeto}}…", "labels.project.settings.stop-project.ok": "Sim, parar projeto", "labels.projects": "Projetos", + "labels.public_paths": "Caminhos Públicos", "labels.published_files": "Ficheiros Publicados", "labels.purchases": "Compras", "labels.ready": "Pronto", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "Escolha um título. Pode alterá-lo facilmente mais tarde!", "projects.create-project.requireLicense": "É necessária uma licença para criar projetos adicionais.", "projects.filename-search.placeholder": "Procurar por nomes de ficheiros que editou...", - "projects.list.no_starred_found": "Nenhum projeto com estrela encontrado. Use o ícone de estrela ao lado dos títulos dos projetos para marcar os seus projetos favoritos.", "projects.load-all.label": "Mostrar todos os projetos...", "projects.operations.clear-filter": "Limpar Filtro", "projects.operations.delete.button": "{deleted, select, true {Recuperar Tudo} other {Eliminar Tudo}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "Filtrar por hashtags...", "projects.table-controls.hidden.label": "Oculto", "projects.table-controls.search.placeholder": "Procurar projetos...", + "projects.table.keyboard-row-hint": "Projeto {title}. Use as setas para cima e para baixo para mover; pressione Enter ou Espaço para abrir.", "projects.table.last-edited": "Última Edição", + "projects.table.untitled": "Sem título", "purchases.automatic-payments-warning.description": "Os pagamentos automáticos são muito mais convenientes, irão poupar-lhe tempo e garantir que as subscrições não sejam canceladas por acidente.", "purchases.automatic-payments-warning.title": "Não são necessários pagamentos automáticos para ter uma subscrição", "purchases.automatic-payments.are-enabled": "Pagamentos Automáticos estão Ativados", diff --git a/src/packages/frontend/i18n/trans/ru_RU.json b/src/packages/frontend/i18n/trans/ru_RU.json index 74e072a11cd..32d163c0835 100644 --- a/src/packages/frontend/i18n/trans/ru_RU.json +++ b/src/packages/frontend/i18n/trans/ru_RU.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "удалить при каждом сохранении файла", "account.editor-settings-autosave-interval.label": "Интервал автосохранения", "account.editor-settings.basic.title": "Основные настройки", - "account.editor-settings.color-schemes.label": "Цветовая схема редактора", "account.editor-settings.color-schemes.panel_title": "Цветовая схема редактора", "account.editor-settings.font-size.label": "Размер шрифта по умолчанию", "account.editor-settings.indent-size.label": "Размер отступа", "account.editor-settings.keyboard-bindings.label": "Клавиатурные привязки редактора", "account.editor-settings.keyboard.title": "Клавиатура", - "account.editor-settings.title": "Редактор", "account.editor-settings.x11-keyboard-variant.label": "Вариант клавиатуры (для X11 Desktop)", "account.editor-settings.x11-physical-keyboard.label": "Раскладка клавиатуры (для X11 Desktop)", "account.global-ssh-keys.help": "Для SSH в проект используйте следующий username@host: [project-id-without-dashes]@ssh.cocalc.com Идентификатор проекта без тире можно найти в разделе настроек проекта о SSH-ключах. Для SSH между проектами используйте [project-id-without-dashes]@ssh", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Разделить вид в рабочем листе Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Переключить комментирование выделения", "account.other-settings._page_size.label": "Количество файлов на странице", - "account.other-settings.browser_performance.title": "Браузер", + "account.other-settings.auto_focus": "Автоматическая фокусировка текстового ввода: автоматически фокусировать поля ввода текста, когда они появляются (например, проводник файлов, проекты, ...)", "account.other-settings.button_tooltips": "Скрыть подсказки кнопок: скрывает некоторые подсказки кнопок (это только частично)", "account.other-settings.confirm_close": "Подтвердите закрытие: всегда запрашивать подтверждение перед закрытием окна браузера", - "account.other-settings.content_display.title": "Отображение контента", "account.other-settings.default_file_sort.by_name": "Сортировать по имени", "account.other-settings.default_file_sort.by_time": "Сортировать по времени", "account.other-settings.default_file_sort.label": "Сортировка файлов по умолчанию", + "account.other-settings.dim_file_extensions": "Затемнить расширения файлов: сделать расширения файлов серыми, чтобы их названия выделялись.", "account.other-settings.file_popovers": "Скрыть всплывающие подсказки вкладок файлов: не показывать всплывающие подсказки над вкладками файлов", "account.other-settings.filename_generator.description": "Выберите способ автоматической генерации имен файлов. В частности, чтобы сделать их уникальными или включить текущее время.", "account.other-settings.filename_generator.label": "Генератор имен файлов", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "Настройки AI", "account.other-settings.markdown_codebar": "Отключить панель кода markdown во всех markdown документах. Если установить этот флажок, будут скрыты дополнительные кнопки запуска, копирования и объяснения в блоках кода.", "account.other-settings.mask_files": "Маскировать файлы: затенить файлы в просмотрщике файлов, которые вы, вероятно, не захотите открывать", - "account.other-settings.messages.title": "Сообщения", "account.other-settings.project_popovers": "Скрыть всплывающие подсказки вкладок проекта: не показывать всплывающие подсказки над вкладками проекта", - "account.other-settings.projects.title": "Проекты", "account.other-settings.standby_timeout": "Таймаут ожидания", "account.other-settings.symbol_bar_labels": "Показать метки панели символов: показывать метки на панели символов в редакторе фреймов", - "account.other-settings.theme": "Тема", "account.other-settings.theme.antd.animations": "Анимации: кратко анимировать некоторые аспекты, например, кнопки", "account.other-settings.theme.antd.color_scheme": "Цветовая схема: использовать фирменные цвета вместо стандартных цветов", "account.other-settings.theme.antd.compact": "Компактный дизайн: использовать более компактный дизайн", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "Сгенерировать документ {docName} с помощью ИИ", "ai-generator.select_llm": "Выберите языковую модель", "app.fullscreen-button.tooltip": "Полноэкранный режим, сосредоточенный на текущем документе или странице", + "app.hotkey.dialog.help_text": "Щелкните по кадрам сверху • Клавиша 0 переключает чат • Клавиши 1–9 фокусируются на кадрах • Введите для поиска • ↑↓ навигация • Return для открытия • ESC для закрытия", + "app.hotkey.dialog.search_placeholder": "Искать файлы и страницы...", + "app.hotkey.dialog.title": "Быстрая навигация", "app.verify-email-banner.edit": "Если адрес электронной почты неверен, пожалуйста, отредактируйте его в настройках аккаунта.", "app.verify-email-banner.help.text": "Важно иметь работающий адрес электронной почты. Мы используем его для сброса пароля, отправки сообщений, уведомлений о выставлении счетов и поддержки. Пожалуйста, убедитесь, что ваш адрес электронной почты правильный, чтобы оставаться в курсе событий.", "app.verify-email-banner.text": "{sent, select, true {Электронное письмо отправлено! Пожалуйста, проверьте ваш почтовый ящик (и, возможно, спам) и нажмите на ссылку для подтверждения.} other {Пожалуйста, проверьте и подтвердите ваш адрес электронной почты:}}", @@ -988,6 +986,7 @@ "labels.config": "Конфигурация", "labels.configuration": "Конфигурация", "labels.configuration.short": "Конфиг", + "labels.connected": "Подключено", "labels.connecting": "Соединение", "labels.connection": "Соединение", "labels.copied": "скопировано", @@ -1016,6 +1015,7 @@ "labels.environment": "Окружение", "labels.explorer": "Проводник", "labels.file_explorer": "Проводник файлов", + "labels.file_use_notifications": "Уведомления об использовании файлов", "labels.files": "Файлы", "labels.folder": "папка", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Публичный} read_only {Только чтение} other {Сохранить}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "Остановить{short, select, true {} other { Проект}}…", "labels.project.settings.stop-project.ok": "Да, остановить проект", "labels.projects": "Проекты", + "labels.public_paths": "Публичные пути", "labels.published_files": "Опубликованные файлы", "labels.purchases": "Покупки", "labels.ready": "Готово", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "Выберите название. Вы всегда можете изменить его позже!", "projects.create-project.requireLicense": "Для создания дополнительных проектов требуется лицензия.", "projects.filename-search.placeholder": "Искать измененные вами файлы...", - "projects.list.no_starred_found": "Не найдено избранных проектов. Используйте значок звезды рядом с названиями проектов, чтобы добавить в закладки ваши любимые проекты.", "projects.load-all.label": "Показать все проекты...", "projects.operations.clear-filter": "Очистить фильтр", "projects.operations.delete.button": "{deleted, select, true {Восстановить все} other {Удалить все}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "Фильтр по хэштегам...", "projects.table-controls.hidden.label": "Скрыто", "projects.table-controls.search.placeholder": "Поиск проектов...", + "projects.table.keyboard-row-hint": "Проект {title}. Используйте стрелки вверх и вниз для перемещения; нажмите Enter или пробел для открытия.", "projects.table.last-edited": "Последнее изменение", + "projects.table.untitled": "Без названия", "purchases.automatic-payments-warning.description": "Автоматические платежи гораздо удобнее, сэкономят вам время и гарантируют, что подписки не будут случайно отменены.", "purchases.automatic-payments-warning.title": "Автоматические платежи НЕ требуются для подписки", "purchases.automatic-payments.are-enabled": "Автоматические платежи включены", diff --git a/src/packages/frontend/i18n/trans/tr_TR.json b/src/packages/frontend/i18n/trans/tr_TR.json index 60add4cbb48..f797776b2e8 100644 --- a/src/packages/frontend/i18n/trans/tr_TR.json +++ b/src/packages/frontend/i18n/trans/tr_TR.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "dosya her kaydedildiğinde kaldır", "account.editor-settings-autosave-interval.label": "Otomatik kaydetme aralığı", "account.editor-settings.basic.title": "Temel Ayarlar", - "account.editor-settings.color-schemes.label": "Editör renk şeması", "account.editor-settings.color-schemes.panel_title": "Editör Renk Şeması", "account.editor-settings.font-size.label": "Varsayılan genel yazı tipi boyutu", "account.editor-settings.indent-size.label": "Girinti boyutu", "account.editor-settings.keyboard-bindings.label": "Editör klavye bağlamaları", "account.editor-settings.keyboard.title": "Klavye", - "account.editor-settings.title": "Editör", "account.editor-settings.x11-keyboard-variant.label": "Klavye çeşidi (X11 Masaüstü için)", "account.editor-settings.x11-physical-keyboard.label": "Klavye düzeni (X11 Masaüstü için)", "account.global-ssh-keys.help": "Bir projeye SSH ile bağlanmak için şu adresi kullanın username@host: [project-id-without-dashes]@ssh.cocalc.com Proje id'si tireler olmadan, SSH anahtarlarıyla ilgili proje ayarları bölümünde bulunabilir. Projeler arasında SSH yapmak için [project-id-without-dashes]@ssh kullanın", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Sage çalışma sayfasında bölünmüş görünüm", "account.keyboard-shortcuts.shortcut.toggle-comment": "Yorum satırını değiştir", "account.other-settings._page_size.label": "Sayfa başına dosya sayısı", - "account.other-settings.browser_performance.title": "Tarayıcı", + "account.other-settings.auto_focus": "Otomatik Odak Metin Girişi: metin giriş alanlarını göründüklerinde otomatik olarak odakla (ör. dosya gezgini, projeler, ...)", "account.other-settings.button_tooltips": "Düğme İpuçlarını Gizle: bazı düğme ipuçlarını gizler (bu sadece kısmi)", "account.other-settings.confirm_close": "Kapatmayı Onayla: tarayıcı penceresini kapatmadan önce her zaman onay iste", - "account.other-settings.content_display.title": "İçerik Görüntüleme", "account.other-settings.default_file_sort.by_name": "Ada göre sırala", "account.other-settings.default_file_sort.by_time": "Zamana göre sırala", "account.other-settings.default_file_sort.label": "Varsayılan dosya sıralaması", + "account.other-settings.dim_file_extensions": "Dosya uzantılarını soluklaştır: dosya uzantılarını gri yaparak adlarının öne çıkmasını sağla.", "account.other-settings.file_popovers": "Dosya Sekmesi Açılır Pencerelerini Gizle: dosya sekmelerinin üzerindeki açılır pencereleri gösterme", "account.other-settings.filename_generator.description": "Otomatik olarak oluşturulan dosya adlarının nasıl oluşturulacağını seçin. Özellikle, onları benzersiz yapmak veya mevcut zamanı eklemek için.", "account.other-settings.filename_generator.label": "Dosya adı oluşturucu", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "AI Ayarları", "account.other-settings.markdown_codebar": "Tüm markdown belgelerinde markdown kod çubuğunu devre dışı bırak. Bunu işaretlemek, çitlenmiş kod bloklarındaki ekstra çalıştır, kopyala ve açıkla düğmelerini gizler.", "account.other-settings.mask_files": "Dosya maskesi: Dosya görüntüleyicide muhtemelen açmak istemediğiniz dosyaları gri renkte göster", - "account.other-settings.messages.title": "Mesajlar", "account.other-settings.project_popovers": "Proje Sekmesi Açılır Pencerelerini Gizle: proje sekmelerinin üzerindeki açılır pencereleri gösterme", - "account.other-settings.projects.title": "Projeler", "account.other-settings.standby_timeout": "Bekleme zaman aşımı", "account.other-settings.symbol_bar_labels": "Sembol Çubuğu Etiketlerini Göster: çerçeve düzenleyici sembol çubuğunda etiketleri göster", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animasyonlar: bazı unsurları, örneğin düğmeleri kısaca canlandır", "account.other-settings.theme.antd.color_scheme": "Renk Şeması: varsayılan renkler yerine marka renklerini kullan", "account.other-settings.theme.antd.compact": "Kompakt Tasarım: daha kompakt bir tasarım kullan", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "Yapay Zeka kullanarak bir {docName} Belgesi oluştur", "ai-generator.select_llm": "Dil modelini seç", "app.fullscreen-button.tooltip": "Tam ekran modu, mevcut belge veya sayfaya odaklanılmış.", + "app.hotkey.dialog.help_text": "Yukarıdaki çerçevelere tıklayın • 0 tuşu sohbeti açar/kapatır • 1–9 tuşları çerçevelere odaklanır • Aramak için yazın • ↑↓ gezin • Açmak için Enter • Kapatmak için ESC", + "app.hotkey.dialog.search_placeholder": "Dosyaları ve sayfaları ara...", + "app.hotkey.dialog.title": "Hızlı Gezinme", "app.verify-email-banner.edit": "E-posta adresi yanlışsa, lütfen hesap ayarlarında düzenleyin.", "app.verify-email-banner.help.text": "Çalışan bir e-posta adresine sahip olmak önemlidir. Şifre sıfırlama, mesaj gönderme, fatura bildirimleri ve destek için kullanıyoruz. Bilgilendirilmek için e-postanızın doğru olduğundan emin olun.", "app.verify-email-banner.text": "{sent, select, true {E-posta Gönderildi! Lütfen e-posta gelen kutunuzu (ve belki spam klasörünü) kontrol edin ve onay bağlantısına tıklayın.} other {Lütfen e-posta adresinizi kontrol edin ve doğrulayın:}}", @@ -988,6 +986,7 @@ "labels.config": "Yapılandırma", "labels.configuration": "Yapılandırma", "labels.configuration.short": "Yapılandırma", + "labels.connected": "Bağlandı", "labels.connecting": "Bağlanıyor", "labels.connection": "Bağlantı", "labels.copied": "kopyalandı", @@ -1016,6 +1015,7 @@ "labels.environment": "Ortam", "labels.explorer": "Gezer", "labels.file_explorer": "Dosya Gezgini", + "labels.file_use_notifications": "Dosya Kullanım Bildirimleri", "labels.files": "Dosyalar", "labels.folder": "klasör", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Herkese Açık} read_only {Salt Okunur} other {Kaydet}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "Durdur{short, select, true {} other { Proje}}…", "labels.project.settings.stop-project.ok": "Evet, projeyi durdur", "labels.projects": "Projeler", + "labels.public_paths": "Genel Yollar", "labels.published_files": "Yayınlanan Dosyalar", "labels.purchases": "Satın Alımlar", "labels.ready": "Hazır", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "Bir başlık seçin. Daha sonra kolayca değiştirebilirsiniz!", "projects.create-project.requireLicense": "Ek projeler oluşturmak için bir lisans gereklidir.", "projects.filename-search.placeholder": "Düzenlediğiniz dosya adlarını arayın...", - "projects.list.no_starred_found": "Yıldızlı proje bulunamadı. Favori projelerinizi işaretlemek için proje başlıklarının yanındaki yıldız simgesini kullanın.", "projects.load-all.label": "Tüm projeleri göster...", "projects.operations.clear-filter": "Filtreyi Temizle", "projects.operations.delete.button": "{deleted, select, true {Tümünü Geri Al} other {Tümünü Sil}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "Etiketlere göre filtrele...", "projects.table-controls.hidden.label": "Gizli", "projects.table-controls.search.placeholder": "Projeleri ara...", + "projects.table.keyboard-row-hint": "Proje {title}. Hareket etmek için Yukarı ve Aşağı oklarını kullanın; açmak için Enter veya Boşluk tuşuna basın.", "projects.table.last-edited": "Son Düzenleme", + "projects.table.untitled": "Adsız", "purchases.automatic-payments-warning.description": "Otomatik ödemeler çok daha uygun, zaman kazandırır ve aboneliklerin kazara iptal edilmesini önler.", "purchases.automatic-payments-warning.title": "Otomatik ödemeler bir abonelik için GEREKLİ DEĞİLDİR", "purchases.automatic-payments.are-enabled": "Otomatik Ödemeler Etkinleştirildi", diff --git a/src/packages/frontend/i18n/trans/zh_CN.json b/src/packages/frontend/i18n/trans/zh_CN.json index 66264c8a02d..3f2c02470d4 100644 --- a/src/packages/frontend/i18n/trans/zh_CN.json +++ b/src/packages/frontend/i18n/trans/zh_CN.json @@ -31,13 +31,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "每次保存文件时删除", "account.editor-settings-autosave-interval.label": "自动保存间隔", "account.editor-settings.basic.title": "基本设置", - "account.editor-settings.color-schemes.label": "编辑器配色方案", "account.editor-settings.color-schemes.panel_title": "编辑器配色方案", "account.editor-settings.font-size.label": "默认全局字体大小", "account.editor-settings.indent-size.label": "缩进大小", "account.editor-settings.keyboard-bindings.label": "编辑器键盘绑定", "account.editor-settings.keyboard.title": "键盘", - "account.editor-settings.title": "编辑器", "account.editor-settings.x11-keyboard-variant.label": "键盘变体(用于X11桌面)", "account.editor-settings.x11-physical-keyboard.label": "键盘布局(用于X11桌面)", "account.global-ssh-keys.help": "要 SSH 进入一个项目,请使用以下 username@host: [project-id-without-dashes]@ssh.cocalc.com 无破折号的项目 ID 可以在项目设置中关于 SSH 密钥的部分找到。要在项目之间 SSH,请使用 [project-id-without-dashes]@ssh", @@ -66,13 +64,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "在 Sage 工作表中分割视图", "account.keyboard-shortcuts.shortcut.toggle-comment": "切换注释选择", "account.other-settings._page_size.label": "每页文件数量", - "account.other-settings.browser_performance.title": "浏览器", + "account.other-settings.auto_focus": "自动聚焦文本输入:文本输入字段出现时自动聚焦(例如,文件浏览器,项目,...)", "account.other-settings.button_tooltips": "隐藏按钮提示:隐藏部分按钮提示(这只是部分隐藏)", "account.other-settings.confirm_close": "确认关闭:在关闭浏览器窗口前始终要求确认", - "account.other-settings.content_display.title": "内容显示", "account.other-settings.default_file_sort.by_name": "按名称排序", "account.other-settings.default_file_sort.by_time": "按时间排序", "account.other-settings.default_file_sort.label": "默认文件排序", + "account.other-settings.dim_file_extensions": "文件扩展名变暗:将文件扩展名变灰,以突出其名称。", "account.other-settings.file_popovers": "隐藏文件标签弹出窗口:不在文件标签上显示弹出窗口", "account.other-settings.filename_generator.description": "选择如何生成自动生成的文件名。特别是,使它们唯一或包含当前时间。", "account.other-settings.filename_generator.label": "文件名生成器", @@ -85,12 +83,9 @@ "account.other-settings.llm.title": "AI设置", "account.other-settings.markdown_codebar": "禁用markdown代码栏 在所有markdown文档中。选中此项会隐藏围栏代码块中的额外运行、复制和解释按钮。", "account.other-settings.mask_files": "屏蔽文件:在文件查看器中将您可能不想打开的文件置灰", - "account.other-settings.messages.title": "消息", "account.other-settings.project_popovers": "隐藏项目标签弹出框:不在项目标签上显示弹出框", - "account.other-settings.projects.title": "项目", "account.other-settings.standby_timeout": "待机超时", "account.other-settings.symbol_bar_labels": "显示符号栏标签:在框架编辑器符号栏中显示标签", - "account.other-settings.theme": "主题", "account.other-settings.theme.antd.animations": "动画:简要动画某些方面,例如按钮", "account.other-settings.theme.antd.color_scheme": "配色方案: 使用品牌颜色而非默认颜色", "account.other-settings.theme.antd.compact": "紧凑设计:使用更紧凑的设计", @@ -155,6 +150,9 @@ "ai-generate-document.modal.title": "使用AI生成{docName}文档", "ai-generator.select_llm": "选择语言模型", "app.fullscreen-button.tooltip": "全屏模式,专注于当前文档或页面", + "app.hotkey.dialog.help_text": "单击上方的框架 • 按键 0 切换聊天 • 按键 1–9 聚焦框架 • 输入以搜索 • ↑↓ 导航 • 回车以打开 • ESC 关闭", + "app.hotkey.dialog.search_placeholder": "搜索文件和页面...", + "app.hotkey.dialog.title": "快速导航", "app.verify-email-banner.edit": "如果电子邮件地址错误,请在账户设置中编辑。", "app.verify-email-banner.help.text": "拥有一个有效的电子邮件地址非常重要。我们用它来重置密码、发送消息、账单通知和支持。请确保您的电子邮件正确以保持信息畅通。", "app.verify-email-banner.text": "{sent, select, true {邮件已发送!请检查您的电子邮箱收件箱(可能还有垃圾邮件)并点击确认链接。} other {请检查并验证您的电子邮箱地址:}}", @@ -988,6 +986,7 @@ "labels.config": "配置", "labels.configuration": "配置", "labels.configuration.short": "配置", + "labels.connected": "已连接", "labels.connecting": "连接中", "labels.connection": "连接", "labels.copied": "已复制", @@ -1016,6 +1015,7 @@ "labels.environment": "环境", "labels.explorer": "文件", "labels.file_explorer": "文件资源管理器", + "labels.file_use_notifications": "文件使用通知", "labels.files": "文件", "labels.folder": "文件夹", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {公开} read_only {只读} other {保存}}", @@ -1095,6 +1095,7 @@ "labels.project.settings.stop-project.label": "停止{short, select, true {} other {项目}}…", "labels.project.settings.stop-project.ok": "是,停止项目", "labels.projects": "项目", + "labels.public_paths": "公共路径", "labels.published_files": "已发布文件", "labels.purchases": "购买", "labels.ready": "准备好", @@ -1423,7 +1424,6 @@ "projects.create-project.helpTxt": "选择一个标题。您可以稍后轻松更改!", "projects.create-project.requireLicense": "创建额外的项目需要许可证。", "projects.filename-search.placeholder": "搜索你编辑过的文件名", - "projects.list.no_starred_found": "未找到加星标的项目。使用项目标题旁边的星标图标来收藏您喜欢的项目。", "projects.load-all.label": "显示所有项目...", "projects.operations.clear-filter": "清除筛选器", "projects.operations.delete.button": "{deleted, select, true {全部取消删除} other {全部删除}}", @@ -1455,7 +1455,9 @@ "projects.table-controls.hashtags.placeholder": "按标签过滤...", "projects.table-controls.hidden.label": "隐藏", "projects.table-controls.search.placeholder": "搜索项目...", + "projects.table.keyboard-row-hint": "项目 {title}。使用向上和向下箭头移动;按 Enter 或空格键打开。", "projects.table.last-edited": "上次编辑", + "projects.table.untitled": "无标题", "purchases.automatic-payments-warning.description": "自动支付更加方便,可以节省时间,并且确保订阅不会意外取消。", "purchases.automatic-payments-warning.title": "自动付款不需要订阅", "purchases.automatic-payments.are-enabled": "自动付款已启用", From e107d428c395e1d6e71c4c2adebf1b29176b09fd Mon Sep 17 00:00:00 2001 From: Andrey Novoseltsev Date: Wed, 26 Nov 2025 19:24:04 -0700 Subject: [PATCH 37/58] settings: accordion behavior: start closed, single open submenu --- .../frontend/account/account-page.tsx | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/packages/frontend/account/account-page.tsx b/src/packages/frontend/account/account-page.tsx index 82681a6b702..2f69040374d 100644 --- a/src/packages/frontend/account/account-page.tsx +++ b/src/packages/frontend/account/account-page.tsx @@ -89,6 +89,13 @@ import { UpgradesPage } from "./upgrades/upgrades-page"; export const ACCOUNT_SETTINGS_ICON_NAME: IconName = "settings"; +function getParentMenuKey(key?: string): string | undefined { + if (!key) return; + const dashIndex = key.indexOf("-"); + if (dashIndex === -1) return; + return key.slice(0, dashIndex); +} + // Type for valid menu keys type MenuKey = | "settings" @@ -119,7 +126,7 @@ const LOAD_ACCOUNT_INFO_TIMEOUT = 15_000; export const AccountPage: React.FC = () => { const intl = useIntl(); const [hidden, setHidden] = useState(IS_MOBILE); - const [openKeys, setOpenKeys] = useState(["preferences"]); + const [openKeys, setOpenKeys] = useState([]); const { width: windowWidth } = useWindowDimensions(); const isWide = windowWidth > 800; @@ -447,6 +454,33 @@ export const AccountPage: React.FC = () => { const tabs = getTabs(); + useEffect(() => { + if (!active_sub_tab) return; + const parentKey = getParentMenuKey(active_sub_tab); + if (!parentKey) return; + setOpenKeys((prevOpenKeys) => + prevOpenKeys.length === 1 && prevOpenKeys[0] === parentKey + ? prevOpenKeys + : [parentKey], + ); + }, [active_sub_tab]); + + useEffect(() => { + if (active_page !== "preferences") { + if (active_sub_tab) { + redux.getActions("account").setState({ active_sub_tab: undefined }); + } + setOpenKeys([]); + } + }, [active_page, active_sub_tab]); + + function handleOpenChange(keys: string[]) { + setOpenKeys((prevOpenKeys) => { + const newlyOpened = keys.find((key) => !prevOpenKeys.includes(key)); + return newlyOpened ? [newlyOpened] : []; + }); + } + // Process tabs to handle nested children for sub-tabs const children = {}; const titles = {}; // Always store full labels for renderTitle() @@ -565,9 +599,8 @@ export const AccountPage: React.FC = () => { }} > { From 364938d817ce569d3f90da4a46987da7eb23125c Mon Sep 17 00:00:00 2001 From: Andrey Novoseltsev Date: Wed, 26 Nov 2025 20:14:44 -0700 Subject: [PATCH 38/58] settings: create billing submenu similar to preferences --- .../frontend/account/account-page.tsx | 211 +++++++++++------- 1 file changed, 129 insertions(+), 82 deletions(-) diff --git a/src/packages/frontend/account/account-page.tsx b/src/packages/frontend/account/account-page.tsx index 2f69040374d..e013ab3af2e 100644 --- a/src/packages/frontend/account/account-page.tsx +++ b/src/packages/frontend/account/account-page.tsx @@ -89,8 +89,20 @@ import { UpgradesPage } from "./upgrades/upgrades-page"; export const ACCOUNT_SETTINGS_ICON_NAME: IconName = "settings"; +const BILLING_MENU_KEYS = new Set([ + "subscriptions", + "licenses", + "payg", + "upgrades", + "purchases", + "payments", + "payment-methods", + "statements", +]); + function getParentMenuKey(key?: string): string | undefined { if (!key) return; + if (BILLING_MENU_KEYS.has(key)) return "billing"; const dashIndex = key.indexOf("-"); if (dashIndex === -1) return; return key.slice(0, dashIndex); @@ -323,85 +335,102 @@ export const AccountPage: React.FC = () => { items.push({ type: "divider" }); if (is_commercial) { - items.push({ - key: "subscriptions", - label: ( - - {intl.formatMessage(labels.subscriptions)} - - ), - children: active_page === "subscriptions" && , - }); - items.push({ - key: "licenses", - label: ( - - {intl.formatMessage(labels.licenses)} - - ), - children: active_page === "licenses" && , - }); - items.push({ - key: "payg", - label: ( - - {" "} - {intl.formatMessage(labels.pay_as_you_go)} - - ), - children: active_page === "payg" && , - }); - if (is_commercial && kucalc === KUCALC_COCALC_COM) { - // these have been deprecated for ~ 5 years, but some customers still have them. - items.push({ - key: "upgrades", + const billingChildren = [ + { + key: "subscriptions", label: ( - {" "} - {intl.formatMessage(labels.upgrades)} + {" "} + {intl.formatMessage(labels.subscriptions)} ), - children: active_page === "upgrades" && , - }); - } - items.push({ type: "divider" }); - items.push({ - key: "purchases", - label: ( - - {intl.formatMessage(labels.purchases)} - - ), - children: active_page === "purchases" && , - }); - items.push({ - key: "payments", - label: ( - - {intl.formatMessage(labels.payments)} - - ), - children: active_page === "payments" && , - }); - items.push({ - key: "payment-methods", - label: ( - - {" "} - {intl.formatMessage(labels.payment_methods)} - - ), - children: active_page === "payment-methods" && , - }); + children: active_page === "subscriptions" && , + }, + { + key: "licenses", + label: ( + + {intl.formatMessage(labels.licenses)} + + ), + children: active_page === "licenses" && , + }, + { + key: "payg", + label: ( + + {" "} + {intl.formatMessage(labels.pay_as_you_go)} + + ), + children: active_page === "payg" && , + }, + ...(is_commercial && kucalc === KUCALC_COCALC_COM + ? [ + { + key: "upgrades", + label: ( + + {" "} + {intl.formatMessage(labels.upgrades)} + + ), + children: active_page === "upgrades" && , + }, + ] + : []), + { + key: "purchases", + label: ( + + {" "} + {intl.formatMessage(labels.purchases)} + + ), + children: active_page === "purchases" && , + }, + { + key: "payments", + label: ( + + {" "} + {intl.formatMessage(labels.payments)} + + ), + children: active_page === "payments" && , + }, + { + key: "payment-methods", + label: ( + + {" "} + {intl.formatMessage(labels.payment_methods)} + + ), + children: active_page === "payment-methods" && ( + + ), + }, + { + key: "statements", + label: ( + + {" "} + {intl.formatMessage(labels.statements)} + + ), + children: active_page === "statements" && , + }, + ]; + items.push({ - key: "statements", + key: "billing", label: ( - {" "} - {intl.formatMessage(labels.statements)} + {intl.formatMessage(labels.billing)} ), - children: active_page === "statements" && , + children: billingChildren, }); items.push({ type: "divider" }); } @@ -455,22 +484,21 @@ export const AccountPage: React.FC = () => { const tabs = getTabs(); useEffect(() => { - if (!active_sub_tab) return; - const parentKey = getParentMenuKey(active_sub_tab); - if (!parentKey) return; + const parentKey = getParentMenuKey( + active_sub_tab ? active_sub_tab : active_page, + ); setOpenKeys((prevOpenKeys) => - prevOpenKeys.length === 1 && prevOpenKeys[0] === parentKey - ? prevOpenKeys - : [parentKey], + parentKey == null + ? [] + : prevOpenKeys.length === 1 && prevOpenKeys[0] === parentKey + ? prevOpenKeys + : [parentKey], ); - }, [active_sub_tab]); + }, [active_page, active_sub_tab]); useEffect(() => { - if (active_page !== "preferences") { - if (active_sub_tab) { - redux.getActions("account").setState({ active_sub_tab: undefined }); - } - setOpenKeys([]); + if (active_page !== "preferences" && active_sub_tab) { + redux.getActions("account").setState({ active_sub_tab: undefined }); } }, [active_page, active_sub_tab]); @@ -510,6 +538,25 @@ export const AccountPage: React.FC = () => { children[subTab.key] = subTab.children; titles[subTab.key] = subTab.label; // Always store original full label } + } else if (tab.key === "billing" && Array.isArray(tab.children)) { + const subTabs = tab.children; + tab.children = subTabs.map((subTab) => { + const label = hidden ? ( + + {subTab.label.props.children[0]} + + ) : ( + subTab.label + ); + return { + key: subTab.key, + label, + }; + }); + for (const subTab of subTabs) { + children[subTab.key] = subTab.children; + titles[subTab.key] = subTab.label; + } } else if (tab.key === "settings" || tab.key === "profile") { // Handle settings and profile as top-level pages // Store original full label for renderTitle() From 233a7fe7966788cd40489c054ef6bb64b54a70ff Mon Sep 17 00:00:00 2001 From: Andrey Novoseltsev Date: Wed, 26 Nov 2025 21:04:14 -0700 Subject: [PATCH 39/58] settings: unify and simplify treatment of preferences and billing --- .../frontend/account/account-page.tsx | 72 +++++-------------- 1 file changed, 18 insertions(+), 54 deletions(-) diff --git a/src/packages/frontend/account/account-page.tsx b/src/packages/frontend/account/account-page.tsx index e013ab3af2e..a73bc4391f7 100644 --- a/src/packages/frontend/account/account-page.tsx +++ b/src/packages/frontend/account/account-page.tsx @@ -89,25 +89,6 @@ import { UpgradesPage } from "./upgrades/upgrades-page"; export const ACCOUNT_SETTINGS_ICON_NAME: IconName = "settings"; -const BILLING_MENU_KEYS = new Set([ - "subscriptions", - "licenses", - "payg", - "upgrades", - "purchases", - "payments", - "payment-methods", - "statements", -]); - -function getParentMenuKey(key?: string): string | undefined { - if (!key) return; - if (BILLING_MENU_KEYS.has(key)) return "billing"; - const dashIndex = key.indexOf("-"); - if (dashIndex === -1) return; - return key.slice(0, dashIndex); -} - // Type for valid menu keys type MenuKey = | "settings" @@ -365,7 +346,7 @@ export const AccountPage: React.FC = () => { ), children: active_page === "payg" && , }, - ...(is_commercial && kucalc === KUCALC_COCALC_COM + ...(kucalc === KUCALC_COCALC_COM ? [ { key: "upgrades", @@ -482,11 +463,12 @@ export const AccountPage: React.FC = () => { } const tabs = getTabs(); + const parentByChildKey = new Map(); useEffect(() => { - const parentKey = getParentMenuKey( - active_sub_tab ? active_sub_tab : active_page, - ); + const parentKey = + parentByChildKey.get(active_sub_tab ?? "") ?? + parentByChildKey.get(active_page ?? ""); setOpenKeys((prevOpenKeys) => parentKey == null ? [] @@ -497,10 +479,13 @@ export const AccountPage: React.FC = () => { }, [active_page, active_sub_tab]); useEffect(() => { - if (active_page !== "preferences" && active_sub_tab) { + if ( + active_sub_tab && + parentByChildKey.get(active_sub_tab) !== active_page + ) { redux.getActions("account").setState({ active_sub_tab: undefined }); } - }, [active_page, active_sub_tab]); + }, [active_page, active_sub_tab, parentByChildKey]); function handleOpenChange(keys: string[]) { setOpenKeys((prevOpenKeys) => { @@ -516,11 +501,11 @@ export const AccountPage: React.FC = () => { if (tab.type == "divider") { continue; } - if (tab.key === "preferences" && Array.isArray(tab.children)) { - // Handle sub-tabs for preferences + if (Array.isArray(tab.children)) { + // Handle nested submenus generically (preferences, billing, etc.) const subTabs = tab.children; tab.children = subTabs.map((subTab) => { - // Extract just the icon (first child) from the span when hidden + // When collapsed, show only the icon; otherwise show full label. const label = hidden ? ( {subTab.label.props.children[0]} @@ -533,30 +518,12 @@ export const AccountPage: React.FC = () => { label, }; }); - // Store sub-tab children and full labels for (const subTab of subTabs) { + // Track child -> parent mapping for openKeys/title lookup. + parentByChildKey.set(subTab.key, tab.key); children[subTab.key] = subTab.children; titles[subTab.key] = subTab.label; // Always store original full label } - } else if (tab.key === "billing" && Array.isArray(tab.children)) { - const subTabs = tab.children; - tab.children = subTabs.map((subTab) => { - const label = hidden ? ( - - {subTab.label.props.children[0]} - - ) : ( - subTab.label - ); - return { - key: subTab.key, - label, - }; - }); - for (const subTab of subTabs) { - children[subTab.key] = subTab.children; - titles[subTab.key] = subTab.label; - } } else if (tab.key === "settings" || tab.key === "profile") { // Handle settings and profile as top-level pages // Store original full label for renderTitle() @@ -589,12 +556,11 @@ export const AccountPage: React.FC = () => { } } + const activeChildKey = active_sub_tab ?? active_page; function renderTitle() { return ( - {active_page === "preferences" && active_sub_tab - ? titles[active_sub_tab] - : titles[active_page]} + {titles[activeChildKey]} ); } @@ -702,9 +668,7 @@ export const AccountPage: React.FC = () => {
{renderExtraContent()} - {active_page === "preferences" && active_sub_tab - ? children[active_sub_tab] - : children[active_page]} + {children[activeChildKey] ?? children[active_page]}
From 8557638030fde329a7f645b06abd4370122c9d2c Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Thu, 27 Nov 2025 11:07:54 +0100 Subject: [PATCH 40/58] npm: update older node-forge 1.3.1 to 1.3.2 --- src/packages/frontend/cspell.json | 5 ++++- src/packages/frontend/package.json | 2 +- src/packages/package.json | 1 + src/packages/pnpm-lock.yaml | 17 ++++++++++------- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/packages/frontend/cspell.json b/src/packages/frontend/cspell.json index 1fa2c0fb60c..ab68c4b2b0e 100644 --- a/src/packages/frontend/cspell.json +++ b/src/packages/frontend/cspell.json @@ -43,6 +43,7 @@ "onprem", "pdflatex", "plotly", + "pulseaudio", "pythontex", "rclass", "rereturn", @@ -81,7 +82,8 @@ "undeletes", "undeleting", "xelatex", - "xetex" + "xetex", + "xpra" ], "ignoreWords": [ "antd", @@ -110,6 +112,7 @@ "noconf", "nprocs", "pchildren", + "pgrep", "pids", "Popconfirm", "PoweroffOutlined", diff --git a/src/packages/frontend/package.json b/src/packages/frontend/package.json index 56f237e6522..63d7f8b3b06 100644 --- a/src/packages/frontend/package.json +++ b/src/packages/frontend/package.json @@ -116,7 +116,7 @@ "md5": "^2", "memoize-one": "^5.1.1", "mermaid": "^11.10.0", - "node-forge": "^1.0.0", + "node-forge": "^1.3.2", "onecolor": "^3.1.0", "pdfjs-dist": "^4.6.82", "plotly.js": "^2.29.1", diff --git a/src/packages/package.json b/src/packages/package.json index 19eb7bddbbf..1285ac60971 100644 --- a/src/packages/package.json +++ b/src/packages/package.json @@ -27,6 +27,7 @@ "langchain": "^0.3.34", "katex@<0.16.9": "^0.16.10", "nanoid@<3.3.8": "^3.3.8", + "node-forge@<=1.3.1": "^1.3.2", "tar-fs@<=2.1.3": "2.1.4", "tar-fs@3.0.8": "3.0.9", "@types/request@2.48.12": "2.48.13", diff --git a/src/packages/pnpm-lock.yaml b/src/packages/pnpm-lock.yaml index 91710cdd4e3..e451b2b9902 100644 --- a/src/packages/pnpm-lock.yaml +++ b/src/packages/pnpm-lock.yaml @@ -14,6 +14,7 @@ overrides: langchain: ^0.3.34 katex@<0.16.9: ^0.16.10 nanoid@<3.3.8: ^3.3.8 + node-forge@<=1.3.1: ^1.3.2 tar-fs@<=2.1.3: 2.1.4 tar-fs@3.0.8: 3.0.9 '@types/request@2.48.12': 2.48.13 @@ -562,8 +563,8 @@ importers: specifier: ^11.10.0 version: 11.10.0 node-forge: - specifier: ^1.0.0 - version: 1.3.1 + specifier: ^1.3.2 + version: 1.3.2 onecolor: specifier: ^3.1.0 version: 3.1.0 @@ -5358,6 +5359,7 @@ packages: bootstrap@3.4.1: resolution: {integrity: sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==} engines: {node: '>=6'} + deprecated: This version of Bootstrap is no longer supported. Please upgrade to the latest version. brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -8536,6 +8538,7 @@ packages: keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} engines: {node: '>= 0.6'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -9153,8 +9156,8 @@ packages: encoding: optional: true - node-forge@1.3.1: - resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} + node-forge@1.3.2: + resolution: {integrity: sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==} engines: {node: '>= 6.13.0'} node-gyp-build@4.8.4: @@ -21099,7 +21102,7 @@ snapshots: optionalDependencies: encoding: 0.1.13 - node-forge@1.3.1: {} + node-forge@1.3.2: {} node-gyp-build@4.8.4: {} @@ -21112,7 +21115,7 @@ snapshots: es6-promise: 4.2.8 lodash: 4.17.21 long: 5.3.2 - node-forge: 1.3.1 + node-forge: 1.3.2 pako: 2.1.0 process: 0.11.10 uuid: 9.0.1 @@ -22905,7 +22908,7 @@ snapshots: selfsigned@2.4.1: dependencies: '@types/node-forge': 1.3.14 - node-forge: 1.3.1 + node-forge: 1.3.2 semver@5.7.2: {} From 4f313968d5b926099e6cb41c51f4ef99416fe9be Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Thu, 27 Nov 2025 11:21:21 +0100 Subject: [PATCH 41/58] frontend/x11: select wss vs ws for https vs http -- useful for local dev! --- .../frontend/frame-editors/x11-editor/xpra-client.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/packages/frontend/frame-editors/x11-editor/xpra-client.ts b/src/packages/frontend/frame-editors/x11-editor/xpra-client.ts index d60e32f6e00..16676eba344 100644 --- a/src/packages/frontend/frame-editors/x11-editor/xpra-client.ts +++ b/src/packages/frontend/frame-editors/x11-editor/xpra-client.ts @@ -156,9 +156,10 @@ export class XpraClient extends EventEmitter { // Get origin, but with http[s] stripped. // Do not use window.location.hostname, since that doesn't // include the port, if there is one. - let origin = window.location.origin; + const { origin, protocol: pageProtocol } = window.location; const i = origin.indexOf(":"); - origin = origin.slice(i); + const originTail = origin.slice(i); + const wsProtocol = pageProtocol === "https:" ? "wss" : "ws"; const path = join( appBasePath, @@ -166,7 +167,7 @@ export class XpraClient extends EventEmitter { "server", `${port}`, ); - const uri = `wss${origin}${path}`; + const uri = `${wsProtocol}${originTail}${path}`; const dpi = Math.round(BASE_DPI * window.devicePixelRatio); return { uri, dpi }; } From ccd1b33b22016b22656279f807c8954bda7b0834 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Thu, 27 Nov 2025 11:52:50 +0100 Subject: [PATCH 42/58] npm: update js-yaml in non-dev dependencies and bump jest/ts-jest --- src/packages/next/package.json | 2 +- src/packages/package.json | 5 +- src/packages/pnpm-lock.yaml | 1092 ++++-------------------------- src/packages/static/package.json | 2 +- src/packages/sync/package.json | 14 +- 5 files changed, 140 insertions(+), 975 deletions(-) diff --git a/src/packages/next/package.json b/src/packages/next/package.json index 4e4fce6e846..68d140a9077 100644 --- a/src/packages/next/package.json +++ b/src/packages/next/package.json @@ -91,7 +91,7 @@ "timeago-react": "^3.0.4", "use-async-effect": "^2.2.7", "uuid": "^8.3.2", - "xmlbuilder2": "^3.0.2", + "xmlbuilder2": "^4.0.1", "zod": "^3.23.5" }, "devDependencies": { diff --git a/src/packages/package.json b/src/packages/package.json index 19eb7bddbbf..212ea643480 100644 --- a/src/packages/package.json +++ b/src/packages/package.json @@ -7,9 +7,9 @@ "devDependencies": { "@types/jest": "^30.0.0", "check-dependency-version-consistency": "^5.0.0", - "jest": "^30.0.5", + "jest": "^30.2.0", "jest-junit": "^16.0.0", - "ts-jest": "^29.2.3", + "ts-jest": "^29.4.5", "typescript": "^5.9.2" }, "dependencies": { @@ -28,6 +28,7 @@ "katex@<0.16.9": "^0.16.10", "nanoid@<3.3.8": "^3.3.8", "tar-fs@<=2.1.3": "2.1.4", + "js-yaml@4.1.0": "4.1.1", "tar-fs@3.0.8": "3.0.9", "@types/request@2.48.12": "2.48.13", "@types/node-fetch@2.6.12": "2.6.13", diff --git a/src/packages/pnpm-lock.yaml b/src/packages/pnpm-lock.yaml index 91710cdd4e3..08f58b1671a 100644 --- a/src/packages/pnpm-lock.yaml +++ b/src/packages/pnpm-lock.yaml @@ -15,6 +15,7 @@ overrides: katex@<0.16.9: ^0.16.10 nanoid@<3.3.8: ^3.3.8 tar-fs@<=2.1.3: 2.1.4 + js-yaml@4.1.0: 4.1.1 tar-fs@3.0.8: 3.0.9 '@types/request@2.48.12': 2.48.13 '@types/node-fetch@2.6.12': 2.6.13 @@ -36,14 +37,14 @@ importers: specifier: ^5.0.0 version: 5.0.1 jest: - specifier: ^30.0.5 - version: 30.0.5(@types/node@18.19.122) + specifier: ^30.2.0 + version: 30.2.0(@types/node@18.19.130) jest-junit: specifier: ^16.0.0 version: 16.0.0 ts-jest: - specifier: ^29.2.3 - version: 29.4.1(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.0.5(@types/node@18.19.122))(typescript@5.9.2) + specifier: ^29.4.5 + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@18.19.130))(typescript@5.9.2) typescript: specifier: ^5.9.2 version: 5.9.2 @@ -1018,8 +1019,8 @@ importers: specifier: ^8.3.2 version: 8.3.2 xmlbuilder2: - specifier: ^3.0.2 - version: 3.1.1 + specifier: ^4.0.1 + version: 4.0.1 zod: specifier: ^3.23.5 version: 3.25.76 @@ -1631,8 +1632,8 @@ importers: specifier: ^1.6.3 version: 1.6.7 ts-jest: - specifier: ^29.2.3 - version: 29.4.1(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@18.19.122))(typescript@5.9.3) + specifier: ^29.4.5 + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@18.19.122))(typescript@5.9.3) tsd: specifier: ^0.22.0 version: 0.22.0 @@ -1686,8 +1687,8 @@ importers: specifier: ^18.16.14 version: 18.19.122 ts-jest: - specifier: ^29.2.3 - version: 29.4.1(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@18.19.122))(typescript@5.9.3) + specifier: ^29.4.5 + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@18.19.122))(typescript@5.9.3) sync-client: dependencies: @@ -1883,10 +1884,6 @@ packages: '@adobe/css-tools@4.4.3': resolution: {integrity: sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==} - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - '@ant-design/colors@6.0.0': resolution: {integrity: sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==} @@ -2060,18 +2057,10 @@ packages: resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.0': - resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} - engines: {node: '>=6.9.0'} - '@babel/core@7.28.5': resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.0': - resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.28.5': resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} @@ -2088,12 +2077,6 @@ packages: resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.27.3': - resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-module-transforms@7.28.3': resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} @@ -2120,19 +2103,10 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.27.6': - resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} - engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.4': resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.0': - resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.28.5': resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} @@ -2249,18 +2223,10 @@ packages: resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.0': - resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.5': resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.1': - resolution: {integrity: sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==} - engines: {node: '>=6.9.0'} - '@babel/types@7.28.5': resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} @@ -2912,23 +2878,10 @@ packages: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} - '@jest/console@30.0.5': - resolution: {integrity: sha512-xY6b0XiL0Nav3ReresUarwl2oIz1gTnxGbGpho9/rbUWsLH0f1OD/VT84xs8c7VmH7MChnLb0pag6PhZhAdDiA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/console@30.2.0': resolution: {integrity: sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/core@30.0.5': - resolution: {integrity: sha512-fKD0OulvRsXF1hmaFgHhVJzczWzA1RXMMo9LTPuFXo9q/alDbME3JIyWYqovWsUBWSoBcsHaGPSLF9rz4l9Qeg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - '@jest/core@30.2.0': resolution: {integrity: sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -2968,10 +2921,6 @@ packages: resolution: {integrity: sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/expect@30.0.5': - resolution: {integrity: sha512-6udac8KKrtTtC+AXZ2iUN/R7dp7Ydry+Fo6FPFnDG54wjVMnb6vW/XNlf7Xj8UDjAE3aAVAsR4KFyKk3TCXmTA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/expect@30.2.0': resolution: {integrity: sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -2992,10 +2941,6 @@ packages: resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/globals@30.0.5': - resolution: {integrity: sha512-7oEJT19WW4oe6HR7oLRvHxwlJk2gev0U9px3ufs8sX9PoD1Eza68KF0/tlN7X0dq/WVsBScXQGgCldA1V9Y/jA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/globals@30.2.0': resolution: {integrity: sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -3004,15 +2949,6 @@ packages: resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/reporters@30.0.5': - resolution: {integrity: sha512-mafft7VBX4jzED1FwGC1o/9QUM2xebzavImZMeqnsklgcyxBto8mV4HzNSzUrryJ+8R9MFOM3HgYuDradWR+4g==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - '@jest/reporters@30.2.0': resolution: {integrity: sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -3026,10 +2962,6 @@ packages: resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/snapshot-utils@30.0.5': - resolution: {integrity: sha512-XcCQ5qWHLvi29UUrowgDFvV4t7ETxX91CbDczMnoqXPOIcZOxyNdSjm6kV5XMc8+HkxfRegU/MUmnTbJRzGrUQ==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/snapshot-utils@30.2.0': resolution: {integrity: sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -3038,26 +2970,14 @@ packages: resolution: {integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/test-result@30.0.5': - resolution: {integrity: sha512-wPyztnK0gbDMQAJZ43tdMro+qblDHH1Ru/ylzUo21TBKqt88ZqnKKK2m30LKmLLoKtR2lxdpCC/P3g1vfKcawQ==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/test-result@30.2.0': resolution: {integrity: sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/test-sequencer@30.0.5': - resolution: {integrity: sha512-Aea/G1egWoIIozmDD7PBXUOxkekXl7ueGzrsGGi1SbeKgQqCYCIf+wfbflEbf2LiPxL8j2JZGLyrzZagjvW4YQ==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/test-sequencer@30.2.0': resolution: {integrity: sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/transform@30.0.5': - resolution: {integrity: sha512-Vk8amLQCmuZyy6GbBht1Jfo9RSdBtg7Lks+B0PecnjI8J+PCLQPGh7uI8Q/2wwpW2gLdiAfiHNsmekKlywULqg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/transform@30.2.0': resolution: {integrity: sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -3096,9 +3016,6 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.30': - resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} - '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -3617,21 +3534,21 @@ packages: engines: {node: '>=8.0.0', npm: '>=5.0.0'} hasBin: true - '@oozcitak/dom@1.15.10': - resolution: {integrity: sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ==} - engines: {node: '>=8.0'} + '@oozcitak/dom@2.0.2': + resolution: {integrity: sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w==} + engines: {node: '>=20.0'} - '@oozcitak/infra@1.0.8': - resolution: {integrity: sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==} - engines: {node: '>=6.0'} + '@oozcitak/infra@2.0.2': + resolution: {integrity: sha512-2g+E7hoE2dgCz/APPOEK5s3rMhJvNxSMBrP+U+j1OWsIbtSpWxxlUjq1lU8RIsFJNYv7NMlnVsCuHcUzJW+8vA==} + engines: {node: '>=20.0'} - '@oozcitak/url@1.0.4': - resolution: {integrity: sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==} - engines: {node: '>=8.0'} + '@oozcitak/url@3.0.0': + resolution: {integrity: sha512-ZKfET8Ak1wsLAiLWNfFkZc/BraDccuTJKR6svTYc7sVjbR+Iu0vtXdiDMY4o6jaFl5TW2TlS7jbLl4VovtAJWQ==} + engines: {node: '>=20.0'} - '@oozcitak/util@8.3.8': - resolution: {integrity: sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==} - engines: {node: '>=8.0'} + '@oozcitak/util@10.0.0': + resolution: {integrity: sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA==} + engines: {node: '>=20.0'} '@openapitools/openapi-generator-cli@2.22.0': resolution: {integrity: sha512-HdjSiKsXpbnXBcSCnft494fv5pFZxPKFAV1eR+yMjo3bt1ONLb7OGy1D/5SrbjRfy9b82JcYUJ3gssh49suWKg==} @@ -4612,9 +4529,6 @@ packages: '@types/yargs@15.0.19': resolution: {integrity: sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==} - '@types/yargs@17.0.33': - resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - '@types/yargs@17.0.34': resolution: {integrity: sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==} @@ -5184,50 +5098,25 @@ packages: axios@1.12.2: resolution: {integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==} - babel-jest@30.0.5: - resolution: {integrity: sha512-mRijnKimhGDMsizTvBTWotwNpzrkHr+VvZUQBof2AufXKB8NXrL1W69TG20EvOz7aevx6FTJIaBuBkYxS8zolg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - peerDependencies: - '@babel/core': ^7.11.0 - babel-jest@30.2.0: resolution: {integrity: sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: '@babel/core': ^7.11.0 || ^8.0.0-0 - babel-plugin-istanbul@7.0.0: - resolution: {integrity: sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==} - engines: {node: '>=12'} - babel-plugin-istanbul@7.0.1: resolution: {integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==} engines: {node: '>=12'} - babel-plugin-jest-hoist@30.0.1: - resolution: {integrity: sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - babel-plugin-jest-hoist@30.2.0: resolution: {integrity: sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - babel-preset-current-node-syntax@1.1.0: - resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} - peerDependencies: - '@babel/core': ^7.0.0 - babel-preset-current-node-syntax@1.2.0: resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} peerDependencies: '@babel/core': ^7.0.0 || ^8.0.0-0 - babel-preset-jest@30.0.1: - resolution: {integrity: sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - peerDependencies: - '@babel/core': ^7.11.0 - babel-preset-jest@30.2.0: resolution: {integrity: sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -5358,6 +5247,7 @@ packages: bootstrap@3.4.1: resolution: {integrity: sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==} engines: {node: '>=6'} + deprecated: This version of Bootstrap is no longer supported. Please upgrade to the latest version. brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -5369,11 +5259,6 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.25.2: - resolution: {integrity: sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - browserslist@4.27.0: resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -5644,9 +5529,6 @@ packages: engines: {node: '>=6'} hasBin: true - collect-v8-coverage@1.0.2: - resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} - collect-v8-coverage@1.0.3: resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} @@ -6304,14 +6186,6 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} - dedent@1.6.0: - resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - dedent@1.7.0: resolution: {integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==} peerDependencies: @@ -6553,9 +6427,6 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.200: - resolution: {integrity: sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==} - electron-to-chromium@1.5.240: resolution: {integrity: sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==} @@ -8029,10 +7900,6 @@ packages: resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} engines: {node: '>=10'} - istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} - engines: {node: '>=8'} - istanbul-reports@3.2.0: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} @@ -8055,32 +7922,14 @@ packages: resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} engines: {node: 20 || >=22} - jest-changed-files@30.0.5: - resolution: {integrity: sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-changed-files@30.2.0: resolution: {integrity: sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-circus@30.0.5: - resolution: {integrity: sha512-h/sjXEs4GS+NFFfqBDYT7y5Msfxh04EwWLhQi0F8kuWpe+J/7tICSlswU8qvBqumR3kFgHbfu7vU6qruWWBPug==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-circus@30.2.0: resolution: {integrity: sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-cli@30.0.5: - resolution: {integrity: sha512-Sa45PGMkBZzF94HMrlX4kUyPOwUpdZasaliKN3mifvDmkhLYqLLg8HQTzn6gq7vJGahFYMQjXgyJWfYImKZzOw==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - jest-cli@30.2.0: resolution: {integrity: sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -8091,21 +7940,6 @@ packages: node-notifier: optional: true - jest-config@30.0.5: - resolution: {integrity: sha512-aIVh+JNOOpzUgzUnPn5FLtyVnqc3TQHVMupYtyeURSb//iLColiMIR8TxCIDKyx9ZgjKnXGucuW68hCxgbrwmA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - peerDependencies: - '@types/node': '*' - esbuild-register: '>=3.4.0' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - esbuild-register: - optional: true - ts-node: - optional: true - jest-config@30.2.0: resolution: {integrity: sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -8133,18 +7967,10 @@ packages: resolution: {integrity: sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-docblock@30.0.1: - resolution: {integrity: sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-docblock@30.2.0: resolution: {integrity: sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-each@30.0.5: - resolution: {integrity: sha512-dKjRsx1uZ96TVyejD3/aAWcNKy6ajMaN531CwWIsrazIqIoXI9TnnpPlkrEYku/8rkS3dh2rbH+kMOyiEIv0xQ==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-each@30.2.0: resolution: {integrity: sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -8158,10 +7984,6 @@ packages: canvas: optional: true - jest-environment-node@30.0.5: - resolution: {integrity: sha512-ppYizXdLMSvciGsRsMEnv/5EFpvOdXBaXRBzFUDPWrsfmog4kYrOGWXarLllz6AXan6ZAA/kYokgDWuos1IKDA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-environment-node@30.2.0: resolution: {integrity: sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -8170,10 +7992,6 @@ packages: resolution: {integrity: sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==} engines: {node: '>= 10.14.2'} - jest-haste-map@30.0.5: - resolution: {integrity: sha512-dkmlWNlsTSR0nH3nRfW5BKbqHefLZv0/6LCccG0xFCTWcJu8TuEwG+5Cm75iBfjVoockmO6J35o5gxtFSn5xeg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-haste-map@30.2.0: resolution: {integrity: sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -8182,10 +8000,6 @@ packages: resolution: {integrity: sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==} engines: {node: '>=10.12.0'} - jest-leak-detector@30.0.5: - resolution: {integrity: sha512-3Uxr5uP8jmHMcsOtYMRB/zf1gXN3yUIc+iPorhNETG54gErFIiUhLvyY/OggYpSMOEYqsmRxmuU4ZOoX5jpRFg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-leak-detector@30.2.0: resolution: {integrity: sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -8239,42 +8053,22 @@ packages: resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-resolve-dependencies@30.0.5: - resolution: {integrity: sha512-/xMvBR4MpwkrHW4ikZIWRttBBRZgWK4d6xt3xW1iRDSKt4tXzYkMkyPfBnSCgv96cpkrctfXs6gexeqMYqdEpw==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-resolve-dependencies@30.2.0: resolution: {integrity: sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-resolve@30.0.5: - resolution: {integrity: sha512-d+DjBQ1tIhdz91B79mywH5yYu76bZuE96sSbxj8MkjWVx5WNdt1deEFRONVL4UkKLSrAbMkdhb24XN691yDRHg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-resolve@30.2.0: resolution: {integrity: sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-runner@30.0.5: - resolution: {integrity: sha512-JcCOucZmgp+YuGgLAXHNy7ualBx4wYSgJVWrYMRBnb79j9PD0Jxh0EHvR5Cx/r0Ce+ZBC4hCdz2AzFFLl9hCiw==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-runner@30.2.0: resolution: {integrity: sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-runtime@30.0.5: - resolution: {integrity: sha512-7oySNDkqpe4xpX5PPiJTe5vEa+Ak/NnNz2bGYZrA1ftG3RL3EFlHaUkA1Cjx+R8IhK0Vg43RML5mJedGTPNz3A==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-runtime@30.2.0: resolution: {integrity: sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-snapshot@30.0.5: - resolution: {integrity: sha512-T00dWU/Ek3LqTp4+DcW6PraVxjk28WY5Ua/s+3zUKSERZSNyxTqhDXCWKG5p2HAJ+crVQ3WJ2P9YVHpj1tkW+g==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-snapshot@30.2.0: resolution: {integrity: sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -8287,18 +8081,10 @@ packages: resolution: {integrity: sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-validate@30.0.5: - resolution: {integrity: sha512-ouTm6VFHaS2boyl+k4u+Qip4TSH7Uld5tyD8psQ8abGgt2uYYB8VwVfAHWHjHc0NWmGGbwO5h0sCPOGHHevefw==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-validate@30.2.0: resolution: {integrity: sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-watcher@30.0.5: - resolution: {integrity: sha512-z9slj/0vOwBDBjN3L4z4ZYaA+pG56d6p3kTUhFRYGvXbXMWhXmb/FIxREZCD06DYUwDKKnj2T80+Pb71CQ0KEg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-watcher@30.2.0: resolution: {integrity: sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -8307,24 +8093,10 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} - jest-worker@30.0.5: - resolution: {integrity: sha512-ojRXsWzEP16NdUuBw/4H/zkZdHOa7MMYCk4E430l+8fELeLg/mqmMlRhjL7UNZvQrDmnovWZV4DxX03fZF48fQ==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-worker@30.2.0: resolution: {integrity: sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest@30.0.5: - resolution: {integrity: sha512-y2mfcJywuTUkvLm2Lp1/pFX8kTgMO5yyQGq/Sk/n2mN7XWYp4JsCZ/QXW34M8YScgk8bPZlREH04f6blPnoHnQ==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - jest@30.2.0: resolution: {integrity: sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -8399,8 +8171,8 @@ packages: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsbn@1.1.0: @@ -8536,6 +8308,7 @@ packages: keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} engines: {node: '>= 0.6'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -9179,9 +8952,6 @@ packages: '@types/node': optional: true - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - node-releases@2.0.26: resolution: {integrity: sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==} @@ -11316,8 +11086,8 @@ packages: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} - ts-jest@29.4.1: - resolution: {integrity: sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==} + ts-jest@29.4.5: + resolution: {integrity: sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -11544,12 +11314,6 @@ packages: resolution: {integrity: sha512-N0XH6lqDtFH84JxptQoZYmloF4nzrQqqrAymNj+/gW60AO2AZgOcf4O/nUXJcYfyQkqvMo9lSupBZmmgvuVXlw==} engines: {node: '>=4'} - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - update-browserslist-db@1.1.4: resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} hasBin: true @@ -11995,9 +11759,9 @@ packages: xml@1.0.1: resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==} - xmlbuilder2@3.1.1: - resolution: {integrity: sha512-WCSfbfZnQDdLQLiMdGUQpMxxckeQ4oZNMNhLVkcekTu7xhD4tuUDyAPoY8CwXvBYE6LwBHd6QW2WZXlOWr1vCw==} - engines: {node: '>=12.0'} + xmlbuilder2@4.0.1: + resolution: {integrity: sha512-vXeky0YRVjhx5pseJDQLk0F6u7gyA8++ceVOS88r4dWu4lWdY/ZjbL45QrN+g0GzZLg1D5AkzThpikZa98SC/g==} + engines: {node: '>=20.0'} xmlbuilder@11.0.1: resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} @@ -12110,11 +11874,6 @@ snapshots: '@adobe/css-tools@4.4.3': {} - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.12 - '@jridgewell/trace-mapping': 0.3.30 - '@ant-design/colors@6.0.0': dependencies: '@ctrl/tinycolor': 3.6.1 @@ -12275,26 +12034,6 @@ snapshots: '@babel/compat-data@7.28.0': {} - '@babel/core@7.28.0': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) - '@babel/helpers': 7.27.6 - '@babel/parser': 7.28.0 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.1 - convert-source-map: 2.0.0 - debug: 4.4.1 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/core@7.28.5': dependencies: '@babel/code-frame': 7.27.1 @@ -12315,14 +12054,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.0': - dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.1 - '@jridgewell/gen-mapping': 0.3.12 - '@jridgewell/trace-mapping': 0.3.30 - jsesc: 3.1.0 - '@babel/generator@7.28.5': dependencies: '@babel/parser': 7.28.5 @@ -12335,7 +12066,7 @@ snapshots: dependencies: '@babel/compat-data': 7.28.0 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.25.2 + browserslist: 4.27.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -12343,17 +12074,8 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.1 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.0 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -12376,189 +12098,95 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.27.6': - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.1 - '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 '@babel/types': 7.28.5 - '@babel/parser@7.28.0': - dependencies: - '@babel/types': 7.28.1 - '@babel/parser@7.28.5': dependencies: '@babel/types': 7.28.5 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -12575,20 +12203,8 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.0 - '@babel/types': 7.28.1 - - '@babel/traverse@7.28.0': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.0 - '@babel/template': 7.27.2 - '@babel/types': 7.28.1 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@babel/traverse@7.28.5': dependencies: @@ -12602,11 +12218,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.28.1': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -12969,7 +12580,7 @@ snapshots: globals: 13.24.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -13264,15 +12875,6 @@ snapshots: '@istanbuljs/schema@0.1.3': {} - '@jest/console@30.0.5': - dependencies: - '@jest/types': 30.0.5 - '@types/node': 18.19.122 - chalk: 4.1.2 - jest-message-util: 30.0.5 - jest-util: 30.0.5 - slash: 3.0.0 - '@jest/console@30.2.0': dependencies: '@jest/types': 30.2.0 @@ -13282,45 +12884,9 @@ snapshots: jest-util: 30.2.0 slash: 3.0.0 - '@jest/core@30.0.5': + '@jest/core@30.2.0': dependencies: - '@jest/console': 30.0.5 - '@jest/pattern': 30.0.1 - '@jest/reporters': 30.0.5 - '@jest/test-result': 30.0.5 - '@jest/transform': 30.0.5 - '@jest/types': 30.0.5 - '@types/node': 18.19.122 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 4.3.0 - exit-x: 0.2.2 - graceful-fs: 4.2.11 - jest-changed-files: 30.0.5 - jest-config: 30.0.5(@types/node@18.19.122) - jest-haste-map: 30.0.5 - jest-message-util: 30.0.5 - jest-regex-util: 30.0.1 - jest-resolve: 30.0.5 - jest-resolve-dependencies: 30.0.5 - jest-runner: 30.0.5 - jest-runtime: 30.0.5 - jest-snapshot: 30.0.5 - jest-util: 30.0.5 - jest-validate: 30.0.5 - jest-watcher: 30.0.5 - micromatch: 4.0.8 - pretty-format: 30.0.5 - slash: 3.0.0 - transitivePeerDependencies: - - babel-plugin-macros - - esbuild-register - - supports-color - - ts-node - - '@jest/core@30.2.0': - dependencies: - '@jest/console': 30.2.0 + '@jest/console': 30.2.0 '@jest/pattern': 30.0.1 '@jest/reporters': 30.2.0 '@jest/test-result': 30.2.0 @@ -13389,13 +12955,6 @@ snapshots: dependencies: '@jest/get-type': 30.1.0 - '@jest/expect@30.0.5': - dependencies: - expect: 30.0.5 - jest-snapshot: 30.0.5 - transitivePeerDependencies: - - supports-color - '@jest/expect@30.2.0': dependencies: expect: 30.2.0 @@ -13407,7 +12966,7 @@ snapshots: dependencies: '@jest/types': 30.0.5 '@sinonjs/fake-timers': 13.0.5 - '@types/node': 24.2.1 + '@types/node': 18.19.130 jest-message-util: 30.0.5 jest-mock: 30.0.5 jest-util: 30.0.5 @@ -13425,15 +12984,6 @@ snapshots: '@jest/get-type@30.1.0': {} - '@jest/globals@30.0.5': - dependencies: - '@jest/environment': 30.0.5 - '@jest/expect': 30.0.5 - '@jest/types': 30.0.5 - jest-mock: 30.0.5 - transitivePeerDependencies: - - supports-color - '@jest/globals@30.2.0': dependencies: '@jest/environment': 30.2.0 @@ -13445,37 +12995,9 @@ snapshots: '@jest/pattern@30.0.1': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 jest-regex-util: 30.0.1 - '@jest/reporters@30.0.5': - dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 30.0.5 - '@jest/test-result': 30.0.5 - '@jest/transform': 30.0.5 - '@jest/types': 30.0.5 - '@jridgewell/trace-mapping': 0.3.30 - '@types/node': 18.19.122 - chalk: 4.1.2 - collect-v8-coverage: 1.0.2 - exit-x: 0.2.2 - glob: 10.4.5 - graceful-fs: 4.2.11 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 6.0.3 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.1.7 - jest-message-util: 30.0.5 - jest-util: 30.0.5 - jest-worker: 30.0.5 - slash: 3.0.0 - string-length: 4.0.2 - v8-to-istanbul: 9.3.0 - transitivePeerDependencies: - - supports-color - '@jest/reporters@30.2.0': dependencies: '@bcoe/v8-coverage': 0.2.3 @@ -13508,13 +13030,6 @@ snapshots: dependencies: '@sinclair/typebox': 0.34.38 - '@jest/snapshot-utils@30.0.5': - dependencies: - '@jest/types': 30.0.5 - chalk: 4.1.2 - graceful-fs: 4.2.11 - natural-compare: 1.4.0 - '@jest/snapshot-utils@30.2.0': dependencies: '@jest/types': 30.2.0 @@ -13524,17 +13039,10 @@ snapshots: '@jest/source-map@30.0.1': dependencies: - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 callsites: 3.1.0 graceful-fs: 4.2.11 - '@jest/test-result@30.0.5': - dependencies: - '@jest/console': 30.0.5 - '@jest/types': 30.0.5 - '@types/istanbul-lib-coverage': 2.0.6 - collect-v8-coverage: 1.0.2 - '@jest/test-result@30.2.0': dependencies: '@jest/console': 30.2.0 @@ -13542,13 +13050,6 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 collect-v8-coverage: 1.0.3 - '@jest/test-sequencer@30.0.5': - dependencies: - '@jest/test-result': 30.0.5 - graceful-fs: 4.2.11 - jest-haste-map: 30.0.5 - slash: 3.0.0 - '@jest/test-sequencer@30.2.0': dependencies: '@jest/test-result': 30.2.0 @@ -13556,26 +13057,6 @@ snapshots: jest-haste-map: 30.2.0 slash: 3.0.0 - '@jest/transform@30.0.5': - dependencies: - '@babel/core': 7.28.0 - '@jest/types': 30.0.5 - '@jridgewell/trace-mapping': 0.3.30 - babel-plugin-istanbul: 7.0.0 - chalk: 4.1.2 - convert-source-map: 2.0.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.11 - jest-haste-map: 30.0.5 - jest-regex-util: 30.0.1 - jest-util: 30.0.5 - micromatch: 4.0.8 - pirates: 4.0.7 - slash: 3.0.0 - write-file-atomic: 5.0.1 - transitivePeerDependencies: - - supports-color - '@jest/transform@30.2.0': dependencies: '@babel/core': 7.28.5 @@ -13610,8 +13091,8 @@ snapshots: '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 18.19.122 - '@types/yargs': 17.0.33 + '@types/node': 18.19.130 + '@types/yargs': 17.0.34 chalk: 4.1.2 '@jest/types@30.2.0': @@ -13627,7 +13108,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.12': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -13644,7 +13125,7 @@ snapshots: '@jridgewell/source-map@0.3.10': dependencies: '@jridgewell/gen-mapping': 0.3.12 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/source-map@0.3.11': dependencies: @@ -13653,11 +13134,6 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.30': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -14328,22 +13804,22 @@ snapshots: transitivePeerDependencies: - encoding - '@oozcitak/dom@1.15.10': + '@oozcitak/dom@2.0.2': dependencies: - '@oozcitak/infra': 1.0.8 - '@oozcitak/url': 1.0.4 - '@oozcitak/util': 8.3.8 + '@oozcitak/infra': 2.0.2 + '@oozcitak/url': 3.0.0 + '@oozcitak/util': 10.0.0 - '@oozcitak/infra@1.0.8': + '@oozcitak/infra@2.0.2': dependencies: - '@oozcitak/util': 8.3.8 + '@oozcitak/util': 10.0.0 - '@oozcitak/url@1.0.4': + '@oozcitak/url@3.0.0': dependencies: - '@oozcitak/infra': 1.0.8 - '@oozcitak/util': 8.3.8 + '@oozcitak/infra': 2.0.2 + '@oozcitak/util': 10.0.0 - '@oozcitak/util@8.3.8': {} + '@oozcitak/util@10.0.0': {} '@openapitools/openapi-generator-cli@2.22.0(@types/node@18.19.122)(encoding@0.1.13)': dependencies: @@ -14964,24 +14440,24 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.7 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.1 + '@babel/types': 7.28.5 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@types/babel__traverse@7.20.7': dependencies: - '@babel/types': 7.28.1 + '@babel/types': 7.28.5 '@types/backbone@1.4.14': dependencies: @@ -15006,7 +14482,7 @@ snapshots: '@types/bonjour@3.5.13': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/caseless@0.12.5': {} @@ -15019,7 +14495,7 @@ snapshots: '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 4.19.6 - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/connect@3.4.38': dependencies: @@ -15204,7 +14680,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 6.0.0 - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/hast@2.3.10': dependencies: @@ -15225,7 +14701,7 @@ snapshots: '@types/http-proxy@1.17.16': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/istanbul-lib-coverage@2.0.6': {} @@ -15264,7 +14740,7 @@ snapshots: '@types/keyv@3.1.4': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/linkify-it@5.0.0': {} @@ -15307,7 +14783,7 @@ snapshots: '@types/node-forge@1.3.14': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/node-zendesk@2.0.15': dependencies: @@ -15333,7 +14809,7 @@ snapshots: '@types/oauth@0.9.6': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/passport-google-oauth20@2.0.16': dependencies: @@ -15392,13 +14868,13 @@ snapshots: '@types/request@2.48.13': dependencies: '@types/caseless': 0.12.5 - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/tough-cookie': 4.0.5 form-data: 2.5.5 '@types/responselike@1.0.3': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/retry@0.12.0': {} @@ -15415,7 +14891,7 @@ snapshots: '@types/send@0.17.5': dependencies: '@types/mime': 1.3.5 - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/serve-index@1.9.4': dependencies: @@ -15431,7 +14907,7 @@ snapshots: '@types/sockjs@0.3.36': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/stack-utils@2.0.3': {} @@ -15462,15 +14938,15 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/xml-encryption@1.2.4': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/xml2js@0.4.14': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/yargs-parser@21.0.3': {} @@ -15478,10 +14954,6 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@types/yargs@17.0.33': - dependencies: - '@types/yargs-parser': 21.0.3 - '@types/yargs@17.0.34': dependencies: '@types/yargs-parser': 21.0.3 @@ -15546,7 +15018,7 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.7.2 + semver: 7.7.3 ts-api-utils: 1.4.3(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 @@ -15562,7 +15034,7 @@ snapshots: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) eslint: 8.57.1 - semver: 7.7.2 + semver: 7.7.3 transitivePeerDependencies: - supports-color - typescript @@ -16105,19 +15577,6 @@ snapshots: transitivePeerDependencies: - debug - babel-jest@30.0.5(@babel/core@7.28.0): - dependencies: - '@babel/core': 7.28.0 - '@jest/transform': 30.0.5 - '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 7.0.0 - babel-preset-jest: 30.0.1(@babel/core@7.28.0) - chalk: 4.1.2 - graceful-fs: 4.2.11 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - babel-jest@30.2.0(@babel/core@7.28.5): dependencies: '@babel/core': 7.28.5 @@ -16131,16 +15590,6 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-istanbul@7.0.0: - dependencies: - '@babel/helper-plugin-utils': 7.27.1 - '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-instrument: 6.0.3 - test-exclude: 6.0.0 - transitivePeerDependencies: - - supports-color - babel-plugin-istanbul@7.0.1: dependencies: '@babel/helper-plugin-utils': 7.27.1 @@ -16151,35 +15600,10 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-jest-hoist@30.0.1: - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.1 - '@types/babel__core': 7.20.5 - babel-plugin-jest-hoist@30.2.0: dependencies: '@types/babel__core': 7.20.5 - babel-preset-current-node-syntax@1.1.0(@babel/core@7.28.0): - dependencies: - '@babel/core': 7.28.0 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.0) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.0) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.0) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.0) - babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.5): dependencies: '@babel/core': 7.28.5 @@ -16199,12 +15623,6 @@ snapshots: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.5) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.5) - babel-preset-jest@30.0.1(@babel/core@7.28.0): - dependencies: - '@babel/core': 7.28.0 - babel-plugin-jest-hoist: 30.0.1 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.0) - babel-preset-jest@30.2.0(@babel/core@7.28.5): dependencies: '@babel/core': 7.28.5 @@ -16341,13 +15759,6 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.25.2: - dependencies: - caniuse-lite: 1.0.30001734 - electron-to-chromium: 1.5.200 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.2) - browserslist@4.27.0: dependencies: baseline-browser-mapping: 2.8.20 @@ -16484,7 +15895,7 @@ snapshots: commander: 13.1.0 edit-json-file: 1.8.1 globby: 14.1.0 - js-yaml: 4.1.0 + js-yaml: 4.1.1 semver: 7.7.2 table: 6.9.0 type-fest: 4.41.0 @@ -16644,8 +16055,6 @@ snapshots: coffeescript@2.7.0: {} - collect-v8-coverage@1.0.2: {} - collect-v8-coverage@1.0.3: {} color-alpha@1.0.4: @@ -17379,8 +16788,6 @@ snapshots: dependencies: mimic-response: 3.1.0 - dedent@1.6.0: {} - dedent@1.7.0: {} deep-extend@0.6.0: {} @@ -17634,8 +17041,6 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.200: {} - electron-to-chromium@1.5.240: {} element-size@1.1.1: {} @@ -17982,7 +17387,7 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 - js-yaml: 4.1.0 + js-yaml: 4.1.1 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 lodash.merge: 4.6.2 @@ -19423,11 +18828,11 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.28.0 - '@babel/parser': 7.28.0 + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.2 + semver: 7.7.3 transitivePeerDependencies: - supports-color @@ -19439,17 +18844,12 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: - '@jridgewell/trace-mapping': 0.3.30 - debug: 4.4.1 + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color - istanbul-reports@3.1.7: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - istanbul-reports@3.2.0: dependencies: html-escaper: 2.0.2 @@ -19478,44 +18878,12 @@ snapshots: dependencies: '@isaacs/cliui': 8.0.2 - jest-changed-files@30.0.5: - dependencies: - execa: 5.1.1 - jest-util: 30.0.5 - p-limit: 3.1.0 - jest-changed-files@30.2.0: dependencies: execa: 5.1.1 jest-util: 30.2.0 p-limit: 3.1.0 - jest-circus@30.0.5: - dependencies: - '@jest/environment': 30.0.5 - '@jest/expect': 30.0.5 - '@jest/test-result': 30.0.5 - '@jest/types': 30.0.5 - '@types/node': 18.19.122 - chalk: 4.1.2 - co: 4.6.0 - dedent: 1.6.0 - is-generator-fn: 2.1.0 - jest-each: 30.0.5 - jest-matcher-utils: 30.0.5 - jest-message-util: 30.0.5 - jest-runtime: 30.0.5 - jest-snapshot: 30.0.5 - jest-util: 30.0.5 - p-limit: 3.1.0 - pretty-format: 30.0.5 - pure-rand: 7.0.1 - slash: 3.0.0 - stack-utils: 2.0.6 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - jest-circus@30.2.0: dependencies: '@jest/environment': 30.2.0 @@ -19542,17 +18910,17 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.0.5(@types/node@18.19.122): + jest-cli@30.2.0(@types/node@18.19.122): dependencies: - '@jest/core': 30.0.5 - '@jest/test-result': 30.0.5 - '@jest/types': 30.0.5 + '@jest/core': 30.2.0 + '@jest/test-result': 30.2.0 + '@jest/types': 30.2.0 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.0.5(@types/node@18.19.122) - jest-util: 30.0.5 - jest-validate: 30.0.5 + jest-config: 30.2.0(@types/node@18.19.122) + jest-util: 30.2.0 + jest-validate: 30.2.0 yargs: 17.7.2 transitivePeerDependencies: - '@types/node' @@ -19561,7 +18929,7 @@ snapshots: - supports-color - ts-node - jest-cli@30.2.0(@types/node@18.19.122): + jest-cli@30.2.0(@types/node@18.19.130): dependencies: '@jest/core': 30.2.0 '@jest/test-result': 30.2.0 @@ -19569,7 +18937,7 @@ snapshots: chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.2.0(@types/node@18.19.122) + jest-config: 30.2.0(@types/node@18.19.130) jest-util: 30.2.0 jest-validate: 30.2.0 yargs: 17.7.2 @@ -19580,38 +18948,6 @@ snapshots: - supports-color - ts-node - jest-config@30.0.5(@types/node@18.19.122): - dependencies: - '@babel/core': 7.28.0 - '@jest/get-type': 30.0.1 - '@jest/pattern': 30.0.1 - '@jest/test-sequencer': 30.0.5 - '@jest/types': 30.0.5 - babel-jest: 30.0.5(@babel/core@7.28.0) - chalk: 4.1.2 - ci-info: 4.3.0 - deepmerge: 4.3.1 - glob: 10.4.5 - graceful-fs: 4.2.11 - jest-circus: 30.0.5 - jest-docblock: 30.0.1 - jest-environment-node: 30.0.5 - jest-regex-util: 30.0.1 - jest-resolve: 30.0.5 - jest-runner: 30.0.5 - jest-util: 30.0.5 - jest-validate: 30.0.5 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 30.0.5 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 18.19.122 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - jest-config@30.2.0(@types/node@18.19.122): dependencies: '@babel/core': 7.28.5 @@ -19697,22 +19033,10 @@ snapshots: chalk: 4.1.2 pretty-format: 30.2.0 - jest-docblock@30.0.1: - dependencies: - detect-newline: 3.1.0 - jest-docblock@30.2.0: dependencies: detect-newline: 3.1.0 - jest-each@30.0.5: - dependencies: - '@jest/get-type': 30.0.1 - '@jest/types': 30.0.5 - chalk: 4.1.2 - jest-util: 30.0.5 - pretty-format: 30.0.5 - jest-each@30.2.0: dependencies: '@jest/get-type': 30.1.0 @@ -19733,16 +19057,6 @@ snapshots: - supports-color - utf-8-validate - jest-environment-node@30.0.5: - dependencies: - '@jest/environment': 30.0.5 - '@jest/fake-timers': 30.0.5 - '@jest/types': 30.0.5 - '@types/node': 18.19.122 - jest-mock: 30.0.5 - jest-util: 30.0.5 - jest-validate: 30.0.5 - jest-environment-node@30.2.0: dependencies: '@jest/environment': 30.2.0 @@ -19755,21 +19069,6 @@ snapshots: jest-get-type@26.3.0: {} - jest-haste-map@30.0.5: - dependencies: - '@jest/types': 30.0.5 - '@types/node': 18.19.122 - anymatch: 3.1.3 - fb-watchman: 2.0.2 - graceful-fs: 4.2.11 - jest-regex-util: 30.0.1 - jest-util: 30.0.5 - jest-worker: 30.0.5 - micromatch: 4.0.8 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.3 - jest-haste-map@30.2.0: dependencies: '@jest/types': 30.2.0 @@ -19792,11 +19091,6 @@ snapshots: uuid: 8.3.2 xml: 1.0.1 - jest-leak-detector@30.0.5: - dependencies: - '@jest/get-type': 30.0.1 - pretty-format: 30.0.5 - jest-leak-detector@30.2.0: dependencies: '@jest/get-type': 30.1.0 @@ -19871,10 +19165,6 @@ snapshots: '@types/node': 18.19.130 jest-util: 30.2.0 - jest-pnp-resolver@1.2.3(jest-resolve@30.0.5): - optionalDependencies: - jest-resolve: 30.0.5 - jest-pnp-resolver@1.2.3(jest-resolve@30.2.0): optionalDependencies: jest-resolve: 30.2.0 @@ -19883,13 +19173,6 @@ snapshots: jest-regex-util@30.0.1: {} - jest-resolve-dependencies@30.0.5: - dependencies: - jest-regex-util: 30.0.1 - jest-snapshot: 30.0.5 - transitivePeerDependencies: - - supports-color - jest-resolve-dependencies@30.2.0: dependencies: jest-regex-util: 30.0.1 @@ -19897,17 +19180,6 @@ snapshots: transitivePeerDependencies: - supports-color - jest-resolve@30.0.5: - dependencies: - chalk: 4.1.2 - graceful-fs: 4.2.11 - jest-haste-map: 30.0.5 - jest-pnp-resolver: 1.2.3(jest-resolve@30.0.5) - jest-util: 30.0.5 - jest-validate: 30.0.5 - slash: 3.0.0 - unrs-resolver: 1.11.1 - jest-resolve@30.2.0: dependencies: chalk: 4.1.2 @@ -19919,33 +19191,6 @@ snapshots: slash: 3.0.0 unrs-resolver: 1.11.1 - jest-runner@30.0.5: - dependencies: - '@jest/console': 30.0.5 - '@jest/environment': 30.0.5 - '@jest/test-result': 30.0.5 - '@jest/transform': 30.0.5 - '@jest/types': 30.0.5 - '@types/node': 18.19.122 - chalk: 4.1.2 - emittery: 0.13.1 - exit-x: 0.2.2 - graceful-fs: 4.2.11 - jest-docblock: 30.0.1 - jest-environment-node: 30.0.5 - jest-haste-map: 30.0.5 - jest-leak-detector: 30.0.5 - jest-message-util: 30.0.5 - jest-resolve: 30.0.5 - jest-runtime: 30.0.5 - jest-util: 30.0.5 - jest-watcher: 30.0.5 - jest-worker: 30.0.5 - p-limit: 3.1.0 - source-map-support: 0.5.13 - transitivePeerDependencies: - - supports-color - jest-runner@30.2.0: dependencies: '@jest/console': 30.2.0 @@ -19973,33 +19218,6 @@ snapshots: transitivePeerDependencies: - supports-color - jest-runtime@30.0.5: - dependencies: - '@jest/environment': 30.0.5 - '@jest/fake-timers': 30.0.5 - '@jest/globals': 30.0.5 - '@jest/source-map': 30.0.1 - '@jest/test-result': 30.0.5 - '@jest/transform': 30.0.5 - '@jest/types': 30.0.5 - '@types/node': 18.19.122 - chalk: 4.1.2 - cjs-module-lexer: 2.1.0 - collect-v8-coverage: 1.0.2 - glob: 10.4.5 - graceful-fs: 4.2.11 - jest-haste-map: 30.0.5 - jest-message-util: 30.0.5 - jest-mock: 30.0.5 - jest-regex-util: 30.0.1 - jest-resolve: 30.0.5 - jest-snapshot: 30.0.5 - jest-util: 30.0.5 - slash: 3.0.0 - strip-bom: 4.0.0 - transitivePeerDependencies: - - supports-color - jest-runtime@30.2.0: dependencies: '@jest/environment': 30.2.0 @@ -20027,32 +19245,6 @@ snapshots: transitivePeerDependencies: - supports-color - jest-snapshot@30.0.5: - dependencies: - '@babel/core': 7.28.0 - '@babel/generator': 7.28.0 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0) - '@babel/types': 7.28.1 - '@jest/expect-utils': 30.0.5 - '@jest/get-type': 30.0.1 - '@jest/snapshot-utils': 30.0.5 - '@jest/transform': 30.0.5 - '@jest/types': 30.0.5 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.0) - chalk: 4.1.2 - expect: 30.0.5 - graceful-fs: 4.2.11 - jest-diff: 30.0.5 - jest-matcher-utils: 30.0.5 - jest-message-util: 30.0.5 - jest-util: 30.0.5 - pretty-format: 30.0.5 - semver: 7.7.2 - synckit: 0.11.8 - transitivePeerDependencies: - - supports-color - jest-snapshot@30.2.0: dependencies: '@babel/core': 7.28.5 @@ -20097,15 +19289,6 @@ snapshots: graceful-fs: 4.2.11 picomatch: 4.0.3 - jest-validate@30.0.5: - dependencies: - '@jest/get-type': 30.0.1 - '@jest/types': 30.0.5 - camelcase: 6.3.0 - chalk: 4.1.2 - leven: 3.1.0 - pretty-format: 30.0.5 - jest-validate@30.2.0: dependencies: '@jest/get-type': 30.1.0 @@ -20115,17 +19298,6 @@ snapshots: leven: 3.1.0 pretty-format: 30.2.0 - jest-watcher@30.0.5: - dependencies: - '@jest/test-result': 30.0.5 - '@jest/types': 30.0.5 - '@types/node': 18.19.122 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - emittery: 0.13.1 - jest-util: 30.0.5 - string-length: 4.0.2 - jest-watcher@30.2.0: dependencies: '@jest/test-result': 30.2.0 @@ -20143,14 +19315,6 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest-worker@30.0.5: - dependencies: - '@types/node': 18.19.122 - '@ungap/structured-clone': 1.3.0 - jest-util: 30.0.5 - merge-stream: 2.0.0 - supports-color: 8.1.1 - jest-worker@30.2.0: dependencies: '@types/node': 18.19.130 @@ -20159,12 +19323,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.0.5(@types/node@18.19.122): + jest@30.2.0(@types/node@18.19.122): dependencies: - '@jest/core': 30.0.5 - '@jest/types': 30.0.5 + '@jest/core': 30.2.0 + '@jest/types': 30.2.0 import-local: 3.2.0 - jest-cli: 30.0.5(@types/node@18.19.122) + jest-cli: 30.2.0(@types/node@18.19.122) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -20172,12 +19336,12 @@ snapshots: - supports-color - ts-node - jest@30.2.0(@types/node@18.19.122): + jest@30.2.0(@types/node@18.19.130): dependencies: '@jest/core': 30.2.0 '@jest/types': 30.2.0 import-local: 3.2.0 - jest-cli: 30.2.0(@types/node@18.19.122) + jest-cli: 30.2.0(@types/node@18.19.130) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -20242,7 +19406,7 @@ snapshots: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -20359,7 +19523,7 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.2 - semver: 7.7.2 + semver: 7.7.3 jsx-ast-utils@3.3.5: dependencies: @@ -20453,7 +19617,7 @@ snapshots: console-table-printer: 2.14.6 p-queue: 6.6.2 p-retry: 4.6.2 - semver: 7.7.2 + semver: 7.7.3 uuid: 10.0.0 optionalDependencies: '@opentelemetry/api': 1.9.0 @@ -20659,7 +19823,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 make-error@1.3.6: {} @@ -21085,7 +20249,7 @@ snapshots: node-abi@3.75.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 node-addon-api@7.1.1: {} @@ -21133,8 +20297,6 @@ snapshots: '@types/express': 4.17.23 '@types/node': 18.19.122 - node-releases@2.0.19: {} - node-releases@2.0.26: {} node-uuid@1.4.8: {} @@ -21158,7 +20320,7 @@ snapshots: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.16.1 - semver: 7.7.2 + semver: 7.7.3 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} @@ -21908,7 +21070,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 18.19.122 + '@types/node': 18.19.130 long: 5.3.2 protocol-buffers-schema@3.6.0: {} @@ -23006,7 +22168,7 @@ snapshots: dependencies: color: 4.2.3 detect-libc: 2.0.4 - semver: 7.7.2 + semver: 7.7.3 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.3 '@img/sharp-darwin-x64': 0.34.3 @@ -23215,7 +22377,7 @@ snapshots: spdy-transport@3.0.0: dependencies: - debug: 4.4.1 + debug: 4.4.3 detect-node: 2.1.0 hpack.js: 2.1.6 obuf: 1.1.2 @@ -23704,18 +22866,18 @@ snapshots: ts-dedent@2.2.0: {} - ts-jest@29.4.1(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.0.5(@types/node@18.19.122))(typescript@5.9.2): + ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@18.19.122))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.8 - jest: 30.0.5(@types/node@18.19.122) + jest: 30.2.0(@types/node@18.19.122) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.2 + semver: 7.7.3 type-fest: 4.41.0 - typescript: 5.9.2 + typescript: 5.9.3 yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 7.28.5 @@ -23724,18 +22886,18 @@ snapshots: babel-jest: 30.2.0(@babel/core@7.28.5) jest-util: 30.2.0 - ts-jest@29.4.1(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@18.19.122))(typescript@5.9.3): + ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@18.19.130))(typescript@5.9.2): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.8 - jest: 30.2.0(@types/node@18.19.122) + jest: 30.2.0(@types/node@18.19.130) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.2 + semver: 7.7.3 type-fest: 4.41.0 - typescript: 5.9.3 + typescript: 5.9.2 yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 7.28.5 @@ -23960,12 +23122,6 @@ snapshots: unzip-response@2.0.1: {} - update-browserslist-db@1.1.3(browserslist@4.25.2): - dependencies: - browserslist: 4.25.2 - escalade: 3.2.0 - picocolors: 1.1.1 - update-browserslist-db@1.1.4(browserslist@4.27.0): dependencies: browserslist: 4.27.0 @@ -24065,7 +23221,7 @@ snapshots: v8-to-istanbul@9.3.0: dependencies: - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 @@ -24510,12 +23666,12 @@ snapshots: xml@1.0.1: {} - xmlbuilder2@3.1.1: + xmlbuilder2@4.0.1: dependencies: - '@oozcitak/dom': 1.15.10 - '@oozcitak/infra': 1.0.8 - '@oozcitak/util': 8.3.8 - js-yaml: 3.14.1 + '@oozcitak/dom': 2.0.2 + '@oozcitak/infra': 2.0.2 + '@oozcitak/util': 10.0.0 + js-yaml: 4.1.1 xmlbuilder@11.0.1: {} diff --git a/src/packages/static/package.json b/src/packages/static/package.json index f0c43483713..fc4d10557a5 100644 --- a/src/packages/static/package.json +++ b/src/packages/static/package.json @@ -108,7 +108,7 @@ "stream-browserify": "^3.0.0", "style-loader": "^2.0.0", "timeago": "^1.6.3", - "ts-jest": "^29.2.3", + "ts-jest": "^29.4.5", "tsd": "^0.22.0", "util": "^0.12.3", "webpack-hot-middleware": "^2.26.1", diff --git a/src/packages/sync/package.json b/src/packages/sync/package.json index 1a362a81824..bc859d775d5 100644 --- a/src/packages/sync/package.json +++ b/src/packages/sync/package.json @@ -15,9 +15,17 @@ "depcheck": "pnpx depcheck --ignores events", "prepublishOnly": "pnpm test" }, - "files": ["dist/**", "bin/**", "README.md", "package.json"], + "files": [ + "dist/**", + "bin/**", + "README.md", + "package.json" + ], "author": "SageMath, Inc.", - "keywords": ["cocalc", "realtime synchronization"], + "keywords": [ + "cocalc", + "realtime synchronization" + ], "license": "SEE LICENSE.md", "dependencies": { "@cocalc/conat": "workspace:*", @@ -39,6 +47,6 @@ "devDependencies": { "@types/lodash": "^4.14.202", "@types/node": "^18.16.14", - "ts-jest": "^29.2.3" + "ts-jest": "^29.4.5" } } From 4e8b146b9f6c126d1d262da426f87da86c76c99d Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Thu, 27 Nov 2025 13:11:52 +0100 Subject: [PATCH 43/58] frontend/account: add "Accessibility" setting (for now just boolean) and a query parameter. --- .../account-preferences-appearance.tsx | 59 +++++++++++++- src/packages/frontend/account/types.ts | 1 + src/packages/frontend/app/context.tsx | 24 ++++++ src/packages/frontend/app/query-params.ts | 77 ++++++++++++++++++- src/packages/frontend/embed/index.ts | 1 - src/packages/frontend/entry-point.ts | 2 +- src/packages/frontend/i18n/trans/ar_EG.json | 20 +++-- src/packages/frontend/i18n/trans/de_DE.json | 20 +++-- src/packages/frontend/i18n/trans/es_ES.json | 20 +++-- src/packages/frontend/i18n/trans/es_PV.json | 20 +++-- src/packages/frontend/i18n/trans/fr_FR.json | 20 +++-- src/packages/frontend/i18n/trans/he_IL.json | 20 +++-- src/packages/frontend/i18n/trans/hi_IN.json | 20 +++-- src/packages/frontend/i18n/trans/hu_HU.json | 20 +++-- src/packages/frontend/i18n/trans/it_IT.json | 20 +++-- src/packages/frontend/i18n/trans/ja_JP.json | 20 +++-- src/packages/frontend/i18n/trans/ko_KR.json | 20 +++-- src/packages/frontend/i18n/trans/nl_NL.json | 20 +++-- src/packages/frontend/i18n/trans/pl_PL.json | 20 +++-- src/packages/frontend/i18n/trans/pt_BR.json | 20 +++-- src/packages/frontend/i18n/trans/pt_PT.json | 20 +++-- src/packages/frontend/i18n/trans/ru_RU.json | 20 +++-- src/packages/frontend/i18n/trans/tr_TR.json | 20 +++-- src/packages/frontend/i18n/trans/zh_CN.json | 20 +++-- src/packages/util/consts/ui.ts | 10 ++- 25 files changed, 384 insertions(+), 150 deletions(-) diff --git a/src/packages/frontend/account/account-preferences-appearance.tsx b/src/packages/frontend/account/account-preferences-appearance.tsx index 1794ceada22..8c22dcbd433 100644 --- a/src/packages/frontend/account/account-preferences-appearance.tsx +++ b/src/packages/frontend/account/account-preferences-appearance.tsx @@ -18,7 +18,11 @@ import { LabeledRow, } from "@cocalc/frontend/components"; import { labels } from "@cocalc/frontend/i18n"; -import { DARK_MODE_ICON } from "@cocalc/util/consts/ui"; +import { + A11Y, + ACCESSIBILITY_ICON, + DARK_MODE_ICON, +} from "@cocalc/util/consts/ui"; import { DARK_MODE_DEFAULTS } from "@cocalc/util/db-schema/accounts"; import { COLORS } from "@cocalc/util/theme"; import { @@ -62,6 +66,18 @@ const DARK_MODE_LABELS = defineMessages({ }, }); +const ACCESSIBILITY_MESSAGES = defineMessages({ + title: { + id: "account.appearance.accessibility.title", + defaultMessage: "Accessibility", + }, + enabled: { + id: "account.appearance.accessibility.enabled", + defaultMessage: + "Enable Accessibility Mode: optimize the user interface for accessibility features", + }, +}); + export function AccountPreferencesAppearance() { const intl = useIntl(); const other_settings = useTypedRedux("account", "other_settings"); @@ -107,6 +123,46 @@ export function AccountPreferencesAppearance() { ); } + function getAccessibilitySettings(): { enabled: boolean } { + const settingsStr = other_settings.get(A11Y); + if (!settingsStr) { + return { enabled: false }; + } + try { + return JSON.parse(settingsStr); + } catch { + return { enabled: false }; + } + } + + function setAccessibilitySettings(settings: { enabled: boolean }): void { + on_change(A11Y, JSON.stringify(settings)); + } + + function renderAccessibilityPanel(): ReactElement { + const settings = getAccessibilitySettings(); + return ( + + {" "} + {intl.formatMessage(ACCESSIBILITY_MESSAGES.title)} + + } + > + + setAccessibilitySettings({ ...settings, enabled: e.target.checked }) + } + > + + + + ); + } + function renderDarkModePanel(): ReactElement { const checked = !!other_settings.get("dark_mode"); const config = get_dark_mode_config(other_settings.toJS()); @@ -303,6 +359,7 @@ export function AccountPreferencesAppearance() { mode="appearance" /> {renderDarkModePanel()} + {renderAccessibilityPanel()} ; stripe_customer?: TypedMap<{ subscriptions: { data: Map }; diff --git a/src/packages/frontend/app/context.tsx b/src/packages/frontend/app/context.tsx index f60f097d8d3..44aad83b4da 100644 --- a/src/packages/frontend/app/context.tsx +++ b/src/packages/frontend/app/context.tsx @@ -11,6 +11,7 @@ import { useIntl } from "react-intl"; import { useTypedRedux } from "@cocalc/frontend/app-framework"; import { IntlMessage, isIntlMessage } from "@cocalc/frontend/i18n"; import { ACTIVITY_BAR_LABELS } from "@cocalc/frontend/project/page/activity-bar-consts"; +import { A11Y } from "@cocalc/util/consts/ui"; import { COLORS } from "@cocalc/util/theme"; import { getBaseAntdTheme } from "./antd-base-theme"; import { NARROW_THRESHOLD_PX, PageStyle } from "./top-nav-consts"; @@ -86,6 +87,18 @@ export function useAntdStyleProvider() { const branded = other_settings?.get("antd_brandcolors", false); const compact = other_settings?.get("antd_compact", false); + // Parse accessibility settings + const accessibilityStr = other_settings?.get(A11Y); + let accessibilityEnabled = false; + if (accessibilityStr) { + try { + const accessibilitySettings = JSON.parse(accessibilityStr); + accessibilityEnabled = accessibilitySettings.enabled ?? false; + } catch { + // Ignore parse errors + } + } + const borderStyle = rounded ? undefined : { borderRadius: 0, borderRadiusLG: 0, borderRadiusSM: 0 }; @@ -96,6 +109,16 @@ export function useAntdStyleProvider() { ? undefined : { colorPrimary: COLORS.ANTD_LINK_BLUE }; + // Accessibility: Set all text to pure black for maximum contrast + const accessibilityTextColor = accessibilityEnabled + ? { + colorText: "#000000", + colorTextSecondary: "#000000", + colorTextTertiary: "#000000", + colorTextQuaternary: "#000000", + } + : undefined; + const algorithm = compact ? { algorithm: theme.compactAlgorithm } : undefined; const antdTheme: ThemeConfig = { @@ -106,6 +129,7 @@ export function useAntdStyleProvider() { ...primaryColor, ...borderStyle, ...animationStyle, + ...accessibilityTextColor, }, components: { Button: { diff --git a/src/packages/frontend/app/query-params.ts b/src/packages/frontend/app/query-params.ts index 83599348e8c..11e8e5f3453 100644 --- a/src/packages/frontend/app/query-params.ts +++ b/src/packages/frontend/app/query-params.ts @@ -14,9 +14,10 @@ import { set_local_storage, } from "@cocalc/frontend/misc/local-storage"; import { QueryParams } from "@cocalc/frontend/misc/query-params"; +import { A11Y } from "@cocalc/util/consts/ui"; import { is_valid_uuid_string } from "@cocalc/util/misc"; -export function init_query_params(): void { +function init_fullscreen_mode(): void { const actions = redux.getActions("page"); // enable fullscreen mode upon loading a URL like /app?fullscreen and // additionally kiosk-mode upon /app?fullscreen=kiosk @@ -40,13 +41,19 @@ export function init_query_params(): void { } else if (COCALC_FULLSCREEN === "project") { actions.set_fullscreen("project"); } +} +function init_api_key(): void { + const actions = redux.getActions("page"); const get_api_key_query_value = QueryParams.get("get_api_key"); if (get_api_key_query_value) { actions.set_get_api_key(get_api_key_query_value); actions.set_fullscreen("project"); } +} +function init_session(): void { + const actions = redux.getActions("page"); // configure the session // This makes it so the default session is 'default' and there is no // way to NOT have a session, except via session=, which is treated @@ -79,5 +86,73 @@ export function init_query_params(): void { // not have session in the URL, so we can share url's without infected // other user's session. QueryParams.remove("session"); +} + +function parse_accessibility_param(param: string): boolean | null { + if (param === "true" || param === "on" || param === "1") { + return true; + } + if (param === "false" || param === "off" || param === "0") { + return false; + } + return null; +} + +async function init_accessibility(): Promise { + // Handle accessibility query parameter + // If ?accessibility=true or =on, enable accessibility mode permanently + // If ?accessibility=false or =off, disable it permanently + // This allows sharing URLs that automatically enable accessibility + const accessibilityParam = QueryParams.get(A11Y); + if (accessibilityParam == null) { + return; + } + + const enabled = parse_accessibility_param(accessibilityParam); + QueryParams.remove(A11Y); + if (enabled == null) { + return; + } + + try { + // Wait for account store to be ready before setting accessibility + const store = redux.getStore("account"); + if (!store || typeof store.async_wait !== "function") { + console.warn("Account store not ready"); + return; + } + + await store.async_wait({ + until: () => store.get_account_id() != null, + timeout: 0, + }); + + // Preserve existing accessibility settings + const existingSettingsStr = store.getIn(["other_settings", A11Y]); + let existingSettings = { enabled: false }; + if (existingSettingsStr) { + try { + existingSettings = JSON.parse(existingSettingsStr); + } catch { + // Ignore parse errors, use default + } + } + + // Merge with new enabled value + const settings = { ...existingSettings, enabled }; + const accountActions = redux.getActions("account"); + accountActions.set_other_settings(A11Y, JSON.stringify(settings)); + } catch (err) { + console.warn("Failed to set accessibility from query param:", err); + } +} + +export function init_query_params(): void { + init_fullscreen_mode(); + init_api_key(); + init_session(); + // Run accessibility init in background without blocking + // to avoid delaying other store initializations + init_accessibility(); } diff --git a/src/packages/frontend/embed/index.ts b/src/packages/frontend/embed/index.ts index 2884acae908..55f6e7f6eb2 100644 --- a/src/packages/frontend/embed/index.ts +++ b/src/packages/frontend/embed/index.ts @@ -23,7 +23,6 @@ import { init as initMarkdown } from "../markdown/markdown-input/main"; import { init as initCrashBanner } from "../crash-banner"; import { init as initCustomize } from "../customize"; - // Do not delete this without first looking at https://github.com/sagemathinc/cocalc/issues/5390 // This import of codemirror forces the initial full load of codemirror // as part of the main webpack entry point. diff --git a/src/packages/frontend/entry-point.ts b/src/packages/frontend/entry-point.ts index fe1631e5354..041a84c6b09 100644 --- a/src/packages/frontend/entry-point.ts +++ b/src/packages/frontend/entry-point.ts @@ -15,7 +15,7 @@ import { COCALC_MINIMAL } from "./fullscreen"; // Load/initialize Redux-based react functionality import { redux } from "./app-framework"; -// Systemwide notifications that are broadcast to all users (and set by admins) +// system-wide notifications that are broadcast to all users (and set by admins) import "./system-notifications"; // News about the platform, features, etc. – also shown at https://$DNS/news diff --git a/src/packages/frontend/i18n/trans/ar_EG.json b/src/packages/frontend/i18n/trans/ar_EG.json index 969c3308ec6..6705a075893 100644 --- a/src/packages/frontend/i18n/trans/ar_EG.json +++ b/src/packages/frontend/i18n/trans/ar_EG.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "معلومات الترجمة", "account.account-button.confirm.ok": "نعم، سجل الخروج", "account.account-button.confirm.title": "تسجيل الخروج من حسابك؟", + "account.appearance.accessibility.enabled": "تمكين وضع الوصول: تحسين واجهة المستخدم لميزات الوصول", + "account.appearance.accessibility.title": "إمكانية الوصول", "account.appearance.user_interface.title": "واجهة المستخدم", "account.delete-account.alert.description": "سوف تفقد فوراً الوصول إلى جميع مشاريعك، سيتم إلغاء أي اشتراكات، وستفقد جميع الأرصدة غير المستخدمة. {br} {hr} لحذف حسابك، أدخل أولاً \"{required_text}\" أدناه:", "account.delete-account.alert.message": "هل أنت متأكد أنك تريد حذف حسابك؟", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "إزالة كلما تم حفظ الملف", "account.editor-settings-autosave-interval.label": "فترة الحفظ التلقائي", "account.editor-settings.basic.title": "الإعدادات الأساسية", - "account.editor-settings.color-schemes.label": "نظام ألوان المحرر", "account.editor-settings.color-schemes.panel_title": "نظام ألوان المحرر", "account.editor-settings.font-size.label": "حجم الخط العالمي الافتراضي", "account.editor-settings.indent-size.label": "حجم المسافة البادئة", "account.editor-settings.keyboard-bindings.label": "اختصارات لوحة المفاتيح للمحرر", "account.editor-settings.keyboard.title": "لوحة المفاتيح", - "account.editor-settings.title": "المحرر", "account.editor-settings.x11-keyboard-variant.label": "متغير لوحة المفاتيح (لجهاز سطح المكتب X11)", "account.editor-settings.x11-physical-keyboard.label": "تخطيط لوحة المفاتيح (لـ X11 Desktop)", "account.global-ssh-keys.help": "للدخول إلى مشروع باستخدام SSH، استخدم التالي username@host: [project-id-without-dashes]@ssh.cocalc.com يمكن العثور على معرف المشروع بدون فواصل في جزء إعدادات المشروع المتعلق بمفاتيح SSH. للدخول بين المشاريع باستخدام SSH، استخدم [project-id-without-dashes]@ssh.", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "عرض منقسم في ورقة عمل Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "تبديل تعليق التحديد", "account.other-settings._page_size.label": "عدد الملفات لكل صفحة", - "account.other-settings.browser_performance.title": "متصفح", + "account.other-settings.auto_focus": "إدخال نص التركيز التلقائي: التركيز تلقائيًا على حقول إدخال النص عند ظهورها (مثل مستكشف الملفات، المشاريع، ...)", "account.other-settings.button_tooltips": "إخفاء تلميحات الأزرار: يخفي بعض تلميحات الأزرار (هذا جزئي فقط)", "account.other-settings.confirm_close": "تأكيد الإغلاق: اطلب دائمًا التأكيد قبل إغلاق نافذة المتصفح", - "account.other-settings.content_display.title": "عرض المحتوى", "account.other-settings.default_file_sort.by_name": "الترتيب حسب الاسم", "account.other-settings.default_file_sort.by_time": "الترتيب حسب الوقت", "account.other-settings.default_file_sort.label": "ترتيب الملفات الافتراضي", + "account.other-settings.dim_file_extensions": "تعتيم امتدادات الملفات: تظليل امتدادات الملفات حتى تبرز أسماؤها.", "account.other-settings.file_popovers": "إخفاء النوافذ المنبثقة لعلامات تبويب الملفات: عدم عرض النوافذ المنبثقة فوق علامات تبويب الملفات", "account.other-settings.filename_generator.description": "اختر كيفية توليد أسماء الملفات التلقائية. بشكل خاص، لجعلها فريدة أو لتضمين الوقت الحالي.", "account.other-settings.filename_generator.label": "مولد اسم الملف", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "إعدادات AI", "account.other-settings.markdown_codebar": "تعطيل شريط كود العلامات في جميع مستندات العلامات. يؤدي تحديد هذا إلى إخفاء أزرار التشغيل والنسخ والشرح الإضافية في كتل الكود المسورة.", "account.other-settings.mask_files": "إخفاء الملفات: تظليل الملفات في عارض الملفات التي ربما لا تريد فتحها", - "account.other-settings.messages.title": "رسائل", "account.other-settings.project_popovers": "إخفاء النوافذ المنبثقة لعلامات التبويب في المشروع: لا تعرض النوافذ المنبثقة فوق علامات تبويب المشروع", - "account.other-settings.projects.title": "مشاريع", "account.other-settings.standby_timeout": "مهلة الانتظار", "account.other-settings.symbol_bar_labels": "إظهار تسميات شريط الرموز: إظهار التسميات في شريط رموز محرر الإطار", - "account.other-settings.theme": "السمة", "account.other-settings.theme.antd.animations": "الرسوم المتحركة: تحريك بعض العناصر بإيجاز، مثل الأزرار", "account.other-settings.theme.antd.color_scheme": "مخطط الألوان: استخدم ألوان العلامة التجارية بدلاً من الألوان الافتراضية", "account.other-settings.theme.antd.compact": "تصميم مضغوط: استخدم تصميمًا أكثر إحكامًا", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "إنشاء مستند {docName} باستخدام الذكاء الاصطناعي", "ai-generator.select_llm": "اختر نموذج اللغة", "app.fullscreen-button.tooltip": "وضع الشاشة الكاملة، يركز على المستند أو الصفحة الحالية.", + "app.hotkey.dialog.help_text": "انقر على الإطارات أعلاه • المفتاح 0 يبدل الدردشة • المفاتيح 1-9 تركز على الإطارات • اكتب للبحث • ↑↓ للتنقل • عودة لفتح • ESC للإغلاق", + "app.hotkey.dialog.search_placeholder": "البحث في الملفات والصفحات...", + "app.hotkey.dialog.title": "التنقل السريع", "app.verify-email-banner.edit": "إذا كانت عنوان البريد الإلكتروني خاطئة، يرجى تعديله في إعدادات الحساب.", "app.verify-email-banner.help.text": "من المهم أن يكون لديك عنوان بريد إلكتروني يعمل. نستخدمه لإعادة تعيين كلمة المرور، وإرسال الرسائل، وإشعارات الفوترة، والدعم. يرجى التأكد من صحة بريدك الإلكتروني للبقاء على اطلاع.", "app.verify-email-banner.text": "{sent, select, true {تم إرسال البريد الإلكتروني! يرجى التحقق من صندوق البريد الإلكتروني (وربما الرسائل غير المرغوب فيها) والنقر على رابط التأكيد.} other {يرجى التحقق من عنوان بريدك الإلكتروني وتأكيده:}}", @@ -988,6 +988,7 @@ "labels.config": "الإعدادات", "labels.configuration": "التكوين", "labels.configuration.short": "الإعدادات", + "labels.connected": "متصل", "labels.connecting": "الاتصال", "labels.connection": "الاتصال", "labels.copied": "تم النسخ", @@ -1016,6 +1017,7 @@ "labels.environment": "بيئة", "labels.explorer": "المستكشف", "labels.file_explorer": "مستكشف الملفات", + "labels.file_use_notifications": "إشعارات استخدام الملفات", "labels.files": "الملفات", "labels.folder": "مجلد", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {عام} read_only {للقراءة فقط} other {حفظ}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "إيقاف{short, select, true {} other { المشروع}}…", "labels.project.settings.stop-project.ok": "نعم، أوقف المشروع", "labels.projects": "المشاريع", + "labels.public_paths": "المسارات العامة", "labels.published_files": "ملفات منشورة", "labels.purchases": "المشتريات", "labels.ready": "جاهز", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "اختر عنوانًا. يمكنك تغييره بسهولة لاحقًا!", "projects.create-project.requireLicense": "مطلوب ترخيص لإنشاء مشاريع إضافية.", "projects.filename-search.placeholder": "ابحث عن أسماء الملفات التي قمت بتحريرها...", - "projects.list.no_starred_found": "لم يتم العثور على مشاريع مميزة. استخدم رمز النجمة بجانب عناوين المشاريع لوضع إشارة مرجعية على مشاريعك المفضلة.", "projects.load-all.label": "عرض كل المشاريع...", "projects.operations.clear-filter": "مسح الفلتر", "projects.operations.delete.button": "{deleted, select, true {استعادة الكل} other {حذف الكل}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "التصفية حسب الوسوم...", "projects.table-controls.hidden.label": "مخفي", "projects.table-controls.search.placeholder": "ابحث عن المشاريع...", + "projects.table.keyboard-row-hint": "المشروع {title}. استخدم الأسهم لأعلى ولأسفل للتحرك؛ اضغط على Enter أو Space للفتح.", "projects.table.last-edited": "آخر تعديل", + "projects.table.untitled": "بدون عنوان", "purchases.automatic-payments-warning.description": "المدفوعات التلقائية هي أكثر ملاءمة بكثير، وستوفر لك الوقت، وتضمن عدم إلغاء الاشتراكات عن طريق الخطأ.", "purchases.automatic-payments-warning.title": "الدفع التلقائي ليس مطلوبًا للاشتراك", "purchases.automatic-payments.are-enabled": "المدفوعات التلقائية مفعلة", diff --git a/src/packages/frontend/i18n/trans/de_DE.json b/src/packages/frontend/i18n/trans/de_DE.json index 0933088f29f..87eda977f61 100644 --- a/src/packages/frontend/i18n/trans/de_DE.json +++ b/src/packages/frontend/i18n/trans/de_DE.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Informationen zur Übersetzung", "account.account-button.confirm.ok": "Ja, ausloggen", "account.account-button.confirm.title": "Vom Konto ausloggen?", + "account.appearance.accessibility.enabled": "Barrierefreiheitsmodus aktivieren: die Benutzeroberfläche für Barrierefreiheitsfunktionen optimieren", + "account.appearance.accessibility.title": "Barrierefreiheit", "account.appearance.user_interface.title": "Benutzeroberfläche", "account.delete-account.alert.description": "Sie werden sofort den Zugriff auf alle Ihrer Projekte verlieren, alle Abonnements werden storniert, und alle nicht ausgegebenen Guthaben gehen verloren. {br} {hr} Um IHR KONTO ZU LÖSCHEN, geben Sie zuerst unten \"{required_text}\" ein:", "account.delete-account.alert.message": "Sind Sie sicher, dass Sie IHR KONTO LÖSCHEN möchten?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "entfernen, wenn Datei gespeichert wird", "account.editor-settings-autosave-interval.label": "Automatisches Speicherintervall", "account.editor-settings.basic.title": "Grundeinstellungen", - "account.editor-settings.color-schemes.label": "Editor-Farbschema", "account.editor-settings.color-schemes.panel_title": "Editor Farbschema", "account.editor-settings.font-size.label": "Standard globale Schriftgröße", "account.editor-settings.indent-size.label": "Einzugsgröße", "account.editor-settings.keyboard-bindings.label": "Editor-Tastenkombinationen", "account.editor-settings.keyboard.title": "Tastatur", - "account.editor-settings.title": "Editor", "account.editor-settings.x11-keyboard-variant.label": "Tastaturvariante (für X11-Desktop)", "account.editor-settings.x11-physical-keyboard.label": "Tastaturlayout (für X11-Desktop)", "account.global-ssh-keys.help": "Um über SSH in ein Projekt zu gelangen, verwenden Sie folgendes username@host: [project-id-without-dashes]@ssh.cocalc.com Die Projekt-ID ohne Bindestriche finden Sie im Teil der Projekteinstellungen über SSH-Schlüssel. Um zwischen Projekten über SSH zu wechseln, verwenden Sie [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Geteilte Ansicht im Sage-Arbeitsblatt", "account.keyboard-shortcuts.shortcut.toggle-comment": "Auswahl kommentieren umschalten", "account.other-settings._page_size.label": "Anzahl der Dateien pro Seite", - "account.other-settings.browser_performance.title": "Browser", + "account.other-settings.auto_focus": "Textfelder automatisch fokussieren: Textfelder automatisch fokussieren, wenn sie erscheinen (z.B. Dateiexplorer, Projekte, ...)", "account.other-settings.button_tooltips": "Schaltflächen-Tooltips ausblenden: blendet einige Schaltflächen-Tooltips aus (dies ist nur teilweise)", "account.other-settings.confirm_close": "Schließen bestätigen: immer um Bestätigung bitten, bevor das Browserfenster geschlossen wird", - "account.other-settings.content_display.title": "Inhaltsanzeige", "account.other-settings.default_file_sort.by_name": "Nach Name sortieren", "account.other-settings.default_file_sort.by_time": "Nach Zeit sortieren", "account.other-settings.default_file_sort.label": "Standarddatei sortieren", + "account.other-settings.dim_file_extensions": "Dateierweiterungen abdunkeln: Dateierweiterungen ausgrauen, damit ihre Namen hervorstechen.", "account.other-settings.file_popovers": "Datei-Tab-Popovers ausblenden: Popovers über Dateitabs nicht anzeigen", "account.other-settings.filename_generator.description": "Wählen Sie aus, wie automatisch generierte Dateinamen erstellt werden. Insbesondere, um sie einzigartig zu machen oder die aktuelle Uhrzeit einzubeziehen.", "account.other-settings.filename_generator.label": "Dateinamengenerator", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "KI-Einstellungen", "account.other-settings.markdown_codebar": "Deaktiviere die Markdown-Codeleiste in allen Markdown-Dokumenten. Wenn Sie dies aktivieren, werden die zusätzlichen Ausführen-, Kopieren- und Erklären-Buttons in umrandeten Codeblöcken ausgeblendet.", "account.other-settings.mask_files": "Dateien maskieren: Dateien im Dateibetrachter ausgrauen, die Sie wahrscheinlich nicht öffnen möchten", - "account.other-settings.messages.title": "Nachrichten", "account.other-settings.project_popovers": "Projekt-Tab-Popovers ausblenden: die Popovers über den Projekt-Tabs nicht anzeigen", - "account.other-settings.projects.title": "Projekte", "account.other-settings.standby_timeout": "Standby-Timeout", "account.other-settings.symbol_bar_labels": "Symbolleistensymbol-Beschriftungen anzeigen: Beschriftungen in der Symbolleiste des Rahmeneditors anzeigen", - "account.other-settings.theme": "Thema", "account.other-settings.theme.antd.animations": "Animationen: einige Aspekte kurz animieren, z. B. Schaltflächen", "account.other-settings.theme.antd.color_scheme": "Farbschema: Verwenden Sie Markenfarben anstelle der Standardfarben", "account.other-settings.theme.antd.compact": "Kompaktes Design: ein kompakteres Design verwenden", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "Erzeuge ein {docName} Dokument mit KI", "ai-generator.select_llm": "Sprachmodell auswählen", "app.fullscreen-button.tooltip": "Vollbildmodus, fokussiert auf das aktuelle Dokument oder die aktuelle Seite", + "app.hotkey.dialog.help_text": "Klicke auf Rahmen oben • Taste 0 schaltet Chat um • Tasten 1–9 fokussieren Rahmen • Tippen, um zu suchen • ↑↓ navigieren • Eingabetaste zum Öffnen • ESC zum Schließen", + "app.hotkey.dialog.search_placeholder": "Dateien und Seiten durchsuchen...", + "app.hotkey.dialog.title": "Schnellnavigation", "app.verify-email-banner.edit": "Wenn die E-Mail-Adresse falsch ist, bitte bearbeiten Sie sie in den Kontoeinstellungen.", "app.verify-email-banner.help.text": "Es ist wichtig, eine funktionierende E-Mail-Adresse zu haben. Wir verwenden sie für das Zurücksetzen von Passwörtern, das Senden von Nachrichten, Abrechnungsbenachrichtigungen und Unterstützung. Bitte stellen Sie sicher, dass Ihre E-Mail korrekt ist, um informiert zu bleiben.", "app.verify-email-banner.text": "{sent, select, true {E-Mail gesendet! Bitte überprüfen Sie Ihren E-Mail-Posteingang (und vielleicht Spam) und klicken Sie auf den Bestätigungslink.} other {Bitte überprüfen und verifizieren Sie Ihre E-Mail-Adresse:}}", @@ -988,6 +988,7 @@ "labels.config": "Konfiguration", "labels.configuration": "Konfiguration", "labels.configuration.short": "Konfig", + "labels.connected": "Verbunden", "labels.connecting": "Verbinde", "labels.connection": "Verbindung", "labels.copied": "kopiert", @@ -1016,6 +1017,7 @@ "labels.environment": "Umgebung", "labels.explorer": "Explorer", "labels.file_explorer": "Dateimanager", + "labels.file_use_notifications": "Benachrichtigungen zur Dateiverwendung", "labels.files": "Dateien", "labels.folder": "Ordner", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Öffentlich} read_only {Schreibgeschützt} other {Speichern}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "Stop{short, select, true {} other { Projekt}}…", "labels.project.settings.stop-project.ok": "Ja, stoppe Projekt", "labels.projects": "Projekte", + "labels.public_paths": "Öffentliche Pfade", "labels.published_files": "Veröffentlichte Dateien", "labels.purchases": "Einkäufe", "labels.ready": "Bereit", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "Wählen Sie einen Titel. Sie können ihn später einfach ändern!", "projects.create-project.requireLicense": "Eine Lizenz ist erforderlich, um zusätzliche Projekte zu erstellen.", "projects.filename-search.placeholder": "Suche nach Dateien, die Sie bearbeitet haben...", - "projects.list.no_starred_found": "Keine mit Stern markierten Projekte gefunden. Verwenden Sie das Sternsymbol neben Projekttiteln, um Ihre Lieblingsprojekte zu speichern.", "projects.load-all.label": "Zeige alle Projekte...", "projects.operations.clear-filter": "Filter löschen", "projects.operations.delete.button": "{deleted, select, true {Alle wiederherstellen} other {Alle löschen}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "Nach Hashtags filtern...", "projects.table-controls.hidden.label": "Versteckt", "projects.table-controls.search.placeholder": "Projekte durchsuchen...", + "projects.table.keyboard-row-hint": "Projekt {title}. Verwenden Sie die Pfeiltasten nach oben und unten, um sich zu bewegen; drücken Sie Enter oder Leertaste, um zu öffnen.", "projects.table.last-edited": "Zuletzt bearbeitet", + "projects.table.untitled": "Unbenannt", "purchases.automatic-payments-warning.description": "Automatische Zahlungen sind viel bequemer, ersparen Ihnen Zeit und stellen sicher, dass Abonnements nicht versehentlich gekündigt werden.", "purchases.automatic-payments-warning.title": "Automatische Zahlungen sind NICHT erforderlich, um ein Abonnement zu haben", "purchases.automatic-payments.are-enabled": "Automatische Zahlungen sind aktiviert", diff --git a/src/packages/frontend/i18n/trans/es_ES.json b/src/packages/frontend/i18n/trans/es_ES.json index ff03e994cdc..8c35287676f 100644 --- a/src/packages/frontend/i18n/trans/es_ES.json +++ b/src/packages/frontend/i18n/trans/es_ES.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Información de traducción", "account.account-button.confirm.ok": "Sí, cerrar sesión", "account.account-button.confirm.title": "¿Cerrar sesión de tu cuenta?", + "account.appearance.accessibility.enabled": "Activar el Modo de Accesibilidad: optimizar la interfaz de usuario para características de accesibilidad", + "account.appearance.accessibility.title": "Accesibilidad", "account.appearance.user_interface.title": "Interfaz de usuario", "account.delete-account.alert.description": "Perderá inmediatamente acceso a todos sus proyectos, cualquier suscripción será cancelada y todo el crédito no utilizado se perderá. {br} {hr} Para ELIMINAR SU CUENTA, primero ingrese \"{required_text}\" a continuación:", "account.delete-account.alert.message": "¿Está seguro de que desea ELIMINAR SU CUENTA?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "eliminar siempre que se guarde el archivo", "account.editor-settings-autosave-interval.label": "Intervalo de guardado automático", "account.editor-settings.basic.title": "Configuración básica", - "account.editor-settings.color-schemes.label": "Esquema de color del editor", "account.editor-settings.color-schemes.panel_title": "Esquema de Color del Editor", "account.editor-settings.font-size.label": "Tamaño de fuente global predeterminado", "account.editor-settings.indent-size.label": "Tamaño de sangría", "account.editor-settings.keyboard-bindings.label": "Asignaciones de teclado del editor", "account.editor-settings.keyboard.title": "Teclado", - "account.editor-settings.title": "Editor", "account.editor-settings.x11-keyboard-variant.label": "Variante de teclado (para escritorio X11)", "account.editor-settings.x11-physical-keyboard.label": "Distribución del teclado (para el escritorio X11)", "account.global-ssh-keys.help": "Para hacer SSH en un proyecto, usa el siguiente username@host: [project-id-without-dashes]@ssh.cocalc.com El ID del proyecto sin guiones se puede encontrar en la parte de configuración del proyecto sobre claves SSH. Para hacer SSH entre proyectos, usa [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Vista dividida en hoja de trabajo Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Alternar selección de comentarios", "account.other-settings._page_size.label": "Número de archivos por página", - "account.other-settings.browser_performance.title": "Navegador", + "account.other-settings.auto_focus": "Enfoque Automático en la Entrada de Texto: enfocar automáticamente los campos de entrada de texto cuando aparezcan (por ejemplo, explorador de archivos, proyectos, ...)", "account.other-settings.button_tooltips": "Ocultar descripciones emergentes de botones: oculta algunas descripciones emergentes de botones (esto es solo parcial)", "account.other-settings.confirm_close": "Confirmar cierre: siempre pedir confirmación antes de cerrar la ventana del navegador", - "account.other-settings.content_display.title": "Visualización de contenido", "account.other-settings.default_file_sort.by_name": "Ordenar por nombre", "account.other-settings.default_file_sort.by_time": "Ordenar por tiempo", "account.other-settings.default_file_sort.label": "Orden predeterminado de archivos", + "account.other-settings.dim_file_extensions": "Atenuar las extensiones de archivo: atenuar las extensiones de archivo para que sus nombres destaquen.", "account.other-settings.file_popovers": "Ocultar Popovers de Pestañas de Archivos: no mostrar los popovers sobre pestañas de archivos", "account.other-settings.filename_generator.description": "Seleccione cómo se generan los nombres de archivo automáticamente. En particular, para hacerlos únicos o incluir la hora actual.", "account.other-settings.filename_generator.label": "Generador de nombres de archivo", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "Configuración de IA", "account.other-settings.markdown_codebar": "Desactivar la barra de código markdown en todos los documentos markdown. Al marcar esto, se ocultan los botones adicionales de ejecutar, copiar y explicar en los bloques de código delimitados.", "account.other-settings.mask_files": "Archivos ocultos: atenuar archivos en el visor de archivos que probablemente no desees abrir", - "account.other-settings.messages.title": "Mensajes", "account.other-settings.project_popovers": "Ocultar los Popovers de Pestañas de Proyecto: no mostrar los popovers sobre las pestañas del proyecto", - "account.other-settings.projects.title": "Proyectos", "account.other-settings.standby_timeout": "Tiempo de espera en espera", "account.other-settings.symbol_bar_labels": "Mostrar etiquetas de la barra de símbolos: mostrar etiquetas en la barra de símbolos del editor de marcos", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animaciones: animar brevemente algunos aspectos, p. ej., botones", "account.other-settings.theme.antd.color_scheme": "Esquema de Color: usar colores de marca en lugar de colores predeterminados", "account.other-settings.theme.antd.compact": "Diseño Compacto: usar un diseño más compacto", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "Generar un documento {docName} usando IA", "ai-generator.select_llm": "Seleccionar modelo de idioma", "app.fullscreen-button.tooltip": "Modo de pantalla completa, centrado en el documento o página actual.", + "app.hotkey.dialog.help_text": "Haz clic en los marcos de arriba • La tecla 0 alterna el chat • Las teclas 1–9 enfocan los marcos • Escribe para buscar • ↑↓ navega • Return para abrir • ESC para cerrar", + "app.hotkey.dialog.search_placeholder": "Buscar archivos y páginas...", + "app.hotkey.dialog.title": "Navegación Rápida", "app.verify-email-banner.edit": "Si la dirección de correo electrónico es incorrecta, por favor edítala en la configuración de la cuenta.", "app.verify-email-banner.help.text": "Es importante tener una dirección de correo electrónico funcional. La usamos para restablecer contraseñas, enviar mensajes, notificaciones de facturación y soporte. Por favor, asegúrate de que tu correo electrónico sea correcto para mantenerte informado.", "app.verify-email-banner.text": "{sent, select, true {¡Correo electrónico enviado! Por favor, revisa tu bandeja de entrada de correo (y quizá el spam) y haz clic en el enlace de confirmación.} other {Por favor, revisa y verifica tu dirección de correo electrónico:}}", @@ -988,6 +988,7 @@ "labels.config": "Configurar", "labels.configuration": "Configuración", "labels.configuration.short": "Configurar", + "labels.connected": "Conectado", "labels.connecting": "Conectando", "labels.connection": "Conexión", "labels.copied": "copiado", @@ -1016,6 +1017,7 @@ "labels.environment": "Entorno", "labels.explorer": "Explorador", "labels.file_explorer": "Explorador de Archivos", + "labels.file_use_notifications": "Notificaciones de uso de archivos", "labels.files": "Archivos", "labels.folder": "carpeta", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Público} read_only {Solo lectura} other {Guardar}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "Detener{short, select, true {} other { Proyecto}}…", "labels.project.settings.stop-project.ok": "Sí, detener proyecto", "labels.projects": "Proyectos", + "labels.public_paths": "Rutas Públicas", "labels.published_files": "Archivos Publicados", "labels.purchases": "Compras", "labels.ready": "Listo", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "Elige un título. ¡Puedes cambiarlo fácilmente más tarde!", "projects.create-project.requireLicense": "Se requiere una licencia para crear proyectos adicionales.", "projects.filename-search.placeholder": "Buscar nombres de archivos que editaste", - "projects.list.no_starred_found": "No se encontraron proyectos destacados. Usa el icono de estrella junto a los títulos de los proyectos para marcar tus proyectos favoritos.", "projects.load-all.label": "Mostrar todos los proyectos...", "projects.operations.clear-filter": "Borrar filtro", "projects.operations.delete.button": "{deleted, select, true {Restaurar todo} other {Eliminar todo}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "Filtrar por hashtags...", "projects.table-controls.hidden.label": "Oculto", "projects.table-controls.search.placeholder": "Buscar proyectos...", + "projects.table.keyboard-row-hint": "Proyecto {title}. Usa las flechas Arriba y Abajo para moverte; presiona Enter o Espacio para abrir.", "projects.table.last-edited": "Última edición", + "projects.table.untitled": "Sin título", "purchases.automatic-payments-warning.description": "Los pagos automáticos son mucho más convenientes, te ahorrarán tiempo y asegurarán que las suscripciones no se cancelen por accidente.", "purchases.automatic-payments-warning.title": "Los pagos automáticos NO son necesarios para tener una suscripción", "purchases.automatic-payments.are-enabled": "Los pagos automáticos están habilitados", diff --git a/src/packages/frontend/i18n/trans/es_PV.json b/src/packages/frontend/i18n/trans/es_PV.json index dc59eb628b5..19516cc6672 100644 --- a/src/packages/frontend/i18n/trans/es_PV.json +++ b/src/packages/frontend/i18n/trans/es_PV.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Itzulpen Informazioa", "account.account-button.confirm.ok": "Bai, saioa amaitu", "account.account-button.confirm.title": "Saioa amaitu zure kontuan?", + "account.appearance.accessibility.enabled": "Gaitu Irisgarritasun Modua: optimizatu erabiltzailearen interfazea irisgarritasun ezaugarrientzat", + "account.appearance.accessibility.title": "Irisgarritasuna", "account.appearance.user_interface.title": "Erabiltzaile Interfazea", "account.delete-account.alert.description": "Berehala galduko duzu zure proiektu guztietara sartzeko aukera, harpidetzak bertan behera utziko dira, eta gastatu gabeko kreditu guztia galduko da. {br} {hr} ZURE KONTUA EZABATZEKO, lehenik sartu behean \"{required_text}\":", "account.delete-account.alert.message": "Ziur zaude ZURE KONTUA EZABATU nahi duzula?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "kendu, fitxategia gordetzen den bakoitzean", "account.editor-settings-autosave-interval.label": "Autogordetze tartea", "account.editor-settings.basic.title": "Oinarrizko Ezarpenak", - "account.editor-settings.color-schemes.label": "Editorearen kolore eskema", "account.editor-settings.color-schemes.panel_title": "Editorearen Kolore Eskema", "account.editor-settings.font-size.label": "Lehenetsitako letra-tamaina globala", "account.editor-settings.indent-size.label": "Indentazio tamaina", "account.editor-settings.keyboard-bindings.label": "Editorearen teklatu loturak", "account.editor-settings.keyboard.title": "Teklatua", - "account.editor-settings.title": "Editorearen Ezarpenak", "account.editor-settings.x11-keyboard-variant.label": "Teklatuaren aldaera (X11 Mahaigainarentzat)", "account.editor-settings.x11-physical-keyboard.label": "Teklatuaren diseinua (X11 Mahaigainarentzat)", "account.global-ssh-keys.help": "SSH bidez proiektu batean sartzeko, erabili honako hau username@host: [proiektuaren-id-gidoi-barik]@ssh.cocalc.com Proiektuaren IDa gidoirik gabe SSH gakoen inguruko proiektuaren ezarpenetan aurki daiteke. Proiektuen artean SSH egiteko, erabili [proiektuaren-id-gidoi-barik]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Bista zatitua Sageko kalkulu orrian", "account.keyboard-shortcuts.shortcut.toggle-comment": "Gaitu hautaketa komentatzea", "account.other-settings._page_size.label": "Fitxategi kopurua orrialde bakoitzeko", - "account.other-settings.browser_performance.title": "Nabigatzailea", + "account.other-settings.auto_focus": "Testu Sarrera Automatikoki Fokatzea: automatikoki fokatu testu sarrera eremuak agertzen direnean (adibidez, fitxategi arakatzailea, proiektuak, ...)", "account.other-settings.button_tooltips": "Ezkutatu Botoiaren Tresna-aholkuak: botoi batzuen tresna-aholkuak ezkutatzen ditu (hau partziala da)", "account.other-settings.confirm_close": "Berrestu Ixtea: beti eskatu berrespena nabigatzailearen leihoa itxi aurretik", - "account.other-settings.content_display.title": "Edukien Erakusketa", "account.other-settings.default_file_sort.by_name": "Izenez ordenatu", "account.other-settings.default_file_sort.by_time": "Ordenatu denboraren arabera", "account.other-settings.default_file_sort.label": "Fitxategi ordenazio lehenetsia", + "account.other-settings.dim_file_extensions": "Fitxategiaren luzapenak ilundu: gristatu fitxategiaren luzapenak, haien izenak nabarmentzeko.", "account.other-settings.file_popovers": "Ezkutatu Fitxategi Fitxen Popoverrak: ez erakutsi popoverrak fitxategi fitxetan", "account.other-settings.filename_generator.description": "Hautatu automatikoki sortutako fitxategi-izenak nola sortzen diren. Bereziki, bakarrak bihurtzeko edo egungo denbora sartzeko.", "account.other-settings.filename_generator.label": "Fitxategi-izenen sortzailea", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "AI Ezarpenak", "account.other-settings.markdown_codebar": "Desgaitu markdown kode barra markdown dokumentu guztietan. Hau markatzeak \"exekutatu\", \"kopiatu\" eta \"azaldu\" botoi gehigarriak ezkutatzen ditu.", "account.other-settings.mask_files": "Fitxategiak Maskaratu: ziurrenik ireki nahi ez dituzun fitxategiak esploratzailean grisez apaldu", - "account.other-settings.messages.title": "Mezuak", "account.other-settings.project_popovers": "Proiektuaren fitxa popoverrak ezkutatu: ez erakutsi popoverrak proiektuaren fitxen gainean", - "account.other-settings.projects.title": "Proiektuak", "account.other-settings.standby_timeout": "Itxaronaldi epea", "account.other-settings.symbol_bar_labels": "Ikusi Sinbolo Barra Etiketak: erakutsi etiketak marko editorearen sinbolo barran", - "account.other-settings.theme": "Gaia", "account.other-settings.theme.antd.animations": "Animazioak: gauza batzuk labur animatu, adibidez, botoiak", "account.other-settings.theme.antd.color_scheme": "Kolore Eskema: erabili marka koloreak lehenetsitako koloreen ordez", "account.other-settings.theme.antd.compact": "Diseinu Trinkoa: Diseinu trinkoagoa erabili", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "Sortu {docName} Dokumentua AI erabiliz", "ai-generator.select_llm": "Aukeratu hizkuntza eredua", "app.fullscreen-button.tooltip": "Pantaila osoa modua, uneko dokumentuan edo orrian zentratuta.", + "app.hotkey.dialog.help_text": "Egin klik goiko markoetan • Tekla 0k txata aktibatu/desaktibatzen du • Teklak 1–9 markoetan zentratzen dira • Idatzi bilatzeko • ↑↓ nabigatzeko • Return irekitzeko • ESC ixteko", + "app.hotkey.dialog.search_placeholder": "Bilatu fitxategiak eta orriak...", + "app.hotkey.dialog.title": "Nabigazio Bizkorra", "app.verify-email-banner.edit": "Email helbidea ez bada zuzena editatu egizu kontu ezarpenetan.", "app.verify-email-banner.help.text": "Garrantzitsua da funtzionatzen duen helbide elektroniko bat izatea. Pasahitzak berrezartzeko, mezuak bidaltzeko, fakturazio jakinarazpenak egiteko eta laguntza emateko bidaltzen dugu. Mesedez, ziurtatu zure helbide elektronikoa zuzena dela informazioa jasotzeko.", "app.verify-email-banner.text": "{sent, select, true {Emaila Bidalita! Mesedez, begiratu zure email sarrera-ontzia (eta agian spam karpeta) eta klik egin berrespen estekan.} other {Mesedez, egiaztatu eta baieztatu zure helbide elektronikoa:}}", @@ -988,6 +988,7 @@ "labels.config": "Konfigurazio", "labels.configuration": "Konfigurazioa", "labels.configuration.short": "Konfigurazio", + "labels.connected": "Konektatuta", "labels.connecting": "Konexioa egiten", "labels.connection": "Konexioa", "labels.copied": "kopiatuta", @@ -1016,6 +1017,7 @@ "labels.environment": "Ingurunea", "labels.explorer": "Arakatzailea", "labels.file_explorer": "Fitxategi Arakatzailea", + "labels.file_use_notifications": "Fitxategi Erabilera Jakinarazpenak", "labels.files": "Fitxategiak", "labels.folder": "karpeta", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Publikoa} read_only {Iragargaitza} other {Gorde}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "Gelditu{short, select, true {} other { Proiektua}}…", "labels.project.settings.stop-project.ok": "Bai, gelditu proiektua", "labels.projects": "Proiektuak", + "labels.public_paths": "Bide Publikoak", "labels.published_files": "Argitaratua", "labels.purchases": "Erosketak", "labels.ready": "Prest", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "Hautatu izenburua. Erraz alda dezakezu geroago!", "projects.create-project.requireLicense": "Proiektu gehigarriak sortzeko lizentzia behar da.", "projects.filename-search.placeholder": "Bilatu editatu dituzun fitxategi izenak...", - "projects.list.no_starred_found": "Ez da izarrez markatutako proiekturik aurkitu. Erabili izar ikonoa proiektuen izenburuen ondoan zure proiektu gogokoenak laster-markan jartzeko.", "projects.load-all.label": "Erakutsi proiektu guztiak...", "projects.operations.clear-filter": "Garbitu Iragazkia", "projects.operations.delete.button": "{deleted, select, true {Berreskuratu Guztiak} other {Ezabatu Guztiak}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "Iragazi etiketen arabera...", "projects.table-controls.hidden.label": "Ezkutua", "projects.table-controls.search.placeholder": "Proiektuak bilatu...", + "projects.table.keyboard-row-hint": "Proiektua {title}. Erabili gora eta behera gezien tekla mugitzeko; sakatu Enter edo Space irekitzeko.", "projects.table.last-edited": "Azken aldiz editatua", + "projects.table.untitled": "Izenbururik gabe", "purchases.automatic-payments-warning.description": "Ordainketa automatikoak askoz erosoagoak dira, denbora aurreztuko dizute eta harpidetzak ez direla istripuz bertan behera geldituko ziurtatuko dute.", "purchases.automatic-payments-warning.title": "Ez da beharrezkoa ordainketa automatikoak izatea harpidetza izateko", "purchases.automatic-payments.are-enabled": "Ordainketa Automatikoak Gaituta Daude", diff --git a/src/packages/frontend/i18n/trans/fr_FR.json b/src/packages/frontend/i18n/trans/fr_FR.json index 29624a48bb1..74cd3a3af72 100644 --- a/src/packages/frontend/i18n/trans/fr_FR.json +++ b/src/packages/frontend/i18n/trans/fr_FR.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Informations de traduction", "account.account-button.confirm.ok": "Oui, déconnexion", "account.account-button.confirm.title": "Se déconnecter de votre compte ?", + "account.appearance.accessibility.enabled": "Activer le mode Accessibilité : optimiser l'interface utilisateur pour les fonctionnalités d'accessibilité", + "account.appearance.accessibility.title": "Accessibilité", "account.appearance.user_interface.title": "Interface utilisateur", "account.delete-account.alert.description": "Vous perdrez immédiatement l'accès à tous vos projets, tous les abonnements seront annulés, et tout crédit non dépensé sera perdu. {br} {hr} Pour SUPPRIMER VOTRE COMPTE, entrez d'abord \"{required_text}\" ci-dessous :", "account.delete-account.alert.message": "Êtes-vous sûr de vouloir SUPPRIMER VOTRE COMPTE ?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "supprimer chaque fois que le fichier est enregistré", "account.editor-settings-autosave-interval.label": "Intervalle de sauvegarde automatique", "account.editor-settings.basic.title": "Paramètres de base", - "account.editor-settings.color-schemes.label": "Schéma de couleurs de l'éditeur", "account.editor-settings.color-schemes.panel_title": "Schéma de couleurs de l'éditeur", "account.editor-settings.font-size.label": "Taille de police globale par défaut", "account.editor-settings.indent-size.label": "Taille de l'indentation", "account.editor-settings.keyboard-bindings.label": "Raccourcis clavier de l'éditeur", "account.editor-settings.keyboard.title": "Clavier", - "account.editor-settings.title": "Éditeur", "account.editor-settings.x11-keyboard-variant.label": "Variante de clavier (pour le bureau X11)", "account.editor-settings.x11-physical-keyboard.label": "Disposition du clavier (pour le bureau X11)", "account.global-ssh-keys.help": "Pour SSH dans un projet, utilisez le format suivant username@host: [project-id-without-dashes]@ssh.cocalc.com L'identifiant du projet sans tirets se trouve dans la section des paramètres du projet concernant les clés SSH. Pour SSH entre projets, utilisez [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Vue partagée dans la feuille de calcul Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Basculer le commentaire de la sélection", "account.other-settings._page_size.label": "Nombre de fichiers par page", - "account.other-settings.browser_performance.title": "Navigateur", + "account.other-settings.auto_focus": "Texte d'entrée de mise au point automatique : met automatiquement au point les champs de saisie de texte lorsqu'ils apparaissent (par exemple, explorateur de fichiers, projets, ...)", "account.other-settings.button_tooltips": "Masquer les info-bulles des boutons : masque certaines info-bulles des boutons (ceci est seulement partiel)", "account.other-settings.confirm_close": "Confirmer la fermeture : toujours demander confirmation avant de fermer la fenêtre du navigateur", - "account.other-settings.content_display.title": "Affichage du contenu", "account.other-settings.default_file_sort.by_name": "Trier par nom", "account.other-settings.default_file_sort.by_time": "Trier par temps", "account.other-settings.default_file_sort.label": "Tri par défaut des fichiers", + "account.other-settings.dim_file_extensions": "Extensions de fichier en gris : griser les extensions de fichier pour que leurs noms se démarquent.", "account.other-settings.file_popovers": "Cacher les info-bulles des onglets de fichiers : ne pas afficher les info-bulles sur les onglets de fichiers", "account.other-settings.filename_generator.description": "Sélectionnez comment les noms de fichiers générés automatiquement sont créés. En particulier, pour les rendre uniques ou inclure l'heure actuelle.", "account.other-settings.filename_generator.label": "Générateur de nom de fichier", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "Paramètres AI", "account.other-settings.markdown_codebar": "Désactiver la barre de code markdown dans tous les documents markdown. Cocher ceci masque les boutons supplémentaires d'exécution, de copie et d'explication dans les blocs de code délimités.", "account.other-settings.mask_files": "Masquer les fichiers : griser les fichiers dans le visualiseur de fichiers que vous ne souhaitez probablement pas ouvrir", - "account.other-settings.messages.title": "Messages", "account.other-settings.project_popovers": "Masquer les popovers des onglets de projet : ne pas afficher les popovers au-dessus des onglets de projet", - "account.other-settings.projects.title": "Projets", "account.other-settings.standby_timeout": "Délai d'attente en veille", "account.other-settings.symbol_bar_labels": "Afficher les étiquettes de la barre de symboles : afficher les étiquettes dans la barre de symboles de l'éditeur de cadre", - "account.other-settings.theme": "Thème", "account.other-settings.theme.antd.animations": "Animations : animer brièvement certains aspects, par exemple les boutons", "account.other-settings.theme.antd.color_scheme": "Schéma de couleurs : utiliser les couleurs de marque au lieu des couleurs par défaut", "account.other-settings.theme.antd.compact": "Conception compacte: utiliser une conception plus compacte", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "Générer un document {docName} en utilisant l'IA", "ai-generator.select_llm": "Sélectionner le modèle de langue", "app.fullscreen-button.tooltip": "Mode plein écran, concentré sur le document ou la page actuelle.", + "app.hotkey.dialog.help_text": "Cliquez sur les cadres ci-dessus • Touche 0 pour basculer le chat • Touches 1–9 pour focaliser les cadres • Tapez pour rechercher • ↑↓ naviguer • Entrée pour ouvrir • Échap pour fermer", + "app.hotkey.dialog.search_placeholder": "Rechercher des fichiers et des pages...", + "app.hotkey.dialog.title": "Navigation rapide", "app.verify-email-banner.edit": "Si l'adresse e-mail est incorrecte, veuillez la modifier dans les paramètres du compte.", "app.verify-email-banner.help.text": "Il est important d'avoir une adresse e-mail fonctionnelle. Nous l'utilisons pour les réinitialisations de mot de passe, l'envoi de messages, les notifications de facturation et le support. Veuillez vous assurer que votre e-mail est correct pour rester informé.", "app.verify-email-banner.text": "{sent, select, true {Email envoyé ! Veuillez vérifier votre boîte de réception (et peut-être les spams) et cliquez sur le lien de confirmation.} other {Veuillez vérifier et confirmer votre adresse e-mail :}}", @@ -988,6 +988,7 @@ "labels.config": "Config", "labels.configuration": "Configuration", "labels.configuration.short": "Config", + "labels.connected": "Connecté", "labels.connecting": "Connexion", "labels.connection": "Connexion", "labels.copied": "copié", @@ -1016,6 +1017,7 @@ "labels.environment": "Environnement", "labels.explorer": "Explorateur", "labels.file_explorer": "Explorateur de fichiers", + "labels.file_use_notifications": "Notifications d'utilisation de fichier", "labels.files": "Fichiers", "labels.folder": "dossier", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Public} read_only {Lecture seule} other {Enregistrer}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "Arrêter{short, select, true {} other { Projet}}…", "labels.project.settings.stop-project.ok": "Oui, arrêter le projet", "labels.projects": "Projets", + "labels.public_paths": "Chemins Publics", "labels.published_files": "Fichiers publiés", "labels.purchases": "Achats", "labels.ready": "Prêt", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "Choisissez un titre. Vous pouvez facilement le changer plus tard !", "projects.create-project.requireLicense": "Une licence est requise pour créer des projets supplémentaires.", "projects.filename-search.placeholder": "Rechercher les noms de fichiers que vous avez modifiés...", - "projects.list.no_starred_found": "Aucun projet étoilé trouvé. Utilisez l'icône en forme d'étoile à côté des titres de projet pour ajouter vos projets favoris en signet.", "projects.load-all.label": "Afficher tous les projets...", "projects.operations.clear-filter": "Effacer le filtre", "projects.operations.delete.button": "{deleted, select, true {Restaurer tout} other {Tout supprimer}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "Filtrer par hashtags...", "projects.table-controls.hidden.label": "Caché", "projects.table-controls.search.placeholder": "Rechercher des projets...", + "projects.table.keyboard-row-hint": "Projet {title}. Utilisez les flèches Haut et Bas pour vous déplacer ; appuyez sur Entrée ou Espace pour ouvrir.", "projects.table.last-edited": "Dernière modification", + "projects.table.untitled": "Sans titre", "purchases.automatic-payments-warning.description": "Les paiements automatiques sont beaucoup plus pratiques, vous feront gagner du temps, et assurent que les abonnements ne soient pas annulés par accident.", "purchases.automatic-payments-warning.title": "Les paiements automatiques NE sont PAS nécessaires pour avoir un abonnement", "purchases.automatic-payments.are-enabled": "Les paiements automatiques sont activés", diff --git a/src/packages/frontend/i18n/trans/he_IL.json b/src/packages/frontend/i18n/trans/he_IL.json index fe22315927b..40fa724e9bc 100644 --- a/src/packages/frontend/i18n/trans/he_IL.json +++ b/src/packages/frontend/i18n/trans/he_IL.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "מידע על תרגום", "account.account-button.confirm.ok": "כן, התנתק", "account.account-button.confirm.title": "לצאת מהחשבון שלך?", + "account.appearance.accessibility.enabled": "הפעל מצב נגישות: מיטוב ממשק המשתמש עבור תכונות נגישות", + "account.appearance.accessibility.title": "נגישות", "account.appearance.user_interface.title": "ממשק משתמש", "account.delete-account.alert.description": "תאבד מיד גישה לכל הפרויקטים שלך, כל המנויים יבוטלו, וכל הקרדיט שלא נוצל יאבד. {br} {hr} כדי למחוק את החשבון שלך, ראשית הכנס את \"{required_text}\" למטה:", "account.delete-account.alert.message": "האם אתה בטוח שברצונך למחוק את החשבון שלך?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "הסר בכל פעם שקובץ נשמר", "account.editor-settings-autosave-interval.label": "מרווח שמירה אוטומטית", "account.editor-settings.basic.title": "הגדרות בסיסיות", - "account.editor-settings.color-schemes.label": "ערכת צבעים של העורך", "account.editor-settings.color-schemes.panel_title": "ערכת צבעים לעורך", "account.editor-settings.font-size.label": "גודל גופן גלובלי ברירת מחדל", "account.editor-settings.indent-size.label": "גודל הזחה", "account.editor-settings.keyboard-bindings.label": "קיצורי מקלדת לעורך", "account.editor-settings.keyboard.title": "מקלדת", - "account.editor-settings.title": "עורך", "account.editor-settings.x11-keyboard-variant.label": "גרסת מקלדת (לשולחן עבודה X11)", "account.editor-settings.x11-physical-keyboard.label": "פריסת מקלדת (לשולחן העבודה X11)", "account.global-ssh-keys.help": "כדי להתחבר בפרוטוקול SSH לפרויקט, השתמש ב-username@host: [project-id-without-dashes]@ssh.cocalc.com ניתן למצוא את מזהה הפרויקט ללא מקפים בחלק של הגדרות הפרויקט לגבי מפתחות SSH. כדי להתחבר בפרוטוקול SSH בין פרויקטים, השתמש ב-[project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "תצוגה מפוצלת בגליון העבודה של Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "החלפת מצב הערות בחירה", "account.other-settings._page_size.label": "מספר הקבצים לכל עמוד", - "account.other-settings.browser_performance.title": "דפדפן", + "account.other-settings.auto_focus": "מיקוד אוטומטי על שדה טקסט: מיקוד אוטומטי על שדות טקסט כשאלה מופיעים (לדוגמה, סייר קבצים, פרויקטים, ...)", "account.other-settings.button_tooltips": "הסתרת תיאורי כלים של כפתורים: מסתיר חלק מתיאורי הכלים של הכפתורים (זה חלקי בלבד)", "account.other-settings.confirm_close": "אישור סגירה: תמיד לבקש אישור לפני סגירת חלון הדפדפן", - "account.other-settings.content_display.title": "תצוגת תוכן", "account.other-settings.default_file_sort.by_name": "מיין לפי שם", "account.other-settings.default_file_sort.by_time": "מיין לפי זמן", "account.other-settings.default_file_sort.label": "מיון קבצים ברירת מחדל", + "account.other-settings.dim_file_extensions": "סיומות קבצים בגוון אפור: צביעת סיומות קבצים באפור כדי ששמותיהם יבלטו.", "account.other-settings.file_popovers": "הסתרת פופאברים בכרטיסיות קבצים: אל תציג את הפופאברים מעל כרטיסיות הקבצים", "account.other-settings.filename_generator.description": "בחר כיצד שמות קבצים הנוצרים באופן אוטומטי נוצרים. במיוחד, כדי להפוך אותם לייחודיים או לכלול את הזמן הנוכחי.", "account.other-settings.filename_generator.label": "מחולל שמות קבצים", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "הגדרות AI", "account.other-settings.markdown_codebar": "השבת את סרגל הקוד של Markdown בכל מסמכי ה-Markdown. סימון זה מסתיר את כפתורי הריצה, ההעתקה וההסבר הנוספים בקטעי קוד מגודרים.", "account.other-settings.mask_files": "הסתר קבצים: האפור קבצים בצופה הקבצים שבדרך כלל לא תרצה לפתוח", - "account.other-settings.messages.title": "הודעות", "account.other-settings.project_popovers": "הסתרת פופאברים בכרטיסיות הפרויקט: אל תציג את הפופאברים מעל כרטיסיות הפרויקט", - "account.other-settings.projects.title": "פרויקטים", "account.other-settings.standby_timeout": "פסק זמן במצב המתנה", "account.other-settings.symbol_bar_labels": "הצג תוויות סרגל סמלים: הצג תוויות בסרגל הסמלים בעורך המסגרת", - "account.other-settings.theme": "ערכת נושא", "account.other-settings.theme.antd.animations": "אנימציות: הנפשה קצרה של חלקים מסוימים, למשל כפתורים", "account.other-settings.theme.antd.color_scheme": "ערכת צבעים: השתמש בצבעי המותג במקום בצבעי ברירת המחדל", "account.other-settings.theme.antd.compact": "עיצוב קומפקטי: השתמש בעיצוב יותר קומפקטי", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "צור מסמך {docName} באמצעות AI", "ai-generator.select_llm": "בחר מודל שפה", "app.fullscreen-button.tooltip": "מצב מסך מלא, ממוקד במסמך או בעמוד הנוכחי", + "app.hotkey.dialog.help_text": "לחץ על המסגרות למעלה • מקש 0 מחליף צ'אט • מקשים 1–9 ממקדים מסגרות • הקלד לחיפוש • ↑↓ ניווט • Enter לפתיחה • ESC לסגירה", + "app.hotkey.dialog.search_placeholder": "חפש קבצים ודפים...", + "app.hotkey.dialog.title": "ניווט מהיר", "app.verify-email-banner.edit": "אם כתובת הדוא\"ל שגויה, אנא ערוך אותה בהגדרות החשבון.", "app.verify-email-banner.help.text": "חשוב שתהיה כתובת דוא\"ל פעילה. אנו משתמשים בה לאיפוס סיסמאות, שליחת הודעות, התראות חיוב ותמיכה. אנא ודא שכתובת הדוא\"ל שלך נכונה כדי להישאר מעודכן.", "app.verify-email-banner.text": "{sent, select, true {האימייל נשלח! בדוק את תיבת הדואר הנכנס שלך (ואולי דואר זבל) ולחץ על קישור האישור.} other {אנא בדוק ואמת את כתובת האימייל שלך:}}", @@ -988,6 +988,7 @@ "labels.config": "הגדרות", "labels.configuration": "תצורה", "labels.configuration.short": "הגדרות", + "labels.connected": "מחובר", "labels.connecting": "מתחבר", "labels.connection": "חיבור", "labels.copied": "הועתק", @@ -1016,6 +1017,7 @@ "labels.environment": "סביבה", "labels.explorer": "סייר", "labels.file_explorer": "סייר קבצים", + "labels.file_use_notifications": "התראות שימוש בקבצים", "labels.files": "קבצים", "labels.folder": "תיקייה", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {ציבורי} read_only {לקריאה בלבד} other {שמור}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "עצור{short, select, true {} other { פרויקט}}…", "labels.project.settings.stop-project.ok": "כן, עצור פרויקט", "labels.projects": "פרויקטים", + "labels.public_paths": "נתיבים ציבוריים", "labels.published_files": "קבצים פורסמו", "labels.purchases": "רכישות", "labels.ready": "מוכן", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "בחר כותרת. תוכל לשנות אותה בקלות מאוחר יותר!", "projects.create-project.requireLicense": "נדרשת רישיון כדי ליצור פרויקטים נוספים.", "projects.filename-search.placeholder": "חפש קבצים שערכת...", - "projects.list.no_starred_found": "לא נמצאו פרויקטים מסומנים בכוכב. השתמש באייקון הכוכב ליד כותרות הפרויקטים כדי לסמן את הפרויקטים המועדפים עליך.", "projects.load-all.label": "הצג את כל הפרויקטים...", "projects.operations.clear-filter": "נקה מסנן", "projects.operations.delete.button": "{deleted, select, true {שחזר הכל} other {מחק הכל}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "סנן לפי האשטגים...", "projects.table-controls.hidden.label": "מוסתר", "projects.table-controls.search.placeholder": "חיפוש פרויקטים...", + "projects.table.keyboard-row-hint": "פרויקט {title}. השתמש בחצים למעלה ולמטה כדי לנוע; לחץ על Enter או על רווח כדי לפתוח.", "projects.table.last-edited": "נערך לאחרונה", + "projects.table.untitled": "ללא כותרת", "purchases.automatic-payments-warning.description": "תשלומים אוטומטיים הם הרבה יותר נוחים, יחסכו לך זמן, ויבטיחו שמנויים לא יבוטלו בטעות.", "purchases.automatic-payments-warning.title": "תשלומים אוטומטיים אינם נדרשים למנוי", "purchases.automatic-payments.are-enabled": "תשלומים אוטומטיים מופעלים", diff --git a/src/packages/frontend/i18n/trans/hi_IN.json b/src/packages/frontend/i18n/trans/hi_IN.json index 545a80e5d49..a3a68cd33af 100644 --- a/src/packages/frontend/i18n/trans/hi_IN.json +++ b/src/packages/frontend/i18n/trans/hi_IN.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "अनुवाद जानकारी", "account.account-button.confirm.ok": "हां, साइन आउट", "account.account-button.confirm.title": "अपने खाते से साइन आउट करें?", + "account.appearance.accessibility.enabled": "एक्सेसिबिलिटी मोड सक्षम करें: एक्सेसिबिलिटी सुविधाओं के लिए उपयोगकर्ता इंटरफ़ेस को अनुकूलित करें", + "account.appearance.accessibility.title": "पहुँच योग्यता", "account.appearance.user_interface.title": "उपयोगकर्ता इंटरफ़ेस", "account.delete-account.alert.description": "आप तुरंत अपने सभी प्रोजेक्ट्स की पहुंच खो देंगे, कोई भी सब्सक्रिप्शन रद्द कर दिए जाएंगे, और सभी बिना खर्च किया हुआ क्रेडिट खो जाएगा। {br} {hr} अपना खाता हटाने के लिए, पहले नीचे \"{required_text}\" दर्ज करें:", "account.delete-account.alert.message": "क्या आप वाकई अपना खाता हटाना चाहते हैं?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "जब भी फ़ाइल सहेजी जाती है, हटाएं", "account.editor-settings-autosave-interval.label": "ऑटोसेव अंतराल", "account.editor-settings.basic.title": "मूलभूत सेटिंग्स", - "account.editor-settings.color-schemes.label": "एडिटर रंग योजना", "account.editor-settings.color-schemes.panel_title": "संपादक रंग योजना", "account.editor-settings.font-size.label": "डिफ़ॉल्ट वैश्विक फ़ॉन्ट आकार", "account.editor-settings.indent-size.label": "इंडेंट आकार", "account.editor-settings.keyboard-bindings.label": "संपादक कीबोर्ड बाइंडिंग्स", "account.editor-settings.keyboard.title": "कीबोर्ड", - "account.editor-settings.title": "संपादक", "account.editor-settings.x11-keyboard-variant.label": "कीबोर्ड प्रकार (X11 डेस्कटॉप के लिए)", "account.editor-settings.x11-physical-keyboard.label": "कीबोर्ड लेआउट (X11 डेस्कटॉप के लिए)", "account.global-ssh-keys.help": "एक प्रोजेक्ट में SSH करने के लिए, निम्नलिखित का उपयोग करें username@host: [project-id-without-dashes]@ssh.cocalc.com SSH कुंजियों के बारे में प्रोजेक्ट सेटिंग्स के भाग में प्रोजेक्ट आईडी बिना डैश के पाई जा सकती है। प्रोजेक्ट्स के बीच SSH करने के लिए, [project-id-without-dashes]@ssh का उपयोग करें।", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Sage कार्यपत्रक में दृश्य विभाजन", "account.keyboard-shortcuts.shortcut.toggle-comment": "चयन पर टिप्पणी टॉगल करें", "account.other-settings._page_size.label": "प्रति पृष्ठ फ़ाइलों की संख्या", - "account.other-settings.browser_performance.title": "ब्राउज़र", + "account.other-settings.auto_focus": "ऑटो फोकस टेक्स्ट इनपुट: जब वे प्रकट होते हैं तो टेक्स्ट इनपुट फ़ील्ड्स पर स्वचालित रूप से फोकस करें (जैसे, फाइल एक्सप्लोरर, प्रोजेक्ट्स, ...)", "account.other-settings.button_tooltips": "हाइड बटन टूलटिप्स: कुछ बटन टूलटिप्स को छुपाता है (यह केवल आंशिक है)", "account.other-settings.confirm_close": "बंद करने की पुष्टि करें: ब्राउज़र विंडो बंद करने से पहले हमेशा पुष्टि के लिए पूछें", - "account.other-settings.content_display.title": "सामग्री प्रदर्शन", "account.other-settings.default_file_sort.by_name": "नाम के अनुसार क्रमबद्ध करें", "account.other-settings.default_file_sort.by_time": "समय के अनुसार छाँटें", "account.other-settings.default_file_sort.label": "डिफ़ॉल्ट फ़ाइल क्रम", + "account.other-settings.dim_file_extensions": "फ़ाइल एक्सटेंशन को धुंधला करें: फ़ाइल एक्सटेंशन को ग्रे कर दें ताकि उनके नाम अलग दिखें।", "account.other-settings.file_popovers": "फाइल टैब पॉपओवर छुपाएं: फाइल टैब पर पॉपओवर न दिखाएं", "account.other-settings.filename_generator.description": "स्वचालित रूप से उत्पन्न फ़ाइल नाम कैसे उत्पन्न होते हैं, इसका चयन करें। विशेष रूप से, उन्हें अद्वितीय बनाने या वर्तमान समय शामिल करने के लिए।", "account.other-settings.filename_generator.label": "फाइलनाम जनरेटर", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "एआई सेटिंग्स", "account.other-settings.markdown_codebar": "सभी मार्कडाउन दस्तावेज़ों में मार्कडाउन कोड बार को अक्षम करें. इसे चेक करने से बाड़े गए कोड ब्लॉकों में अतिरिक्त चलाएं, कॉपी करें और समझाएं बटन छिप जाते हैं।", "account.other-settings.mask_files": "मास्क फाइल्स: उन फाइलों को ग्रे आउट करें जिन्हें आप शायद खोलना नहीं चाहते फाइल्स व्यूअर में", - "account.other-settings.messages.title": "संदेश", "account.other-settings.project_popovers": "प्रोजेक्ट टैब पॉपओवर छुपाएं: प्रोजेक्ट टैब पर पॉपओवर न दिखाएं", - "account.other-settings.projects.title": "प्रोजेक्ट्स", "account.other-settings.standby_timeout": "स्टैंडबाय टाइमआउट", "account.other-settings.symbol_bar_labels": "प्रतीक पट्टी लेबल दिखाएं: फ्रेम संपादक प्रतीक पट्टी में लेबल दिखाएं", - "account.other-settings.theme": "थीम", "account.other-settings.theme.antd.animations": "एनिमेशन: कुछ पहलुओं को संक्षेप में एनिमेट करें, जैसे बटन", "account.other-settings.theme.antd.color_scheme": "रंग योजना: डिफ़ॉल्ट रंगों के बजाय ब्रांड रंगों का उपयोग करें", "account.other-settings.theme.antd.compact": "कॉम्पैक्ट डिज़ाइन: अधिक कॉम्पैक्ट डिज़ाइन का उपयोग करें", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "AI का उपयोग करके {docName} दस्तावेज़ उत्पन्न करें", "ai-generator.select_llm": "भाषा मॉडल चुनें", "app.fullscreen-button.tooltip": "पूर्ण स्क्रीन मोड, वर्तमान दस्तावेज़ या पृष्ठ पर केंद्रित।", + "app.hotkey.dialog.help_text": "ऊपर फ्रेम्स पर क्लिक करें • कुंजी 0 चैट टॉगल करती है • कुंजियाँ 1–9 फ्रेम्स पर फोकस करती हैं • खोजने के लिए टाइप करें • ↑↓ नेविगेट करें • खोलने के लिए रिटर्न दबाएं • बंद करने के लिए ESC दबाएं", + "app.hotkey.dialog.search_placeholder": "फ़ाइलें और पृष्ठ खोजें...", + "app.hotkey.dialog.title": "त्वरित नेविगेशन", "app.verify-email-banner.edit": "यदि ईमेल पता गलत है, कृपया इसे खाता सेटिंग्स में संपादित करें।", "app.verify-email-banner.help.text": "यह महत्वपूर्ण है कि आपके पास एक कार्यशील ईमेल पता हो। हम इसे पासवर्ड रीसेट, संदेश भेजने, बिलिंग सूचनाएं और सहायता के लिए उपयोग करते हैं। कृपया सूचित रहने के लिए सुनिश्चित करें कि आपका ईमेल सही है।", "app.verify-email-banner.text": "{sent, select, true {ईमेल भेजा गया! कृपया अपने ईमेल इनबॉक्स (और शायद स्पैम) की जाँच करें और पुष्टि लिंक पर क्लिक करें।} other {कृपया अपना ईमेल पता जाँचें और सत्यापित करें:}}", @@ -988,6 +988,7 @@ "labels.config": "कॉन्फ़िग", "labels.configuration": "कॉन्फ़िगरेशन", "labels.configuration.short": "कॉन्फ़िग", + "labels.connected": "कनेक्टेड", "labels.connecting": "कनेक्टिंग", "labels.connection": "कनेक्शन", "labels.copied": "कॉपी किया गया", @@ -1016,6 +1017,7 @@ "labels.environment": "पर्यावरण", "labels.explorer": "एक्सप्लोरर", "labels.file_explorer": "फ़ाइल एक्सप्लोरर", + "labels.file_use_notifications": "फ़ाइल उपयोग सूचनाएँ", "labels.files": "फ़ाइलें", "labels.folder": "फ़ोल्डर", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {सार्वजनिक} read_only {केवल पढ़ने के लिए} other {सहेजें}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "रुकें {short, select, true {} other { प्रोजेक्ट}}…", "labels.project.settings.stop-project.ok": "हाँ, प्रोजेक्ट बंद करें", "labels.projects": "प्रोजेक्ट्स", + "labels.public_paths": "सार्वजनिक पथ", "labels.published_files": "प्रकाशित फ़ाइलें", "labels.purchases": "खरीदें", "labels.ready": "तैयार", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "शीर्षक चुनें। आप इसे बाद में आसानी से बदल सकते हैं!", "projects.create-project.requireLicense": "अतिरिक्त प्रोजेक्ट्स बनाने के लिए एक लाइसेंस आवश्यक है।", "projects.filename-search.placeholder": "संपादित की गई फ़ाइल नाम खोजें...", - "projects.list.no_starred_found": "कोई स्टार किए गए प्रोजेक्ट नहीं मिले। अपने पसंदीदा प्रोजेक्ट को बुकमार्क करने के लिए प्रोजेक्ट शीर्षकों के पास स्टार आइकन का उपयोग करें।", "projects.load-all.label": "सभी प्रोजेक्ट दिखाएँ...", "projects.operations.clear-filter": "फ़िल्टर साफ करें", "projects.operations.delete.button": "{deleted, select, true {सभी पुनःस्थापित करें} other {सभी हटाएं}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "हैशटैग द्वारा फ़िल्टर करें...", "projects.table-controls.hidden.label": "छिपा हुआ", "projects.table-controls.search.placeholder": "प्रोजेक्ट खोजें...", + "projects.table.keyboard-row-hint": "प्रोजेक्ट {title}. स्थानांतरित करने के लिए ऊपर और नीचे तीरों का उपयोग करें; खोलने के लिए Enter या Space दबाएं।", "projects.table.last-edited": "अंतिम संपादन", + "projects.table.untitled": "शीर्षकहीन", "purchases.automatic-payments-warning.description": "स्वचालित भुगतान बहुत अधिक सुविधाजनक हैं, आपका समय बचाएंगे, और सुनिश्चित करेंगे कि सदस्यताएँ गलती से रद्द न हों।", "purchases.automatic-payments-warning.title": "स्वत: भुगतान के लिए सदस्यता की आवश्यकता नहीं है", "purchases.automatic-payments.are-enabled": "स्वचालित भुगतान सक्षम हैं", diff --git a/src/packages/frontend/i18n/trans/hu_HU.json b/src/packages/frontend/i18n/trans/hu_HU.json index 196feacdd46..2f1ec8163cd 100644 --- a/src/packages/frontend/i18n/trans/hu_HU.json +++ b/src/packages/frontend/i18n/trans/hu_HU.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Fordítási információk", "account.account-button.confirm.ok": "Igen, kijelentkezés", "account.account-button.confirm.title": "Kijelentkezik a fiókjából?", + "account.appearance.accessibility.enabled": "Akkorítsa el a hozzáférhetőség módot: optimalizálja a felhasználói felületet a hozzáférhetőségi funkciókhoz", + "account.appearance.accessibility.title": "Akadálymentesség", "account.appearance.user_interface.title": "Felhasználói felület", "account.delete-account.alert.description": "Azonnal elveszíti a hozzáférést az összes projektjéhez, minden előfizetés törlésre kerül, és minden el nem költött jóváírás elveszik. {br} {hr} A FIÓK TÖRLÉSÉHEZ először írja be az alábbi \"{required_text}\" szöveget:", "account.delete-account.alert.message": "Biztosan törölni akarja a FIÓKJÁT?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "távolítsa el, amikor a fájl mentésre kerül", "account.editor-settings-autosave-interval.label": "Automatikus mentés intervalluma", "account.editor-settings.basic.title": "Alapbeállítások", - "account.editor-settings.color-schemes.label": "Szerkesztő színséma", "account.editor-settings.color-schemes.panel_title": "Szerkesztő Színséma", "account.editor-settings.font-size.label": "Alapértelmezett globális betűméret", "account.editor-settings.indent-size.label": "Behúzás mérete", "account.editor-settings.keyboard-bindings.label": "Szerkesztő billentyűkötések", "account.editor-settings.keyboard.title": "Billentyűzet", - "account.editor-settings.title": "Szerkesztő", "account.editor-settings.x11-keyboard-variant.label": "Billentyűzet variáns (X11 asztali környezethez)", "account.editor-settings.x11-physical-keyboard.label": "Billentyűzetkiosztás (X11 asztalhoz)", "account.global-ssh-keys.help": "A projektbe való SSH belépéshez használja a következőt username@host: [project-id-without-dashes]@ssh.cocalc.com A kötőjelek nélküli projektazonosító az SSH kulcsok projektbeállításai részben található. Projektek közötti SSH-hoz használja [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Megosztott nézet Sage munkalapban", "account.keyboard-shortcuts.shortcut.toggle-comment": "Kommentár kijelölésének váltása", "account.other-settings._page_size.label": "Fájlok száma oldalanként", - "account.other-settings.browser_performance.title": "Böngésző", + "account.other-settings.auto_focus": "Automatikus fókusz a szövegbeviteli mezőkre: a szövegbeviteli mezők automatikus fókuszálása, amikor megjelennek (pl. fájlkezelő, projektek, ...)", "account.other-settings.button_tooltips": "Gombok tippjeinek elrejtése: elrejti néhány gomb tippjét (ez csak részleges)", "account.other-settings.confirm_close": "Zárás megerősítése: mindig kérjen megerősítést, mielőtt bezárja a böngészőablakot", - "account.other-settings.content_display.title": "Tartalom megjelenítés", "account.other-settings.default_file_sort.by_name": "Rendezés név szerint", "account.other-settings.default_file_sort.by_time": "Idő szerinti rendezés", "account.other-settings.default_file_sort.label": "Alapértelmezett fájlrendezés", + "account.other-settings.dim_file_extensions": "Fájl kiterjesztések elhalványítása: a fájl kiterjesztések szürkévé tétele, hogy a nevük kiemelkedjen.", "account.other-settings.file_popovers": "Fájltáb Popoverek Elrejtése: ne jelenítse meg a fájltábok feletti popovereket", "account.other-settings.filename_generator.description": "Válassza ki, hogyan generálódjanak automatikusan a fájlnevek. Különösen, hogy egyediek legyenek vagy tartalmazzák az aktuális időt.", "account.other-settings.filename_generator.label": "Fájlnév generátor", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "AI beállítások", "account.other-settings.markdown_codebar": "Tiltsa le a markdown kódsávot minden markdown dokumentumban. Ennek bejelölése elrejti a további futtatás, másolás és magyarázat gombokat a keretezett kódrészletekben.", "account.other-settings.mask_files": "Fájlok elrejtése: szürkítse el a fájlokat a fájlnézegetőben, amelyeket valószínűleg nem akar megnyitni", - "account.other-settings.messages.title": "Üzenetek", "account.other-settings.project_popovers": "Projektfülek felugróinak elrejtése: ne jelenítse meg a felugrókat a projektfülek felett", - "account.other-settings.projects.title": "Projektek", "account.other-settings.standby_timeout": "Készenléti időkorlát", "account.other-settings.symbol_bar_labels": "Szimbólumsáv címkéinek megjelenítése: címkék megjelenítése a keret szerkesztő szimbólumsávjában", - "account.other-settings.theme": "Téma", "account.other-settings.theme.antd.animations": "Animációk: röviden animálni néhány elemet, pl. gombokat", "account.other-settings.theme.antd.color_scheme": "Színséma: márka színek használata az alapértelmezett színek helyett", "account.other-settings.theme.antd.compact": "Kompakt Dizájn: használjon kompaktabb dizájnt", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "Hozzon létre egy {docName} dokumentumot mesterséges intelligencia segítségével", "ai-generator.select_llm": "Nyelvi modell kiválasztása", "app.fullscreen-button.tooltip": "Teljes képernyős mód, a jelenlegi dokumentumra vagy oldalra összpontosítva.", + "app.hotkey.dialog.help_text": "Kattintson a keretekre fent • A 0 gomb váltja a csevegést • Az 1–9 gombok a keretekre fókuszálnak • Gépeljen a kereséshez • ↑↓ navigálás • Enter a megnyitáshoz • ESC a bezáráshoz", + "app.hotkey.dialog.search_placeholder": "Fájlok és oldalak keresése...", + "app.hotkey.dialog.title": "Gyors navigáció", "app.verify-email-banner.edit": "Ha az e-mail cím helytelen, kérjük, szerkessze azt a fiókbeállításokban.", "app.verify-email-banner.help.text": "Fontos, hogy legyen működő e-mail címe. Jelszó-visszaállításhoz, üzenetek küldéséhez, számlázási értesítésekhez és támogatáshoz használjuk. Kérjük, győződjön meg róla, hogy az e-mail címe helyes, hogy tájékozott maradhasson.", "app.verify-email-banner.text": "{sent, select, true {Email elküldve! Kérjük, ellenőrizze az e-mail fiókját (és esetleg a spam mappát), és kattintson a megerősítő linkre.} other {Kérjük, ellenőrizze és igazolja az e-mail címét:}}", @@ -988,6 +988,7 @@ "labels.config": "Beállítás", "labels.configuration": "Konfiguráció", "labels.configuration.short": "Beállítás", + "labels.connected": "Csatlakoztatva", "labels.connecting": "Csatlakozás", "labels.connection": "Csatlakozás", "labels.copied": "másolva", @@ -1016,6 +1017,7 @@ "labels.environment": "Környezet", "labels.explorer": "Felfedező", "labels.file_explorer": "Fájlkezelő", + "labels.file_use_notifications": "Fájlhasználati értesítések", "labels.files": "Fájlok", "labels.folder": "mappa", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Nyilvános} read_only {Csak olvasható} other {Mentés}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "Állítsa le{short, select, true {} other { Projekt}}…", "labels.project.settings.stop-project.ok": "Igen, állítsa le a projektet", "labels.projects": "Projektek", + "labels.public_paths": "Nyilvános útvonalak", "labels.published_files": "Közzétett fájlok", "labels.purchases": "Vásárlások", "labels.ready": "Kész", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "Válasszon egy címet. Ezt később könnyedén megváltoztathatja!", "projects.create-project.requireLicense": "További projektek létrehozásához licenc szükséges.", "projects.filename-search.placeholder": "Keressen a szerkesztett fájlnevek között...", - "projects.list.no_starred_found": "Nincsenek csillagozott projektek. A projektcímek melletti csillag ikon használatával jelölheti meg a kedvenc projektjeit.", "projects.load-all.label": "Összes projekt megjelenítése...", "projects.operations.clear-filter": "Szűrő törlése", "projects.operations.delete.button": "{deleted, select, true {Összes visszaállítása} other {Összes törlése}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "Szűrés hashtagek alapján...", "projects.table-controls.hidden.label": "Rejtett", "projects.table-controls.search.placeholder": "Projektek keresése...", + "projects.table.keyboard-row-hint": "Projekt {title}. Használja a Fel és Le nyilakat a mozgatáshoz; nyomja meg az Enter vagy a Space gombot a megnyitáshoz.", "projects.table.last-edited": "Utoljára szerkesztve", + "projects.table.untitled": "Cím nélküli", "purchases.automatic-payments-warning.description": "Az automatikus fizetések sokkal kényelmesebbek, időt takarítanak meg, és biztosítják, hogy az előfizetések ne szűnjenek meg véletlenül.", "purchases.automatic-payments-warning.title": "Az automatikus fizetések NEM szükségesek előfizetéshez", "purchases.automatic-payments.are-enabled": "Az automatikus fizetések engedélyezve vannak", diff --git a/src/packages/frontend/i18n/trans/it_IT.json b/src/packages/frontend/i18n/trans/it_IT.json index 0d20a28b245..e5971c27bb5 100644 --- a/src/packages/frontend/i18n/trans/it_IT.json +++ b/src/packages/frontend/i18n/trans/it_IT.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Informazioni sulla traduzione", "account.account-button.confirm.ok": "Sì, esci", "account.account-button.confirm.title": "Esci dal tuo account?", + "account.appearance.accessibility.enabled": "Abilita Modalità Accessibilità: ottimizza l'interfaccia utente per le funzionalità di accessibilità", + "account.appearance.accessibility.title": "Accessibilità", "account.appearance.user_interface.title": "Interfaccia utente", "account.delete-account.alert.description": "Perderai immediatamente l'accesso a tutti i tuoi progetti, eventuali abbonamenti saranno annullati e tutto il credito non speso sarà perso. {br} {hr} Per CANCELLARE IL TUO ACCOUNT, inserisci prima \"{required_text}\" qui sotto:", "account.delete-account.alert.message": "Sei sicuro di voler ELIMINARE IL TUO ACCOUNT?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "rimuovi ogni volta che il file viene salvato", "account.editor-settings-autosave-interval.label": "Intervallo di salvataggio automatico", "account.editor-settings.basic.title": "Impostazioni di base", - "account.editor-settings.color-schemes.label": "Schema di colori dell'editor", "account.editor-settings.color-schemes.panel_title": "Schema di colori dell'editor", "account.editor-settings.font-size.label": "Dimensione predefinita del carattere globale", "account.editor-settings.indent-size.label": "Dimensione rientro", "account.editor-settings.keyboard-bindings.label": "Associazioni tastiera dell'editor", "account.editor-settings.keyboard.title": "Tastiera", - "account.editor-settings.title": "Editor", "account.editor-settings.x11-keyboard-variant.label": "Variante tastiera (per Desktop X11)", "account.editor-settings.x11-physical-keyboard.label": "Layout tastiera (per desktop X11)", "account.global-ssh-keys.help": "Per SSH in un progetto, usa il seguente username@host: [project-id-without-dashes]@ssh.cocalc.com L'id del progetto senza trattini può essere trovato nella parte delle impostazioni del progetto riguardante le chiavi SSH. Per SSH tra progetti, usa [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Visualizzazione divisa nel foglio di lavoro Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Attiva/disattiva commento selezione", "account.other-settings._page_size.label": "Numero di file per pagina", - "account.other-settings.browser_performance.title": "Browser", + "account.other-settings.auto_focus": "Auto Focus Text Input: concentra automaticamente i campi di inserimento testo quando appaiono (ad esempio, esploratore di file, progetti, ...)", "account.other-settings.button_tooltips": "Nascondi suggerimenti dei pulsanti: nasconde alcuni suggerimenti dei pulsanti (questo è solo parziale)", "account.other-settings.confirm_close": "Conferma Chiusura: chiedi sempre conferma prima di chiudere la finestra del browser", - "account.other-settings.content_display.title": "Visualizzazione contenuti", "account.other-settings.default_file_sort.by_name": "Ordina per nome", "account.other-settings.default_file_sort.by_time": "Ordina per tempo", "account.other-settings.default_file_sort.label": "Ordinamento file predefinito", + "account.other-settings.dim_file_extensions": "Estensioni file opache: rendi opache le estensioni dei file in modo che i loro nomi risaltino.", "account.other-settings.file_popovers": "Nascondi Popup Schede File: non mostrare i popup sulle schede dei file", "account.other-settings.filename_generator.description": "Seleziona come vengono generati automaticamente i nomi dei file. In particolare, per renderli unici o includere l'ora attuale.", "account.other-settings.filename_generator.label": "Generatore di nomi file", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "Impostazioni AI", "account.other-settings.markdown_codebar": "Disabilita la barra del codice markdown in tutti i documenti markdown. Se selezionato, questo nasconde i pulsanti aggiuntivi di esecuzione, copia e spiegazione nei blocchi di codice recintati.", "account.other-settings.mask_files": "Maschera file: disattiva i file nel visualizzatore di file che probabilmente non vuoi aprire", - "account.other-settings.messages.title": "Messaggi", "account.other-settings.project_popovers": "Nascondi i Popover delle Schede del Progetto: non mostrare i popover sulle schede del progetto", - "account.other-settings.projects.title": "Progetti", "account.other-settings.standby_timeout": "Timeout di attesa", "account.other-settings.symbol_bar_labels": "Mostra etichette barra simboli: mostra etichette nella barra simboli dell'editor di frame", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animazioni: animare brevemente alcuni aspetti, ad es. pulsanti", "account.other-settings.theme.antd.color_scheme": "Schema Colori: usa i colori del marchio invece dei colori predefiniti", "account.other-settings.theme.antd.compact": "Design compatto: usa un design più compatto", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "Genera un documento {docName} utilizzando l'IA", "ai-generator.select_llm": "Seleziona modello linguistico", "app.fullscreen-button.tooltip": "Modalità a schermo intero, focalizzata sul documento o pagina corrente", + "app.hotkey.dialog.help_text": "Clicca sui frame sopra • Tasto 0 attiva/disattiva chat • Tasti 1–9 mettono a fuoco i frame • Digita per cercare • ↑↓ naviga • Invio per aprire • ESC per chiudere", + "app.hotkey.dialog.search_placeholder": "Cerca file e pagine...", + "app.hotkey.dialog.title": "Navigazione Rapida", "app.verify-email-banner.edit": "Se l'indirizzo email è sbagliato, per favore modificalo nelle impostazioni dell'account.", "app.verify-email-banner.help.text": "È importante avere un indirizzo email funzionante. Lo utilizziamo per reimpostare le password, inviare messaggi, notifiche di fatturazione e supporto. Assicurati che la tua email sia corretta per rimanere informato.", "app.verify-email-banner.text": "{sent, select, true {Email inviata! Controlla la tua casella di posta elettronica (e magari spam) e clicca sul link di conferma.} other {Per favore controlla e verifica il tuo indirizzo email:}}", @@ -988,6 +988,7 @@ "labels.config": "Configura", "labels.configuration": "Configurazione", "labels.configuration.short": "Configura", + "labels.connected": "Connesso", "labels.connecting": "Connessione", "labels.connection": "Connessione", "labels.copied": "copiato", @@ -1016,6 +1017,7 @@ "labels.environment": "Ambiente", "labels.explorer": "Esploratore", "labels.file_explorer": "Esplora File", + "labels.file_use_notifications": "Notifiche di utilizzo file", "labels.files": "File", "labels.folder": "cartella", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Pubblico} read_only {Sola lettura} other {Salva}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "Stop{short, select, true {} other { Progetto}}…", "labels.project.settings.stop-project.ok": "Sì, ferma progetto", "labels.projects": "Progetti", + "labels.public_paths": "Percorsi Pubblici", "labels.published_files": "File Pubblicati", "labels.purchases": "Acquisti", "labels.ready": "Pronto", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "Scegli un titolo. Puoi cambiarlo facilmente in seguito!", "projects.create-project.requireLicense": "È necessaria una licenza per creare progetti aggiuntivi.", "projects.filename-search.placeholder": "Cerca i nomi dei file che hai modificato...", - "projects.list.no_starred_found": "Nessun progetto con stella trovato. Usa l'icona a stella accanto ai titoli dei progetti per aggiungere ai segnalibri i tuoi progetti preferiti.", "projects.load-all.label": "Mostra tutti i progetti...", "projects.operations.clear-filter": "Cancella filtro", "projects.operations.delete.button": "{deleted, select, true {Ripristina Tutto} other {Elimina Tutto}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "Filtra per hashtag...", "projects.table-controls.hidden.label": "Nascosto", "projects.table-controls.search.placeholder": "Cerca progetti...", + "projects.table.keyboard-row-hint": "Progetto {title}. Usa le frecce Su e Giù per spostarti; premi Invio o Spazio per aprire.", "projects.table.last-edited": "Ultima modifica", + "projects.table.untitled": "Senza titolo", "purchases.automatic-payments-warning.description": "I pagamenti automatici sono molto più convenienti, ti fanno risparmiare tempo e assicurano che gli abbonamenti non vengano annullati per errore.", "purchases.automatic-payments-warning.title": "I pagamenti automatici NON sono necessari per avere un abbonamento", "purchases.automatic-payments.are-enabled": "I pagamenti automatici sono abilitati", diff --git a/src/packages/frontend/i18n/trans/ja_JP.json b/src/packages/frontend/i18n/trans/ja_JP.json index 97112dca3e3..969aebd5862 100644 --- a/src/packages/frontend/i18n/trans/ja_JP.json +++ b/src/packages/frontend/i18n/trans/ja_JP.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "翻訳情報", "account.account-button.confirm.ok": "はい、サインアウト", "account.account-button.confirm.title": "アカウントからサインアウトしますか?", + "account.appearance.accessibility.enabled": "アクセシビリティモードを有効化:アクセシビリティ機能に合わせてユーザーインターフェースを最適化", + "account.appearance.accessibility.title": "アクセシビリティ", "account.appearance.user_interface.title": "ユーザーインターフェース", "account.delete-account.alert.description": "すぐにすべてのプロジェクトへのアクセスを失い、すべてのサブスクリプションがキャンセルされ、未使用のクレジットはすべて失われます。{br} {hr} アカウントを削除するには、まず下に「{required_text}」を入力してください。", "account.delete-account.alert.message": "本当にアカウントを削除しますか?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "ファイルが保存されるたびに削除", "account.editor-settings-autosave-interval.label": "自動保存間隔", "account.editor-settings.basic.title": "基本設定", - "account.editor-settings.color-schemes.label": "エディターのカラースキーム", "account.editor-settings.color-schemes.panel_title": "エディターのカラースキーム", "account.editor-settings.font-size.label": "デフォルトのグローバルフォントサイズ", "account.editor-settings.indent-size.label": "インデントサイズ", "account.editor-settings.keyboard-bindings.label": "エディターのキーボードバインディング", "account.editor-settings.keyboard.title": "キーボード", - "account.editor-settings.title": "エディター", "account.editor-settings.x11-keyboard-variant.label": "X11デスクトップ用のキーボードバリアント", "account.editor-settings.x11-physical-keyboard.label": "キーボードレイアウト(X11デスクトップ用)", "account.global-ssh-keys.help": "プロジェクトにSSHするには、次のusername@host: [project-id-without-dashes]@ssh.cocalc.comを使用します。ダッシュのないプロジェクトIDは、SSHキーに関するプロジェクト設定の部分で見つけることができます。プロジェクト間でSSHするには、[project-id-without-dashes]@sshを使用します。", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Sageワークシートで分割表示", "account.keyboard-shortcuts.shortcut.toggle-comment": "選択範囲のコメントを切り替え", "account.other-settings._page_size.label": "1ページあたりのファイル数", - "account.other-settings.browser_performance.title": "ブラウザー", + "account.other-settings.auto_focus": "自動フォーカステキスト入力: テキスト入力フィールドが表示されたときに自動的にフォーカスする (例: ファイルエクスプローラー、プロジェクト、...)", "account.other-settings.button_tooltips": "ボタンのツールチップを非表示: 一部のボタンのツールチップを非表示にします(これは部分的です)", "account.other-settings.confirm_close": "閉じる確認: ブラウザーウィンドウを閉じる前に必ず確認を求める", - "account.other-settings.content_display.title": "コンテンツ表示", "account.other-settings.default_file_sort.by_name": "名前で並べ替え", "account.other-settings.default_file_sort.by_time": "時間順に並べ替え", "account.other-settings.default_file_sort.label": "デフォルトファイルソート", + "account.other-settings.dim_file_extensions": "ファイル拡張子を薄く表示: ファイル名が目立つようにファイル拡張子をグレーアウトします。", "account.other-settings.file_popovers": "ファイルタブポップオーバーを非表示: ファイルタブ上にポップオーバーを表示しない", "account.other-settings.filename_generator.description": "自動生成されるファイル名の生成方法を選択します。特に、それらをユニークにするか、現在の時間を含めるかを選択します。", "account.other-settings.filename_generator.label": "ファイル名ジェネレーター", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "AI設定", "account.other-settings.markdown_codebar": "すべてのMarkdown文書でMarkdownコードバーを無効化します。これをチェックすると、囲まれたコードブロック内の追加の実行、コピー、説明ボタンが非表示になります。", "account.other-settings.mask_files": "マスクファイル: 開きたくないファイルをファイルビューアでグレーアウト", - "account.other-settings.messages.title": "メッセージ", "account.other-settings.project_popovers": "プロジェクトタブのポップオーバーを非表示: プロジェクトタブ上のポップオーバーを表示しない", - "account.other-settings.projects.title": "プロジェクト", "account.other-settings.standby_timeout": "待機タイムアウト", "account.other-settings.symbol_bar_labels": "シンボルバーラベルを表示:フレームエディタのシンボルバーにラベルを表示", - "account.other-settings.theme": "テーマ", "account.other-settings.theme.antd.animations": "アニメーション: ボタンなどの要素を簡潔にアニメーション化", "account.other-settings.theme.antd.color_scheme": "カラー スキーム: デフォルトの色ではなくブランドの色を使用", "account.other-settings.theme.antd.compact": "コンパクトデザイン: よりコンパクトなデザインを使用", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "AIを使用して{docName}ドキュメントを生成", "ai-generator.select_llm": "言語モデルを選択", "app.fullscreen-button.tooltip": "全画面モード、現在のドキュメントまたはページに集中。", + "app.hotkey.dialog.help_text": "上のフレームをクリック • キー0でチャットを切り替え • キー1–9でフレームにフォーカス • 検索するには入力 • ↑↓でナビゲート • Enterで開く • ESCで閉じる", + "app.hotkey.dialog.search_placeholder": "ファイルとページを検索...", + "app.hotkey.dialog.title": "クイックナビゲーション", "app.verify-email-banner.edit": "メールアドレスが間違っている場合は、アカウント設定で編集してください。", "app.verify-email-banner.help.text": "作業可能なメールアドレスを持つことが重要です。パスワードのリセット、メッセージの送信、請求通知、サポートのために使用します。情報を受け取るために、メールアドレスが正しいことを確認してください。", "app.verify-email-banner.text": "{sent, select, true {メールが送信されました!メールの受信トレイ(場合によってはスパムフォルダ)を確認し、確認リンクをクリックしてください。} other {メールアドレスを確認してください:}}", @@ -988,6 +988,7 @@ "labels.config": "設定", "labels.configuration": "設定", "labels.configuration.short": "設定", + "labels.connected": "接続済み", "labels.connecting": "接続中", "labels.connection": "接続", "labels.copied": "コピー済み", @@ -1016,6 +1017,7 @@ "labels.environment": "環境", "labels.explorer": "書類", "labels.file_explorer": "ファイルエクスプローラー", + "labels.file_use_notifications": "ファイル使用通知", "labels.files": "ファイル", "labels.folder": "フォルダー", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {公開} read_only {読み取り専用} other {保存}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "停止{short, select, true {} other {プロジェクト}}…", "labels.project.settings.stop-project.ok": "はい、プロジェクトを停止", "labels.projects": "プロジェクト", + "labels.public_paths": "公開パス", "labels.published_files": "公開されたファイル", "labels.purchases": "購入", "labels.ready": "準備完了", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "タイトルを選んでください。後で簡単に変更できます!", "projects.create-project.requireLicense": "追加のプロジェクトを作成するにはライセンスが必要です。", "projects.filename-search.placeholder": "編集したファイル名を検索...", - "projects.list.no_starred_found": "スター付きのプロジェクトが見つかりませんでした。お気に入りのプロジェクトをブックマークするには、プロジェクトタイトルの横にある星アイコンを使用してください。", "projects.load-all.label": "すべてのプロジェクトを表示...", "projects.operations.clear-filter": "フィルターをクリア", "projects.operations.delete.button": "{deleted, select, true {すべてを復元} other {すべてを削除}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "ハッシュタグでフィルター...", "projects.table-controls.hidden.label": "非表示", "projects.table-controls.search.placeholder": "プロジェクトを検索...", + "projects.table.keyboard-row-hint": "プロジェクト {title}。上下矢印キーで移動し、EnterキーまたはSpaceキーで開く。", "projects.table.last-edited": "最終編集", + "projects.table.untitled": "無題", "purchases.automatic-payments-warning.description": "自動支払いはずっと便利で、時間を節約し、サブスクリプションが誤ってキャンセルされないようにします。", "purchases.automatic-payments-warning.title": "自動支払いはサブスクリプションに必要ありません", "purchases.automatic-payments.are-enabled": "自動支払いが有効です", diff --git a/src/packages/frontend/i18n/trans/ko_KR.json b/src/packages/frontend/i18n/trans/ko_KR.json index 1234fd4c23d..1d4d3768145 100644 --- a/src/packages/frontend/i18n/trans/ko_KR.json +++ b/src/packages/frontend/i18n/trans/ko_KR.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "번역 정보", "account.account-button.confirm.ok": "예, 로그아웃", "account.account-button.confirm.title": "계정에서 로그아웃하시겠습니까?", + "account.appearance.accessibility.enabled": "접근성 모드 활성화: 접근성 기능에 맞춰 사용자 인터페이스 최적화", + "account.appearance.accessibility.title": "접근성", "account.appearance.user_interface.title": "사용자 인터페이스", "account.delete-account.alert.description": "즉시 모든 프로젝트에 대한 접근 권한을 잃게 되며, 모든 구독이 취소되고 사용하지 않은 크레딧이 모두 소멸됩니다. {br} {hr} 계정을 삭제하려면 먼저 아래에 \"{required_text}\"를 입력하십시오:", "account.delete-account.alert.message": "계정을 삭제하시겠습니까?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "파일이 저장될 때마다 제거", "account.editor-settings-autosave-interval.label": "자동 저장 간격", "account.editor-settings.basic.title": "기본 설정", - "account.editor-settings.color-schemes.label": "편집기 색상 테마", "account.editor-settings.color-schemes.panel_title": "편집기 색상 테마", "account.editor-settings.font-size.label": "기본 전역 글꼴 크기", "account.editor-settings.indent-size.label": "들여쓰기 크기", "account.editor-settings.keyboard-bindings.label": "편집기 키보드 바인딩", "account.editor-settings.keyboard.title": "키보드", - "account.editor-settings.title": "에디터", "account.editor-settings.x11-keyboard-variant.label": "키보드 변형 (X11 데스크탑용)", "account.editor-settings.x11-physical-keyboard.label": "키보드 레이아웃 (X11 데스크탑용)", "account.global-ssh-keys.help": "프로젝트에 SSH 접속을 하려면 다음을 사용하세요 username@host: [project-id-without-dashes]@ssh.cocalc.com 대시 없이 프로젝트 ID는 SSH 키와 관련된 프로젝트 설정 부분에서 찾을 수 있습니다. 프로젝트 간에 SSH 접속을 하려면 [project-id-without-dashes]@ssh를 사용하세요.", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Sage 워크시트에서 분할 보기", "account.keyboard-shortcuts.shortcut.toggle-comment": "선택 주석 전환", "account.other-settings._page_size.label": "페이지당 파일 수", - "account.other-settings.browser_performance.title": "브라우저", + "account.other-settings.auto_focus": "자동 포커스 텍스트 입력: 텍스트 입력 필드가 나타날 때 자동으로 포커스 (예: 파일 탐색기, 프로젝트, ...)", "account.other-settings.button_tooltips": "버튼 툴팁 숨기기: 일부 버튼 툴팁 숨기기 (부분적으로만 적용됨)", "account.other-settings.confirm_close": "종료 확인: 브라우저 창을 닫기 전에 항상 확인 요청", - "account.other-settings.content_display.title": "콘텐츠 표시", "account.other-settings.default_file_sort.by_name": "이름으로 정렬", "account.other-settings.default_file_sort.by_time": "시간순 정렬", "account.other-settings.default_file_sort.label": "기본 파일 정렬", + "account.other-settings.dim_file_extensions": "파일 확장자 흐리게: 파일 이름이 돋보이도록 확장자를 회색으로 표시합니다.", "account.other-settings.file_popovers": "파일 탭 팝오버 숨기기: 파일 탭 위에 팝오버를 표시하지 않음", "account.other-settings.filename_generator.description": "자동 생성된 파일 이름이 생성되는 방식을 선택하십시오. 특히, 고유하게 만들거나 현재 시간을 포함하도록 선택하십시오.", "account.other-settings.filename_generator.label": "파일명 생성기", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "AI 설정", "account.other-settings.markdown_codebar": "모든 마크다운 문서에서 마크다운 코드 바 비활성화. 이 옵션을 선택하면 코드 블록의 실행, 복사 및 설명 버튼이 숨겨집니다.", "account.other-settings.mask_files": "파일 숨기기: 파일 뷰어에서 열고 싶지 않은 파일을 회색으로 표시", - "account.other-settings.messages.title": "메시지", "account.other-settings.project_popovers": "프로젝트 탭 팝오버 숨기기: 프로젝트 탭 위에 팝오버를 표시하지 않음", - "account.other-settings.projects.title": "프로젝트", "account.other-settings.standby_timeout": "대기 시간 초과", "account.other-settings.symbol_bar_labels": "기호 막대 레이블 표시: 프레임 편집기 기호 막대에 레이블 표시", - "account.other-settings.theme": "테마", "account.other-settings.theme.antd.animations": "애니메이션: 버튼과 같은 일부 측면을 간단히 애니메이션화합니다", "account.other-settings.theme.antd.color_scheme": "색상 테마: 기본 색상 대신 브랜드 색상 사용", "account.other-settings.theme.antd.compact": "간결한 디자인: 더 간결한 디자인 사용", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "AI를 사용하여 {docName} 문서 생성", "ai-generator.select_llm": "언어 모델 선택", "app.fullscreen-button.tooltip": "풀스크린 모드, 현재 문서나 페이지에 집중.", + "app.hotkey.dialog.help_text": "위의 프레임을 클릭 • 0 키로 채팅 전환 • 1–9 키로 프레임 선택 • 검색하려면 입력 • ↑↓ 탐색 • Return으로 열기 • ESC로 닫기", + "app.hotkey.dialog.search_placeholder": "파일 및 페이지 검색...", + "app.hotkey.dialog.title": "빠른 탐색", "app.verify-email-banner.edit": "이메일 주소가 잘못된 경우 계정 설정에서 편집하십시오.", "app.verify-email-banner.help.text": "작동하는 이메일 주소를 가지고 있는 것이 중요합니다. 우리는 비밀번호 재설정, 메시지 전송, 청구 알림 및 지원을 위해 이메일을 사용합니다. 정보를 받으려면 이메일이 올바른지 확인하세요.", "app.verify-email-banner.text": "{sent, select, true {이메일이 전송되었습니다! 이메일 받은 편지함(그리고 스팸 폴더도)에서 확인하고 확인 링크를 클릭하세요.} other {이메일 주소를 확인하고 인증해 주세요:}}", @@ -988,6 +988,7 @@ "labels.config": "구성", "labels.configuration": "구성", "labels.configuration.short": "구성", + "labels.connected": "연결됨", "labels.connecting": "연결 중", "labels.connection": "연결", "labels.copied": "복사됨", @@ -1016,6 +1017,7 @@ "labels.environment": "환경", "labels.explorer": "탐색기", "labels.file_explorer": "파일 탐색기", + "labels.file_use_notifications": "파일 사용 알림", "labels.files": "파일", "labels.folder": "폴더", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {공개} read_only {읽기 전용} other {저장}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "중지{short, select, true {} other { 프로젝트}}…", "labels.project.settings.stop-project.ok": "예, 프로젝트 중지", "labels.projects": "프로젝트", + "labels.public_paths": "공개 경로", "labels.published_files": "게시된 파일", "labels.purchases": "구매", "labels.ready": "준비됨", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "제목을 선택하세요. 나중에 쉽게 변경할 수 있습니다!", "projects.create-project.requireLicense": "추가 프로젝트를 만들려면 라이선스가 필요합니다.", "projects.filename-search.placeholder": "편집한 파일 이름 검색...", - "projects.list.no_starred_found": "별표가 있는 프로젝트를 찾을 수 없습니다. 프로젝트 제목 옆의 별 아이콘을 사용하여 즐겨찾는 프로젝트를 북마크하세요.", "projects.load-all.label": "모든 프로젝트 보기...", "projects.operations.clear-filter": "필터 지우기", "projects.operations.delete.button": "{deleted, select, true {모두 복구} other {모두 삭제}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "해시태그로 필터링...", "projects.table-controls.hidden.label": "숨김", "projects.table-controls.search.placeholder": "프로젝트 검색...", + "projects.table.keyboard-row-hint": "프로젝트 {title}. 이동하려면 위아래 화살표를 사용하고, 열려면 Enter 또는 Space 키를 누르세요.", "projects.table.last-edited": "마지막 편집", + "projects.table.untitled": "제목 없음", "purchases.automatic-payments-warning.description": "자동 결제는 훨씬 더 편리하고, 시간을 절약하며, 구독이 실수로 취소되지 않도록 보장합니다.", "purchases.automatic-payments-warning.title": "자동 결제는 구독에 필수적이지 않습니다", "purchases.automatic-payments.are-enabled": "자동 결제가 활성화되었습니다", diff --git a/src/packages/frontend/i18n/trans/nl_NL.json b/src/packages/frontend/i18n/trans/nl_NL.json index 8ddfd4b7b54..ce7df938cbb 100644 --- a/src/packages/frontend/i18n/trans/nl_NL.json +++ b/src/packages/frontend/i18n/trans/nl_NL.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Vertaalinformatie", "account.account-button.confirm.ok": "Ja, afmelden", "account.account-button.confirm.title": "Afmelden van uw account?", + "account.appearance.accessibility.enabled": "Toegankelijkheidsmodus inschakelen: optimaliseer de gebruikersinterface voor toegankelijkheidsfuncties", + "account.appearance.accessibility.title": "Toegankelijkheid", "account.appearance.user_interface.title": "Gebruikersinterface", "account.delete-account.alert.description": "U verliest onmiddellijk toegang tot al uw projecten, eventuele abonnementen worden geannuleerd en al het ongebruikte tegoed gaat verloren. {br} {hr} Om UW ACCOUNT TE VERWIJDEREN, voert u eerst hieronder \"{required_text}\" in:", "account.delete-account.alert.message": "Weet u zeker dat u UW ACCOUNT WILT VERWIJDEREN?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "verwijderen wanneer bestand wordt opgeslagen", "account.editor-settings-autosave-interval.label": "Automatisch opslaan interval", "account.editor-settings.basic.title": "Basisinstellingen", - "account.editor-settings.color-schemes.label": "Editor kleurenschema", "account.editor-settings.color-schemes.panel_title": "Editor Kleurschema", "account.editor-settings.font-size.label": "Standaard globale lettergrootte", "account.editor-settings.indent-size.label": "Inspringingsgrootte", "account.editor-settings.keyboard-bindings.label": "Editor-toetsenbordkoppelingen", "account.editor-settings.keyboard.title": "Toetsenbord", - "account.editor-settings.title": "Editorinstellingen", "account.editor-settings.x11-keyboard-variant.label": "Toetsenbordvariant (voor X11 Desktop)", "account.editor-settings.x11-physical-keyboard.label": "Toetsenbordindeling (voor X11 Desktop)", "account.global-ssh-keys.help": "Om SSH in een project te gebruiken, gebruik de volgende gebruikersnaam@host: [project-id-zonder-streepjes]@ssh.cocalc.com De project-id zonder streepjes is te vinden in het gedeelte van de projectinstellingen over SSH-sleutels. Om SSH tussen projecten te gebruiken, gebruik [project-id-zonder-streepjes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Gesplitste weergave in Sage-werkblad", "account.keyboard-shortcuts.shortcut.toggle-comment": "Selectie van commentaar voorzien/ongedaan maken", "account.other-settings._page_size.label": "Aantal bestanden per pagina", - "account.other-settings.browser_performance.title": "Browser", + "account.other-settings.auto_focus": "Auto Focus Tekstinvoer: tekstinvoervelden automatisch focussen wanneer ze verschijnen (bijv. bestandsverkenner, projecten, ...)", "account.other-settings.button_tooltips": "Verberg knopinfo: verbergt enkele knopinfo (dit is slechts gedeeltelijk)", "account.other-settings.confirm_close": "Bevestig Sluiten: altijd om bevestiging vragen voordat het browservenster wordt gesloten", - "account.other-settings.content_display.title": "Inhoudweergave", "account.other-settings.default_file_sort.by_name": "Sorteren op naam", "account.other-settings.default_file_sort.by_time": "Sorteren op tijd", "account.other-settings.default_file_sort.label": "Standaard bestandssortering", + "account.other-settings.dim_file_extensions": "Bestandsextensies dempen: bestandsextensies grijs maken zodat hun namen opvallen.", "account.other-settings.file_popovers": "Bestandstab-Popovers verbergen: toon de popovers over bestandstabs niet", "account.other-settings.filename_generator.description": "Selecteer hoe automatisch gegenereerde bestandsnamen worden gegenereerd. Met name om ze uniek te maken of de huidige tijd op te nemen.", "account.other-settings.filename_generator.label": "Bestandsnaam generator", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "AI-instellingen", "account.other-settings.markdown_codebar": "Deactiveer de markdown codebalk in alle markdown-documenten. Als u dit aanvinkt, worden de extra uitvoer-, kopieer- en uitlegknoppen in omkaderde codeblokken verborgen.", "account.other-settings.mask_files": "Masker bestanden: grijs bestanden uit in de bestandsweergave die je waarschijnlijk niet wilt openen", - "account.other-settings.messages.title": "Berichten", "account.other-settings.project_popovers": "Verberg Projecttabblad Popovers: toon de popovers niet boven de projecttabbladen", - "account.other-settings.projects.title": "Projecten", "account.other-settings.standby_timeout": "Standby-timeout", "account.other-settings.symbol_bar_labels": "Toon Symbolenbalk Labels: toon labels in de frame-editor symbolenbalk", - "account.other-settings.theme": "Thema", "account.other-settings.theme.antd.animations": "Animaties: kort sommige aspecten animeren, bijv. knoppen", "account.other-settings.theme.antd.color_scheme": "Kleurschema: gebruik merkkleuren in plaats van standaardkleuren", "account.other-settings.theme.antd.compact": "Compact Design: gebruik een compacter ontwerp", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "Genereer een {docName} Document met AI", "ai-generator.select_llm": "Selecteer taalmodel", "app.fullscreen-button.tooltip": "Volledig schermmodus, gericht op het huidige document of de pagina.", + "app.hotkey.dialog.help_text": "Klik op frames hierboven • Toets 0 schakelt chat in/uit • Toetsen 1–9 focussen frames • Typ om te zoeken • ↑↓ navigeer • Return om te openen • ESC om te sluiten", + "app.hotkey.dialog.search_placeholder": "Bestanden en pagina's zoeken...", + "app.hotkey.dialog.title": "Snelle Navigatie", "app.verify-email-banner.edit": "Als het e-mailadres verkeerd is, pas het dan aan in de accountinstellingen.", "app.verify-email-banner.help.text": "Het is belangrijk om een werkend e-mailadres te hebben. We gebruiken het voor het opnieuw instellen van wachtwoorden, het verzenden van berichten, factureringsmeldingen en ondersteuning. Zorg ervoor dat uw e-mailadres correct is om op de hoogte te blijven.", "app.verify-email-banner.text": "{sent, select, true {E-mail verzonden! Controleer je e-mailinbox (en misschien spam) en klik op de bevestigingslink.} other {Controleer en verifieer je e-mailadres:}}", @@ -988,6 +988,7 @@ "labels.config": "Config", "labels.configuration": "Configuratie", "labels.configuration.short": "Config", + "labels.connected": "Verbonden", "labels.connecting": "Verbinden", "labels.connection": "Verbinding", "labels.copied": "gekopieerd", @@ -1016,6 +1017,7 @@ "labels.environment": "Omgeving", "labels.explorer": "Verkenner", "labels.file_explorer": "Bestandsverkenner", + "labels.file_use_notifications": "Meldingen Bestandsgebruik", "labels.files": "Bestanden", "labels.folder": "map", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Openbaar} read_only {Alleen lezen} other {Opslaan}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "Stop{short, select, true {} other { Project}}…", "labels.project.settings.stop-project.ok": "Ja, stop project", "labels.projects": "Projecten", + "labels.public_paths": "Openbare Paden", "labels.published_files": "Gepubliceerde bestanden", "labels.purchases": "Aankopen", "labels.ready": "Klaar", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "Kies een titel. Je kunt het later eenvoudig wijzigen!", "projects.create-project.requireLicense": "Er is een licentie vereist om extra projecten te maken.", "projects.filename-search.placeholder": "Zoek naar bestandsnamen die je hebt bewerkt...", - "projects.list.no_starred_found": "Geen gemarkeerde projecten gevonden. Gebruik het sterpictogram naast projecttitels om je favoriete projecten te markeren.", "projects.load-all.label": "Toon alle projecten...", "projects.operations.clear-filter": "Filter wissen", "projects.operations.delete.button": "{deleted, select, true {Alles Herstellen} other {Alles Verwijderen}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "Filteren op hashtags...", "projects.table-controls.hidden.label": "Verborgen", "projects.table-controls.search.placeholder": "Zoek projecten...", + "projects.table.keyboard-row-hint": "Project {title}. Gebruik de pijltjestoetsen omhoog en omlaag om te bewegen; druk op Enter of Spatiebalk om te openen.", "projects.table.last-edited": "Laatst bewerkt", + "projects.table.untitled": "Naamloos", "purchases.automatic-payments-warning.description": "Automatische betalingen zijn veel handiger, zullen je tijd besparen, en ervoor zorgen dat abonnementen niet per ongeluk worden opgezegd.", "purchases.automatic-payments-warning.title": "Automatische betalingen zijn NIET vereist om een abonnement te hebben", "purchases.automatic-payments.are-enabled": "Automatische Betalingen zijn Ingeschakeld", diff --git a/src/packages/frontend/i18n/trans/pl_PL.json b/src/packages/frontend/i18n/trans/pl_PL.json index 3ce0be42a5a..4100a111f93 100644 --- a/src/packages/frontend/i18n/trans/pl_PL.json +++ b/src/packages/frontend/i18n/trans/pl_PL.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Informacje o tłumaczeniu", "account.account-button.confirm.ok": "Tak, wyloguj się", "account.account-button.confirm.title": "Wylogować się z konta?", + "account.appearance.accessibility.enabled": "Włącz tryb dostępności: optymalizuj interfejs użytkownika pod kątem funkcji dostępności", + "account.appearance.accessibility.title": "Dostępność", "account.appearance.user_interface.title": "Interfejs użytkownika", "account.delete-account.alert.description": "Natychmiast utracisz dostęp do wszystkich swoich projektów, wszelkie subskrypcje zostaną anulowane, a cały niewykorzystany kredyt zostanie utracony. {br} {hr} Aby USUNĄĆ SWOJE KONTO, najpierw wpisz poniżej \"{required_text}\":", "account.delete-account.alert.message": "Czy na pewno chcesz USUNĄĆ SWOJE KONTO?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "usuń za każdym razem, gdy plik jest zapisywany", "account.editor-settings-autosave-interval.label": "Interwał automatycznego zapisu", "account.editor-settings.basic.title": "Podstawowe ustawienia", - "account.editor-settings.color-schemes.label": "Schemat kolorów edytora", "account.editor-settings.color-schemes.panel_title": "Schemat kolorów edytora", "account.editor-settings.font-size.label": "Domyślny globalny rozmiar czcionki", "account.editor-settings.indent-size.label": "Rozmiar wcięcia", "account.editor-settings.keyboard-bindings.label": "Powiązania klawiatury edytora", "account.editor-settings.keyboard.title": "Klawiatura", - "account.editor-settings.title": "Edytor", "account.editor-settings.x11-keyboard-variant.label": "Odmiana klawiatury (dla pulpitu X11)", "account.editor-settings.x11-physical-keyboard.label": "Układ klawiatury (dla pulpitu X11)", "account.global-ssh-keys.help": "Aby zalogować się do projektu przez SSH, użyj następującego username@host: [project-id-without-dashes]@ssh.cocalc.com Identyfikator projektu bez myślników można znaleźć w części ustawień projektu dotyczącej kluczy SSH. Aby zalogować się przez SSH między projektami, użyj [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Podziel widok w arkuszu Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Przełącz komentarzowanie zaznaczenia", "account.other-settings._page_size.label": "Liczba plików na stronę", - "account.other-settings.browser_performance.title": "Przeglądarka", + "account.other-settings.auto_focus": "Automatyczne ustawianie fokusu na polach tekstowych: automatycznie ustawiaj fokus na polach tekstowych, gdy się pojawiają (np. eksplorator plików, projekty, ...)", "account.other-settings.button_tooltips": "Ukryj podpowiedzi przycisków: ukrywa niektóre podpowiedzi przycisków (to jest tylko częściowe)", "account.other-settings.confirm_close": "Potwierdź zamknięcie: zawsze pytaj o potwierdzenie przed zamknięciem okna przeglądarki", - "account.other-settings.content_display.title": "Wyświetlanie treści", "account.other-settings.default_file_sort.by_name": "Sortuj według nazwy", "account.other-settings.default_file_sort.by_time": "Sortuj według czasu", "account.other-settings.default_file_sort.label": "Domyślne sortowanie plików", + "account.other-settings.dim_file_extensions": "Przyciemnij rozszerzenia plików: wyszarz rozszerzenia plików, aby ich nazwy się wyróżniały.", "account.other-settings.file_popovers": "Ukryj dymki zakładek plików: nie pokazuj dymków nad zakładkami plików", "account.other-settings.filename_generator.description": "Wybierz, jak automatycznie generowane nazwy plików są tworzone. W szczególności, aby były unikalne lub zawierały bieżący czas.", "account.other-settings.filename_generator.label": "Generator nazw plików", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "Ustawienia AI", "account.other-settings.markdown_codebar": "Wyłącz pasek kodu markdown we wszystkich dokumentach markdown. Zaznaczenie tego ukrywa dodatkowe przyciski uruchamiania, kopiowania i wyjaśniania w blokach kodu.", "account.other-settings.mask_files": "Maskuj pliki: wyszarz pliki w przeglądarce plików, których prawdopodobnie nie chcesz otworzyć", - "account.other-settings.messages.title": "Wiadomości", "account.other-settings.project_popovers": "Ukryj dymki kart projektu: nie pokazuj dymków nad kartami projektu", - "account.other-settings.projects.title": "Projekty", "account.other-settings.standby_timeout": "Limit czasu w trybie gotowości", "account.other-settings.symbol_bar_labels": "Pokaż etykiety paska symboli: pokaż etykiety na pasku symboli edytora ramki", - "account.other-settings.theme": "Motyw", "account.other-settings.theme.antd.animations": "Animacje: krótko animuj niektóre aspekty, np. przyciski", "account.other-settings.theme.antd.color_scheme": "Schemat kolorów: użyj kolorów marki zamiast domyślnych kolorów", "account.other-settings.theme.antd.compact": "Kompaktowy układ: użyj bardziej zwartego układu", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "Wygeneruj dokument {docName} za pomocą AI", "ai-generator.select_llm": "Wybierz model językowy", "app.fullscreen-button.tooltip": "Tryb pełnoekranowy, skupiony na bieżącym dokumencie lub stronie.", + "app.hotkey.dialog.help_text": "Kliknij ramki powyżej • Klawisz 0 przełącza czat • Klawisze 1–9 skupiają ramki • Wpisz, aby wyszukać • ↑↓ nawigacja • Return, aby otworzyć • ESC, aby zamknąć", + "app.hotkey.dialog.search_placeholder": "Szukaj plików i stron...", + "app.hotkey.dialog.title": "Szybka nawigacja", "app.verify-email-banner.edit": "Jeśli adres e-mail jest niepoprawny, proszę edytować go w ustawieniach konta.", "app.verify-email-banner.help.text": "Ważne jest posiadanie działającego adresu e-mail. Używamy go do resetowania haseł, wysyłania wiadomości, powiadomień o rozliczeniach i wsparcia. Upewnij się, że Twój e-mail jest poprawny, aby być na bieżąco.", "app.verify-email-banner.text": "{sent, select, true {Email wysłany! Sprawdź swoją skrzynkę odbiorczą (a może spam) i kliknij link potwierdzający.} other {Proszę sprawdzić i zweryfikować swój adres e-mail:}}", @@ -988,6 +988,7 @@ "labels.config": "Konfiguracja", "labels.configuration": "Konfiguracja", "labels.configuration.short": "Konfiguracja", + "labels.connected": "Połączono", "labels.connecting": "Łączenie", "labels.connection": "Połączenie", "labels.copied": "skopiowano", @@ -1016,6 +1017,7 @@ "labels.environment": "Środowisko", "labels.explorer": "Eksplorator", "labels.file_explorer": "Eksplorator plików", + "labels.file_use_notifications": "Powiadomienia o użyciu plików", "labels.files": "Pliki", "labels.folder": "folder", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Publiczny} read_only {Tylko do odczytu} other {Zapisz}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "Zatrzymaj{short, select, true {} other { Projekt}}…", "labels.project.settings.stop-project.ok": "Tak, zatrzymaj projekt", "labels.projects": "Projekty", + "labels.public_paths": "Ścieżki publiczne", "labels.published_files": "Opublikowane Pliki", "labels.purchases": "Zakupy", "labels.ready": "Gotowe", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "Wybierz tytuł. Możesz łatwo zmienić go później!", "projects.create-project.requireLicense": "Do utworzenia dodatkowych projektów wymagana jest licencja.", "projects.filename-search.placeholder": "Szukaj nazw plików, które edytowałeś...", - "projects.list.no_starred_found": "Nie znaleziono oznaczonych projektów. Użyj ikony gwiazdki obok tytułów projektów, aby dodać do zakładek swoje ulubione projekty.", "projects.load-all.label": "Pokaż wszystkie projekty...", "projects.operations.clear-filter": "Wyczyść filtr", "projects.operations.delete.button": "{deleted, select, true {Przywróć wszystko} other {Usuń wszystko}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "Filtruj według hashtagów...", "projects.table-controls.hidden.label": "Ukryty", "projects.table-controls.search.placeholder": "Szukaj projektów...", + "projects.table.keyboard-row-hint": "Projekt {title}. Użyj strzałek w górę i w dół, aby się poruszać; naciśnij Enter lub Spację, aby otworzyć.", "projects.table.last-edited": "Ostatnia edycja", + "projects.table.untitled": "Bez tytułu", "purchases.automatic-payments-warning.description": "Automatyczne płatności są znacznie wygodniejsze, zaoszczędzą Twój czas i zapewnią, że subskrypcje nie zostaną anulowane przez przypadek.", "purchases.automatic-payments-warning.title": "Automatyczne płatności NIE są wymagane do posiadania subskrypcji", "purchases.automatic-payments.are-enabled": "Automatyczne Płatności są Włączone", diff --git a/src/packages/frontend/i18n/trans/pt_BR.json b/src/packages/frontend/i18n/trans/pt_BR.json index ae7b547ba09..a3699e2010f 100644 --- a/src/packages/frontend/i18n/trans/pt_BR.json +++ b/src/packages/frontend/i18n/trans/pt_BR.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Informações de Tradução", "account.account-button.confirm.ok": "Sim, sair", "account.account-button.confirm.title": "Sair da sua conta?", + "account.appearance.accessibility.enabled": "Ativar o Modo de Acessibilidade: otimizar a interface do usuário para recursos de acessibilidade", + "account.appearance.accessibility.title": "Acessibilidade", "account.appearance.user_interface.title": "Interface do Usuário", "account.delete-account.alert.description": "Você imediatamente perderá acesso a todos os seus projetos, quaisquer assinaturas serão canceladas e todo crédito não utilizado será perdido. {br} {hr} Para DELETAR SUA CONTA, primeiro digite \"{required_text}\" abaixo:", "account.delete-account.alert.message": "Tem certeza de que deseja EXCLUIR SUA CONTA?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "remover sempre que o arquivo for salvo", "account.editor-settings-autosave-interval.label": "Intervalo de salvamento automático", "account.editor-settings.basic.title": "Configurações Básicas", - "account.editor-settings.color-schemes.label": "Esquema de cores do editor", "account.editor-settings.color-schemes.panel_title": "Esquema de Cores do Editor", "account.editor-settings.font-size.label": "Tamanho padrão da fonte global", "account.editor-settings.indent-size.label": "Tamanho da indentação", "account.editor-settings.keyboard-bindings.label": "Atalhos de teclado do editor", "account.editor-settings.keyboard.title": "Teclado", - "account.editor-settings.title": "Configurações do Editor", "account.editor-settings.x11-keyboard-variant.label": "Variante do teclado (para X11 Desktop)", "account.editor-settings.x11-physical-keyboard.label": "Layout de teclado (para X11 Desktop)", "account.global-ssh-keys.help": "Para acessar um projeto via SSH, use o seguinte username@host: [project-id-without-dashes]@ssh.cocalc.com O ID do projeto sem traços pode ser encontrado na parte das configurações do projeto sobre chaves SSH. Para acessar entre projetos via SSH, use [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Divisão de visualização na planilha Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Alternar comentário da seleção", "account.other-settings._page_size.label": "Número de arquivos por página", - "account.other-settings.browser_performance.title": "Navegador", + "account.other-settings.auto_focus": "Focar Automaticamente no Campo de Texto: focar automaticamente nos campos de entrada de texto quando eles aparecem (por exemplo, explorador de arquivos, projetos, ...)", "account.other-settings.button_tooltips": "Ocultar Dicas de Botões: oculta algumas dicas de botões (isso é apenas parcial)", "account.other-settings.confirm_close": "Confirmar Fechamento: sempre pedir confirmação antes de fechar a janela do navegador", - "account.other-settings.content_display.title": "Exibição de Conteúdo", "account.other-settings.default_file_sort.by_name": "Ordenar por nome", "account.other-settings.default_file_sort.by_time": "Ordenar por tempo", "account.other-settings.default_file_sort.label": "Ordenação padrão de arquivos", + "account.other-settings.dim_file_extensions": "Extensões de arquivo em cinza: desbotar extensões de arquivo para que seus nomes se destaquem.", "account.other-settings.file_popovers": "Ocultar Popovers de Abas de Arquivos: não mostrar os popovers sobre as abas de arquivos", "account.other-settings.filename_generator.description": "Selecione como os nomes de arquivos gerados automaticamente são criados. Em particular, para torná-los únicos ou incluir a hora atual.", "account.other-settings.filename_generator.label": "Gerador de nomes de arquivo", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "Configurações de IA", "account.other-settings.markdown_codebar": "Desativar a barra de código markdown em todos os documentos markdown. Marcar isso oculta os botões extras de executar, copiar e explicar em blocos de código delimitados.", "account.other-settings.mask_files": "Mascarar Arquivos: escurecer arquivos no visualizador de arquivos que você provavelmente não deseja abrir", - "account.other-settings.messages.title": "Mensagens", "account.other-settings.project_popovers": "Ocultar Pop-ups das Abas do Projeto: não mostrar os pop-ups sobre as abas do projeto", - "account.other-settings.projects.title": "Projetos", "account.other-settings.standby_timeout": "Tempo de espera em espera", "account.other-settings.symbol_bar_labels": "Mostrar Rótulos da Barra de Símbolos: mostrar rótulos na barra de símbolos do editor de quadros", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animações: animar brevemente alguns aspectos, por exemplo, botões", "account.other-settings.theme.antd.color_scheme": "Esquema de Cores: use cores da marca em vez de cores padrão", "account.other-settings.theme.antd.compact": "Design Compacto: use um design mais compacto", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "Gerar um Documento {docName} usando IA", "ai-generator.select_llm": "Selecionar modelo de linguagem", "app.fullscreen-button.tooltip": "Modo de tela cheia, focado no documento ou página atual.", + "app.hotkey.dialog.help_text": "Clique nos quadros acima • Tecla 0 alterna chat • Teclas 1–9 focam quadros • Digite para pesquisar • ↑↓ navegar • Enter para abrir • ESC para fechar", + "app.hotkey.dialog.search_placeholder": "Pesquisar arquivos e páginas...", + "app.hotkey.dialog.title": "Navegação Rápida", "app.verify-email-banner.edit": "Se o endereço de e-mail estiver errado, por favor edite nas configurações da conta.", "app.verify-email-banner.help.text": "É importante ter um endereço de e-mail funcional. Nós o usamos para redefinir senhas, enviar mensagens, notificações de cobrança e suporte. Por favor, certifique-se de que seu e-mail está correto para se manter informado.", "app.verify-email-banner.text": "{sent, select, true {Email enviado! Por favor, verifique a caixa de entrada do seu email (e talvez o spam) e clique no link de confirmação.} other {Por favor, verifique e confirme seu endereço de email:}}", @@ -988,6 +988,7 @@ "labels.config": "Configuração", "labels.configuration": "Configuração", "labels.configuration.short": "Configuração", + "labels.connected": "Conectado", "labels.connecting": "Conectando", "labels.connection": "Conexão", "labels.copied": "copiado", @@ -1016,6 +1017,7 @@ "labels.environment": "Ambiente", "labels.explorer": "Explorador", "labels.file_explorer": "Explorador de Arquivos", + "labels.file_use_notifications": "Notificações de Uso de Arquivo", "labels.files": "Arquivos", "labels.folder": "pasta", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Público} read_only {Somente leitura} other {Salvar}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "Parar{short, select, true {} other { Projeto}}…", "labels.project.settings.stop-project.ok": "Sim, parar projeto", "labels.projects": "Projetos", + "labels.public_paths": "Caminhos Públicos", "labels.published_files": "Publicado", "labels.purchases": "Compras", "labels.ready": "Pronto", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "Escolha um título. Você pode alterá-lo facilmente depois!", "projects.create-project.requireLicense": "É necessária uma licença para criar projetos adicionais.", "projects.filename-search.placeholder": "Pesquisar por nomes de arquivos que você editou...", - "projects.list.no_starred_found": "Nenhum projeto marcado com estrela encontrado. Use o ícone de estrela ao lado dos títulos dos projetos para marcar seus projetos favoritos.", "projects.load-all.label": "Mostrar todos os projetos...", "projects.operations.clear-filter": "Limpar Filtro", "projects.operations.delete.button": "{deleted, select, true {Restaurar Tudo} other {Excluir Tudo}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "Filtrar por hashtags...", "projects.table-controls.hidden.label": "Oculto", "projects.table-controls.search.placeholder": "Buscar projetos...", + "projects.table.keyboard-row-hint": "Projeto {title}. Use as setas para cima e para baixo para mover; pressione Enter ou Espaço para abrir.", "projects.table.last-edited": "Última Edição", + "projects.table.untitled": "Sem título", "purchases.automatic-payments-warning.description": "Pagamentos automáticos são muito mais convenientes, vão economizar seu tempo e garantir que assinaturas não sejam canceladas por acidente.", "purchases.automatic-payments-warning.title": "Pagamentos automáticos NÃO são necessários para ter uma assinatura", "purchases.automatic-payments.are-enabled": "Pagamentos Automáticos estão Ativados", diff --git a/src/packages/frontend/i18n/trans/pt_PT.json b/src/packages/frontend/i18n/trans/pt_PT.json index e5c65f6b920..73adcfff584 100644 --- a/src/packages/frontend/i18n/trans/pt_PT.json +++ b/src/packages/frontend/i18n/trans/pt_PT.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Informação sobre a Tradução", "account.account-button.confirm.ok": "Sim, terminar sessão", "account.account-button.confirm.title": "Sair da sua conta?", + "account.appearance.accessibility.enabled": "Ativar Modo de Acessibilidade: otimizar a interface do utilizador para funcionalidades de acessibilidade", + "account.appearance.accessibility.title": "Acessibilidade", "account.appearance.user_interface.title": "Interface do Utilizador", "account.delete-account.alert.description": "Perderá imediatamente o acesso a todos os seus projetos, quaisquer subscrições serão canceladas e todo o crédito não utilizado será perdido. {br} {hr} Para ELIMINAR A SUA CONTA, primeiro introduza \"{required_text}\" abaixo:", "account.delete-account.alert.message": "Tem a certeza de que quer ELIMINAR A SUA CONTA?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "remover sempre que o ficheiro for guardado", "account.editor-settings-autosave-interval.label": "Intervalo de Autossalvamento", "account.editor-settings.basic.title": "Configurações Básicas", - "account.editor-settings.color-schemes.label": "Esquema de cores do editor", "account.editor-settings.color-schemes.panel_title": "Esquema de Cores do Editor", "account.editor-settings.font-size.label": "Tamanho de fonte global padrão", "account.editor-settings.indent-size.label": "Tamanho da indentação", "account.editor-settings.keyboard-bindings.label": "Ligações de teclado do editor", "account.editor-settings.keyboard.title": "Teclado", - "account.editor-settings.title": "Editor", "account.editor-settings.x11-keyboard-variant.label": "Variante de teclado (para Desktop X11)", "account.editor-settings.x11-physical-keyboard.label": "Distribuição do teclado (para X11 Desktop)", "account.global-ssh-keys.help": "Para SSH em um projeto, use o seguinte username@host: [project-id-without-dashes]@ssh.cocalc.com O ID do projeto sem traços pode ser encontrado na parte das configurações do projeto sobre chaves SSH. Para SSH entre projetos, use [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Vista dividida na folha de cálculo Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Alternar comentário da seleção", "account.other-settings._page_size.label": "Número de ficheiros por página", - "account.other-settings.browser_performance.title": "Navegador", + "account.other-settings.auto_focus": "Focar Automaticamente no Campo de Texto: focar automaticamente nos campos de texto quando eles aparecem (por exemplo, explorador de ficheiros, projetos, ...)", "account.other-settings.button_tooltips": "Ocultar Dicas de Botão: oculta algumas dicas de botão (isto é apenas parcial)", "account.other-settings.confirm_close": "Confirmar Fecho: pedir sempre confirmação antes de fechar a janela do navegador", - "account.other-settings.content_display.title": "Exibição de Conteúdo", "account.other-settings.default_file_sort.by_name": "Ordenar por nome", "account.other-settings.default_file_sort.by_time": "Ordenar por tempo", "account.other-settings.default_file_sort.label": "Ordenação padrão de ficheiros", + "account.other-settings.dim_file_extensions": "Extensões de ficheiros em cinzento: atenuar as extensões de ficheiros para que os seus nomes se destaquem.", "account.other-settings.file_popovers": "Ocultar Popovers das Abas de Ficheiro: não mostrar os popovers sobre as abas de ficheiro", "account.other-settings.filename_generator.description": "Selecione como os nomes de ficheiros gerados automaticamente são criados. Em particular, para torná-los únicos ou incluir a hora atual.", "account.other-settings.filename_generator.label": "Gerador de nomes de ficheiros", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "Configurações de IA", "account.other-settings.markdown_codebar": "Desativar a barra de código markdown em todos os documentos markdown. Marcar esta opção oculta os botões extra de execução, cópia e explicação em blocos de código delimitados.", "account.other-settings.mask_files": "Mascarar ficheiros: esbater ficheiros no visualizador de ficheiros que provavelmente não quer abrir", - "account.other-settings.messages.title": "Mensagens", "account.other-settings.project_popovers": "Ocultar Pop-ups das Abas do Projeto: não mostrar os pop-ups sobre as abas do projeto", - "account.other-settings.projects.title": "Projetos", "account.other-settings.standby_timeout": "Tempo limite de espera", "account.other-settings.symbol_bar_labels": "Mostrar Etiquetas da Barra de Símbolos: mostrar etiquetas na barra de símbolos do editor de frames", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animações: animar brevemente alguns aspetos, por exemplo, botões", "account.other-settings.theme.antd.color_scheme": "Esquema de Cores: usar cores da marca em vez de cores padrão", "account.other-settings.theme.antd.compact": "Design Compacto: usar um design mais compacto", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "Gerar um Documento {docName} usando IA", "ai-generator.select_llm": "Selecionar modelo de linguagem", "app.fullscreen-button.tooltip": "Modo de ecrã completo, focado no documento ou página atual", + "app.hotkey.dialog.help_text": "Clique nas molduras acima • A tecla 0 alterna o chat • As teclas 1–9 focam nas molduras • Digite para pesquisar • ↑↓ navegue • Enter para abrir • ESC para fechar", + "app.hotkey.dialog.search_placeholder": "Procurar ficheiros e páginas...", + "app.hotkey.dialog.title": "Navegação Rápida", "app.verify-email-banner.edit": "Se o endereço de email estiver errado, por favor edite nas configurações da conta.", "app.verify-email-banner.help.text": "É importante ter um endereço de email funcional. Usamo-lo para redefinições de senha, envio de mensagens, notificações de faturação e suporte. Por favor, certifique-se de que o seu email está correto para se manter informado.", "app.verify-email-banner.text": "{sent, select, true {Email Enviado! Por favor, verifique a sua caixa de entrada de email (e talvez o spam) e clique no link de confirmação.} other {Por favor, verifique e confirme o seu endereço de email:}}", @@ -988,6 +988,7 @@ "labels.config": "Configuração", "labels.configuration": "Configuração", "labels.configuration.short": "Configurar", + "labels.connected": "Ligado", "labels.connecting": "A ligar", "labels.connection": "Ligação", "labels.copied": "copiado", @@ -1016,6 +1017,7 @@ "labels.environment": "Ambiente", "labels.explorer": "Explorador", "labels.file_explorer": "Explorador de Ficheiros", + "labels.file_use_notifications": "Notificações de Uso de Ficheiro", "labels.files": "Ficheiros", "labels.folder": "pasta", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Público} read_only {Só de leitura} other {Guardar}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "Parar{short, select, true {} other { Projeto}}…", "labels.project.settings.stop-project.ok": "Sim, parar projeto", "labels.projects": "Projetos", + "labels.public_paths": "Caminhos Públicos", "labels.published_files": "Ficheiros Publicados", "labels.purchases": "Compras", "labels.ready": "Pronto", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "Escolha um título. Pode alterá-lo facilmente mais tarde!", "projects.create-project.requireLicense": "É necessária uma licença para criar projetos adicionais.", "projects.filename-search.placeholder": "Procurar por nomes de ficheiros que editou...", - "projects.list.no_starred_found": "Nenhum projeto com estrela encontrado. Use o ícone de estrela ao lado dos títulos dos projetos para marcar os seus projetos favoritos.", "projects.load-all.label": "Mostrar todos os projetos...", "projects.operations.clear-filter": "Limpar Filtro", "projects.operations.delete.button": "{deleted, select, true {Recuperar Tudo} other {Eliminar Tudo}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "Filtrar por hashtags...", "projects.table-controls.hidden.label": "Oculto", "projects.table-controls.search.placeholder": "Procurar projetos...", + "projects.table.keyboard-row-hint": "Projeto {title}. Use as setas para cima e para baixo para mover; pressione Enter ou Espaço para abrir.", "projects.table.last-edited": "Última Edição", + "projects.table.untitled": "Sem título", "purchases.automatic-payments-warning.description": "Os pagamentos automáticos são muito mais convenientes, irão poupar-lhe tempo e garantir que as subscrições não sejam canceladas por acidente.", "purchases.automatic-payments-warning.title": "Não são necessários pagamentos automáticos para ter uma subscrição", "purchases.automatic-payments.are-enabled": "Pagamentos Automáticos estão Ativados", diff --git a/src/packages/frontend/i18n/trans/ru_RU.json b/src/packages/frontend/i18n/trans/ru_RU.json index 74e072a11cd..04d0331b7c4 100644 --- a/src/packages/frontend/i18n/trans/ru_RU.json +++ b/src/packages/frontend/i18n/trans/ru_RU.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Информация о переводе", "account.account-button.confirm.ok": "Да, выйти", "account.account-button.confirm.title": "Выйти из вашего аккаунта?", + "account.appearance.accessibility.enabled": "Включить режим доступности: оптимизировать пользовательский интерфейс для функций доступности", + "account.appearance.accessibility.title": "Доступность", "account.appearance.user_interface.title": "Пользовательский интерфейс", "account.delete-account.alert.description": "Вы немедленно потеряете доступ ко всем вашим проектам, все подписки будут отменены, а все неиспользованные средства будут утрачены. {br} {hr} Чтобы УДАЛИТЬ ВАШ АККАУНТ, сначала введите \"{required_text}\" ниже:", "account.delete-account.alert.message": "Вы уверены, что хотите УДАЛИТЬ ВАШ АККАУНТ?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "удалить при каждом сохранении файла", "account.editor-settings-autosave-interval.label": "Интервал автосохранения", "account.editor-settings.basic.title": "Основные настройки", - "account.editor-settings.color-schemes.label": "Цветовая схема редактора", "account.editor-settings.color-schemes.panel_title": "Цветовая схема редактора", "account.editor-settings.font-size.label": "Размер шрифта по умолчанию", "account.editor-settings.indent-size.label": "Размер отступа", "account.editor-settings.keyboard-bindings.label": "Клавиатурные привязки редактора", "account.editor-settings.keyboard.title": "Клавиатура", - "account.editor-settings.title": "Редактор", "account.editor-settings.x11-keyboard-variant.label": "Вариант клавиатуры (для X11 Desktop)", "account.editor-settings.x11-physical-keyboard.label": "Раскладка клавиатуры (для X11 Desktop)", "account.global-ssh-keys.help": "Для SSH в проект используйте следующий username@host: [project-id-without-dashes]@ssh.cocalc.com Идентификатор проекта без тире можно найти в разделе настроек проекта о SSH-ключах. Для SSH между проектами используйте [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Разделить вид в рабочем листе Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Переключить комментирование выделения", "account.other-settings._page_size.label": "Количество файлов на странице", - "account.other-settings.browser_performance.title": "Браузер", + "account.other-settings.auto_focus": "Автоматическая фокусировка текстового ввода: автоматически фокусировать поля ввода текста, когда они появляются (например, проводник файлов, проекты, ...)", "account.other-settings.button_tooltips": "Скрыть подсказки кнопок: скрывает некоторые подсказки кнопок (это только частично)", "account.other-settings.confirm_close": "Подтвердите закрытие: всегда запрашивать подтверждение перед закрытием окна браузера", - "account.other-settings.content_display.title": "Отображение контента", "account.other-settings.default_file_sort.by_name": "Сортировать по имени", "account.other-settings.default_file_sort.by_time": "Сортировать по времени", "account.other-settings.default_file_sort.label": "Сортировка файлов по умолчанию", + "account.other-settings.dim_file_extensions": "Затемнить расширения файлов: сделать расширения файлов серыми, чтобы их названия выделялись.", "account.other-settings.file_popovers": "Скрыть всплывающие подсказки вкладок файлов: не показывать всплывающие подсказки над вкладками файлов", "account.other-settings.filename_generator.description": "Выберите способ автоматической генерации имен файлов. В частности, чтобы сделать их уникальными или включить текущее время.", "account.other-settings.filename_generator.label": "Генератор имен файлов", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "Настройки AI", "account.other-settings.markdown_codebar": "Отключить панель кода markdown во всех markdown документах. Если установить этот флажок, будут скрыты дополнительные кнопки запуска, копирования и объяснения в блоках кода.", "account.other-settings.mask_files": "Маскировать файлы: затенить файлы в просмотрщике файлов, которые вы, вероятно, не захотите открывать", - "account.other-settings.messages.title": "Сообщения", "account.other-settings.project_popovers": "Скрыть всплывающие подсказки вкладок проекта: не показывать всплывающие подсказки над вкладками проекта", - "account.other-settings.projects.title": "Проекты", "account.other-settings.standby_timeout": "Таймаут ожидания", "account.other-settings.symbol_bar_labels": "Показать метки панели символов: показывать метки на панели символов в редакторе фреймов", - "account.other-settings.theme": "Тема", "account.other-settings.theme.antd.animations": "Анимации: кратко анимировать некоторые аспекты, например, кнопки", "account.other-settings.theme.antd.color_scheme": "Цветовая схема: использовать фирменные цвета вместо стандартных цветов", "account.other-settings.theme.antd.compact": "Компактный дизайн: использовать более компактный дизайн", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "Сгенерировать документ {docName} с помощью ИИ", "ai-generator.select_llm": "Выберите языковую модель", "app.fullscreen-button.tooltip": "Полноэкранный режим, сосредоточенный на текущем документе или странице", + "app.hotkey.dialog.help_text": "Щелкните по кадрам сверху • Клавиша 0 переключает чат • Клавиши 1–9 фокусируются на кадрах • Введите для поиска • ↑↓ навигация • Return для открытия • ESC для закрытия", + "app.hotkey.dialog.search_placeholder": "Искать файлы и страницы...", + "app.hotkey.dialog.title": "Быстрая навигация", "app.verify-email-banner.edit": "Если адрес электронной почты неверен, пожалуйста, отредактируйте его в настройках аккаунта.", "app.verify-email-banner.help.text": "Важно иметь работающий адрес электронной почты. Мы используем его для сброса пароля, отправки сообщений, уведомлений о выставлении счетов и поддержки. Пожалуйста, убедитесь, что ваш адрес электронной почты правильный, чтобы оставаться в курсе событий.", "app.verify-email-banner.text": "{sent, select, true {Электронное письмо отправлено! Пожалуйста, проверьте ваш почтовый ящик (и, возможно, спам) и нажмите на ссылку для подтверждения.} other {Пожалуйста, проверьте и подтвердите ваш адрес электронной почты:}}", @@ -988,6 +988,7 @@ "labels.config": "Конфигурация", "labels.configuration": "Конфигурация", "labels.configuration.short": "Конфиг", + "labels.connected": "Подключено", "labels.connecting": "Соединение", "labels.connection": "Соединение", "labels.copied": "скопировано", @@ -1016,6 +1017,7 @@ "labels.environment": "Окружение", "labels.explorer": "Проводник", "labels.file_explorer": "Проводник файлов", + "labels.file_use_notifications": "Уведомления об использовании файлов", "labels.files": "Файлы", "labels.folder": "папка", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Публичный} read_only {Только чтение} other {Сохранить}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "Остановить{short, select, true {} other { Проект}}…", "labels.project.settings.stop-project.ok": "Да, остановить проект", "labels.projects": "Проекты", + "labels.public_paths": "Публичные пути", "labels.published_files": "Опубликованные файлы", "labels.purchases": "Покупки", "labels.ready": "Готово", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "Выберите название. Вы всегда можете изменить его позже!", "projects.create-project.requireLicense": "Для создания дополнительных проектов требуется лицензия.", "projects.filename-search.placeholder": "Искать измененные вами файлы...", - "projects.list.no_starred_found": "Не найдено избранных проектов. Используйте значок звезды рядом с названиями проектов, чтобы добавить в закладки ваши любимые проекты.", "projects.load-all.label": "Показать все проекты...", "projects.operations.clear-filter": "Очистить фильтр", "projects.operations.delete.button": "{deleted, select, true {Восстановить все} other {Удалить все}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "Фильтр по хэштегам...", "projects.table-controls.hidden.label": "Скрыто", "projects.table-controls.search.placeholder": "Поиск проектов...", + "projects.table.keyboard-row-hint": "Проект {title}. Используйте стрелки вверх и вниз для перемещения; нажмите Enter или пробел для открытия.", "projects.table.last-edited": "Последнее изменение", + "projects.table.untitled": "Без названия", "purchases.automatic-payments-warning.description": "Автоматические платежи гораздо удобнее, сэкономят вам время и гарантируют, что подписки не будут случайно отменены.", "purchases.automatic-payments-warning.title": "Автоматические платежи НЕ требуются для подписки", "purchases.automatic-payments.are-enabled": "Автоматические платежи включены", diff --git a/src/packages/frontend/i18n/trans/tr_TR.json b/src/packages/frontend/i18n/trans/tr_TR.json index 60add4cbb48..29d9ebc0baa 100644 --- a/src/packages/frontend/i18n/trans/tr_TR.json +++ b/src/packages/frontend/i18n/trans/tr_TR.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Çeviri Bilgisi", "account.account-button.confirm.ok": "Evet, çıkış yap", "account.account-button.confirm.title": "Hesabınızdan çıkış yapmak mı?", + "account.appearance.accessibility.enabled": "Erişilebilirlik Modunu Etkinleştir: kullanıcı arayüzünü erişilebilirlik özelliklerine göre optimize et", + "account.appearance.accessibility.title": "Erişilebilirlik", "account.appearance.user_interface.title": "Kullanıcı Arayüzü", "account.delete-account.alert.description": "Derhal tüm projelerinize erişimi kaybedeceksiniz, abonelikler iptal edilecek ve kullanılmamış tüm krediler kaybolacak. {br} {hr} HESABINIZI SİLMEK için önce aşağıya \"{required_text}\" yazın:", "account.delete-account.alert.message": "HESABINIZI SİLMEK istediğinizden emin misiniz?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "dosya her kaydedildiğinde kaldır", "account.editor-settings-autosave-interval.label": "Otomatik kaydetme aralığı", "account.editor-settings.basic.title": "Temel Ayarlar", - "account.editor-settings.color-schemes.label": "Editör renk şeması", "account.editor-settings.color-schemes.panel_title": "Editör Renk Şeması", "account.editor-settings.font-size.label": "Varsayılan genel yazı tipi boyutu", "account.editor-settings.indent-size.label": "Girinti boyutu", "account.editor-settings.keyboard-bindings.label": "Editör klavye bağlamaları", "account.editor-settings.keyboard.title": "Klavye", - "account.editor-settings.title": "Editör", "account.editor-settings.x11-keyboard-variant.label": "Klavye çeşidi (X11 Masaüstü için)", "account.editor-settings.x11-physical-keyboard.label": "Klavye düzeni (X11 Masaüstü için)", "account.global-ssh-keys.help": "Bir projeye SSH ile bağlanmak için şu adresi kullanın username@host: [project-id-without-dashes]@ssh.cocalc.com Proje id'si tireler olmadan, SSH anahtarlarıyla ilgili proje ayarları bölümünde bulunabilir. Projeler arasında SSH yapmak için [project-id-without-dashes]@ssh kullanın", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Sage çalışma sayfasında bölünmüş görünüm", "account.keyboard-shortcuts.shortcut.toggle-comment": "Yorum satırını değiştir", "account.other-settings._page_size.label": "Sayfa başına dosya sayısı", - "account.other-settings.browser_performance.title": "Tarayıcı", + "account.other-settings.auto_focus": "Otomatik Odak Metin Girişi: metin giriş alanlarını göründüklerinde otomatik olarak odakla (ör. dosya gezgini, projeler, ...)", "account.other-settings.button_tooltips": "Düğme İpuçlarını Gizle: bazı düğme ipuçlarını gizler (bu sadece kısmi)", "account.other-settings.confirm_close": "Kapatmayı Onayla: tarayıcı penceresini kapatmadan önce her zaman onay iste", - "account.other-settings.content_display.title": "İçerik Görüntüleme", "account.other-settings.default_file_sort.by_name": "Ada göre sırala", "account.other-settings.default_file_sort.by_time": "Zamana göre sırala", "account.other-settings.default_file_sort.label": "Varsayılan dosya sıralaması", + "account.other-settings.dim_file_extensions": "Dosya uzantılarını soluklaştır: dosya uzantılarını gri yaparak adlarının öne çıkmasını sağla.", "account.other-settings.file_popovers": "Dosya Sekmesi Açılır Pencerelerini Gizle: dosya sekmelerinin üzerindeki açılır pencereleri gösterme", "account.other-settings.filename_generator.description": "Otomatik olarak oluşturulan dosya adlarının nasıl oluşturulacağını seçin. Özellikle, onları benzersiz yapmak veya mevcut zamanı eklemek için.", "account.other-settings.filename_generator.label": "Dosya adı oluşturucu", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "AI Ayarları", "account.other-settings.markdown_codebar": "Tüm markdown belgelerinde markdown kod çubuğunu devre dışı bırak. Bunu işaretlemek, çitlenmiş kod bloklarındaki ekstra çalıştır, kopyala ve açıkla düğmelerini gizler.", "account.other-settings.mask_files": "Dosya maskesi: Dosya görüntüleyicide muhtemelen açmak istemediğiniz dosyaları gri renkte göster", - "account.other-settings.messages.title": "Mesajlar", "account.other-settings.project_popovers": "Proje Sekmesi Açılır Pencerelerini Gizle: proje sekmelerinin üzerindeki açılır pencereleri gösterme", - "account.other-settings.projects.title": "Projeler", "account.other-settings.standby_timeout": "Bekleme zaman aşımı", "account.other-settings.symbol_bar_labels": "Sembol Çubuğu Etiketlerini Göster: çerçeve düzenleyici sembol çubuğunda etiketleri göster", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animasyonlar: bazı unsurları, örneğin düğmeleri kısaca canlandır", "account.other-settings.theme.antd.color_scheme": "Renk Şeması: varsayılan renkler yerine marka renklerini kullan", "account.other-settings.theme.antd.compact": "Kompakt Tasarım: daha kompakt bir tasarım kullan", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "Yapay Zeka kullanarak bir {docName} Belgesi oluştur", "ai-generator.select_llm": "Dil modelini seç", "app.fullscreen-button.tooltip": "Tam ekran modu, mevcut belge veya sayfaya odaklanılmış.", + "app.hotkey.dialog.help_text": "Yukarıdaki çerçevelere tıklayın • 0 tuşu sohbeti açar/kapatır • 1–9 tuşları çerçevelere odaklanır • Aramak için yazın • ↑↓ gezin • Açmak için Enter • Kapatmak için ESC", + "app.hotkey.dialog.search_placeholder": "Dosyaları ve sayfaları ara...", + "app.hotkey.dialog.title": "Hızlı Gezinme", "app.verify-email-banner.edit": "E-posta adresi yanlışsa, lütfen hesap ayarlarında düzenleyin.", "app.verify-email-banner.help.text": "Çalışan bir e-posta adresine sahip olmak önemlidir. Şifre sıfırlama, mesaj gönderme, fatura bildirimleri ve destek için kullanıyoruz. Bilgilendirilmek için e-postanızın doğru olduğundan emin olun.", "app.verify-email-banner.text": "{sent, select, true {E-posta Gönderildi! Lütfen e-posta gelen kutunuzu (ve belki spam klasörünü) kontrol edin ve onay bağlantısına tıklayın.} other {Lütfen e-posta adresinizi kontrol edin ve doğrulayın:}}", @@ -988,6 +988,7 @@ "labels.config": "Yapılandırma", "labels.configuration": "Yapılandırma", "labels.configuration.short": "Yapılandırma", + "labels.connected": "Bağlandı", "labels.connecting": "Bağlanıyor", "labels.connection": "Bağlantı", "labels.copied": "kopyalandı", @@ -1016,6 +1017,7 @@ "labels.environment": "Ortam", "labels.explorer": "Gezer", "labels.file_explorer": "Dosya Gezgini", + "labels.file_use_notifications": "Dosya Kullanım Bildirimleri", "labels.files": "Dosyalar", "labels.folder": "klasör", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Herkese Açık} read_only {Salt Okunur} other {Kaydet}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "Durdur{short, select, true {} other { Proje}}…", "labels.project.settings.stop-project.ok": "Evet, projeyi durdur", "labels.projects": "Projeler", + "labels.public_paths": "Genel Yollar", "labels.published_files": "Yayınlanan Dosyalar", "labels.purchases": "Satın Alımlar", "labels.ready": "Hazır", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "Bir başlık seçin. Daha sonra kolayca değiştirebilirsiniz!", "projects.create-project.requireLicense": "Ek projeler oluşturmak için bir lisans gereklidir.", "projects.filename-search.placeholder": "Düzenlediğiniz dosya adlarını arayın...", - "projects.list.no_starred_found": "Yıldızlı proje bulunamadı. Favori projelerinizi işaretlemek için proje başlıklarının yanındaki yıldız simgesini kullanın.", "projects.load-all.label": "Tüm projeleri göster...", "projects.operations.clear-filter": "Filtreyi Temizle", "projects.operations.delete.button": "{deleted, select, true {Tümünü Geri Al} other {Tümünü Sil}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "Etiketlere göre filtrele...", "projects.table-controls.hidden.label": "Gizli", "projects.table-controls.search.placeholder": "Projeleri ara...", + "projects.table.keyboard-row-hint": "Proje {title}. Hareket etmek için Yukarı ve Aşağı oklarını kullanın; açmak için Enter veya Boşluk tuşuna basın.", "projects.table.last-edited": "Son Düzenleme", + "projects.table.untitled": "Adsız", "purchases.automatic-payments-warning.description": "Otomatik ödemeler çok daha uygun, zaman kazandırır ve aboneliklerin kazara iptal edilmesini önler.", "purchases.automatic-payments-warning.title": "Otomatik ödemeler bir abonelik için GEREKLİ DEĞİLDİR", "purchases.automatic-payments.are-enabled": "Otomatik Ödemeler Etkinleştirildi", diff --git a/src/packages/frontend/i18n/trans/zh_CN.json b/src/packages/frontend/i18n/trans/zh_CN.json index 66264c8a02d..fd75dd8c88f 100644 --- a/src/packages/frontend/i18n/trans/zh_CN.json +++ b/src/packages/frontend/i18n/trans/zh_CN.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "翻译信息", "account.account-button.confirm.ok": "是,退出", "account.account-button.confirm.title": "要退出您的账户吗", + "account.appearance.accessibility.enabled": "启用辅助功能模式:优化用户界面以支持辅助功能", + "account.appearance.accessibility.title": "无障碍", "account.appearance.user_interface.title": "用户界面", "account.delete-account.alert.description": "您将立即失去对所有项目的访问权限,所有订阅将被取消,所有未用完的积分将丢失。{br} {hr} 要删除您的账户,请首先在下方输入“{required_text}”:", "account.delete-account.alert.message": "您确定要删除您的账户吗?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "每次保存文件时删除", "account.editor-settings-autosave-interval.label": "自动保存间隔", "account.editor-settings.basic.title": "基本设置", - "account.editor-settings.color-schemes.label": "编辑器配色方案", "account.editor-settings.color-schemes.panel_title": "编辑器配色方案", "account.editor-settings.font-size.label": "默认全局字体大小", "account.editor-settings.indent-size.label": "缩进大小", "account.editor-settings.keyboard-bindings.label": "编辑器键盘绑定", "account.editor-settings.keyboard.title": "键盘", - "account.editor-settings.title": "编辑器", "account.editor-settings.x11-keyboard-variant.label": "键盘变体(用于X11桌面)", "account.editor-settings.x11-physical-keyboard.label": "键盘布局(用于X11桌面)", "account.global-ssh-keys.help": "要 SSH 进入一个项目,请使用以下 username@host: [project-id-without-dashes]@ssh.cocalc.com 无破折号的项目 ID 可以在项目设置中关于 SSH 密钥的部分找到。要在项目之间 SSH,请使用 [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "在 Sage 工作表中分割视图", "account.keyboard-shortcuts.shortcut.toggle-comment": "切换注释选择", "account.other-settings._page_size.label": "每页文件数量", - "account.other-settings.browser_performance.title": "浏览器", + "account.other-settings.auto_focus": "自动聚焦文本输入:文本输入字段出现时自动聚焦(例如,文件浏览器,项目,...)", "account.other-settings.button_tooltips": "隐藏按钮提示:隐藏部分按钮提示(这只是部分隐藏)", "account.other-settings.confirm_close": "确认关闭:在关闭浏览器窗口前始终要求确认", - "account.other-settings.content_display.title": "内容显示", "account.other-settings.default_file_sort.by_name": "按名称排序", "account.other-settings.default_file_sort.by_time": "按时间排序", "account.other-settings.default_file_sort.label": "默认文件排序", + "account.other-settings.dim_file_extensions": "文件扩展名变暗:将文件扩展名变灰,以突出其名称。", "account.other-settings.file_popovers": "隐藏文件标签弹出窗口:不在文件标签上显示弹出窗口", "account.other-settings.filename_generator.description": "选择如何生成自动生成的文件名。特别是,使它们唯一或包含当前时间。", "account.other-settings.filename_generator.label": "文件名生成器", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "AI设置", "account.other-settings.markdown_codebar": "禁用markdown代码栏 在所有markdown文档中。选中此项会隐藏围栏代码块中的额外运行、复制和解释按钮。", "account.other-settings.mask_files": "屏蔽文件:在文件查看器中将您可能不想打开的文件置灰", - "account.other-settings.messages.title": "消息", "account.other-settings.project_popovers": "隐藏项目标签弹出框:不在项目标签上显示弹出框", - "account.other-settings.projects.title": "项目", "account.other-settings.standby_timeout": "待机超时", "account.other-settings.symbol_bar_labels": "显示符号栏标签:在框架编辑器符号栏中显示标签", - "account.other-settings.theme": "主题", "account.other-settings.theme.antd.animations": "动画:简要动画某些方面,例如按钮", "account.other-settings.theme.antd.color_scheme": "配色方案: 使用品牌颜色而非默认颜色", "account.other-settings.theme.antd.compact": "紧凑设计:使用更紧凑的设计", @@ -155,6 +152,9 @@ "ai-generate-document.modal.title": "使用AI生成{docName}文档", "ai-generator.select_llm": "选择语言模型", "app.fullscreen-button.tooltip": "全屏模式,专注于当前文档或页面", + "app.hotkey.dialog.help_text": "单击上方的框架 • 按键 0 切换聊天 • 按键 1–9 聚焦框架 • 输入以搜索 • ↑↓ 导航 • 回车以打开 • ESC 关闭", + "app.hotkey.dialog.search_placeholder": "搜索文件和页面...", + "app.hotkey.dialog.title": "快速导航", "app.verify-email-banner.edit": "如果电子邮件地址错误,请在账户设置中编辑。", "app.verify-email-banner.help.text": "拥有一个有效的电子邮件地址非常重要。我们用它来重置密码、发送消息、账单通知和支持。请确保您的电子邮件正确以保持信息畅通。", "app.verify-email-banner.text": "{sent, select, true {邮件已发送!请检查您的电子邮箱收件箱(可能还有垃圾邮件)并点击确认链接。} other {请检查并验证您的电子邮箱地址:}}", @@ -988,6 +988,7 @@ "labels.config": "配置", "labels.configuration": "配置", "labels.configuration.short": "配置", + "labels.connected": "已连接", "labels.connecting": "连接中", "labels.connection": "连接", "labels.copied": "已复制", @@ -1016,6 +1017,7 @@ "labels.environment": "环境", "labels.explorer": "文件", "labels.file_explorer": "文件资源管理器", + "labels.file_use_notifications": "文件使用通知", "labels.files": "文件", "labels.folder": "文件夹", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {公开} read_only {只读} other {保存}}", @@ -1095,6 +1097,7 @@ "labels.project.settings.stop-project.label": "停止{short, select, true {} other {项目}}…", "labels.project.settings.stop-project.ok": "是,停止项目", "labels.projects": "项目", + "labels.public_paths": "公共路径", "labels.published_files": "已发布文件", "labels.purchases": "购买", "labels.ready": "准备好", @@ -1423,7 +1426,6 @@ "projects.create-project.helpTxt": "选择一个标题。您可以稍后轻松更改!", "projects.create-project.requireLicense": "创建额外的项目需要许可证。", "projects.filename-search.placeholder": "搜索你编辑过的文件名", - "projects.list.no_starred_found": "未找到加星标的项目。使用项目标题旁边的星标图标来收藏您喜欢的项目。", "projects.load-all.label": "显示所有项目...", "projects.operations.clear-filter": "清除筛选器", "projects.operations.delete.button": "{deleted, select, true {全部取消删除} other {全部删除}}", @@ -1455,7 +1457,9 @@ "projects.table-controls.hashtags.placeholder": "按标签过滤...", "projects.table-controls.hidden.label": "隐藏", "projects.table-controls.search.placeholder": "搜索项目...", + "projects.table.keyboard-row-hint": "项目 {title}。使用向上和向下箭头移动;按 Enter 或空格键打开。", "projects.table.last-edited": "上次编辑", + "projects.table.untitled": "无标题", "purchases.automatic-payments-warning.description": "自动支付更加方便,可以节省时间,并且确保订阅不会意外取消。", "purchases.automatic-payments-warning.title": "自动付款不需要订阅", "purchases.automatic-payments.are-enabled": "自动付款已启用", diff --git a/src/packages/util/consts/ui.ts b/src/packages/util/consts/ui.ts index 4b15d499ca7..d8d65629f0b 100644 --- a/src/packages/util/consts/ui.ts +++ b/src/packages/util/consts/ui.ts @@ -33,8 +33,14 @@ export const R_IDE = "R IDE"; // Default font size for account settings and UI elements export const DEFAULT_FONT_SIZE = 14; -// Icon unicode character for dark mode toggle (◑ - circle with right half black) -export const DARK_MODE_ICON = 0x25d1; +// Icon unicode character for dark mode toggle (☽ - first quarter moon) +export const DARK_MODE_ICON = 0x263d; + +// Icon unicode character for accessibility (♿ - wheelchair symbol) +export const ACCESSIBILITY_ICON = 0x267f; + +// Keyword for accessibility settings, in account settings and URL query parameter +export const A11Y = "accessibility"; // Icon unicode characters for auto-sync arrows in LaTeX editor export const SYNC_FORWARD_ICON = 0x21a6; // ↦ - rightwards arrow $mapto$ From 55e2641ac6f6c4d467c1cfc7fef5fe33b818d897 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Thu, 27 Nov 2025 15:40:34 +0100 Subject: [PATCH 44/58] frontend/projects: fix icon of starred or recent directories --- src/packages/frontend/projects/util.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/packages/frontend/projects/util.tsx b/src/packages/frontend/projects/util.tsx index 8abdbb4360c..6971ad7fd52 100644 --- a/src/packages/frontend/projects/util.tsx +++ b/src/packages/frontend/projects/util.tsx @@ -348,8 +348,10 @@ export function useFilesMenuItems( return files.map((file) => { const filename = typeof file === "string" ? file : file.filename; - const info = file_options(filename); - const icon: IconName = info?.icon ?? "file"; + const isDirectory = filename.endsWith("/"); + const icon: IconName = isDirectory + ? "folder-open" + : (file_options(filename)?.icon ?? "file"); const label = labelStyle ? ( {trunc_middle(filename, truncLength)} From 1b47cc85b3a9a419bed3e57614fb49857fdb6eee Mon Sep 17 00:00:00 2001 From: Andrey Novoseltsev Date: Thu, 27 Nov 2025 16:54:57 -0700 Subject: [PATCH 45/58] settings: improve menu rendering especially in collapsed mode --- .../frontend/account/account-page.tsx | 61 ++++++++----------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/src/packages/frontend/account/account-page.tsx b/src/packages/frontend/account/account-page.tsx index a73bc4391f7..21a02dae6fa 100644 --- a/src/packages/frontend/account/account-page.tsx +++ b/src/packages/frontend/account/account-page.tsx @@ -89,6 +89,20 @@ import { UpgradesPage } from "./upgrades/upgrades-page"; export const ACCOUNT_SETTINGS_ICON_NAME: IconName = "settings"; +const ACCOUNT_MENU_INLINE_CSS = ` +.account-menu-inline-collapsed .ant-menu-item, +.account-menu-inline-collapsed .ant-menu-submenu-title { + padding-inline: 0px; + text-align: center; +} +.account-menu-inline-collapsed .ant-menu-submenu-title { + padding-right: 20px; +} +.account-menu-inline-collapsed .ant-menu-submenu-arrow { + right: 5px; +} +`; + // Type for valid menu keys type MenuKey = | "settings" @@ -494,6 +508,10 @@ export const AccountPage: React.FC = () => { }); } + function visibleLabel(label) { + return hidden ? {label.props.children[0]} : label; + } + // Process tabs to handle nested children for sub-tabs const children = {}; const titles = {}; // Always store full labels for renderTitle() @@ -501,18 +519,15 @@ export const AccountPage: React.FC = () => { if (tab.type == "divider") { continue; } + const originalLabel = tab.label; + titles[tab.key] = originalLabel; + tab.label = visibleLabel(originalLabel); + if (Array.isArray(tab.children)) { // Handle nested submenus generically (preferences, billing, etc.) const subTabs = tab.children; tab.children = subTabs.map((subTab) => { - // When collapsed, show only the icon; otherwise show full label. - const label = hidden ? ( - - {subTab.label.props.children[0]} - - ) : ( - subTab.label - ); + const label = visibleLabel(subTab.label); return { key: subTab.key, label, @@ -524,34 +539,8 @@ export const AccountPage: React.FC = () => { children[subTab.key] = subTab.children; titles[subTab.key] = subTab.label; // Always store original full label } - } else if (tab.key === "settings" || tab.key === "profile") { - // Handle settings and profile as top-level pages - // Store original full label for renderTitle() - const originalLabel = tab.label; - // Extract just the icon (first child) from the span when hidden - tab.label = hidden ? ( - - {tab.label.props.children[0]} - - ) : ( - tab.label - ); - children[tab.key] = tab.children; - titles[tab.key] = originalLabel; // Store original label - delete tab.children; } else { - // Store original full label for renderTitle() - const originalLabel = tab.label; - // Extract just the icon (first child) from the span when hidden - tab.label = hidden ? ( - - {tab.label.props.children[0]} - - ) : ( - tab.label - ); children[tab.key] = tab.children; - titles[tab.key] = originalLabel; // Store original label delete tab.children; } } @@ -612,6 +601,7 @@ export const AccountPage: React.FC = () => { }} > { } inlineIndent={hidden ? 0 : 24} style={{ - width: hidden ? 50 : 200, + width: hidden ? 50 : 220, background: "#00000005", flex: "1 1 auto", overflowY: "auto", @@ -677,6 +667,7 @@ export const AccountPage: React.FC = () => { return (
+ {is_logged_in && !get_api_key ? ( render_logged_in_view() ) : ( From 9629cda3d0a905e119bcf12a5a116ee02480e66d Mon Sep 17 00:00:00 2001 From: Andrey Novoseltsev Date: Thu, 27 Nov 2025 17:11:38 -0700 Subject: [PATCH 46/58] settings: add tiles for upgrades and payment methods --- .../frontend/account/settings-index.tsx | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/packages/frontend/account/settings-index.tsx b/src/packages/frontend/account/settings-index.tsx index 1ea42581ce3..93276feef2d 100644 --- a/src/packages/frontend/account/settings-index.tsx +++ b/src/packages/frontend/account/settings-index.tsx @@ -13,6 +13,7 @@ import { Icon } from "@cocalc/frontend/components"; import AIAvatar from "@cocalc/frontend/components/ai-avatar"; import { cloudFilesystemsEnabled } from "@cocalc/frontend/compute"; import { labels } from "@cocalc/frontend/i18n"; +import { KUCALC_COCALC_COM } from "@cocalc/util/db-schema/site-defaults"; import { COLORS } from "@cocalc/util/theme"; import { VALID_PREFERENCES_SUB_TYPES, @@ -83,6 +84,10 @@ const MESSAGES = defineMessages({ id: "account.settings.overview.payg", defaultMessage: "Configure pay-as-you-go usage and billing.", }, + upgrades: { + id: "account.settings.overview.upgrades", + defaultMessage: "Manage your legacy quota upgrades.", + }, purchases: { id: "account.settings.overview.purchases", defaultMessage: "View purchase history and receipts.", @@ -91,6 +96,10 @@ const MESSAGES = defineMessages({ id: "account.settings.overview.payments", defaultMessage: "Manage payment methods and transaction history.", }, + paymentMethods: { + id: "account.settings.overview.payment_methods", + defaultMessage: "Manage your saved payment methods or add new ones.", + }, statements: { id: "account.settings.overview.statements", defaultMessage: "View detailed billing statements and invoices.", @@ -133,6 +142,7 @@ const FLEX_PROPS = { export function SettingsOverview() { const intl = useIntl(); const is_commercial = useTypedRedux("customize", "is_commercial"); + const kucalc = useTypedRedux("customize", "kucalc"); function handleNavigate(path: NavigatePath) { // Use the same navigation pattern as the account page @@ -297,6 +307,18 @@ export function SettingsOverview() { description={intl.formatMessage(MESSAGES.payg)} /> + {kucalc === KUCALC_COCALC_COM && ( + handleNavigate("settings/upgrades")} + > + } + title={intl.formatMessage(labels.upgrades)} + description={intl.formatMessage(MESSAGES.upgrades)} + /> + + )} handleNavigate("settings/purchases")} @@ -317,6 +339,16 @@ export function SettingsOverview() { description={intl.formatMessage(MESSAGES.payments)} /> + handleNavigate("settings/payment-methods")} + > + } + title={intl.formatMessage(labels.payment_methods)} + description={intl.formatMessage(MESSAGES.paymentMethods)} + /> + handleNavigate("settings/statements")} From a17627dda4bf9d11c6a49b089fe82f4441e9e6ab Mon Sep 17 00:00:00 2001 From: Andrey Novoseltsev Date: Thu, 27 Nov 2025 22:15:29 -0700 Subject: [PATCH 47/58] settings: add new tiles to the list of valid pages --- src/packages/util/types/settings.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/packages/util/types/settings.ts b/src/packages/util/types/settings.ts index e6c7bcd0450..64e94a8107f 100644 --- a/src/packages/util/types/settings.ts +++ b/src/packages/util/types/settings.ts @@ -31,8 +31,10 @@ export const VALID_SETTINGS_PAGES = [ "subscriptions", "licenses", "payg", + "upgrades", "purchases", "payments", + "payment-methods", "statements", "public-files", "cloud-filesystems", From 67a06b788b155aff28325619db5fabdafb0c09da Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Tue, 2 Dec 2025 11:18:01 +0100 Subject: [PATCH 48/58] npm/server: update nodemailer (patch update) and also its @types package --- src/packages/pnpm-lock.yaml | 1078 ++++++++++++++++++++++++++++-- src/packages/server/package.json | 4 +- 2 files changed, 1039 insertions(+), 43 deletions(-) diff --git a/src/packages/pnpm-lock.yaml b/src/packages/pnpm-lock.yaml index 91710cdd4e3..38ffacb3987 100644 --- a/src/packages/pnpm-lock.yaml +++ b/src/packages/pnpm-lock.yaml @@ -1335,8 +1335,8 @@ importers: specifier: ^5.0.13 version: 5.0.15(encoding@0.1.13) nodemailer: - specifier: ^7.0.9 - version: 7.0.9 + specifier: ^7.0.11 + version: 7.0.11 openai: specifier: ^6.5.0 version: 6.5.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5))(zod@3.25.76) @@ -1426,8 +1426,8 @@ importers: specifier: ^2.0.15 version: 2.0.15 '@types/nodemailer': - specifier: ^6.4.14 - version: 6.4.17 + specifier: ^7.0.4 + version: 7.0.4 '@types/passport': specifier: ^1.0.9 version: 1.0.17 @@ -2052,6 +2052,135 @@ packages: '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-sesv2@3.940.0': + resolution: {integrity: sha512-jDQ4x2HwB2/UXBS7CTeSDiIb+sVsYGDyxTeXdrRAtqNdGv8kC54fbwokDiJ/mnMyB2gyXWw57BqeDJNkZuLmsw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/client-sso@3.940.0': + resolution: {integrity: sha512-SdqJGWVhmIURvCSgkDditHRO+ozubwZk9aCX9MK8qxyOndhobCndW1ozl3hX9psvMAo9Q4bppjuqy/GHWpjB+A==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/core@3.940.0': + resolution: {integrity: sha512-KsGD2FLaX5ngJao1mHxodIVU9VYd1E8810fcYiGwO1PFHDzf5BEkp6D9IdMeQwT8Q6JLYtiiT1Y/o3UCScnGoA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-env@3.940.0': + resolution: {integrity: sha512-/G3l5/wbZYP2XEQiOoIkRJmlv15f1P3MSd1a0gz27lHEMrOJOGq66rF1Ca4OJLzapWt3Fy9BPrZAepoAX11kMw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-http@3.940.0': + resolution: {integrity: sha512-dOrc03DHElNBD6N9Okt4U0zhrG4Wix5QUBSZPr5VN8SvmjD9dkrrxOkkJaMCl/bzrW7kbQEp7LuBdbxArMmOZQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-ini@3.940.0': + resolution: {integrity: sha512-gn7PJQEzb/cnInNFTOaDoCN/hOKqMejNmLof1W5VW95Qk0TPO52lH8R4RmJPnRrwFMswOWswTOpR1roKNLIrcw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-login@3.940.0': + resolution: {integrity: sha512-fOKC3VZkwa9T2l2VFKWRtfHQPQuISqqNl35ZhcXjWKVwRwl/o7THPMkqI4XwgT2noGa7LLYVbWMwnsgSsBqglg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-node@3.940.0': + resolution: {integrity: sha512-M8NFAvgvO6xZjiti5kztFiAYmSmSlG3eUfr4ZHSfXYZUA/KUdZU/D6xJyaLnU8cYRWBludb6K9XPKKVwKfqm4g==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-process@3.940.0': + resolution: {integrity: sha512-pILBzt5/TYCqRsJb7vZlxmRIe0/T+FZPeml417EK75060ajDGnVJjHcuVdLVIeKoTKm9gmJc9l45gon6PbHyUQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-sso@3.940.0': + resolution: {integrity: sha512-q6JMHIkBlDCOMnA3RAzf8cGfup+8ukhhb50fNpghMs1SNBGhanmaMbZSgLigBRsPQW7fOk2l8jnzdVLS+BB9Uw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.940.0': + resolution: {integrity: sha512-9QLTIkDJHHaYL0nyymO41H8g3ui1yz6Y3GmAN1gYQa6plXisuFBnGAbmKVj7zNvjWaOKdF0dV3dd3AFKEDoJ/w==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-host-header@3.936.0': + resolution: {integrity: sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-logger@3.936.0': + resolution: {integrity: sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.936.0': + resolution: {integrity: sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.940.0': + resolution: {integrity: sha512-JYkLjgS1wLoKHJ40G63+afM1ehmsPsjcmrHirKh8+kSCx4ip7+nL1e/twV4Zicxr8RJi9Y0Ahq5mDvneilDDKQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-user-agent@3.940.0': + resolution: {integrity: sha512-nJbLrUj6fY+l2W2rIB9P4Qvpiy0tnTdg/dmixRxrU1z3e8wBdspJlyE+AZN4fuVbeL6rrRrO/zxQC1bB3cw5IA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/nested-clients@3.940.0': + resolution: {integrity: sha512-x0mdv6DkjXqXEcQj3URbCltEzW6hoy/1uIL+i8gExP6YKrnhiZ7SzuB4gPls2UOpK5UqLiqXjhRLfBb1C9i4Dw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/region-config-resolver@3.936.0': + resolution: {integrity: sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.940.0': + resolution: {integrity: sha512-ugHZEoktD/bG6mdgmhzLDjMP2VrYRAUPRPF1DpCyiZexkH7DCU7XrSJyXMvkcf0DHV+URk0q2sLf/oqn1D2uYw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/token-providers@3.940.0': + resolution: {integrity: sha512-k5qbRe/ZFjW9oWEdzLIa2twRVIEx7p/9rutofyrRysrtEnYh3HAWCngAnwbgKMoiwa806UzcTRx0TjyEpnKcCg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/types@3.936.0': + resolution: {integrity: sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-arn-parser@3.893.0': + resolution: {integrity: sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-endpoints@3.936.0': + resolution: {integrity: sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-locate-window@3.893.0': + resolution: {integrity: sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-user-agent-browser@3.936.0': + resolution: {integrity: sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==} + + '@aws-sdk/util-user-agent-node@3.940.0': + resolution: {integrity: sha512-dlD/F+L/jN26I8Zg5x0oDGJiA+/WEQmnSE27fi5ydvYnpfQLwThtQo9SsNS47XSR/SOULaaoC9qx929rZuo74A==} + engines: {node: '>=18.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.930.0': + resolution: {integrity: sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==} + engines: {node: '>=18.0.0'} + + '@aws/lambda-invoke-store@0.2.1': + resolution: {integrity: sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww==} + engines: {node: '>=18.0.0'} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -4100,6 +4229,178 @@ packages: '@sinonjs/fake-timers@13.0.5': resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} + '@smithy/abort-controller@4.2.5': + resolution: {integrity: sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==} + engines: {node: '>=18.0.0'} + + '@smithy/config-resolver@4.4.3': + resolution: {integrity: sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.18.6': + resolution: {integrity: sha512-8Q/ugWqfDUEU1Exw71+DoOzlONJ2Cn9QA8VeeDzLLjzO/qruh9UKFzbszy4jXcIYgGofxYiT0t1TT6+CT/GupQ==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.2.5': + resolution: {integrity: sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.3.6': + resolution: {integrity: sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.2.5': + resolution: {integrity: sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.5': + resolution: {integrity: sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@4.2.0': + resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.2.5': + resolution: {integrity: sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.3.13': + resolution: {integrity: sha512-X4za1qCdyx1hEVVXuAWlZuK6wzLDv1uw1OY9VtaYy1lULl661+frY7FeuHdYdl7qAARUxH2yvNExU2/SmRFfcg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.4.13': + resolution: {integrity: sha512-RzIDF9OrSviXX7MQeKOm8r/372KTyY8Jmp6HNKOOYlrguHADuM3ED/f4aCyNhZZFLG55lv5beBin7nL0Nzy1Dw==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.2.6': + resolution: {integrity: sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.2.5': + resolution: {integrity: sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@4.3.5': + resolution: {integrity: sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.4.5': + resolution: {integrity: sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@4.2.5': + resolution: {integrity: sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.3.5': + resolution: {integrity: sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.2.5': + resolution: {integrity: sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-parser@4.2.5': + resolution: {integrity: sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==} + engines: {node: '>=18.0.0'} + + '@smithy/service-error-classification@4.2.5': + resolution: {integrity: sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@4.4.0': + resolution: {integrity: sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.5': + resolution: {integrity: sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.9.9': + resolution: {integrity: sha512-SUnZJMMo5yCmgjopJbiNeo1vlr8KvdnEfIHV9rlD77QuOGdRotIVBcOrBuMr+sI9zrnhtDtLP054bZVbpZpiQA==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.9.0': + resolution: {integrity: sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.5': + resolution: {integrity: sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.3.0': + resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.2.0': + resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.2.1': + resolution: {integrity: sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@4.2.0': + resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==} + engines: {node: '>=18.0.0'} + + '@smithy/util-config-provider@4.2.0': + resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.3.12': + resolution: {integrity: sha512-TKc6FnOxFULKxLgTNHYjcFqdOYzXVPFFVm5JhI30F3RdhT7nYOtOsjgaOwfDRmA/3U66O9KaBQ3UHoXwayRhAg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.2.15': + resolution: {integrity: sha512-94NqfQVo+vGc5gsQ9SROZqOvBkGNMQu6pjXbnn8aQvBUhc31kx49gxlkBEqgmaZQHUUfdRUin5gK/HlHKmbAwg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.2.5': + resolution: {integrity: sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==} + engines: {node: '>=18.0.0'} + + '@smithy/util-hex-encoding@4.2.0': + resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.2.5': + resolution: {integrity: sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.2.5': + resolution: {integrity: sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.5.6': + resolution: {integrity: sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-uri-escape@4.2.0': + resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.2.0': + resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.0': + resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} + engines: {node: '>=18.0.0'} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -4478,8 +4779,8 @@ packages: '@types/node@24.2.1': resolution: {integrity: sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==} - '@types/nodemailer@6.4.17': - resolution: {integrity: sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==} + '@types/nodemailer@7.0.4': + resolution: {integrity: sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -5358,6 +5659,10 @@ packages: bootstrap@3.4.1: resolution: {integrity: sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==} engines: {node: '>=6'} + deprecated: This version of Bootstrap is no longer supported. Please upgrade to the latest version. + + bowser@2.13.1: + resolution: {integrity: sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==} brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -6941,6 +7246,10 @@ packages: resolution: {integrity: sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==} hasBin: true + fast-xml-parser@5.2.5: + resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} + hasBin: true + fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} engines: {node: '>= 4.9.1'} @@ -8536,6 +8845,7 @@ packages: keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} engines: {node: '>= 0.6'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -9194,8 +9504,8 @@ packages: resolution: {integrity: sha512-D36I9AhLxLG/LvD4GDd701fpevDG+QgQhpOJQnQa3CysSIiztfnttI5bAicqJ4PXNlT2UlCCdnCn8zj/ONkQfw==} engines: {node: '>= 18'} - nodemailer@7.0.9: - resolution: {integrity: sha512-9/Qm0qXIByEP8lEV2qOqcAW7bRpL8CR9jcTwk3NBnHJNmP9fIJ86g2fgmIXqHY+nj55ZEMwWqYAT2QTDpRUYiQ==} + nodemailer@7.0.11: + resolution: {integrity: sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==} engines: {node: '>=6.0.0'} normalize-package-data@2.5.0: @@ -11003,6 +11313,9 @@ packages: strnum@1.1.2: resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} + strnum@2.1.1: + resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==} + strongly-connected-components@1.0.1: resolution: {integrity: sha512-i0TFx4wPcO0FwX+4RkLJi1MxmcTv90jNZgxMu9XRnMXMeFUY1VJlIoXpZunPUvUUqbCT1pg5PEkFqqpcaElNaA==} @@ -12267,6 +12580,404 @@ snapshots: '@csstools/css-tokenizer': 3.0.4 lru-cache: 10.4.3 + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-locate-window': 3.893.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.936.0 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-sesv2@3.940.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.940.0 + '@aws-sdk/credential-provider-node': 3.940.0 + '@aws-sdk/middleware-host-header': 3.936.0 + '@aws-sdk/middleware-logger': 3.936.0 + '@aws-sdk/middleware-recursion-detection': 3.936.0 + '@aws-sdk/middleware-user-agent': 3.940.0 + '@aws-sdk/region-config-resolver': 3.936.0 + '@aws-sdk/signature-v4-multi-region': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@aws-sdk/util-user-agent-browser': 3.936.0 + '@aws-sdk/util-user-agent-node': 3.940.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.6 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.13 + '@smithy/middleware-retry': 4.4.13 + '@smithy/middleware-serde': 4.2.6 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.12 + '@smithy/util-defaults-mode-node': 4.2.15 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sso@3.940.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.940.0 + '@aws-sdk/middleware-host-header': 3.936.0 + '@aws-sdk/middleware-logger': 3.936.0 + '@aws-sdk/middleware-recursion-detection': 3.936.0 + '@aws-sdk/middleware-user-agent': 3.940.0 + '@aws-sdk/region-config-resolver': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@aws-sdk/util-user-agent-browser': 3.936.0 + '@aws-sdk/util-user-agent-node': 3.940.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.6 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.13 + '@smithy/middleware-retry': 4.4.13 + '@smithy/middleware-serde': 4.2.6 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.12 + '@smithy/util-defaults-mode-node': 4.2.15 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.940.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@aws-sdk/xml-builder': 3.930.0 + '@smithy/core': 3.18.6 + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/signature-v4': 5.3.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/node-http-handler': 4.4.5 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + '@smithy/util-stream': 4.5.6 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/credential-provider-env': 3.940.0 + '@aws-sdk/credential-provider-http': 3.940.0 + '@aws-sdk/credential-provider-login': 3.940.0 + '@aws-sdk/credential-provider-process': 3.940.0 + '@aws-sdk/credential-provider-sso': 3.940.0 + '@aws-sdk/credential-provider-web-identity': 3.940.0 + '@aws-sdk/nested-clients': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/nested-clients': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.940.0': + dependencies: + '@aws-sdk/credential-provider-env': 3.940.0 + '@aws-sdk/credential-provider-http': 3.940.0 + '@aws-sdk/credential-provider-ini': 3.940.0 + '@aws-sdk/credential-provider-process': 3.940.0 + '@aws-sdk/credential-provider-sso': 3.940.0 + '@aws-sdk/credential-provider-web-identity': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.940.0': + dependencies: + '@aws-sdk/client-sso': 3.940.0 + '@aws-sdk/core': 3.940.0 + '@aws-sdk/token-providers': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/nested-clients': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/middleware-host-header@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@aws/lambda-invoke-store': 0.2.1 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-arn-parser': 3.893.0 + '@smithy/core': 3.18.6 + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/signature-v4': 5.3.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-stream': 4.5.6 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@smithy/core': 3.18.6 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.940.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.940.0 + '@aws-sdk/middleware-host-header': 3.936.0 + '@aws-sdk/middleware-logger': 3.936.0 + '@aws-sdk/middleware-recursion-detection': 3.936.0 + '@aws-sdk/middleware-user-agent': 3.940.0 + '@aws-sdk/region-config-resolver': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@aws-sdk/util-user-agent-browser': 3.936.0 + '@aws-sdk/util-user-agent-node': 3.940.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.6 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.13 + '@smithy/middleware-retry': 4.4.13 + '@smithy/middleware-serde': 4.2.6 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.12 + '@smithy/util-defaults-mode-node': 4.2.15 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/region-config-resolver@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.940.0': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/signature-v4': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/nested-clients': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.936.0': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/util-arn-parser@3.893.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-endpoints': 3.2.5 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.893.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.9.0 + bowser: 2.13.1 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.940.0': + dependencies: + '@aws-sdk/middleware-user-agent': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.930.0': + dependencies: + '@smithy/types': 4.9.0 + fast-xml-parser: 5.2.5 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.1': {} + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -13267,7 +13978,7 @@ snapshots: '@jest/console@30.0.5': dependencies: '@jest/types': 30.0.5 - '@types/node': 18.19.122 + '@types/node': 18.19.130 chalk: 4.1.2 jest-message-util: 30.0.5 jest-util: 30.0.5 @@ -13407,7 +14118,7 @@ snapshots: dependencies: '@jest/types': 30.0.5 '@sinonjs/fake-timers': 13.0.5 - '@types/node': 24.2.1 + '@types/node': 18.19.130 jest-message-util: 30.0.5 jest-mock: 30.0.5 jest-util: 30.0.5 @@ -13445,7 +14156,7 @@ snapshots: '@jest/pattern@30.0.1': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 jest-regex-util: 30.0.1 '@jest/reporters@30.0.5': @@ -13456,7 +14167,7 @@ snapshots: '@jest/transform': 30.0.5 '@jest/types': 30.0.5 '@jridgewell/trace-mapping': 0.3.30 - '@types/node': 18.19.122 + '@types/node': 18.19.130 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit-x: 0.2.2 @@ -14860,6 +15571,280 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@smithy/abort-controller@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/config-resolver@4.4.3': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + tslib: 2.8.1 + + '@smithy/core@3.18.6': + dependencies: + '@smithy/middleware-serde': 4.2.6 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-stream': 4.5.6 + '@smithy/util-utf8': 4.2.0 + '@smithy/uuid': 1.1.0 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.2.5': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.3.6': + dependencies: + '@smithy/protocol-http': 5.3.5 + '@smithy/querystring-builder': 4.2.5 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + tslib: 2.8.1 + + '@smithy/hash-node@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/invalid-dependency@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/is-array-buffer@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/middleware-content-length@4.2.5': + dependencies: + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@4.3.13': + dependencies: + '@smithy/core': 3.18.6 + '@smithy/middleware-serde': 4.2.6 + '@smithy/node-config-provider': 4.3.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-middleware': 4.2.5 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.4.13': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/service-error-classification': 4.2.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/uuid': 1.1.0 + tslib: 2.8.1 + + '@smithy/middleware-serde@4.2.6': + dependencies: + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/middleware-stack@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/node-config-provider@4.3.5': + dependencies: + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.4.5': + dependencies: + '@smithy/abort-controller': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/querystring-builder': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/property-provider@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/protocol-http@5.3.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/querystring-builder@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + '@smithy/util-uri-escape': 4.2.0 + tslib: 2.8.1 + + '@smithy/querystring-parser@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/service-error-classification@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + + '@smithy/shared-ini-file-loader@4.4.0': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/signature-v4@5.3.5': + dependencies: + '@smithy/is-array-buffer': 4.2.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-uri-escape': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/smithy-client@4.9.9': + dependencies: + '@smithy/core': 3.18.6 + '@smithy/middleware-endpoint': 4.3.13 + '@smithy/middleware-stack': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-stream': 4.5.6 + tslib: 2.8.1 + + '@smithy/types@4.9.0': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@4.2.5': + dependencies: + '@smithy/querystring-parser': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-base64@4.3.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-body-length-node@4.2.1': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-buffer-from@4.2.0': + dependencies: + '@smithy/is-array-buffer': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-config-provider@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-defaults-mode-browser@4.3.12': + dependencies: + '@smithy/property-provider': 4.2.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-defaults-mode-node@4.2.15': + dependencies: + '@smithy/config-resolver': 4.4.3 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-endpoints@3.2.5': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-hex-encoding@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-middleware@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-retry@4.2.5': + dependencies: + '@smithy/service-error-classification': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-stream@4.5.6': + dependencies: + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/node-http-handler': 4.4.5 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-uri-escape@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@4.2.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + tslib: 2.8.1 + + '@smithy/uuid@1.1.0': + dependencies: + tslib: 2.8.1 + '@socket.io/component-emitter@3.1.2': {} '@stripe/react-stripe-js@3.9.0(@stripe/stripe-js@5.10.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': @@ -15006,7 +15991,7 @@ snapshots: '@types/bonjour@3.5.13': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/caseless@0.12.5': {} @@ -15019,11 +16004,11 @@ snapshots: '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 4.19.6 - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/connect@3.4.38': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/cookie@0.3.3': {} @@ -15031,7 +16016,7 @@ snapshots: '@types/cors@2.8.19': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/d3-array@3.2.1': {} @@ -15204,7 +16189,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 6.0.0 - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/hast@2.3.10': dependencies: @@ -15225,7 +16210,7 @@ snapshots: '@types/http-proxy@1.17.16': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/istanbul-lib-coverage@2.0.6': {} @@ -15264,7 +16249,7 @@ snapshots: '@types/keyv@3.1.4': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/linkify-it@5.0.0': {} @@ -15307,7 +16292,7 @@ snapshots: '@types/node-forge@1.3.14': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/node-zendesk@2.0.15': dependencies: @@ -15325,15 +16310,18 @@ snapshots: dependencies: undici-types: 7.10.0 - '@types/nodemailer@6.4.17': + '@types/nodemailer@7.0.4': dependencies: - '@types/node': 18.19.122 + '@aws-sdk/client-sesv2': 3.940.0 + '@types/node': 18.19.130 + transitivePeerDependencies: + - aws-crt '@types/normalize-package-data@2.4.4': {} '@types/oauth@0.9.6': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/passport-google-oauth20@2.0.16': dependencies: @@ -15392,13 +16380,13 @@ snapshots: '@types/request@2.48.13': dependencies: '@types/caseless': 0.12.5 - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/tough-cookie': 4.0.5 form-data: 2.5.5 '@types/responselike@1.0.3': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/retry@0.12.0': {} @@ -15415,7 +16403,7 @@ snapshots: '@types/send@0.17.5': dependencies: '@types/mime': 1.3.5 - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/serve-index@1.9.4': dependencies: @@ -15431,7 +16419,7 @@ snapshots: '@types/sockjs@0.3.36': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/stack-utils@2.0.3': {} @@ -15462,15 +16450,15 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/xml-encryption@1.2.4': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/xml2js@0.4.14': dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@types/yargs-parser@21.0.3': {} @@ -16328,6 +17316,8 @@ snapshots: bootstrap@3.4.1: {} + bowser@2.13.1: {} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -18183,6 +19173,10 @@ snapshots: dependencies: strnum: 1.1.2 + fast-xml-parser@5.2.5: + dependencies: + strnum: 2.1.1 + fastest-levenshtein@1.0.16: {} fastparse@1.1.2: {} @@ -19496,7 +20490,7 @@ snapshots: '@jest/expect': 30.0.5 '@jest/test-result': 30.0.5 '@jest/types': 30.0.5 - '@types/node': 18.19.122 + '@types/node': 18.19.130 chalk: 4.1.2 co: 4.6.0 dedent: 1.6.0 @@ -19738,7 +20732,7 @@ snapshots: '@jest/environment': 30.0.5 '@jest/fake-timers': 30.0.5 '@jest/types': 30.0.5 - '@types/node': 18.19.122 + '@types/node': 18.19.130 jest-mock: 30.0.5 jest-util: 30.0.5 jest-validate: 30.0.5 @@ -19758,7 +20752,7 @@ snapshots: jest-haste-map@30.0.5: dependencies: '@jest/types': 30.0.5 - '@types/node': 18.19.122 + '@types/node': 18.19.130 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -19862,7 +20856,7 @@ snapshots: jest-mock@30.0.5: dependencies: '@jest/types': 30.0.5 - '@types/node': 24.2.1 + '@types/node': 18.19.130 jest-util: 30.0.5 jest-mock@30.2.0: @@ -19926,7 +20920,7 @@ snapshots: '@jest/test-result': 30.0.5 '@jest/transform': 30.0.5 '@jest/types': 30.0.5 - '@types/node': 18.19.122 + '@types/node': 18.19.130 chalk: 4.1.2 emittery: 0.13.1 exit-x: 0.2.2 @@ -19982,7 +20976,7 @@ snapshots: '@jest/test-result': 30.0.5 '@jest/transform': 30.0.5 '@jest/types': 30.0.5 - '@types/node': 18.19.122 + '@types/node': 18.19.130 chalk: 4.1.2 cjs-module-lexer: 2.1.0 collect-v8-coverage: 1.0.2 @@ -20082,7 +21076,7 @@ snapshots: jest-util@30.0.5: dependencies: '@jest/types': 30.0.5 - '@types/node': 18.19.122 + '@types/node': 18.19.130 chalk: 4.1.2 ci-info: 4.3.0 graceful-fs: 4.2.11 @@ -20119,7 +21113,7 @@ snapshots: dependencies: '@jest/test-result': 30.0.5 '@jest/types': 30.0.5 - '@types/node': 18.19.122 + '@types/node': 18.19.130 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -20145,7 +21139,7 @@ snapshots: jest-worker@30.0.5: dependencies: - '@types/node': 18.19.122 + '@types/node': 18.19.130 '@ungap/structured-clone': 1.3.0 jest-util: 30.0.5 merge-stream: 2.0.0 @@ -21145,7 +22139,7 @@ snapshots: transitivePeerDependencies: - encoding - nodemailer@7.0.9: {} + nodemailer@7.0.11: {} normalize-package-data@2.5.0: dependencies: @@ -21908,7 +22902,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 18.19.122 + '@types/node': 18.19.130 long: 5.3.2 protocol-buffers-schema@3.6.0: {} @@ -23396,6 +24390,8 @@ snapshots: strnum@1.1.2: {} + strnum@2.1.1: {} + strongly-connected-components@1.0.1: {} strtok3@10.3.4: diff --git a/src/packages/server/package.json b/src/packages/server/package.json index 4aa6f2a7aea..8b6ff46af03 100644 --- a/src/packages/server/package.json +++ b/src/packages/server/package.json @@ -96,7 +96,7 @@ "ms": "2.1.2", "nanoid": "^3.3.8", "node-zendesk": "^5.0.13", - "nodemailer": "^7.0.9", + "nodemailer": "^7.0.11", "openai": "^6.5.0", "parse-domain": "^5.0.0", "passport": "^0.6.0", @@ -133,7 +133,7 @@ "@types/ms": "^0.7.31", "@types/node": "^18.16.14", "@types/node-zendesk": "^2.0.15", - "@types/nodemailer": "^6.4.14", + "@types/nodemailer": "^7.0.4", "@types/passport": "^1.0.9", "@types/passport-google-oauth20": "^2.0.11", "@types/sanitize-html": "^2.3.1", From e3d9f92206c0b3a62e9187494b9799d28d7068f2 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Tue, 2 Dec 2025 12:28:05 +0100 Subject: [PATCH 49/58] frotend/account: move css to sass file and update translations --- src/packages/frontend/account/_account.sass | 13 ++++++++ .../frontend/account/account-page.tsx | 31 +++---------------- src/packages/frontend/i18n/trans/ar_EG.json | 22 ++++++++----- src/packages/frontend/i18n/trans/de_DE.json | 22 ++++++++----- src/packages/frontend/i18n/trans/es_ES.json | 22 ++++++++----- src/packages/frontend/i18n/trans/es_PV.json | 22 ++++++++----- src/packages/frontend/i18n/trans/fr_FR.json | 22 ++++++++----- src/packages/frontend/i18n/trans/he_IL.json | 22 ++++++++----- src/packages/frontend/i18n/trans/hi_IN.json | 22 ++++++++----- src/packages/frontend/i18n/trans/hu_HU.json | 22 ++++++++----- src/packages/frontend/i18n/trans/it_IT.json | 22 ++++++++----- src/packages/frontend/i18n/trans/ja_JP.json | 22 ++++++++----- src/packages/frontend/i18n/trans/ko_KR.json | 22 ++++++++----- src/packages/frontend/i18n/trans/nl_NL.json | 22 ++++++++----- src/packages/frontend/i18n/trans/pl_PL.json | 22 ++++++++----- src/packages/frontend/i18n/trans/pt_BR.json | 22 ++++++++----- src/packages/frontend/i18n/trans/pt_PT.json | 22 ++++++++----- src/packages/frontend/i18n/trans/ru_RU.json | 22 ++++++++----- src/packages/frontend/i18n/trans/tr_TR.json | 22 ++++++++----- src/packages/frontend/i18n/trans/zh_CN.json | 22 ++++++++----- src/packages/frontend/index.sass | 1 + 21 files changed, 270 insertions(+), 171 deletions(-) create mode 100644 src/packages/frontend/account/_account.sass diff --git a/src/packages/frontend/account/_account.sass b/src/packages/frontend/account/_account.sass new file mode 100644 index 00000000000..1514ed0dbb0 --- /dev/null +++ b/src/packages/frontend/account/_account.sass @@ -0,0 +1,13 @@ +// Account page styles + +.account-menu-inline-collapsed + .ant-menu-item, + .ant-menu-submenu-title + padding-inline: 0px + text-align: center + + .ant-menu-submenu-title + padding-right: 20px + + .ant-menu-submenu-arrow + right: 5px diff --git a/src/packages/frontend/account/account-page.tsx b/src/packages/frontend/account/account-page.tsx index 21a02dae6fa..11fab5a5923 100644 --- a/src/packages/frontend/account/account-page.tsx +++ b/src/packages/frontend/account/account-page.tsx @@ -89,20 +89,6 @@ import { UpgradesPage } from "./upgrades/upgrades-page"; export const ACCOUNT_SETTINGS_ICON_NAME: IconName = "settings"; -const ACCOUNT_MENU_INLINE_CSS = ` -.account-menu-inline-collapsed .ant-menu-item, -.account-menu-inline-collapsed .ant-menu-submenu-title { - padding-inline: 0px; - text-align: center; -} -.account-menu-inline-collapsed .ant-menu-submenu-title { - padding-right: 20px; -} -.account-menu-inline-collapsed .ant-menu-submenu-arrow { - right: 5px; -} -`; - // Type for valid menu keys type MenuKey = | "settings" @@ -378,8 +364,7 @@ export const AccountPage: React.FC = () => { key: "purchases", label: ( - {" "} - {intl.formatMessage(labels.purchases)} + {intl.formatMessage(labels.purchases)} ), children: active_page === "purchases" && , @@ -388,8 +373,7 @@ export const AccountPage: React.FC = () => { key: "payments", label: ( - {" "} - {intl.formatMessage(labels.payments)} + {intl.formatMessage(labels.payments)} ), children: active_page === "payments" && , @@ -402,9 +386,7 @@ export const AccountPage: React.FC = () => { {intl.formatMessage(labels.payment_methods)} ), - children: active_page === "payment-methods" && ( - - ), + children: active_page === "payment-methods" && , }, { key: "statements", @@ -547,11 +529,7 @@ export const AccountPage: React.FC = () => { const activeChildKey = active_sub_tab ?? active_page; function renderTitle() { - return ( - - {titles[activeChildKey]} - - ); + return {titles[activeChildKey]}; } function renderExtraContent() { @@ -667,7 +645,6 @@ export const AccountPage: React.FC = () => { return (
- {is_logged_in && !get_api_key ? ( render_logged_in_view() ) : ( diff --git a/src/packages/frontend/i18n/trans/ar_EG.json b/src/packages/frontend/i18n/trans/ar_EG.json index 969c3308ec6..73aa13b39a6 100644 --- a/src/packages/frontend/i18n/trans/ar_EG.json +++ b/src/packages/frontend/i18n/trans/ar_EG.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "معلومات الترجمة", "account.account-button.confirm.ok": "نعم، سجل الخروج", "account.account-button.confirm.title": "تسجيل الخروج من حسابك؟", + "account.appearance.accessibility.enabled": "تمكين وضع الوصول: تحسين واجهة المستخدم لميزات الوصول", + "account.appearance.accessibility.title": "إمكانية الوصول", "account.appearance.user_interface.title": "واجهة المستخدم", "account.delete-account.alert.description": "سوف تفقد فوراً الوصول إلى جميع مشاريعك، سيتم إلغاء أي اشتراكات، وستفقد جميع الأرصدة غير المستخدمة. {br} {hr} لحذف حسابك، أدخل أولاً \"{required_text}\" أدناه:", "account.delete-account.alert.message": "هل أنت متأكد أنك تريد حذف حسابك؟", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "إزالة كلما تم حفظ الملف", "account.editor-settings-autosave-interval.label": "فترة الحفظ التلقائي", "account.editor-settings.basic.title": "الإعدادات الأساسية", - "account.editor-settings.color-schemes.label": "نظام ألوان المحرر", "account.editor-settings.color-schemes.panel_title": "نظام ألوان المحرر", "account.editor-settings.font-size.label": "حجم الخط العالمي الافتراضي", "account.editor-settings.indent-size.label": "حجم المسافة البادئة", "account.editor-settings.keyboard-bindings.label": "اختصارات لوحة المفاتيح للمحرر", "account.editor-settings.keyboard.title": "لوحة المفاتيح", - "account.editor-settings.title": "المحرر", "account.editor-settings.x11-keyboard-variant.label": "متغير لوحة المفاتيح (لجهاز سطح المكتب X11)", "account.editor-settings.x11-physical-keyboard.label": "تخطيط لوحة المفاتيح (لـ X11 Desktop)", "account.global-ssh-keys.help": "للدخول إلى مشروع باستخدام SSH، استخدم التالي username@host: [project-id-without-dashes]@ssh.cocalc.com يمكن العثور على معرف المشروع بدون فواصل في جزء إعدادات المشروع المتعلق بمفاتيح SSH. للدخول بين المشاريع باستخدام SSH، استخدم [project-id-without-dashes]@ssh.", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "عرض منقسم في ورقة عمل Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "تبديل تعليق التحديد", "account.other-settings._page_size.label": "عدد الملفات لكل صفحة", - "account.other-settings.browser_performance.title": "متصفح", + "account.other-settings.auto_focus": "إدخال نص التركيز التلقائي: التركيز تلقائيًا على حقول إدخال النص عند ظهورها (مثل مستكشف الملفات، المشاريع، ...)", "account.other-settings.button_tooltips": "إخفاء تلميحات الأزرار: يخفي بعض تلميحات الأزرار (هذا جزئي فقط)", "account.other-settings.confirm_close": "تأكيد الإغلاق: اطلب دائمًا التأكيد قبل إغلاق نافذة المتصفح", - "account.other-settings.content_display.title": "عرض المحتوى", "account.other-settings.default_file_sort.by_name": "الترتيب حسب الاسم", "account.other-settings.default_file_sort.by_time": "الترتيب حسب الوقت", "account.other-settings.default_file_sort.label": "ترتيب الملفات الافتراضي", + "account.other-settings.dim_file_extensions": "تعتيم امتدادات الملفات: تظليل امتدادات الملفات حتى تبرز أسماؤها.", "account.other-settings.file_popovers": "إخفاء النوافذ المنبثقة لعلامات تبويب الملفات: عدم عرض النوافذ المنبثقة فوق علامات تبويب الملفات", "account.other-settings.filename_generator.description": "اختر كيفية توليد أسماء الملفات التلقائية. بشكل خاص، لجعلها فريدة أو لتضمين الوقت الحالي.", "account.other-settings.filename_generator.label": "مولد اسم الملف", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "إعدادات AI", "account.other-settings.markdown_codebar": "تعطيل شريط كود العلامات في جميع مستندات العلامات. يؤدي تحديد هذا إلى إخفاء أزرار التشغيل والنسخ والشرح الإضافية في كتل الكود المسورة.", "account.other-settings.mask_files": "إخفاء الملفات: تظليل الملفات في عارض الملفات التي ربما لا تريد فتحها", - "account.other-settings.messages.title": "رسائل", "account.other-settings.project_popovers": "إخفاء النوافذ المنبثقة لعلامات التبويب في المشروع: لا تعرض النوافذ المنبثقة فوق علامات تبويب المشروع", - "account.other-settings.projects.title": "مشاريع", "account.other-settings.standby_timeout": "مهلة الانتظار", "account.other-settings.symbol_bar_labels": "إظهار تسميات شريط الرموز: إظهار التسميات في شريط رموز محرر الإطار", - "account.other-settings.theme": "السمة", "account.other-settings.theme.antd.animations": "الرسوم المتحركة: تحريك بعض العناصر بإيجاز، مثل الأزرار", "account.other-settings.theme.antd.color_scheme": "مخطط الألوان: استخدم ألوان العلامة التجارية بدلاً من الألوان الافتراضية", "account.other-settings.theme.antd.compact": "تصميم مضغوط: استخدم تصميمًا أكثر إحكامًا", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "إدارة تراخيص البرامج وأذونات الوصول.", "account.settings.overview.other": "إعدادات وخيارات متنوعة.", "account.settings.overview.payg": "قم بتكوين الاستخدام والدفع حسب الاستهلاك والفواتير.", + "account.settings.overview.payment_methods": "إدارة طرق الدفع المحفوظة أو إضافة طرق جديدة.", "account.settings.overview.payments": "إدارة طرق الدفع وسجل المعاملات.", "account.settings.overview.profile": "إدارة معلوماتك الشخصية وصورتك الرمزية وتفاصيل حسابك.", "account.settings.overview.purchases": "عرض سجل الشراء والإيصالات.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "عرض وإدارة اشتراكاتك النشطة.", "account.settings.overview.support": "الوصول إلى تذاكر الدعم وموارد المساعدة.", "account.settings.overview.title": "نظرة عامة على الإعدادات", + "account.settings.overview.upgrades": "إدارة ترقيات حصة الإرث الخاصة بك.", "account.settings.sso.account_is_linked": "حسابك مرتبط مع (انقر لإلغاء الارتباط)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {سجل باستخدام حسابك في} other {اضغط لربط حسابك}}", "account.settings.unlisted.label": "غير مدرج: يمكن العثور عليك فقط من خلال مطابقة عنوان البريد الإلكتروني بشكل دقيق", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "إنشاء مستند {docName} باستخدام الذكاء الاصطناعي", "ai-generator.select_llm": "اختر نموذج اللغة", "app.fullscreen-button.tooltip": "وضع الشاشة الكاملة، يركز على المستند أو الصفحة الحالية.", + "app.hotkey.dialog.help_text": "انقر على الإطارات أعلاه • المفتاح 0 يبدل الدردشة • المفاتيح 1-9 تركز على الإطارات • اكتب للبحث • ↑↓ للتنقل • عودة لفتح • ESC للإغلاق", + "app.hotkey.dialog.search_placeholder": "البحث في الملفات والصفحات...", + "app.hotkey.dialog.title": "التنقل السريع", "app.verify-email-banner.edit": "إذا كانت عنوان البريد الإلكتروني خاطئة، يرجى تعديله في إعدادات الحساب.", "app.verify-email-banner.help.text": "من المهم أن يكون لديك عنوان بريد إلكتروني يعمل. نستخدمه لإعادة تعيين كلمة المرور، وإرسال الرسائل، وإشعارات الفوترة، والدعم. يرجى التأكد من صحة بريدك الإلكتروني للبقاء على اطلاع.", "app.verify-email-banner.text": "{sent, select, true {تم إرسال البريد الإلكتروني! يرجى التحقق من صندوق البريد الإلكتروني (وربما الرسائل غير المرغوب فيها) والنقر على رابط التأكيد.} other {يرجى التحقق من عنوان بريدك الإلكتروني وتأكيده:}}", @@ -988,6 +990,7 @@ "labels.config": "الإعدادات", "labels.configuration": "التكوين", "labels.configuration.short": "الإعدادات", + "labels.connected": "متصل", "labels.connecting": "الاتصال", "labels.connection": "الاتصال", "labels.copied": "تم النسخ", @@ -1016,6 +1019,7 @@ "labels.environment": "بيئة", "labels.explorer": "المستكشف", "labels.file_explorer": "مستكشف الملفات", + "labels.file_use_notifications": "إشعارات استخدام الملفات", "labels.files": "الملفات", "labels.folder": "مجلد", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {عام} read_only {للقراءة فقط} other {حفظ}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "إيقاف{short, select, true {} other { المشروع}}…", "labels.project.settings.stop-project.ok": "نعم، أوقف المشروع", "labels.projects": "المشاريع", + "labels.public_paths": "المسارات العامة", "labels.published_files": "ملفات منشورة", "labels.purchases": "المشتريات", "labels.ready": "جاهز", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "اختر عنوانًا. يمكنك تغييره بسهولة لاحقًا!", "projects.create-project.requireLicense": "مطلوب ترخيص لإنشاء مشاريع إضافية.", "projects.filename-search.placeholder": "ابحث عن أسماء الملفات التي قمت بتحريرها...", - "projects.list.no_starred_found": "لم يتم العثور على مشاريع مميزة. استخدم رمز النجمة بجانب عناوين المشاريع لوضع إشارة مرجعية على مشاريعك المفضلة.", "projects.load-all.label": "عرض كل المشاريع...", "projects.operations.clear-filter": "مسح الفلتر", "projects.operations.delete.button": "{deleted, select, true {استعادة الكل} other {حذف الكل}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "التصفية حسب الوسوم...", "projects.table-controls.hidden.label": "مخفي", "projects.table-controls.search.placeholder": "ابحث عن المشاريع...", + "projects.table.keyboard-row-hint": "المشروع {title}. استخدم الأسهم لأعلى ولأسفل للتحرك؛ اضغط على Enter أو Space للفتح.", "projects.table.last-edited": "آخر تعديل", + "projects.table.untitled": "بدون عنوان", "purchases.automatic-payments-warning.description": "المدفوعات التلقائية هي أكثر ملاءمة بكثير، وستوفر لك الوقت، وتضمن عدم إلغاء الاشتراكات عن طريق الخطأ.", "purchases.automatic-payments-warning.title": "الدفع التلقائي ليس مطلوبًا للاشتراك", "purchases.automatic-payments.are-enabled": "المدفوعات التلقائية مفعلة", diff --git a/src/packages/frontend/i18n/trans/de_DE.json b/src/packages/frontend/i18n/trans/de_DE.json index 0933088f29f..95db74315a1 100644 --- a/src/packages/frontend/i18n/trans/de_DE.json +++ b/src/packages/frontend/i18n/trans/de_DE.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Informationen zur Übersetzung", "account.account-button.confirm.ok": "Ja, ausloggen", "account.account-button.confirm.title": "Vom Konto ausloggen?", + "account.appearance.accessibility.enabled": "Barrierefreiheitsmodus aktivieren: die Benutzeroberfläche für Barrierefreiheitsfunktionen optimieren", + "account.appearance.accessibility.title": "Barrierefreiheit", "account.appearance.user_interface.title": "Benutzeroberfläche", "account.delete-account.alert.description": "Sie werden sofort den Zugriff auf alle Ihrer Projekte verlieren, alle Abonnements werden storniert, und alle nicht ausgegebenen Guthaben gehen verloren. {br} {hr} Um IHR KONTO ZU LÖSCHEN, geben Sie zuerst unten \"{required_text}\" ein:", "account.delete-account.alert.message": "Sind Sie sicher, dass Sie IHR KONTO LÖSCHEN möchten?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "entfernen, wenn Datei gespeichert wird", "account.editor-settings-autosave-interval.label": "Automatisches Speicherintervall", "account.editor-settings.basic.title": "Grundeinstellungen", - "account.editor-settings.color-schemes.label": "Editor-Farbschema", "account.editor-settings.color-schemes.panel_title": "Editor Farbschema", "account.editor-settings.font-size.label": "Standard globale Schriftgröße", "account.editor-settings.indent-size.label": "Einzugsgröße", "account.editor-settings.keyboard-bindings.label": "Editor-Tastenkombinationen", "account.editor-settings.keyboard.title": "Tastatur", - "account.editor-settings.title": "Editor", "account.editor-settings.x11-keyboard-variant.label": "Tastaturvariante (für X11-Desktop)", "account.editor-settings.x11-physical-keyboard.label": "Tastaturlayout (für X11-Desktop)", "account.global-ssh-keys.help": "Um über SSH in ein Projekt zu gelangen, verwenden Sie folgendes username@host: [project-id-without-dashes]@ssh.cocalc.com Die Projekt-ID ohne Bindestriche finden Sie im Teil der Projekteinstellungen über SSH-Schlüssel. Um zwischen Projekten über SSH zu wechseln, verwenden Sie [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Geteilte Ansicht im Sage-Arbeitsblatt", "account.keyboard-shortcuts.shortcut.toggle-comment": "Auswahl kommentieren umschalten", "account.other-settings._page_size.label": "Anzahl der Dateien pro Seite", - "account.other-settings.browser_performance.title": "Browser", + "account.other-settings.auto_focus": "Textfelder automatisch fokussieren: Textfelder automatisch fokussieren, wenn sie erscheinen (z.B. Dateiexplorer, Projekte, ...)", "account.other-settings.button_tooltips": "Schaltflächen-Tooltips ausblenden: blendet einige Schaltflächen-Tooltips aus (dies ist nur teilweise)", "account.other-settings.confirm_close": "Schließen bestätigen: immer um Bestätigung bitten, bevor das Browserfenster geschlossen wird", - "account.other-settings.content_display.title": "Inhaltsanzeige", "account.other-settings.default_file_sort.by_name": "Nach Name sortieren", "account.other-settings.default_file_sort.by_time": "Nach Zeit sortieren", "account.other-settings.default_file_sort.label": "Standarddatei sortieren", + "account.other-settings.dim_file_extensions": "Dateierweiterungen abdunkeln: Dateierweiterungen ausgrauen, damit ihre Namen hervorstechen.", "account.other-settings.file_popovers": "Datei-Tab-Popovers ausblenden: Popovers über Dateitabs nicht anzeigen", "account.other-settings.filename_generator.description": "Wählen Sie aus, wie automatisch generierte Dateinamen erstellt werden. Insbesondere, um sie einzigartig zu machen oder die aktuelle Uhrzeit einzubeziehen.", "account.other-settings.filename_generator.label": "Dateinamengenerator", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "KI-Einstellungen", "account.other-settings.markdown_codebar": "Deaktiviere die Markdown-Codeleiste in allen Markdown-Dokumenten. Wenn Sie dies aktivieren, werden die zusätzlichen Ausführen-, Kopieren- und Erklären-Buttons in umrandeten Codeblöcken ausgeblendet.", "account.other-settings.mask_files": "Dateien maskieren: Dateien im Dateibetrachter ausgrauen, die Sie wahrscheinlich nicht öffnen möchten", - "account.other-settings.messages.title": "Nachrichten", "account.other-settings.project_popovers": "Projekt-Tab-Popovers ausblenden: die Popovers über den Projekt-Tabs nicht anzeigen", - "account.other-settings.projects.title": "Projekte", "account.other-settings.standby_timeout": "Standby-Timeout", "account.other-settings.symbol_bar_labels": "Symbolleistensymbol-Beschriftungen anzeigen: Beschriftungen in der Symbolleiste des Rahmeneditors anzeigen", - "account.other-settings.theme": "Thema", "account.other-settings.theme.antd.animations": "Animationen: einige Aspekte kurz animieren, z. B. Schaltflächen", "account.other-settings.theme.antd.color_scheme": "Farbschema: Verwenden Sie Markenfarben anstelle der Standardfarben", "account.other-settings.theme.antd.compact": "Kompaktes Design: ein kompakteres Design verwenden", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "Verwalten Sie Softwarelizenzen und Zugriffsberechtigungen.", "account.settings.overview.other": "Verschiedene Einstellungen und Optionen.", "account.settings.overview.payg": "Konfigurieren Sie die Nutzung und Abrechnung nach Verbrauch.", + "account.settings.overview.payment_methods": "Verwalten Sie Ihre gespeicherten Zahlungsmethoden oder fügen Sie neue hinzu.", "account.settings.overview.payments": "Zahlungsmethoden und Transaktionsverlauf verwalten.", "account.settings.overview.profile": "Verwalten Sie Ihre persönlichen Informationen, Ihren Avatar und Ihre Kontodetails.", "account.settings.overview.purchases": "Kaufhistorie und Belege anzeigen.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "Anzeigen und verwalten Sie Ihre aktiven Abonnements.", "account.settings.overview.support": "Zugriff auf Support-Tickets und Hilfsressourcen.", "account.settings.overview.title": "Einstellungen Übersicht", + "account.settings.overview.upgrades": "Verwalten Sie Ihre Legacy-Quotenerweiterungen.", "account.settings.sso.account_is_linked": "Ihr Konto ist verknüpft mit (klicken, um die Verknüpfung aufzuheben)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {Registrieren Sie sich mit Ihrem Konto bei} other {Klicken Sie, um Ihr Konto zu verknüpfen}}", "account.settings.unlisted.label": "Nicht gelistet: Sie können nur durch eine genaue Übereinstimmung der E-Mail-Adresse gefunden werden", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "Erzeuge ein {docName} Dokument mit KI", "ai-generator.select_llm": "Sprachmodell auswählen", "app.fullscreen-button.tooltip": "Vollbildmodus, fokussiert auf das aktuelle Dokument oder die aktuelle Seite", + "app.hotkey.dialog.help_text": "Klicke auf Rahmen oben • Taste 0 schaltet Chat um • Tasten 1–9 fokussieren Rahmen • Tippen, um zu suchen • ↑↓ navigieren • Eingabetaste zum Öffnen • ESC zum Schließen", + "app.hotkey.dialog.search_placeholder": "Dateien und Seiten durchsuchen...", + "app.hotkey.dialog.title": "Schnellnavigation", "app.verify-email-banner.edit": "Wenn die E-Mail-Adresse falsch ist, bitte bearbeiten Sie sie in den Kontoeinstellungen.", "app.verify-email-banner.help.text": "Es ist wichtig, eine funktionierende E-Mail-Adresse zu haben. Wir verwenden sie für das Zurücksetzen von Passwörtern, das Senden von Nachrichten, Abrechnungsbenachrichtigungen und Unterstützung. Bitte stellen Sie sicher, dass Ihre E-Mail korrekt ist, um informiert zu bleiben.", "app.verify-email-banner.text": "{sent, select, true {E-Mail gesendet! Bitte überprüfen Sie Ihren E-Mail-Posteingang (und vielleicht Spam) und klicken Sie auf den Bestätigungslink.} other {Bitte überprüfen und verifizieren Sie Ihre E-Mail-Adresse:}}", @@ -988,6 +990,7 @@ "labels.config": "Konfiguration", "labels.configuration": "Konfiguration", "labels.configuration.short": "Konfig", + "labels.connected": "Verbunden", "labels.connecting": "Verbinde", "labels.connection": "Verbindung", "labels.copied": "kopiert", @@ -1016,6 +1019,7 @@ "labels.environment": "Umgebung", "labels.explorer": "Explorer", "labels.file_explorer": "Dateimanager", + "labels.file_use_notifications": "Benachrichtigungen zur Dateiverwendung", "labels.files": "Dateien", "labels.folder": "Ordner", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Öffentlich} read_only {Schreibgeschützt} other {Speichern}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "Stop{short, select, true {} other { Projekt}}…", "labels.project.settings.stop-project.ok": "Ja, stoppe Projekt", "labels.projects": "Projekte", + "labels.public_paths": "Öffentliche Pfade", "labels.published_files": "Veröffentlichte Dateien", "labels.purchases": "Einkäufe", "labels.ready": "Bereit", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "Wählen Sie einen Titel. Sie können ihn später einfach ändern!", "projects.create-project.requireLicense": "Eine Lizenz ist erforderlich, um zusätzliche Projekte zu erstellen.", "projects.filename-search.placeholder": "Suche nach Dateien, die Sie bearbeitet haben...", - "projects.list.no_starred_found": "Keine mit Stern markierten Projekte gefunden. Verwenden Sie das Sternsymbol neben Projekttiteln, um Ihre Lieblingsprojekte zu speichern.", "projects.load-all.label": "Zeige alle Projekte...", "projects.operations.clear-filter": "Filter löschen", "projects.operations.delete.button": "{deleted, select, true {Alle wiederherstellen} other {Alle löschen}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "Nach Hashtags filtern...", "projects.table-controls.hidden.label": "Versteckt", "projects.table-controls.search.placeholder": "Projekte durchsuchen...", + "projects.table.keyboard-row-hint": "Projekt {title}. Verwenden Sie die Pfeiltasten nach oben und unten, um sich zu bewegen; drücken Sie Enter oder Leertaste, um zu öffnen.", "projects.table.last-edited": "Zuletzt bearbeitet", + "projects.table.untitled": "Unbenannt", "purchases.automatic-payments-warning.description": "Automatische Zahlungen sind viel bequemer, ersparen Ihnen Zeit und stellen sicher, dass Abonnements nicht versehentlich gekündigt werden.", "purchases.automatic-payments-warning.title": "Automatische Zahlungen sind NICHT erforderlich, um ein Abonnement zu haben", "purchases.automatic-payments.are-enabled": "Automatische Zahlungen sind aktiviert", diff --git a/src/packages/frontend/i18n/trans/es_ES.json b/src/packages/frontend/i18n/trans/es_ES.json index ff03e994cdc..8b11ef9204d 100644 --- a/src/packages/frontend/i18n/trans/es_ES.json +++ b/src/packages/frontend/i18n/trans/es_ES.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Información de traducción", "account.account-button.confirm.ok": "Sí, cerrar sesión", "account.account-button.confirm.title": "¿Cerrar sesión de tu cuenta?", + "account.appearance.accessibility.enabled": "Activar el Modo de Accesibilidad: optimizar la interfaz de usuario para características de accesibilidad", + "account.appearance.accessibility.title": "Accesibilidad", "account.appearance.user_interface.title": "Interfaz de usuario", "account.delete-account.alert.description": "Perderá inmediatamente acceso a todos sus proyectos, cualquier suscripción será cancelada y todo el crédito no utilizado se perderá. {br} {hr} Para ELIMINAR SU CUENTA, primero ingrese \"{required_text}\" a continuación:", "account.delete-account.alert.message": "¿Está seguro de que desea ELIMINAR SU CUENTA?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "eliminar siempre que se guarde el archivo", "account.editor-settings-autosave-interval.label": "Intervalo de guardado automático", "account.editor-settings.basic.title": "Configuración básica", - "account.editor-settings.color-schemes.label": "Esquema de color del editor", "account.editor-settings.color-schemes.panel_title": "Esquema de Color del Editor", "account.editor-settings.font-size.label": "Tamaño de fuente global predeterminado", "account.editor-settings.indent-size.label": "Tamaño de sangría", "account.editor-settings.keyboard-bindings.label": "Asignaciones de teclado del editor", "account.editor-settings.keyboard.title": "Teclado", - "account.editor-settings.title": "Editor", "account.editor-settings.x11-keyboard-variant.label": "Variante de teclado (para escritorio X11)", "account.editor-settings.x11-physical-keyboard.label": "Distribución del teclado (para el escritorio X11)", "account.global-ssh-keys.help": "Para hacer SSH en un proyecto, usa el siguiente username@host: [project-id-without-dashes]@ssh.cocalc.com El ID del proyecto sin guiones se puede encontrar en la parte de configuración del proyecto sobre claves SSH. Para hacer SSH entre proyectos, usa [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Vista dividida en hoja de trabajo Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Alternar selección de comentarios", "account.other-settings._page_size.label": "Número de archivos por página", - "account.other-settings.browser_performance.title": "Navegador", + "account.other-settings.auto_focus": "Enfoque Automático en la Entrada de Texto: enfocar automáticamente los campos de entrada de texto cuando aparezcan (por ejemplo, explorador de archivos, proyectos, ...)", "account.other-settings.button_tooltips": "Ocultar descripciones emergentes de botones: oculta algunas descripciones emergentes de botones (esto es solo parcial)", "account.other-settings.confirm_close": "Confirmar cierre: siempre pedir confirmación antes de cerrar la ventana del navegador", - "account.other-settings.content_display.title": "Visualización de contenido", "account.other-settings.default_file_sort.by_name": "Ordenar por nombre", "account.other-settings.default_file_sort.by_time": "Ordenar por tiempo", "account.other-settings.default_file_sort.label": "Orden predeterminado de archivos", + "account.other-settings.dim_file_extensions": "Atenuar las extensiones de archivo: atenuar las extensiones de archivo para que sus nombres destaquen.", "account.other-settings.file_popovers": "Ocultar Popovers de Pestañas de Archivos: no mostrar los popovers sobre pestañas de archivos", "account.other-settings.filename_generator.description": "Seleccione cómo se generan los nombres de archivo automáticamente. En particular, para hacerlos únicos o incluir la hora actual.", "account.other-settings.filename_generator.label": "Generador de nombres de archivo", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "Configuración de IA", "account.other-settings.markdown_codebar": "Desactivar la barra de código markdown en todos los documentos markdown. Al marcar esto, se ocultan los botones adicionales de ejecutar, copiar y explicar en los bloques de código delimitados.", "account.other-settings.mask_files": "Archivos ocultos: atenuar archivos en el visor de archivos que probablemente no desees abrir", - "account.other-settings.messages.title": "Mensajes", "account.other-settings.project_popovers": "Ocultar los Popovers de Pestañas de Proyecto: no mostrar los popovers sobre las pestañas del proyecto", - "account.other-settings.projects.title": "Proyectos", "account.other-settings.standby_timeout": "Tiempo de espera en espera", "account.other-settings.symbol_bar_labels": "Mostrar etiquetas de la barra de símbolos: mostrar etiquetas en la barra de símbolos del editor de marcos", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animaciones: animar brevemente algunos aspectos, p. ej., botones", "account.other-settings.theme.antd.color_scheme": "Esquema de Color: usar colores de marca en lugar de colores predeterminados", "account.other-settings.theme.antd.compact": "Diseño Compacto: usar un diseño más compacto", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "Gestionar licencias de software y permisos de acceso.", "account.settings.overview.other": "Configuraciones y opciones misceláneas.", "account.settings.overview.payg": "Configurar el uso y la facturación de pago por uso.", + "account.settings.overview.payment_methods": "Administra tus métodos de pago guardados o añade nuevos.", "account.settings.overview.payments": "Administrar métodos de pago e historial de transacciones.", "account.settings.overview.profile": "Administra tu información personal, avatar y detalles de la cuenta.", "account.settings.overview.purchases": "Ver historial de compras y recibos.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "Ver y gestionar tus suscripciones activas.", "account.settings.overview.support": "Accede a los tickets de soporte y recursos de ayuda.", "account.settings.overview.title": "Resumen de configuración", + "account.settings.overview.upgrades": "Administra tus actualizaciones de cuota heredada.", "account.settings.sso.account_is_linked": "Tu cuenta está vinculada con (haz clic para desvincular)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {Regístrate usando tu cuenta en} other {Haz clic para vincular tu cuenta}}", "account.settings.unlisted.label": "No listada: solo se te puede encontrar con una coincidencia exacta de dirección de correo electrónico", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "Generar un documento {docName} usando IA", "ai-generator.select_llm": "Seleccionar modelo de idioma", "app.fullscreen-button.tooltip": "Modo de pantalla completa, centrado en el documento o página actual.", + "app.hotkey.dialog.help_text": "Haz clic en los marcos de arriba • La tecla 0 alterna el chat • Las teclas 1–9 enfocan los marcos • Escribe para buscar • ↑↓ navega • Return para abrir • ESC para cerrar", + "app.hotkey.dialog.search_placeholder": "Buscar archivos y páginas...", + "app.hotkey.dialog.title": "Navegación Rápida", "app.verify-email-banner.edit": "Si la dirección de correo electrónico es incorrecta, por favor edítala en la configuración de la cuenta.", "app.verify-email-banner.help.text": "Es importante tener una dirección de correo electrónico funcional. La usamos para restablecer contraseñas, enviar mensajes, notificaciones de facturación y soporte. Por favor, asegúrate de que tu correo electrónico sea correcto para mantenerte informado.", "app.verify-email-banner.text": "{sent, select, true {¡Correo electrónico enviado! Por favor, revisa tu bandeja de entrada de correo (y quizá el spam) y haz clic en el enlace de confirmación.} other {Por favor, revisa y verifica tu dirección de correo electrónico:}}", @@ -988,6 +990,7 @@ "labels.config": "Configurar", "labels.configuration": "Configuración", "labels.configuration.short": "Configurar", + "labels.connected": "Conectado", "labels.connecting": "Conectando", "labels.connection": "Conexión", "labels.copied": "copiado", @@ -1016,6 +1019,7 @@ "labels.environment": "Entorno", "labels.explorer": "Explorador", "labels.file_explorer": "Explorador de Archivos", + "labels.file_use_notifications": "Notificaciones de uso de archivos", "labels.files": "Archivos", "labels.folder": "carpeta", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Público} read_only {Solo lectura} other {Guardar}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "Detener{short, select, true {} other { Proyecto}}…", "labels.project.settings.stop-project.ok": "Sí, detener proyecto", "labels.projects": "Proyectos", + "labels.public_paths": "Rutas Públicas", "labels.published_files": "Archivos Publicados", "labels.purchases": "Compras", "labels.ready": "Listo", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "Elige un título. ¡Puedes cambiarlo fácilmente más tarde!", "projects.create-project.requireLicense": "Se requiere una licencia para crear proyectos adicionales.", "projects.filename-search.placeholder": "Buscar nombres de archivos que editaste", - "projects.list.no_starred_found": "No se encontraron proyectos destacados. Usa el icono de estrella junto a los títulos de los proyectos para marcar tus proyectos favoritos.", "projects.load-all.label": "Mostrar todos los proyectos...", "projects.operations.clear-filter": "Borrar filtro", "projects.operations.delete.button": "{deleted, select, true {Restaurar todo} other {Eliminar todo}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "Filtrar por hashtags...", "projects.table-controls.hidden.label": "Oculto", "projects.table-controls.search.placeholder": "Buscar proyectos...", + "projects.table.keyboard-row-hint": "Proyecto {title}. Usa las flechas Arriba y Abajo para moverte; presiona Enter o Espacio para abrir.", "projects.table.last-edited": "Última edición", + "projects.table.untitled": "Sin título", "purchases.automatic-payments-warning.description": "Los pagos automáticos son mucho más convenientes, te ahorrarán tiempo y asegurarán que las suscripciones no se cancelen por accidente.", "purchases.automatic-payments-warning.title": "Los pagos automáticos NO son necesarios para tener una suscripción", "purchases.automatic-payments.are-enabled": "Los pagos automáticos están habilitados", diff --git a/src/packages/frontend/i18n/trans/es_PV.json b/src/packages/frontend/i18n/trans/es_PV.json index dc59eb628b5..5814b75b346 100644 --- a/src/packages/frontend/i18n/trans/es_PV.json +++ b/src/packages/frontend/i18n/trans/es_PV.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Itzulpen Informazioa", "account.account-button.confirm.ok": "Bai, saioa amaitu", "account.account-button.confirm.title": "Saioa amaitu zure kontuan?", + "account.appearance.accessibility.enabled": "Gaitu Irisgarritasun Modua: optimizatu erabiltzailearen interfazea irisgarritasun ezaugarrientzat", + "account.appearance.accessibility.title": "Irisgarritasuna", "account.appearance.user_interface.title": "Erabiltzaile Interfazea", "account.delete-account.alert.description": "Berehala galduko duzu zure proiektu guztietara sartzeko aukera, harpidetzak bertan behera utziko dira, eta gastatu gabeko kreditu guztia galduko da. {br} {hr} ZURE KONTUA EZABATZEKO, lehenik sartu behean \"{required_text}\":", "account.delete-account.alert.message": "Ziur zaude ZURE KONTUA EZABATU nahi duzula?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "kendu, fitxategia gordetzen den bakoitzean", "account.editor-settings-autosave-interval.label": "Autogordetze tartea", "account.editor-settings.basic.title": "Oinarrizko Ezarpenak", - "account.editor-settings.color-schemes.label": "Editorearen kolore eskema", "account.editor-settings.color-schemes.panel_title": "Editorearen Kolore Eskema", "account.editor-settings.font-size.label": "Lehenetsitako letra-tamaina globala", "account.editor-settings.indent-size.label": "Indentazio tamaina", "account.editor-settings.keyboard-bindings.label": "Editorearen teklatu loturak", "account.editor-settings.keyboard.title": "Teklatua", - "account.editor-settings.title": "Editorearen Ezarpenak", "account.editor-settings.x11-keyboard-variant.label": "Teklatuaren aldaera (X11 Mahaigainarentzat)", "account.editor-settings.x11-physical-keyboard.label": "Teklatuaren diseinua (X11 Mahaigainarentzat)", "account.global-ssh-keys.help": "SSH bidez proiektu batean sartzeko, erabili honako hau username@host: [proiektuaren-id-gidoi-barik]@ssh.cocalc.com Proiektuaren IDa gidoirik gabe SSH gakoen inguruko proiektuaren ezarpenetan aurki daiteke. Proiektuen artean SSH egiteko, erabili [proiektuaren-id-gidoi-barik]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Bista zatitua Sageko kalkulu orrian", "account.keyboard-shortcuts.shortcut.toggle-comment": "Gaitu hautaketa komentatzea", "account.other-settings._page_size.label": "Fitxategi kopurua orrialde bakoitzeko", - "account.other-settings.browser_performance.title": "Nabigatzailea", + "account.other-settings.auto_focus": "Testu Sarrera Automatikoki Fokatzea: automatikoki fokatu testu sarrera eremuak agertzen direnean (adibidez, fitxategi arakatzailea, proiektuak, ...)", "account.other-settings.button_tooltips": "Ezkutatu Botoiaren Tresna-aholkuak: botoi batzuen tresna-aholkuak ezkutatzen ditu (hau partziala da)", "account.other-settings.confirm_close": "Berrestu Ixtea: beti eskatu berrespena nabigatzailearen leihoa itxi aurretik", - "account.other-settings.content_display.title": "Edukien Erakusketa", "account.other-settings.default_file_sort.by_name": "Izenez ordenatu", "account.other-settings.default_file_sort.by_time": "Ordenatu denboraren arabera", "account.other-settings.default_file_sort.label": "Fitxategi ordenazio lehenetsia", + "account.other-settings.dim_file_extensions": "Fitxategiaren luzapenak ilundu: gristatu fitxategiaren luzapenak, haien izenak nabarmentzeko.", "account.other-settings.file_popovers": "Ezkutatu Fitxategi Fitxen Popoverrak: ez erakutsi popoverrak fitxategi fitxetan", "account.other-settings.filename_generator.description": "Hautatu automatikoki sortutako fitxategi-izenak nola sortzen diren. Bereziki, bakarrak bihurtzeko edo egungo denbora sartzeko.", "account.other-settings.filename_generator.label": "Fitxategi-izenen sortzailea", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "AI Ezarpenak", "account.other-settings.markdown_codebar": "Desgaitu markdown kode barra markdown dokumentu guztietan. Hau markatzeak \"exekutatu\", \"kopiatu\" eta \"azaldu\" botoi gehigarriak ezkutatzen ditu.", "account.other-settings.mask_files": "Fitxategiak Maskaratu: ziurrenik ireki nahi ez dituzun fitxategiak esploratzailean grisez apaldu", - "account.other-settings.messages.title": "Mezuak", "account.other-settings.project_popovers": "Proiektuaren fitxa popoverrak ezkutatu: ez erakutsi popoverrak proiektuaren fitxen gainean", - "account.other-settings.projects.title": "Proiektuak", "account.other-settings.standby_timeout": "Itxaronaldi epea", "account.other-settings.symbol_bar_labels": "Ikusi Sinbolo Barra Etiketak: erakutsi etiketak marko editorearen sinbolo barran", - "account.other-settings.theme": "Gaia", "account.other-settings.theme.antd.animations": "Animazioak: gauza batzuk labur animatu, adibidez, botoiak", "account.other-settings.theme.antd.color_scheme": "Kolore Eskema: erabili marka koloreak lehenetsitako koloreen ordez", "account.other-settings.theme.antd.compact": "Diseinu Trinkoa: Diseinu trinkoagoa erabili", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "Kudeatu software lizentziak eta sarbide-baimenak.", "account.settings.overview.other": "Aukera eta ezarpen desberdinak.", "account.settings.overview.payg": "Konfiguratu ordaindu ahala erabiltzea eta fakturazioa.", + "account.settings.overview.payment_methods": "Kudeatu zure ordainketa-metodo gordeak edo gehitu berriak.", "account.settings.overview.payments": "Kudeatu ordainketa metodoak eta transakzio historia.", "account.settings.overview.profile": "Kudeatu zure informazio pertsonala, avatarra eta kontuaren xehetasunak.", "account.settings.overview.purchases": "Ikusi erosketa historia eta ordainagirienak.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "Ikusi eta kudeatu zure harpidetza aktiboak.", "account.settings.overview.support": "Sarbide laguntza-txarteletara eta laguntza-baliabideetara.", "account.settings.overview.title": "Ezarpenen Ikuspegi Orokorra", + "account.settings.overview.upgrades": "Kudeatu zure kuota berritzeak.", "account.settings.sso.account_is_linked": "Zure kontua honekin dago lotuta (klikatu lotura kentzeko)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {Eman izena zure kontuarekin} other {Egin klik zure kontua lotzeko}}", "account.settings.unlisted.label": "Zerrendatik kanpo: posta elektroniko zehatzarekin baino ez zaitzake aurkitu", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "Sortu {docName} Dokumentua AI erabiliz", "ai-generator.select_llm": "Aukeratu hizkuntza eredua", "app.fullscreen-button.tooltip": "Pantaila osoa modua, uneko dokumentuan edo orrian zentratuta.", + "app.hotkey.dialog.help_text": "Egin klik goiko markoetan • Tekla 0k txata aktibatu/desaktibatzen du • Teklak 1–9 markoetan zentratzen dira • Idatzi bilatzeko • ↑↓ nabigatzeko • Return irekitzeko • ESC ixteko", + "app.hotkey.dialog.search_placeholder": "Bilatu fitxategiak eta orriak...", + "app.hotkey.dialog.title": "Nabigazio Bizkorra", "app.verify-email-banner.edit": "Email helbidea ez bada zuzena editatu egizu kontu ezarpenetan.", "app.verify-email-banner.help.text": "Garrantzitsua da funtzionatzen duen helbide elektroniko bat izatea. Pasahitzak berrezartzeko, mezuak bidaltzeko, fakturazio jakinarazpenak egiteko eta laguntza emateko bidaltzen dugu. Mesedez, ziurtatu zure helbide elektronikoa zuzena dela informazioa jasotzeko.", "app.verify-email-banner.text": "{sent, select, true {Emaila Bidalita! Mesedez, begiratu zure email sarrera-ontzia (eta agian spam karpeta) eta klik egin berrespen estekan.} other {Mesedez, egiaztatu eta baieztatu zure helbide elektronikoa:}}", @@ -988,6 +990,7 @@ "labels.config": "Konfigurazio", "labels.configuration": "Konfigurazioa", "labels.configuration.short": "Konfigurazio", + "labels.connected": "Konektatuta", "labels.connecting": "Konexioa egiten", "labels.connection": "Konexioa", "labels.copied": "kopiatuta", @@ -1016,6 +1019,7 @@ "labels.environment": "Ingurunea", "labels.explorer": "Arakatzailea", "labels.file_explorer": "Fitxategi Arakatzailea", + "labels.file_use_notifications": "Fitxategi Erabilera Jakinarazpenak", "labels.files": "Fitxategiak", "labels.folder": "karpeta", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Publikoa} read_only {Iragargaitza} other {Gorde}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "Gelditu{short, select, true {} other { Proiektua}}…", "labels.project.settings.stop-project.ok": "Bai, gelditu proiektua", "labels.projects": "Proiektuak", + "labels.public_paths": "Bide Publikoak", "labels.published_files": "Argitaratua", "labels.purchases": "Erosketak", "labels.ready": "Prest", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "Hautatu izenburua. Erraz alda dezakezu geroago!", "projects.create-project.requireLicense": "Proiektu gehigarriak sortzeko lizentzia behar da.", "projects.filename-search.placeholder": "Bilatu editatu dituzun fitxategi izenak...", - "projects.list.no_starred_found": "Ez da izarrez markatutako proiekturik aurkitu. Erabili izar ikonoa proiektuen izenburuen ondoan zure proiektu gogokoenak laster-markan jartzeko.", "projects.load-all.label": "Erakutsi proiektu guztiak...", "projects.operations.clear-filter": "Garbitu Iragazkia", "projects.operations.delete.button": "{deleted, select, true {Berreskuratu Guztiak} other {Ezabatu Guztiak}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "Iragazi etiketen arabera...", "projects.table-controls.hidden.label": "Ezkutua", "projects.table-controls.search.placeholder": "Proiektuak bilatu...", + "projects.table.keyboard-row-hint": "Proiektua {title}. Erabili gora eta behera gezien tekla mugitzeko; sakatu Enter edo Space irekitzeko.", "projects.table.last-edited": "Azken aldiz editatua", + "projects.table.untitled": "Izenbururik gabe", "purchases.automatic-payments-warning.description": "Ordainketa automatikoak askoz erosoagoak dira, denbora aurreztuko dizute eta harpidetzak ez direla istripuz bertan behera geldituko ziurtatuko dute.", "purchases.automatic-payments-warning.title": "Ez da beharrezkoa ordainketa automatikoak izatea harpidetza izateko", "purchases.automatic-payments.are-enabled": "Ordainketa Automatikoak Gaituta Daude", diff --git a/src/packages/frontend/i18n/trans/fr_FR.json b/src/packages/frontend/i18n/trans/fr_FR.json index 29624a48bb1..0ad050e6d0d 100644 --- a/src/packages/frontend/i18n/trans/fr_FR.json +++ b/src/packages/frontend/i18n/trans/fr_FR.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Informations de traduction", "account.account-button.confirm.ok": "Oui, déconnexion", "account.account-button.confirm.title": "Se déconnecter de votre compte ?", + "account.appearance.accessibility.enabled": "Activer le mode Accessibilité : optimiser l'interface utilisateur pour les fonctionnalités d'accessibilité", + "account.appearance.accessibility.title": "Accessibilité", "account.appearance.user_interface.title": "Interface utilisateur", "account.delete-account.alert.description": "Vous perdrez immédiatement l'accès à tous vos projets, tous les abonnements seront annulés, et tout crédit non dépensé sera perdu. {br} {hr} Pour SUPPRIMER VOTRE COMPTE, entrez d'abord \"{required_text}\" ci-dessous :", "account.delete-account.alert.message": "Êtes-vous sûr de vouloir SUPPRIMER VOTRE COMPTE ?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "supprimer chaque fois que le fichier est enregistré", "account.editor-settings-autosave-interval.label": "Intervalle de sauvegarde automatique", "account.editor-settings.basic.title": "Paramètres de base", - "account.editor-settings.color-schemes.label": "Schéma de couleurs de l'éditeur", "account.editor-settings.color-schemes.panel_title": "Schéma de couleurs de l'éditeur", "account.editor-settings.font-size.label": "Taille de police globale par défaut", "account.editor-settings.indent-size.label": "Taille de l'indentation", "account.editor-settings.keyboard-bindings.label": "Raccourcis clavier de l'éditeur", "account.editor-settings.keyboard.title": "Clavier", - "account.editor-settings.title": "Éditeur", "account.editor-settings.x11-keyboard-variant.label": "Variante de clavier (pour le bureau X11)", "account.editor-settings.x11-physical-keyboard.label": "Disposition du clavier (pour le bureau X11)", "account.global-ssh-keys.help": "Pour SSH dans un projet, utilisez le format suivant username@host: [project-id-without-dashes]@ssh.cocalc.com L'identifiant du projet sans tirets se trouve dans la section des paramètres du projet concernant les clés SSH. Pour SSH entre projets, utilisez [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Vue partagée dans la feuille de calcul Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Basculer le commentaire de la sélection", "account.other-settings._page_size.label": "Nombre de fichiers par page", - "account.other-settings.browser_performance.title": "Navigateur", + "account.other-settings.auto_focus": "Texte d'entrée de mise au point automatique : met automatiquement au point les champs de saisie de texte lorsqu'ils apparaissent (par exemple, explorateur de fichiers, projets, ...)", "account.other-settings.button_tooltips": "Masquer les info-bulles des boutons : masque certaines info-bulles des boutons (ceci est seulement partiel)", "account.other-settings.confirm_close": "Confirmer la fermeture : toujours demander confirmation avant de fermer la fenêtre du navigateur", - "account.other-settings.content_display.title": "Affichage du contenu", "account.other-settings.default_file_sort.by_name": "Trier par nom", "account.other-settings.default_file_sort.by_time": "Trier par temps", "account.other-settings.default_file_sort.label": "Tri par défaut des fichiers", + "account.other-settings.dim_file_extensions": "Extensions de fichier en gris : griser les extensions de fichier pour que leurs noms se démarquent.", "account.other-settings.file_popovers": "Cacher les info-bulles des onglets de fichiers : ne pas afficher les info-bulles sur les onglets de fichiers", "account.other-settings.filename_generator.description": "Sélectionnez comment les noms de fichiers générés automatiquement sont créés. En particulier, pour les rendre uniques ou inclure l'heure actuelle.", "account.other-settings.filename_generator.label": "Générateur de nom de fichier", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "Paramètres AI", "account.other-settings.markdown_codebar": "Désactiver la barre de code markdown dans tous les documents markdown. Cocher ceci masque les boutons supplémentaires d'exécution, de copie et d'explication dans les blocs de code délimités.", "account.other-settings.mask_files": "Masquer les fichiers : griser les fichiers dans le visualiseur de fichiers que vous ne souhaitez probablement pas ouvrir", - "account.other-settings.messages.title": "Messages", "account.other-settings.project_popovers": "Masquer les popovers des onglets de projet : ne pas afficher les popovers au-dessus des onglets de projet", - "account.other-settings.projects.title": "Projets", "account.other-settings.standby_timeout": "Délai d'attente en veille", "account.other-settings.symbol_bar_labels": "Afficher les étiquettes de la barre de symboles : afficher les étiquettes dans la barre de symboles de l'éditeur de cadre", - "account.other-settings.theme": "Thème", "account.other-settings.theme.antd.animations": "Animations : animer brièvement certains aspects, par exemple les boutons", "account.other-settings.theme.antd.color_scheme": "Schéma de couleurs : utiliser les couleurs de marque au lieu des couleurs par défaut", "account.other-settings.theme.antd.compact": "Conception compacte: utiliser une conception plus compacte", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "Gérer les licences logicielles et les autorisations d'accès.", "account.settings.overview.other": "Paramètres et options divers.", "account.settings.overview.payg": "Configurer l'utilisation et la facturation à la demande.", + "account.settings.overview.payment_methods": "Gérez vos méthodes de paiement enregistrées ou ajoutez-en de nouvelles.", "account.settings.overview.payments": "Gérer les méthodes de paiement et l'historique des transactions.", "account.settings.overview.profile": "Gérez vos informations personnelles, votre avatar et les détails de votre compte.", "account.settings.overview.purchases": "Afficher l'historique des achats et les reçus.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "Afficher et gérer vos abonnements actifs.", "account.settings.overview.support": "Accédez aux tickets de support et aux ressources d'aide.", "account.settings.overview.title": "Aperçu des paramètres", + "account.settings.overview.upgrades": "Gérez vos mises à niveau de quota d'héritage.", "account.settings.sso.account_is_linked": "Votre compte est lié à (cliquez pour dissocier)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {Inscrivez-vous en utilisant votre compte sur} other {Cliquez pour lier votre compte}}", "account.settings.unlisted.label": "Non répertorié : on ne peut vous trouver qu'avec une correspondance exacte d'adresse email", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "Générer un document {docName} en utilisant l'IA", "ai-generator.select_llm": "Sélectionner le modèle de langue", "app.fullscreen-button.tooltip": "Mode plein écran, concentré sur le document ou la page actuelle.", + "app.hotkey.dialog.help_text": "Cliquez sur les cadres ci-dessus • Touche 0 pour basculer le chat • Touches 1–9 pour focaliser les cadres • Tapez pour rechercher • ↑↓ naviguer • Entrée pour ouvrir • Échap pour fermer", + "app.hotkey.dialog.search_placeholder": "Rechercher des fichiers et des pages...", + "app.hotkey.dialog.title": "Navigation rapide", "app.verify-email-banner.edit": "Si l'adresse e-mail est incorrecte, veuillez la modifier dans les paramètres du compte.", "app.verify-email-banner.help.text": "Il est important d'avoir une adresse e-mail fonctionnelle. Nous l'utilisons pour les réinitialisations de mot de passe, l'envoi de messages, les notifications de facturation et le support. Veuillez vous assurer que votre e-mail est correct pour rester informé.", "app.verify-email-banner.text": "{sent, select, true {Email envoyé ! Veuillez vérifier votre boîte de réception (et peut-être les spams) et cliquez sur le lien de confirmation.} other {Veuillez vérifier et confirmer votre adresse e-mail :}}", @@ -988,6 +990,7 @@ "labels.config": "Config", "labels.configuration": "Configuration", "labels.configuration.short": "Config", + "labels.connected": "Connecté", "labels.connecting": "Connexion", "labels.connection": "Connexion", "labels.copied": "copié", @@ -1016,6 +1019,7 @@ "labels.environment": "Environnement", "labels.explorer": "Explorateur", "labels.file_explorer": "Explorateur de fichiers", + "labels.file_use_notifications": "Notifications d'utilisation de fichier", "labels.files": "Fichiers", "labels.folder": "dossier", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Public} read_only {Lecture seule} other {Enregistrer}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "Arrêter{short, select, true {} other { Projet}}…", "labels.project.settings.stop-project.ok": "Oui, arrêter le projet", "labels.projects": "Projets", + "labels.public_paths": "Chemins Publics", "labels.published_files": "Fichiers publiés", "labels.purchases": "Achats", "labels.ready": "Prêt", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "Choisissez un titre. Vous pouvez facilement le changer plus tard !", "projects.create-project.requireLicense": "Une licence est requise pour créer des projets supplémentaires.", "projects.filename-search.placeholder": "Rechercher les noms de fichiers que vous avez modifiés...", - "projects.list.no_starred_found": "Aucun projet étoilé trouvé. Utilisez l'icône en forme d'étoile à côté des titres de projet pour ajouter vos projets favoris en signet.", "projects.load-all.label": "Afficher tous les projets...", "projects.operations.clear-filter": "Effacer le filtre", "projects.operations.delete.button": "{deleted, select, true {Restaurer tout} other {Tout supprimer}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "Filtrer par hashtags...", "projects.table-controls.hidden.label": "Caché", "projects.table-controls.search.placeholder": "Rechercher des projets...", + "projects.table.keyboard-row-hint": "Projet {title}. Utilisez les flèches Haut et Bas pour vous déplacer ; appuyez sur Entrée ou Espace pour ouvrir.", "projects.table.last-edited": "Dernière modification", + "projects.table.untitled": "Sans titre", "purchases.automatic-payments-warning.description": "Les paiements automatiques sont beaucoup plus pratiques, vous feront gagner du temps, et assurent que les abonnements ne soient pas annulés par accident.", "purchases.automatic-payments-warning.title": "Les paiements automatiques NE sont PAS nécessaires pour avoir un abonnement", "purchases.automatic-payments.are-enabled": "Les paiements automatiques sont activés", diff --git a/src/packages/frontend/i18n/trans/he_IL.json b/src/packages/frontend/i18n/trans/he_IL.json index fe22315927b..d31a194da5a 100644 --- a/src/packages/frontend/i18n/trans/he_IL.json +++ b/src/packages/frontend/i18n/trans/he_IL.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "מידע על תרגום", "account.account-button.confirm.ok": "כן, התנתק", "account.account-button.confirm.title": "לצאת מהחשבון שלך?", + "account.appearance.accessibility.enabled": "הפעל מצב נגישות: מיטוב ממשק המשתמש עבור תכונות נגישות", + "account.appearance.accessibility.title": "נגישות", "account.appearance.user_interface.title": "ממשק משתמש", "account.delete-account.alert.description": "תאבד מיד גישה לכל הפרויקטים שלך, כל המנויים יבוטלו, וכל הקרדיט שלא נוצל יאבד. {br} {hr} כדי למחוק את החשבון שלך, ראשית הכנס את \"{required_text}\" למטה:", "account.delete-account.alert.message": "האם אתה בטוח שברצונך למחוק את החשבון שלך?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "הסר בכל פעם שקובץ נשמר", "account.editor-settings-autosave-interval.label": "מרווח שמירה אוטומטית", "account.editor-settings.basic.title": "הגדרות בסיסיות", - "account.editor-settings.color-schemes.label": "ערכת צבעים של העורך", "account.editor-settings.color-schemes.panel_title": "ערכת צבעים לעורך", "account.editor-settings.font-size.label": "גודל גופן גלובלי ברירת מחדל", "account.editor-settings.indent-size.label": "גודל הזחה", "account.editor-settings.keyboard-bindings.label": "קיצורי מקלדת לעורך", "account.editor-settings.keyboard.title": "מקלדת", - "account.editor-settings.title": "עורך", "account.editor-settings.x11-keyboard-variant.label": "גרסת מקלדת (לשולחן עבודה X11)", "account.editor-settings.x11-physical-keyboard.label": "פריסת מקלדת (לשולחן העבודה X11)", "account.global-ssh-keys.help": "כדי להתחבר בפרוטוקול SSH לפרויקט, השתמש ב-username@host: [project-id-without-dashes]@ssh.cocalc.com ניתן למצוא את מזהה הפרויקט ללא מקפים בחלק של הגדרות הפרויקט לגבי מפתחות SSH. כדי להתחבר בפרוטוקול SSH בין פרויקטים, השתמש ב-[project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "תצוגה מפוצלת בגליון העבודה של Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "החלפת מצב הערות בחירה", "account.other-settings._page_size.label": "מספר הקבצים לכל עמוד", - "account.other-settings.browser_performance.title": "דפדפן", + "account.other-settings.auto_focus": "מיקוד אוטומטי על שדה טקסט: מיקוד אוטומטי על שדות טקסט כשאלה מופיעים (לדוגמה, סייר קבצים, פרויקטים, ...)", "account.other-settings.button_tooltips": "הסתרת תיאורי כלים של כפתורים: מסתיר חלק מתיאורי הכלים של הכפתורים (זה חלקי בלבד)", "account.other-settings.confirm_close": "אישור סגירה: תמיד לבקש אישור לפני סגירת חלון הדפדפן", - "account.other-settings.content_display.title": "תצוגת תוכן", "account.other-settings.default_file_sort.by_name": "מיין לפי שם", "account.other-settings.default_file_sort.by_time": "מיין לפי זמן", "account.other-settings.default_file_sort.label": "מיון קבצים ברירת מחדל", + "account.other-settings.dim_file_extensions": "סיומות קבצים בגוון אפור: צביעת סיומות קבצים באפור כדי ששמותיהם יבלטו.", "account.other-settings.file_popovers": "הסתרת פופאברים בכרטיסיות קבצים: אל תציג את הפופאברים מעל כרטיסיות הקבצים", "account.other-settings.filename_generator.description": "בחר כיצד שמות קבצים הנוצרים באופן אוטומטי נוצרים. במיוחד, כדי להפוך אותם לייחודיים או לכלול את הזמן הנוכחי.", "account.other-settings.filename_generator.label": "מחולל שמות קבצים", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "הגדרות AI", "account.other-settings.markdown_codebar": "השבת את סרגל הקוד של Markdown בכל מסמכי ה-Markdown. סימון זה מסתיר את כפתורי הריצה, ההעתקה וההסבר הנוספים בקטעי קוד מגודרים.", "account.other-settings.mask_files": "הסתר קבצים: האפור קבצים בצופה הקבצים שבדרך כלל לא תרצה לפתוח", - "account.other-settings.messages.title": "הודעות", "account.other-settings.project_popovers": "הסתרת פופאברים בכרטיסיות הפרויקט: אל תציג את הפופאברים מעל כרטיסיות הפרויקט", - "account.other-settings.projects.title": "פרויקטים", "account.other-settings.standby_timeout": "פסק זמן במצב המתנה", "account.other-settings.symbol_bar_labels": "הצג תוויות סרגל סמלים: הצג תוויות בסרגל הסמלים בעורך המסגרת", - "account.other-settings.theme": "ערכת נושא", "account.other-settings.theme.antd.animations": "אנימציות: הנפשה קצרה של חלקים מסוימים, למשל כפתורים", "account.other-settings.theme.antd.color_scheme": "ערכת צבעים: השתמש בצבעי המותג במקום בצבעי ברירת המחדל", "account.other-settings.theme.antd.compact": "עיצוב קומפקטי: השתמש בעיצוב יותר קומפקטי", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "נהל רישיונות תוכנה והרשאות גישה.", "account.settings.overview.other": "הגדרות ואפשרויות שונות.", "account.settings.overview.payg": "הגדר שימוש ותשלום לפי צריכה.", + "account.settings.overview.payment_methods": "נהל את אמצעי התשלום השמורים שלך או הוסף חדשים.", "account.settings.overview.payments": "נהל שיטות תשלום והיסטוריית עסקאות.", "account.settings.overview.profile": "נהל את המידע האישי שלך, את הדמות שלך ואת פרטי החשבון.", "account.settings.overview.purchases": "צפה בהיסטוריית רכישות וקבלות.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "צפה ונהל את המנויים הפעילים שלך.", "account.settings.overview.support": "גישה לכרטיסי תמיכה ומשאבי עזרה.", "account.settings.overview.title": "סקירת הגדרות", + "account.settings.overview.upgrades": "נהל את שדרוגי מכסת המורשת שלך.", "account.settings.sso.account_is_linked": "החשבון שלך מקושר עם (לחץ כדי לבטל קישור)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {הירשם באמצעות החשבון שלך ב} other {לחץ לקישור החשבון שלך}}", "account.settings.unlisted.label": "לא רשום: ניתן למצוא אותך רק באמצעות התאמה מדויקת של כתובת אימייל", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "צור מסמך {docName} באמצעות AI", "ai-generator.select_llm": "בחר מודל שפה", "app.fullscreen-button.tooltip": "מצב מסך מלא, ממוקד במסמך או בעמוד הנוכחי", + "app.hotkey.dialog.help_text": "לחץ על המסגרות למעלה • מקש 0 מחליף צ'אט • מקשים 1–9 ממקדים מסגרות • הקלד לחיפוש • ↑↓ ניווט • Enter לפתיחה • ESC לסגירה", + "app.hotkey.dialog.search_placeholder": "חפש קבצים ודפים...", + "app.hotkey.dialog.title": "ניווט מהיר", "app.verify-email-banner.edit": "אם כתובת הדוא\"ל שגויה, אנא ערוך אותה בהגדרות החשבון.", "app.verify-email-banner.help.text": "חשוב שתהיה כתובת דוא\"ל פעילה. אנו משתמשים בה לאיפוס סיסמאות, שליחת הודעות, התראות חיוב ותמיכה. אנא ודא שכתובת הדוא\"ל שלך נכונה כדי להישאר מעודכן.", "app.verify-email-banner.text": "{sent, select, true {האימייל נשלח! בדוק את תיבת הדואר הנכנס שלך (ואולי דואר זבל) ולחץ על קישור האישור.} other {אנא בדוק ואמת את כתובת האימייל שלך:}}", @@ -988,6 +990,7 @@ "labels.config": "הגדרות", "labels.configuration": "תצורה", "labels.configuration.short": "הגדרות", + "labels.connected": "מחובר", "labels.connecting": "מתחבר", "labels.connection": "חיבור", "labels.copied": "הועתק", @@ -1016,6 +1019,7 @@ "labels.environment": "סביבה", "labels.explorer": "סייר", "labels.file_explorer": "סייר קבצים", + "labels.file_use_notifications": "התראות שימוש בקבצים", "labels.files": "קבצים", "labels.folder": "תיקייה", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {ציבורי} read_only {לקריאה בלבד} other {שמור}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "עצור{short, select, true {} other { פרויקט}}…", "labels.project.settings.stop-project.ok": "כן, עצור פרויקט", "labels.projects": "פרויקטים", + "labels.public_paths": "נתיבים ציבוריים", "labels.published_files": "קבצים פורסמו", "labels.purchases": "רכישות", "labels.ready": "מוכן", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "בחר כותרת. תוכל לשנות אותה בקלות מאוחר יותר!", "projects.create-project.requireLicense": "נדרשת רישיון כדי ליצור פרויקטים נוספים.", "projects.filename-search.placeholder": "חפש קבצים שערכת...", - "projects.list.no_starred_found": "לא נמצאו פרויקטים מסומנים בכוכב. השתמש באייקון הכוכב ליד כותרות הפרויקטים כדי לסמן את הפרויקטים המועדפים עליך.", "projects.load-all.label": "הצג את כל הפרויקטים...", "projects.operations.clear-filter": "נקה מסנן", "projects.operations.delete.button": "{deleted, select, true {שחזר הכל} other {מחק הכל}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "סנן לפי האשטגים...", "projects.table-controls.hidden.label": "מוסתר", "projects.table-controls.search.placeholder": "חיפוש פרויקטים...", + "projects.table.keyboard-row-hint": "פרויקט {title}. השתמש בחצים למעלה ולמטה כדי לנוע; לחץ על Enter או על רווח כדי לפתוח.", "projects.table.last-edited": "נערך לאחרונה", + "projects.table.untitled": "ללא כותרת", "purchases.automatic-payments-warning.description": "תשלומים אוטומטיים הם הרבה יותר נוחים, יחסכו לך זמן, ויבטיחו שמנויים לא יבוטלו בטעות.", "purchases.automatic-payments-warning.title": "תשלומים אוטומטיים אינם נדרשים למנוי", "purchases.automatic-payments.are-enabled": "תשלומים אוטומטיים מופעלים", diff --git a/src/packages/frontend/i18n/trans/hi_IN.json b/src/packages/frontend/i18n/trans/hi_IN.json index 545a80e5d49..8ee7b765c72 100644 --- a/src/packages/frontend/i18n/trans/hi_IN.json +++ b/src/packages/frontend/i18n/trans/hi_IN.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "अनुवाद जानकारी", "account.account-button.confirm.ok": "हां, साइन आउट", "account.account-button.confirm.title": "अपने खाते से साइन आउट करें?", + "account.appearance.accessibility.enabled": "एक्सेसिबिलिटी मोड सक्षम करें: एक्सेसिबिलिटी सुविधाओं के लिए उपयोगकर्ता इंटरफ़ेस को अनुकूलित करें", + "account.appearance.accessibility.title": "पहुँच योग्यता", "account.appearance.user_interface.title": "उपयोगकर्ता इंटरफ़ेस", "account.delete-account.alert.description": "आप तुरंत अपने सभी प्रोजेक्ट्स की पहुंच खो देंगे, कोई भी सब्सक्रिप्शन रद्द कर दिए जाएंगे, और सभी बिना खर्च किया हुआ क्रेडिट खो जाएगा। {br} {hr} अपना खाता हटाने के लिए, पहले नीचे \"{required_text}\" दर्ज करें:", "account.delete-account.alert.message": "क्या आप वाकई अपना खाता हटाना चाहते हैं?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "जब भी फ़ाइल सहेजी जाती है, हटाएं", "account.editor-settings-autosave-interval.label": "ऑटोसेव अंतराल", "account.editor-settings.basic.title": "मूलभूत सेटिंग्स", - "account.editor-settings.color-schemes.label": "एडिटर रंग योजना", "account.editor-settings.color-schemes.panel_title": "संपादक रंग योजना", "account.editor-settings.font-size.label": "डिफ़ॉल्ट वैश्विक फ़ॉन्ट आकार", "account.editor-settings.indent-size.label": "इंडेंट आकार", "account.editor-settings.keyboard-bindings.label": "संपादक कीबोर्ड बाइंडिंग्स", "account.editor-settings.keyboard.title": "कीबोर्ड", - "account.editor-settings.title": "संपादक", "account.editor-settings.x11-keyboard-variant.label": "कीबोर्ड प्रकार (X11 डेस्कटॉप के लिए)", "account.editor-settings.x11-physical-keyboard.label": "कीबोर्ड लेआउट (X11 डेस्कटॉप के लिए)", "account.global-ssh-keys.help": "एक प्रोजेक्ट में SSH करने के लिए, निम्नलिखित का उपयोग करें username@host: [project-id-without-dashes]@ssh.cocalc.com SSH कुंजियों के बारे में प्रोजेक्ट सेटिंग्स के भाग में प्रोजेक्ट आईडी बिना डैश के पाई जा सकती है। प्रोजेक्ट्स के बीच SSH करने के लिए, [project-id-without-dashes]@ssh का उपयोग करें।", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Sage कार्यपत्रक में दृश्य विभाजन", "account.keyboard-shortcuts.shortcut.toggle-comment": "चयन पर टिप्पणी टॉगल करें", "account.other-settings._page_size.label": "प्रति पृष्ठ फ़ाइलों की संख्या", - "account.other-settings.browser_performance.title": "ब्राउज़र", + "account.other-settings.auto_focus": "ऑटो फोकस टेक्स्ट इनपुट: जब वे प्रकट होते हैं तो टेक्स्ट इनपुट फ़ील्ड्स पर स्वचालित रूप से फोकस करें (जैसे, फाइल एक्सप्लोरर, प्रोजेक्ट्स, ...)", "account.other-settings.button_tooltips": "हाइड बटन टूलटिप्स: कुछ बटन टूलटिप्स को छुपाता है (यह केवल आंशिक है)", "account.other-settings.confirm_close": "बंद करने की पुष्टि करें: ब्राउज़र विंडो बंद करने से पहले हमेशा पुष्टि के लिए पूछें", - "account.other-settings.content_display.title": "सामग्री प्रदर्शन", "account.other-settings.default_file_sort.by_name": "नाम के अनुसार क्रमबद्ध करें", "account.other-settings.default_file_sort.by_time": "समय के अनुसार छाँटें", "account.other-settings.default_file_sort.label": "डिफ़ॉल्ट फ़ाइल क्रम", + "account.other-settings.dim_file_extensions": "फ़ाइल एक्सटेंशन को धुंधला करें: फ़ाइल एक्सटेंशन को ग्रे कर दें ताकि उनके नाम अलग दिखें।", "account.other-settings.file_popovers": "फाइल टैब पॉपओवर छुपाएं: फाइल टैब पर पॉपओवर न दिखाएं", "account.other-settings.filename_generator.description": "स्वचालित रूप से उत्पन्न फ़ाइल नाम कैसे उत्पन्न होते हैं, इसका चयन करें। विशेष रूप से, उन्हें अद्वितीय बनाने या वर्तमान समय शामिल करने के लिए।", "account.other-settings.filename_generator.label": "फाइलनाम जनरेटर", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "एआई सेटिंग्स", "account.other-settings.markdown_codebar": "सभी मार्कडाउन दस्तावेज़ों में मार्कडाउन कोड बार को अक्षम करें. इसे चेक करने से बाड़े गए कोड ब्लॉकों में अतिरिक्त चलाएं, कॉपी करें और समझाएं बटन छिप जाते हैं।", "account.other-settings.mask_files": "मास्क फाइल्स: उन फाइलों को ग्रे आउट करें जिन्हें आप शायद खोलना नहीं चाहते फाइल्स व्यूअर में", - "account.other-settings.messages.title": "संदेश", "account.other-settings.project_popovers": "प्रोजेक्ट टैब पॉपओवर छुपाएं: प्रोजेक्ट टैब पर पॉपओवर न दिखाएं", - "account.other-settings.projects.title": "प्रोजेक्ट्स", "account.other-settings.standby_timeout": "स्टैंडबाय टाइमआउट", "account.other-settings.symbol_bar_labels": "प्रतीक पट्टी लेबल दिखाएं: फ्रेम संपादक प्रतीक पट्टी में लेबल दिखाएं", - "account.other-settings.theme": "थीम", "account.other-settings.theme.antd.animations": "एनिमेशन: कुछ पहलुओं को संक्षेप में एनिमेट करें, जैसे बटन", "account.other-settings.theme.antd.color_scheme": "रंग योजना: डिफ़ॉल्ट रंगों के बजाय ब्रांड रंगों का उपयोग करें", "account.other-settings.theme.antd.compact": "कॉम्पैक्ट डिज़ाइन: अधिक कॉम्पैक्ट डिज़ाइन का उपयोग करें", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "सॉफ़्टवेयर लाइसेंस और एक्सेस अनुमतियाँ प्रबंधित करें।", "account.settings.overview.other": "विविध सेटिंग्स और विकल्प।", "account.settings.overview.payg": "पे-एज़-यू-गो उपयोग और बिलिंग को कॉन्फ़िगर करें।", + "account.settings.overview.payment_methods": "अपने सहेजे गए भुगतान विधियों का प्रबंधन करें या नई जोड़ें।", "account.settings.overview.payments": "भुगतान विधियों और लेनदेन इतिहास का प्रबंधन करें।", "account.settings.overview.profile": "अपने व्यक्तिगत जानकारी, अवतार और खाता विवरण प्रबंधित करें।", "account.settings.overview.purchases": "खरीद इतिहास और रसीदें देखें।", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "अपनी सक्रिय सदस्यताओं को देखें और प्रबंधित करें।", "account.settings.overview.support": "सहायता टिकट और सहायता संसाधनों तक पहुँचें।", "account.settings.overview.title": "सेटिंग्स अवलोकन", + "account.settings.overview.upgrades": "अपने पुराने कोटा उन्नयन प्रबंधित करें।", "account.settings.sso.account_is_linked": "आपका खाता जुड़ा हुआ है (अनलिंक करने के लिए क्लिक करें)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {अपने खाते का उपयोग करके साइन अप करें} other {अपने खाते को लिंक करने के लिए क्लिक करें}}", "account.settings.unlisted.label": "सूचीबद्ध नहीं: आपको केवल एक सटीक ईमेल पता मिलान द्वारा पाया जा सकता है", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "AI का उपयोग करके {docName} दस्तावेज़ उत्पन्न करें", "ai-generator.select_llm": "भाषा मॉडल चुनें", "app.fullscreen-button.tooltip": "पूर्ण स्क्रीन मोड, वर्तमान दस्तावेज़ या पृष्ठ पर केंद्रित।", + "app.hotkey.dialog.help_text": "ऊपर फ्रेम्स पर क्लिक करें • कुंजी 0 चैट टॉगल करती है • कुंजियाँ 1–9 फ्रेम्स पर फोकस करती हैं • खोजने के लिए टाइप करें • ↑↓ नेविगेट करें • खोलने के लिए रिटर्न दबाएं • बंद करने के लिए ESC दबाएं", + "app.hotkey.dialog.search_placeholder": "फ़ाइलें और पृष्ठ खोजें...", + "app.hotkey.dialog.title": "त्वरित नेविगेशन", "app.verify-email-banner.edit": "यदि ईमेल पता गलत है, कृपया इसे खाता सेटिंग्स में संपादित करें।", "app.verify-email-banner.help.text": "यह महत्वपूर्ण है कि आपके पास एक कार्यशील ईमेल पता हो। हम इसे पासवर्ड रीसेट, संदेश भेजने, बिलिंग सूचनाएं और सहायता के लिए उपयोग करते हैं। कृपया सूचित रहने के लिए सुनिश्चित करें कि आपका ईमेल सही है।", "app.verify-email-banner.text": "{sent, select, true {ईमेल भेजा गया! कृपया अपने ईमेल इनबॉक्स (और शायद स्पैम) की जाँच करें और पुष्टि लिंक पर क्लिक करें।} other {कृपया अपना ईमेल पता जाँचें और सत्यापित करें:}}", @@ -988,6 +990,7 @@ "labels.config": "कॉन्फ़िग", "labels.configuration": "कॉन्फ़िगरेशन", "labels.configuration.short": "कॉन्फ़िग", + "labels.connected": "कनेक्टेड", "labels.connecting": "कनेक्टिंग", "labels.connection": "कनेक्शन", "labels.copied": "कॉपी किया गया", @@ -1016,6 +1019,7 @@ "labels.environment": "पर्यावरण", "labels.explorer": "एक्सप्लोरर", "labels.file_explorer": "फ़ाइल एक्सप्लोरर", + "labels.file_use_notifications": "फ़ाइल उपयोग सूचनाएँ", "labels.files": "फ़ाइलें", "labels.folder": "फ़ोल्डर", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {सार्वजनिक} read_only {केवल पढ़ने के लिए} other {सहेजें}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "रुकें {short, select, true {} other { प्रोजेक्ट}}…", "labels.project.settings.stop-project.ok": "हाँ, प्रोजेक्ट बंद करें", "labels.projects": "प्रोजेक्ट्स", + "labels.public_paths": "सार्वजनिक पथ", "labels.published_files": "प्रकाशित फ़ाइलें", "labels.purchases": "खरीदें", "labels.ready": "तैयार", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "शीर्षक चुनें। आप इसे बाद में आसानी से बदल सकते हैं!", "projects.create-project.requireLicense": "अतिरिक्त प्रोजेक्ट्स बनाने के लिए एक लाइसेंस आवश्यक है।", "projects.filename-search.placeholder": "संपादित की गई फ़ाइल नाम खोजें...", - "projects.list.no_starred_found": "कोई स्टार किए गए प्रोजेक्ट नहीं मिले। अपने पसंदीदा प्रोजेक्ट को बुकमार्क करने के लिए प्रोजेक्ट शीर्षकों के पास स्टार आइकन का उपयोग करें।", "projects.load-all.label": "सभी प्रोजेक्ट दिखाएँ...", "projects.operations.clear-filter": "फ़िल्टर साफ करें", "projects.operations.delete.button": "{deleted, select, true {सभी पुनःस्थापित करें} other {सभी हटाएं}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "हैशटैग द्वारा फ़िल्टर करें...", "projects.table-controls.hidden.label": "छिपा हुआ", "projects.table-controls.search.placeholder": "प्रोजेक्ट खोजें...", + "projects.table.keyboard-row-hint": "प्रोजेक्ट {title}. स्थानांतरित करने के लिए ऊपर और नीचे तीरों का उपयोग करें; खोलने के लिए Enter या Space दबाएं।", "projects.table.last-edited": "अंतिम संपादन", + "projects.table.untitled": "शीर्षकहीन", "purchases.automatic-payments-warning.description": "स्वचालित भुगतान बहुत अधिक सुविधाजनक हैं, आपका समय बचाएंगे, और सुनिश्चित करेंगे कि सदस्यताएँ गलती से रद्द न हों।", "purchases.automatic-payments-warning.title": "स्वत: भुगतान के लिए सदस्यता की आवश्यकता नहीं है", "purchases.automatic-payments.are-enabled": "स्वचालित भुगतान सक्षम हैं", diff --git a/src/packages/frontend/i18n/trans/hu_HU.json b/src/packages/frontend/i18n/trans/hu_HU.json index 196feacdd46..9fbbac33e5d 100644 --- a/src/packages/frontend/i18n/trans/hu_HU.json +++ b/src/packages/frontend/i18n/trans/hu_HU.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Fordítási információk", "account.account-button.confirm.ok": "Igen, kijelentkezés", "account.account-button.confirm.title": "Kijelentkezik a fiókjából?", + "account.appearance.accessibility.enabled": "Akkorítsa el a hozzáférhetőség módot: optimalizálja a felhasználói felületet a hozzáférhetőségi funkciókhoz", + "account.appearance.accessibility.title": "Akadálymentesség", "account.appearance.user_interface.title": "Felhasználói felület", "account.delete-account.alert.description": "Azonnal elveszíti a hozzáférést az összes projektjéhez, minden előfizetés törlésre kerül, és minden el nem költött jóváírás elveszik. {br} {hr} A FIÓK TÖRLÉSÉHEZ először írja be az alábbi \"{required_text}\" szöveget:", "account.delete-account.alert.message": "Biztosan törölni akarja a FIÓKJÁT?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "távolítsa el, amikor a fájl mentésre kerül", "account.editor-settings-autosave-interval.label": "Automatikus mentés intervalluma", "account.editor-settings.basic.title": "Alapbeállítások", - "account.editor-settings.color-schemes.label": "Szerkesztő színséma", "account.editor-settings.color-schemes.panel_title": "Szerkesztő Színséma", "account.editor-settings.font-size.label": "Alapértelmezett globális betűméret", "account.editor-settings.indent-size.label": "Behúzás mérete", "account.editor-settings.keyboard-bindings.label": "Szerkesztő billentyűkötések", "account.editor-settings.keyboard.title": "Billentyűzet", - "account.editor-settings.title": "Szerkesztő", "account.editor-settings.x11-keyboard-variant.label": "Billentyűzet variáns (X11 asztali környezethez)", "account.editor-settings.x11-physical-keyboard.label": "Billentyűzetkiosztás (X11 asztalhoz)", "account.global-ssh-keys.help": "A projektbe való SSH belépéshez használja a következőt username@host: [project-id-without-dashes]@ssh.cocalc.com A kötőjelek nélküli projektazonosító az SSH kulcsok projektbeállításai részben található. Projektek közötti SSH-hoz használja [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Megosztott nézet Sage munkalapban", "account.keyboard-shortcuts.shortcut.toggle-comment": "Kommentár kijelölésének váltása", "account.other-settings._page_size.label": "Fájlok száma oldalanként", - "account.other-settings.browser_performance.title": "Böngésző", + "account.other-settings.auto_focus": "Automatikus fókusz a szövegbeviteli mezőkre: a szövegbeviteli mezők automatikus fókuszálása, amikor megjelennek (pl. fájlkezelő, projektek, ...)", "account.other-settings.button_tooltips": "Gombok tippjeinek elrejtése: elrejti néhány gomb tippjét (ez csak részleges)", "account.other-settings.confirm_close": "Zárás megerősítése: mindig kérjen megerősítést, mielőtt bezárja a böngészőablakot", - "account.other-settings.content_display.title": "Tartalom megjelenítés", "account.other-settings.default_file_sort.by_name": "Rendezés név szerint", "account.other-settings.default_file_sort.by_time": "Idő szerinti rendezés", "account.other-settings.default_file_sort.label": "Alapértelmezett fájlrendezés", + "account.other-settings.dim_file_extensions": "Fájl kiterjesztések elhalványítása: a fájl kiterjesztések szürkévé tétele, hogy a nevük kiemelkedjen.", "account.other-settings.file_popovers": "Fájltáb Popoverek Elrejtése: ne jelenítse meg a fájltábok feletti popovereket", "account.other-settings.filename_generator.description": "Válassza ki, hogyan generálódjanak automatikusan a fájlnevek. Különösen, hogy egyediek legyenek vagy tartalmazzák az aktuális időt.", "account.other-settings.filename_generator.label": "Fájlnév generátor", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "AI beállítások", "account.other-settings.markdown_codebar": "Tiltsa le a markdown kódsávot minden markdown dokumentumban. Ennek bejelölése elrejti a további futtatás, másolás és magyarázat gombokat a keretezett kódrészletekben.", "account.other-settings.mask_files": "Fájlok elrejtése: szürkítse el a fájlokat a fájlnézegetőben, amelyeket valószínűleg nem akar megnyitni", - "account.other-settings.messages.title": "Üzenetek", "account.other-settings.project_popovers": "Projektfülek felugróinak elrejtése: ne jelenítse meg a felugrókat a projektfülek felett", - "account.other-settings.projects.title": "Projektek", "account.other-settings.standby_timeout": "Készenléti időkorlát", "account.other-settings.symbol_bar_labels": "Szimbólumsáv címkéinek megjelenítése: címkék megjelenítése a keret szerkesztő szimbólumsávjában", - "account.other-settings.theme": "Téma", "account.other-settings.theme.antd.animations": "Animációk: röviden animálni néhány elemet, pl. gombokat", "account.other-settings.theme.antd.color_scheme": "Színséma: márka színek használata az alapértelmezett színek helyett", "account.other-settings.theme.antd.compact": "Kompakt Dizájn: használjon kompaktabb dizájnt", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "Kezelje a szoftverlicenceket és a hozzáférési engedélyeket.", "account.settings.overview.other": "Egyéb beállítások és opciók.", "account.settings.overview.payg": "Konfigurálja a használat és számlázás fizetés-alapú használatát.", + "account.settings.overview.payment_methods": "Kezelje a mentett fizetési módokat, vagy adjon hozzá újakat.", "account.settings.overview.payments": "Fizetési módok és tranzakciótörténet kezelése.", "account.settings.overview.profile": "Kezelje személyes adatait, avatarját és fiókadatait.", "account.settings.overview.purchases": "Tekintse meg a vásárlási előzményeket és a nyugtákat.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "Tekintse meg és kezelje aktív előfizetéseit.", "account.settings.overview.support": "Hozzáférés támogatási jegyekhez és segítő forrásokhoz.", "account.settings.overview.title": "Beállítások áttekintése", + "account.settings.overview.upgrades": "Kezelje a régi kvótanöveléseit.", "account.settings.sso.account_is_linked": "Fiókja össze van kapcsolva (kattintson a szétkapcsoláshoz)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {Regisztráljon fiókjával a} other {Kattintson fiókja összekapcsolásához}}", "account.settings.unlisted.label": "Nem nyilvános: csak pontos email cím egyezéssel található meg", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "Hozzon létre egy {docName} dokumentumot mesterséges intelligencia segítségével", "ai-generator.select_llm": "Nyelvi modell kiválasztása", "app.fullscreen-button.tooltip": "Teljes képernyős mód, a jelenlegi dokumentumra vagy oldalra összpontosítva.", + "app.hotkey.dialog.help_text": "Kattintson a keretekre fent • A 0 gomb váltja a csevegést • Az 1–9 gombok a keretekre fókuszálnak • Gépeljen a kereséshez • ↑↓ navigálás • Enter a megnyitáshoz • ESC a bezáráshoz", + "app.hotkey.dialog.search_placeholder": "Fájlok és oldalak keresése...", + "app.hotkey.dialog.title": "Gyors navigáció", "app.verify-email-banner.edit": "Ha az e-mail cím helytelen, kérjük, szerkessze azt a fiókbeállításokban.", "app.verify-email-banner.help.text": "Fontos, hogy legyen működő e-mail címe. Jelszó-visszaállításhoz, üzenetek küldéséhez, számlázási értesítésekhez és támogatáshoz használjuk. Kérjük, győződjön meg róla, hogy az e-mail címe helyes, hogy tájékozott maradhasson.", "app.verify-email-banner.text": "{sent, select, true {Email elküldve! Kérjük, ellenőrizze az e-mail fiókját (és esetleg a spam mappát), és kattintson a megerősítő linkre.} other {Kérjük, ellenőrizze és igazolja az e-mail címét:}}", @@ -988,6 +990,7 @@ "labels.config": "Beállítás", "labels.configuration": "Konfiguráció", "labels.configuration.short": "Beállítás", + "labels.connected": "Csatlakoztatva", "labels.connecting": "Csatlakozás", "labels.connection": "Csatlakozás", "labels.copied": "másolva", @@ -1016,6 +1019,7 @@ "labels.environment": "Környezet", "labels.explorer": "Felfedező", "labels.file_explorer": "Fájlkezelő", + "labels.file_use_notifications": "Fájlhasználati értesítések", "labels.files": "Fájlok", "labels.folder": "mappa", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Nyilvános} read_only {Csak olvasható} other {Mentés}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "Állítsa le{short, select, true {} other { Projekt}}…", "labels.project.settings.stop-project.ok": "Igen, állítsa le a projektet", "labels.projects": "Projektek", + "labels.public_paths": "Nyilvános útvonalak", "labels.published_files": "Közzétett fájlok", "labels.purchases": "Vásárlások", "labels.ready": "Kész", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "Válasszon egy címet. Ezt később könnyedén megváltoztathatja!", "projects.create-project.requireLicense": "További projektek létrehozásához licenc szükséges.", "projects.filename-search.placeholder": "Keressen a szerkesztett fájlnevek között...", - "projects.list.no_starred_found": "Nincsenek csillagozott projektek. A projektcímek melletti csillag ikon használatával jelölheti meg a kedvenc projektjeit.", "projects.load-all.label": "Összes projekt megjelenítése...", "projects.operations.clear-filter": "Szűrő törlése", "projects.operations.delete.button": "{deleted, select, true {Összes visszaállítása} other {Összes törlése}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "Szűrés hashtagek alapján...", "projects.table-controls.hidden.label": "Rejtett", "projects.table-controls.search.placeholder": "Projektek keresése...", + "projects.table.keyboard-row-hint": "Projekt {title}. Használja a Fel és Le nyilakat a mozgatáshoz; nyomja meg az Enter vagy a Space gombot a megnyitáshoz.", "projects.table.last-edited": "Utoljára szerkesztve", + "projects.table.untitled": "Cím nélküli", "purchases.automatic-payments-warning.description": "Az automatikus fizetések sokkal kényelmesebbek, időt takarítanak meg, és biztosítják, hogy az előfizetések ne szűnjenek meg véletlenül.", "purchases.automatic-payments-warning.title": "Az automatikus fizetések NEM szükségesek előfizetéshez", "purchases.automatic-payments.are-enabled": "Az automatikus fizetések engedélyezve vannak", diff --git a/src/packages/frontend/i18n/trans/it_IT.json b/src/packages/frontend/i18n/trans/it_IT.json index 0d20a28b245..887b4059fe2 100644 --- a/src/packages/frontend/i18n/trans/it_IT.json +++ b/src/packages/frontend/i18n/trans/it_IT.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Informazioni sulla traduzione", "account.account-button.confirm.ok": "Sì, esci", "account.account-button.confirm.title": "Esci dal tuo account?", + "account.appearance.accessibility.enabled": "Abilita Modalità Accessibilità: ottimizza l'interfaccia utente per le funzionalità di accessibilità", + "account.appearance.accessibility.title": "Accessibilità", "account.appearance.user_interface.title": "Interfaccia utente", "account.delete-account.alert.description": "Perderai immediatamente l'accesso a tutti i tuoi progetti, eventuali abbonamenti saranno annullati e tutto il credito non speso sarà perso. {br} {hr} Per CANCELLARE IL TUO ACCOUNT, inserisci prima \"{required_text}\" qui sotto:", "account.delete-account.alert.message": "Sei sicuro di voler ELIMINARE IL TUO ACCOUNT?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "rimuovi ogni volta che il file viene salvato", "account.editor-settings-autosave-interval.label": "Intervallo di salvataggio automatico", "account.editor-settings.basic.title": "Impostazioni di base", - "account.editor-settings.color-schemes.label": "Schema di colori dell'editor", "account.editor-settings.color-schemes.panel_title": "Schema di colori dell'editor", "account.editor-settings.font-size.label": "Dimensione predefinita del carattere globale", "account.editor-settings.indent-size.label": "Dimensione rientro", "account.editor-settings.keyboard-bindings.label": "Associazioni tastiera dell'editor", "account.editor-settings.keyboard.title": "Tastiera", - "account.editor-settings.title": "Editor", "account.editor-settings.x11-keyboard-variant.label": "Variante tastiera (per Desktop X11)", "account.editor-settings.x11-physical-keyboard.label": "Layout tastiera (per desktop X11)", "account.global-ssh-keys.help": "Per SSH in un progetto, usa il seguente username@host: [project-id-without-dashes]@ssh.cocalc.com L'id del progetto senza trattini può essere trovato nella parte delle impostazioni del progetto riguardante le chiavi SSH. Per SSH tra progetti, usa [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Visualizzazione divisa nel foglio di lavoro Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Attiva/disattiva commento selezione", "account.other-settings._page_size.label": "Numero di file per pagina", - "account.other-settings.browser_performance.title": "Browser", + "account.other-settings.auto_focus": "Auto Focus Text Input: concentra automaticamente i campi di inserimento testo quando appaiono (ad esempio, esploratore di file, progetti, ...)", "account.other-settings.button_tooltips": "Nascondi suggerimenti dei pulsanti: nasconde alcuni suggerimenti dei pulsanti (questo è solo parziale)", "account.other-settings.confirm_close": "Conferma Chiusura: chiedi sempre conferma prima di chiudere la finestra del browser", - "account.other-settings.content_display.title": "Visualizzazione contenuti", "account.other-settings.default_file_sort.by_name": "Ordina per nome", "account.other-settings.default_file_sort.by_time": "Ordina per tempo", "account.other-settings.default_file_sort.label": "Ordinamento file predefinito", + "account.other-settings.dim_file_extensions": "Estensioni file opache: rendi opache le estensioni dei file in modo che i loro nomi risaltino.", "account.other-settings.file_popovers": "Nascondi Popup Schede File: non mostrare i popup sulle schede dei file", "account.other-settings.filename_generator.description": "Seleziona come vengono generati automaticamente i nomi dei file. In particolare, per renderli unici o includere l'ora attuale.", "account.other-settings.filename_generator.label": "Generatore di nomi file", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "Impostazioni AI", "account.other-settings.markdown_codebar": "Disabilita la barra del codice markdown in tutti i documenti markdown. Se selezionato, questo nasconde i pulsanti aggiuntivi di esecuzione, copia e spiegazione nei blocchi di codice recintati.", "account.other-settings.mask_files": "Maschera file: disattiva i file nel visualizzatore di file che probabilmente non vuoi aprire", - "account.other-settings.messages.title": "Messaggi", "account.other-settings.project_popovers": "Nascondi i Popover delle Schede del Progetto: non mostrare i popover sulle schede del progetto", - "account.other-settings.projects.title": "Progetti", "account.other-settings.standby_timeout": "Timeout di attesa", "account.other-settings.symbol_bar_labels": "Mostra etichette barra simboli: mostra etichette nella barra simboli dell'editor di frame", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animazioni: animare brevemente alcuni aspetti, ad es. pulsanti", "account.other-settings.theme.antd.color_scheme": "Schema Colori: usa i colori del marchio invece dei colori predefiniti", "account.other-settings.theme.antd.compact": "Design compatto: usa un design più compatto", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "Gestisci le licenze software e i permessi di accesso.", "account.settings.overview.other": "Impostazioni e opzioni varie.", "account.settings.overview.payg": "Configura l'uso a consumo e la fatturazione.", + "account.settings.overview.payment_methods": "Gestisci i tuoi metodi di pagamento salvati o aggiungine di nuovi.", "account.settings.overview.payments": "Gestisci i metodi di pagamento e la cronologia delle transazioni.", "account.settings.overview.profile": "Gestisci le tue informazioni personali, avatar e dettagli dell'account.", "account.settings.overview.purchases": "Visualizza la cronologia degli acquisti e le ricevute.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "Visualizza e gestisci i tuoi abbonamenti attivi.", "account.settings.overview.support": "Accedi ai ticket di supporto e alle risorse di aiuto.", "account.settings.overview.title": "Panoramica delle Impostazioni", + "account.settings.overview.upgrades": "Gestisci i tuoi aggiornamenti di quota legacy.", "account.settings.sso.account_is_linked": "Il tuo account è collegato con (clicca per scollegare)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {Iscriviti utilizzando il tuo account su} other {Clicca per collegare il tuo account}}", "account.settings.unlisted.label": "Non in elenco: puoi essere trovato solo con una corrispondenza esatta dell'indirizzo email", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "Genera un documento {docName} utilizzando l'IA", "ai-generator.select_llm": "Seleziona modello linguistico", "app.fullscreen-button.tooltip": "Modalità a schermo intero, focalizzata sul documento o pagina corrente", + "app.hotkey.dialog.help_text": "Clicca sui frame sopra • Tasto 0 attiva/disattiva chat • Tasti 1–9 mettono a fuoco i frame • Digita per cercare • ↑↓ naviga • Invio per aprire • ESC per chiudere", + "app.hotkey.dialog.search_placeholder": "Cerca file e pagine...", + "app.hotkey.dialog.title": "Navigazione Rapida", "app.verify-email-banner.edit": "Se l'indirizzo email è sbagliato, per favore modificalo nelle impostazioni dell'account.", "app.verify-email-banner.help.text": "È importante avere un indirizzo email funzionante. Lo utilizziamo per reimpostare le password, inviare messaggi, notifiche di fatturazione e supporto. Assicurati che la tua email sia corretta per rimanere informato.", "app.verify-email-banner.text": "{sent, select, true {Email inviata! Controlla la tua casella di posta elettronica (e magari spam) e clicca sul link di conferma.} other {Per favore controlla e verifica il tuo indirizzo email:}}", @@ -988,6 +990,7 @@ "labels.config": "Configura", "labels.configuration": "Configurazione", "labels.configuration.short": "Configura", + "labels.connected": "Connesso", "labels.connecting": "Connessione", "labels.connection": "Connessione", "labels.copied": "copiato", @@ -1016,6 +1019,7 @@ "labels.environment": "Ambiente", "labels.explorer": "Esploratore", "labels.file_explorer": "Esplora File", + "labels.file_use_notifications": "Notifiche di utilizzo file", "labels.files": "File", "labels.folder": "cartella", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Pubblico} read_only {Sola lettura} other {Salva}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "Stop{short, select, true {} other { Progetto}}…", "labels.project.settings.stop-project.ok": "Sì, ferma progetto", "labels.projects": "Progetti", + "labels.public_paths": "Percorsi Pubblici", "labels.published_files": "File Pubblicati", "labels.purchases": "Acquisti", "labels.ready": "Pronto", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "Scegli un titolo. Puoi cambiarlo facilmente in seguito!", "projects.create-project.requireLicense": "È necessaria una licenza per creare progetti aggiuntivi.", "projects.filename-search.placeholder": "Cerca i nomi dei file che hai modificato...", - "projects.list.no_starred_found": "Nessun progetto con stella trovato. Usa l'icona a stella accanto ai titoli dei progetti per aggiungere ai segnalibri i tuoi progetti preferiti.", "projects.load-all.label": "Mostra tutti i progetti...", "projects.operations.clear-filter": "Cancella filtro", "projects.operations.delete.button": "{deleted, select, true {Ripristina Tutto} other {Elimina Tutto}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "Filtra per hashtag...", "projects.table-controls.hidden.label": "Nascosto", "projects.table-controls.search.placeholder": "Cerca progetti...", + "projects.table.keyboard-row-hint": "Progetto {title}. Usa le frecce Su e Giù per spostarti; premi Invio o Spazio per aprire.", "projects.table.last-edited": "Ultima modifica", + "projects.table.untitled": "Senza titolo", "purchases.automatic-payments-warning.description": "I pagamenti automatici sono molto più convenienti, ti fanno risparmiare tempo e assicurano che gli abbonamenti non vengano annullati per errore.", "purchases.automatic-payments-warning.title": "I pagamenti automatici NON sono necessari per avere un abbonamento", "purchases.automatic-payments.are-enabled": "I pagamenti automatici sono abilitati", diff --git a/src/packages/frontend/i18n/trans/ja_JP.json b/src/packages/frontend/i18n/trans/ja_JP.json index 97112dca3e3..432dbee4339 100644 --- a/src/packages/frontend/i18n/trans/ja_JP.json +++ b/src/packages/frontend/i18n/trans/ja_JP.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "翻訳情報", "account.account-button.confirm.ok": "はい、サインアウト", "account.account-button.confirm.title": "アカウントからサインアウトしますか?", + "account.appearance.accessibility.enabled": "アクセシビリティモードを有効化:アクセシビリティ機能に合わせてユーザーインターフェースを最適化", + "account.appearance.accessibility.title": "アクセシビリティ", "account.appearance.user_interface.title": "ユーザーインターフェース", "account.delete-account.alert.description": "すぐにすべてのプロジェクトへのアクセスを失い、すべてのサブスクリプションがキャンセルされ、未使用のクレジットはすべて失われます。{br} {hr} アカウントを削除するには、まず下に「{required_text}」を入力してください。", "account.delete-account.alert.message": "本当にアカウントを削除しますか?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "ファイルが保存されるたびに削除", "account.editor-settings-autosave-interval.label": "自動保存間隔", "account.editor-settings.basic.title": "基本設定", - "account.editor-settings.color-schemes.label": "エディターのカラースキーム", "account.editor-settings.color-schemes.panel_title": "エディターのカラースキーム", "account.editor-settings.font-size.label": "デフォルトのグローバルフォントサイズ", "account.editor-settings.indent-size.label": "インデントサイズ", "account.editor-settings.keyboard-bindings.label": "エディターのキーボードバインディング", "account.editor-settings.keyboard.title": "キーボード", - "account.editor-settings.title": "エディター", "account.editor-settings.x11-keyboard-variant.label": "X11デスクトップ用のキーボードバリアント", "account.editor-settings.x11-physical-keyboard.label": "キーボードレイアウト(X11デスクトップ用)", "account.global-ssh-keys.help": "プロジェクトにSSHするには、次のusername@host: [project-id-without-dashes]@ssh.cocalc.comを使用します。ダッシュのないプロジェクトIDは、SSHキーに関するプロジェクト設定の部分で見つけることができます。プロジェクト間でSSHするには、[project-id-without-dashes]@sshを使用します。", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Sageワークシートで分割表示", "account.keyboard-shortcuts.shortcut.toggle-comment": "選択範囲のコメントを切り替え", "account.other-settings._page_size.label": "1ページあたりのファイル数", - "account.other-settings.browser_performance.title": "ブラウザー", + "account.other-settings.auto_focus": "自動フォーカステキスト入力: テキスト入力フィールドが表示されたときに自動的にフォーカスする (例: ファイルエクスプローラー、プロジェクト、...)", "account.other-settings.button_tooltips": "ボタンのツールチップを非表示: 一部のボタンのツールチップを非表示にします(これは部分的です)", "account.other-settings.confirm_close": "閉じる確認: ブラウザーウィンドウを閉じる前に必ず確認を求める", - "account.other-settings.content_display.title": "コンテンツ表示", "account.other-settings.default_file_sort.by_name": "名前で並べ替え", "account.other-settings.default_file_sort.by_time": "時間順に並べ替え", "account.other-settings.default_file_sort.label": "デフォルトファイルソート", + "account.other-settings.dim_file_extensions": "ファイル拡張子を薄く表示: ファイル名が目立つようにファイル拡張子をグレーアウトします。", "account.other-settings.file_popovers": "ファイルタブポップオーバーを非表示: ファイルタブ上にポップオーバーを表示しない", "account.other-settings.filename_generator.description": "自動生成されるファイル名の生成方法を選択します。特に、それらをユニークにするか、現在の時間を含めるかを選択します。", "account.other-settings.filename_generator.label": "ファイル名ジェネレーター", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "AI設定", "account.other-settings.markdown_codebar": "すべてのMarkdown文書でMarkdownコードバーを無効化します。これをチェックすると、囲まれたコードブロック内の追加の実行、コピー、説明ボタンが非表示になります。", "account.other-settings.mask_files": "マスクファイル: 開きたくないファイルをファイルビューアでグレーアウト", - "account.other-settings.messages.title": "メッセージ", "account.other-settings.project_popovers": "プロジェクトタブのポップオーバーを非表示: プロジェクトタブ上のポップオーバーを表示しない", - "account.other-settings.projects.title": "プロジェクト", "account.other-settings.standby_timeout": "待機タイムアウト", "account.other-settings.symbol_bar_labels": "シンボルバーラベルを表示:フレームエディタのシンボルバーにラベルを表示", - "account.other-settings.theme": "テーマ", "account.other-settings.theme.antd.animations": "アニメーション: ボタンなどの要素を簡潔にアニメーション化", "account.other-settings.theme.antd.color_scheme": "カラー スキーム: デフォルトの色ではなくブランドの色を使用", "account.other-settings.theme.antd.compact": "コンパクトデザイン: よりコンパクトなデザインを使用", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "ソフトウェアライセンスとアクセス権限を管理する。", "account.settings.overview.other": "その他の設定とオプション", "account.settings.overview.payg": "従量課金の使用と請求を設定する。", + "account.settings.overview.payment_methods": "保存された支払い方法を管理するか、新しいものを追加します。", "account.settings.overview.payments": "支払い方法と取引履歴を管理します。", "account.settings.overview.profile": "個人情報、アバター、アカウント詳細を管理します。", "account.settings.overview.purchases": "購入履歴と領収書を表示。", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "アクティブなサブスクリプションを表示および管理します。", "account.settings.overview.support": "サポートチケットとヘルプリソースにアクセスする。", "account.settings.overview.title": "設定概要", + "account.settings.overview.upgrades": "レガシークォータのアップグレードを管理します。", "account.settings.sso.account_is_linked": "あなたのアカウントはリンクされています(クリックしてリンクを解除)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {アカウントでサインアップ} other {アカウントをリンクするにはクリック}}", "account.settings.unlisted.label": "非公開:正確なメールアドレスの一致でのみ見つけられます", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "AIを使用して{docName}ドキュメントを生成", "ai-generator.select_llm": "言語モデルを選択", "app.fullscreen-button.tooltip": "全画面モード、現在のドキュメントまたはページに集中。", + "app.hotkey.dialog.help_text": "上のフレームをクリック • キー0でチャットを切り替え • キー1–9でフレームにフォーカス • 検索するには入力 • ↑↓でナビゲート • Enterで開く • ESCで閉じる", + "app.hotkey.dialog.search_placeholder": "ファイルとページを検索...", + "app.hotkey.dialog.title": "クイックナビゲーション", "app.verify-email-banner.edit": "メールアドレスが間違っている場合は、アカウント設定で編集してください。", "app.verify-email-banner.help.text": "作業可能なメールアドレスを持つことが重要です。パスワードのリセット、メッセージの送信、請求通知、サポートのために使用します。情報を受け取るために、メールアドレスが正しいことを確認してください。", "app.verify-email-banner.text": "{sent, select, true {メールが送信されました!メールの受信トレイ(場合によってはスパムフォルダ)を確認し、確認リンクをクリックしてください。} other {メールアドレスを確認してください:}}", @@ -988,6 +990,7 @@ "labels.config": "設定", "labels.configuration": "設定", "labels.configuration.short": "設定", + "labels.connected": "接続済み", "labels.connecting": "接続中", "labels.connection": "接続", "labels.copied": "コピー済み", @@ -1016,6 +1019,7 @@ "labels.environment": "環境", "labels.explorer": "書類", "labels.file_explorer": "ファイルエクスプローラー", + "labels.file_use_notifications": "ファイル使用通知", "labels.files": "ファイル", "labels.folder": "フォルダー", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {公開} read_only {読み取り専用} other {保存}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "停止{short, select, true {} other {プロジェクト}}…", "labels.project.settings.stop-project.ok": "はい、プロジェクトを停止", "labels.projects": "プロジェクト", + "labels.public_paths": "公開パス", "labels.published_files": "公開されたファイル", "labels.purchases": "購入", "labels.ready": "準備完了", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "タイトルを選んでください。後で簡単に変更できます!", "projects.create-project.requireLicense": "追加のプロジェクトを作成するにはライセンスが必要です。", "projects.filename-search.placeholder": "編集したファイル名を検索...", - "projects.list.no_starred_found": "スター付きのプロジェクトが見つかりませんでした。お気に入りのプロジェクトをブックマークするには、プロジェクトタイトルの横にある星アイコンを使用してください。", "projects.load-all.label": "すべてのプロジェクトを表示...", "projects.operations.clear-filter": "フィルターをクリア", "projects.operations.delete.button": "{deleted, select, true {すべてを復元} other {すべてを削除}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "ハッシュタグでフィルター...", "projects.table-controls.hidden.label": "非表示", "projects.table-controls.search.placeholder": "プロジェクトを検索...", + "projects.table.keyboard-row-hint": "プロジェクト {title}。上下矢印キーで移動し、EnterキーまたはSpaceキーで開く。", "projects.table.last-edited": "最終編集", + "projects.table.untitled": "無題", "purchases.automatic-payments-warning.description": "自動支払いはずっと便利で、時間を節約し、サブスクリプションが誤ってキャンセルされないようにします。", "purchases.automatic-payments-warning.title": "自動支払いはサブスクリプションに必要ありません", "purchases.automatic-payments.are-enabled": "自動支払いが有効です", diff --git a/src/packages/frontend/i18n/trans/ko_KR.json b/src/packages/frontend/i18n/trans/ko_KR.json index 1234fd4c23d..8af44511097 100644 --- a/src/packages/frontend/i18n/trans/ko_KR.json +++ b/src/packages/frontend/i18n/trans/ko_KR.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "번역 정보", "account.account-button.confirm.ok": "예, 로그아웃", "account.account-button.confirm.title": "계정에서 로그아웃하시겠습니까?", + "account.appearance.accessibility.enabled": "접근성 모드 활성화: 접근성 기능에 맞춰 사용자 인터페이스 최적화", + "account.appearance.accessibility.title": "접근성", "account.appearance.user_interface.title": "사용자 인터페이스", "account.delete-account.alert.description": "즉시 모든 프로젝트에 대한 접근 권한을 잃게 되며, 모든 구독이 취소되고 사용하지 않은 크레딧이 모두 소멸됩니다. {br} {hr} 계정을 삭제하려면 먼저 아래에 \"{required_text}\"를 입력하십시오:", "account.delete-account.alert.message": "계정을 삭제하시겠습니까?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "파일이 저장될 때마다 제거", "account.editor-settings-autosave-interval.label": "자동 저장 간격", "account.editor-settings.basic.title": "기본 설정", - "account.editor-settings.color-schemes.label": "편집기 색상 테마", "account.editor-settings.color-schemes.panel_title": "편집기 색상 테마", "account.editor-settings.font-size.label": "기본 전역 글꼴 크기", "account.editor-settings.indent-size.label": "들여쓰기 크기", "account.editor-settings.keyboard-bindings.label": "편집기 키보드 바인딩", "account.editor-settings.keyboard.title": "키보드", - "account.editor-settings.title": "에디터", "account.editor-settings.x11-keyboard-variant.label": "키보드 변형 (X11 데스크탑용)", "account.editor-settings.x11-physical-keyboard.label": "키보드 레이아웃 (X11 데스크탑용)", "account.global-ssh-keys.help": "프로젝트에 SSH 접속을 하려면 다음을 사용하세요 username@host: [project-id-without-dashes]@ssh.cocalc.com 대시 없이 프로젝트 ID는 SSH 키와 관련된 프로젝트 설정 부분에서 찾을 수 있습니다. 프로젝트 간에 SSH 접속을 하려면 [project-id-without-dashes]@ssh를 사용하세요.", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Sage 워크시트에서 분할 보기", "account.keyboard-shortcuts.shortcut.toggle-comment": "선택 주석 전환", "account.other-settings._page_size.label": "페이지당 파일 수", - "account.other-settings.browser_performance.title": "브라우저", + "account.other-settings.auto_focus": "자동 포커스 텍스트 입력: 텍스트 입력 필드가 나타날 때 자동으로 포커스 (예: 파일 탐색기, 프로젝트, ...)", "account.other-settings.button_tooltips": "버튼 툴팁 숨기기: 일부 버튼 툴팁 숨기기 (부분적으로만 적용됨)", "account.other-settings.confirm_close": "종료 확인: 브라우저 창을 닫기 전에 항상 확인 요청", - "account.other-settings.content_display.title": "콘텐츠 표시", "account.other-settings.default_file_sort.by_name": "이름으로 정렬", "account.other-settings.default_file_sort.by_time": "시간순 정렬", "account.other-settings.default_file_sort.label": "기본 파일 정렬", + "account.other-settings.dim_file_extensions": "파일 확장자 흐리게: 파일 이름이 돋보이도록 확장자를 회색으로 표시합니다.", "account.other-settings.file_popovers": "파일 탭 팝오버 숨기기: 파일 탭 위에 팝오버를 표시하지 않음", "account.other-settings.filename_generator.description": "자동 생성된 파일 이름이 생성되는 방식을 선택하십시오. 특히, 고유하게 만들거나 현재 시간을 포함하도록 선택하십시오.", "account.other-settings.filename_generator.label": "파일명 생성기", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "AI 설정", "account.other-settings.markdown_codebar": "모든 마크다운 문서에서 마크다운 코드 바 비활성화. 이 옵션을 선택하면 코드 블록의 실행, 복사 및 설명 버튼이 숨겨집니다.", "account.other-settings.mask_files": "파일 숨기기: 파일 뷰어에서 열고 싶지 않은 파일을 회색으로 표시", - "account.other-settings.messages.title": "메시지", "account.other-settings.project_popovers": "프로젝트 탭 팝오버 숨기기: 프로젝트 탭 위에 팝오버를 표시하지 않음", - "account.other-settings.projects.title": "프로젝트", "account.other-settings.standby_timeout": "대기 시간 초과", "account.other-settings.symbol_bar_labels": "기호 막대 레이블 표시: 프레임 편집기 기호 막대에 레이블 표시", - "account.other-settings.theme": "테마", "account.other-settings.theme.antd.animations": "애니메이션: 버튼과 같은 일부 측면을 간단히 애니메이션화합니다", "account.other-settings.theme.antd.color_scheme": "색상 테마: 기본 색상 대신 브랜드 색상 사용", "account.other-settings.theme.antd.compact": "간결한 디자인: 더 간결한 디자인 사용", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "소프트웨어 라이선스 및 접근 권한 관리.", "account.settings.overview.other": "기타 설정 및 옵션.", "account.settings.overview.payg": "사용량 기반 결제 및 청구 설정.", + "account.settings.overview.payment_methods": "저장된 결제 수단을 관리하거나 새 결제 수단을 추가하세요.", "account.settings.overview.payments": "결제 방법 및 거래 내역 관리.", "account.settings.overview.profile": "개인 정보, 아바타 및 계정 세부 정보를 관리하세요.", "account.settings.overview.purchases": "구매 내역 및 영수증 보기.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "활성 구독을 보고 관리하세요.", "account.settings.overview.support": "지원 티켓 및 도움말 리소스에 액세스하십시오.", "account.settings.overview.title": "설정 개요", + "account.settings.overview.upgrades": "레거시 할당량 업그레이드를 관리하세요.", "account.settings.sso.account_is_linked": "귀하의 계정이 연결되었습니다 (클릭하여 연결 해제)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {계정으로 가입하기} other {계정을 연결하려면 클릭하세요}}", "account.settings.unlisted.label": "비공개: 정확한 이메일 주소로만 찾을 수 있습니다", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "AI를 사용하여 {docName} 문서 생성", "ai-generator.select_llm": "언어 모델 선택", "app.fullscreen-button.tooltip": "풀스크린 모드, 현재 문서나 페이지에 집중.", + "app.hotkey.dialog.help_text": "위의 프레임을 클릭 • 0 키로 채팅 전환 • 1–9 키로 프레임 선택 • 검색하려면 입력 • ↑↓ 탐색 • Return으로 열기 • ESC로 닫기", + "app.hotkey.dialog.search_placeholder": "파일 및 페이지 검색...", + "app.hotkey.dialog.title": "빠른 탐색", "app.verify-email-banner.edit": "이메일 주소가 잘못된 경우 계정 설정에서 편집하십시오.", "app.verify-email-banner.help.text": "작동하는 이메일 주소를 가지고 있는 것이 중요합니다. 우리는 비밀번호 재설정, 메시지 전송, 청구 알림 및 지원을 위해 이메일을 사용합니다. 정보를 받으려면 이메일이 올바른지 확인하세요.", "app.verify-email-banner.text": "{sent, select, true {이메일이 전송되었습니다! 이메일 받은 편지함(그리고 스팸 폴더도)에서 확인하고 확인 링크를 클릭하세요.} other {이메일 주소를 확인하고 인증해 주세요:}}", @@ -988,6 +990,7 @@ "labels.config": "구성", "labels.configuration": "구성", "labels.configuration.short": "구성", + "labels.connected": "연결됨", "labels.connecting": "연결 중", "labels.connection": "연결", "labels.copied": "복사됨", @@ -1016,6 +1019,7 @@ "labels.environment": "환경", "labels.explorer": "탐색기", "labels.file_explorer": "파일 탐색기", + "labels.file_use_notifications": "파일 사용 알림", "labels.files": "파일", "labels.folder": "폴더", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {공개} read_only {읽기 전용} other {저장}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "중지{short, select, true {} other { 프로젝트}}…", "labels.project.settings.stop-project.ok": "예, 프로젝트 중지", "labels.projects": "프로젝트", + "labels.public_paths": "공개 경로", "labels.published_files": "게시된 파일", "labels.purchases": "구매", "labels.ready": "준비됨", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "제목을 선택하세요. 나중에 쉽게 변경할 수 있습니다!", "projects.create-project.requireLicense": "추가 프로젝트를 만들려면 라이선스가 필요합니다.", "projects.filename-search.placeholder": "편집한 파일 이름 검색...", - "projects.list.no_starred_found": "별표가 있는 프로젝트를 찾을 수 없습니다. 프로젝트 제목 옆의 별 아이콘을 사용하여 즐겨찾는 프로젝트를 북마크하세요.", "projects.load-all.label": "모든 프로젝트 보기...", "projects.operations.clear-filter": "필터 지우기", "projects.operations.delete.button": "{deleted, select, true {모두 복구} other {모두 삭제}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "해시태그로 필터링...", "projects.table-controls.hidden.label": "숨김", "projects.table-controls.search.placeholder": "프로젝트 검색...", + "projects.table.keyboard-row-hint": "프로젝트 {title}. 이동하려면 위아래 화살표를 사용하고, 열려면 Enter 또는 Space 키를 누르세요.", "projects.table.last-edited": "마지막 편집", + "projects.table.untitled": "제목 없음", "purchases.automatic-payments-warning.description": "자동 결제는 훨씬 더 편리하고, 시간을 절약하며, 구독이 실수로 취소되지 않도록 보장합니다.", "purchases.automatic-payments-warning.title": "자동 결제는 구독에 필수적이지 않습니다", "purchases.automatic-payments.are-enabled": "자동 결제가 활성화되었습니다", diff --git a/src/packages/frontend/i18n/trans/nl_NL.json b/src/packages/frontend/i18n/trans/nl_NL.json index 8ddfd4b7b54..d9f0c91992c 100644 --- a/src/packages/frontend/i18n/trans/nl_NL.json +++ b/src/packages/frontend/i18n/trans/nl_NL.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Vertaalinformatie", "account.account-button.confirm.ok": "Ja, afmelden", "account.account-button.confirm.title": "Afmelden van uw account?", + "account.appearance.accessibility.enabled": "Toegankelijkheidsmodus inschakelen: optimaliseer de gebruikersinterface voor toegankelijkheidsfuncties", + "account.appearance.accessibility.title": "Toegankelijkheid", "account.appearance.user_interface.title": "Gebruikersinterface", "account.delete-account.alert.description": "U verliest onmiddellijk toegang tot al uw projecten, eventuele abonnementen worden geannuleerd en al het ongebruikte tegoed gaat verloren. {br} {hr} Om UW ACCOUNT TE VERWIJDEREN, voert u eerst hieronder \"{required_text}\" in:", "account.delete-account.alert.message": "Weet u zeker dat u UW ACCOUNT WILT VERWIJDEREN?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "verwijderen wanneer bestand wordt opgeslagen", "account.editor-settings-autosave-interval.label": "Automatisch opslaan interval", "account.editor-settings.basic.title": "Basisinstellingen", - "account.editor-settings.color-schemes.label": "Editor kleurenschema", "account.editor-settings.color-schemes.panel_title": "Editor Kleurschema", "account.editor-settings.font-size.label": "Standaard globale lettergrootte", "account.editor-settings.indent-size.label": "Inspringingsgrootte", "account.editor-settings.keyboard-bindings.label": "Editor-toetsenbordkoppelingen", "account.editor-settings.keyboard.title": "Toetsenbord", - "account.editor-settings.title": "Editorinstellingen", "account.editor-settings.x11-keyboard-variant.label": "Toetsenbordvariant (voor X11 Desktop)", "account.editor-settings.x11-physical-keyboard.label": "Toetsenbordindeling (voor X11 Desktop)", "account.global-ssh-keys.help": "Om SSH in een project te gebruiken, gebruik de volgende gebruikersnaam@host: [project-id-zonder-streepjes]@ssh.cocalc.com De project-id zonder streepjes is te vinden in het gedeelte van de projectinstellingen over SSH-sleutels. Om SSH tussen projecten te gebruiken, gebruik [project-id-zonder-streepjes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Gesplitste weergave in Sage-werkblad", "account.keyboard-shortcuts.shortcut.toggle-comment": "Selectie van commentaar voorzien/ongedaan maken", "account.other-settings._page_size.label": "Aantal bestanden per pagina", - "account.other-settings.browser_performance.title": "Browser", + "account.other-settings.auto_focus": "Auto Focus Tekstinvoer: tekstinvoervelden automatisch focussen wanneer ze verschijnen (bijv. bestandsverkenner, projecten, ...)", "account.other-settings.button_tooltips": "Verberg knopinfo: verbergt enkele knopinfo (dit is slechts gedeeltelijk)", "account.other-settings.confirm_close": "Bevestig Sluiten: altijd om bevestiging vragen voordat het browservenster wordt gesloten", - "account.other-settings.content_display.title": "Inhoudweergave", "account.other-settings.default_file_sort.by_name": "Sorteren op naam", "account.other-settings.default_file_sort.by_time": "Sorteren op tijd", "account.other-settings.default_file_sort.label": "Standaard bestandssortering", + "account.other-settings.dim_file_extensions": "Bestandsextensies dempen: bestandsextensies grijs maken zodat hun namen opvallen.", "account.other-settings.file_popovers": "Bestandstab-Popovers verbergen: toon de popovers over bestandstabs niet", "account.other-settings.filename_generator.description": "Selecteer hoe automatisch gegenereerde bestandsnamen worden gegenereerd. Met name om ze uniek te maken of de huidige tijd op te nemen.", "account.other-settings.filename_generator.label": "Bestandsnaam generator", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "AI-instellingen", "account.other-settings.markdown_codebar": "Deactiveer de markdown codebalk in alle markdown-documenten. Als u dit aanvinkt, worden de extra uitvoer-, kopieer- en uitlegknoppen in omkaderde codeblokken verborgen.", "account.other-settings.mask_files": "Masker bestanden: grijs bestanden uit in de bestandsweergave die je waarschijnlijk niet wilt openen", - "account.other-settings.messages.title": "Berichten", "account.other-settings.project_popovers": "Verberg Projecttabblad Popovers: toon de popovers niet boven de projecttabbladen", - "account.other-settings.projects.title": "Projecten", "account.other-settings.standby_timeout": "Standby-timeout", "account.other-settings.symbol_bar_labels": "Toon Symbolenbalk Labels: toon labels in de frame-editor symbolenbalk", - "account.other-settings.theme": "Thema", "account.other-settings.theme.antd.animations": "Animaties: kort sommige aspecten animeren, bijv. knoppen", "account.other-settings.theme.antd.color_scheme": "Kleurschema: gebruik merkkleuren in plaats van standaardkleuren", "account.other-settings.theme.antd.compact": "Compact Design: gebruik een compacter ontwerp", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "Beheer softwarelicenties en toegangsrechten.", "account.settings.overview.other": "Diverse instellingen en opties.", "account.settings.overview.payg": "Configureer gebruik en facturering op basis van verbruik.", + "account.settings.overview.payment_methods": "Beheer uw opgeslagen betaalmethoden of voeg nieuwe toe.", "account.settings.overview.payments": "Beheer betaalmethoden en transactiegeschiedenis.", "account.settings.overview.profile": "Beheer uw persoonlijke informatie, avatar en accountgegevens.", "account.settings.overview.purchases": "Bekijk aankoopgeschiedenis en bonnen.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "Bekijk en beheer uw actieve abonnementen.", "account.settings.overview.support": "Toegangs ondersteuningsverzoeken en hulpbronnen.", "account.settings.overview.title": "Instellingenoverzicht", + "account.settings.overview.upgrades": "Beheer uw verouderde quotum-upgrades.", "account.settings.sso.account_is_linked": "Je account is gekoppeld met (klik om te ontkoppelen)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {Meld je aan met je account bij} other {Klik om je account te koppelen}}", "account.settings.unlisted.label": "Niet vermeld: je kan alleen gevonden worden met een exacte e-mailadressmatch", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "Genereer een {docName} Document met AI", "ai-generator.select_llm": "Selecteer taalmodel", "app.fullscreen-button.tooltip": "Volledig schermmodus, gericht op het huidige document of de pagina.", + "app.hotkey.dialog.help_text": "Klik op frames hierboven • Toets 0 schakelt chat in/uit • Toetsen 1–9 focussen frames • Typ om te zoeken • ↑↓ navigeer • Return om te openen • ESC om te sluiten", + "app.hotkey.dialog.search_placeholder": "Bestanden en pagina's zoeken...", + "app.hotkey.dialog.title": "Snelle Navigatie", "app.verify-email-banner.edit": "Als het e-mailadres verkeerd is, pas het dan aan in de accountinstellingen.", "app.verify-email-banner.help.text": "Het is belangrijk om een werkend e-mailadres te hebben. We gebruiken het voor het opnieuw instellen van wachtwoorden, het verzenden van berichten, factureringsmeldingen en ondersteuning. Zorg ervoor dat uw e-mailadres correct is om op de hoogte te blijven.", "app.verify-email-banner.text": "{sent, select, true {E-mail verzonden! Controleer je e-mailinbox (en misschien spam) en klik op de bevestigingslink.} other {Controleer en verifieer je e-mailadres:}}", @@ -988,6 +990,7 @@ "labels.config": "Config", "labels.configuration": "Configuratie", "labels.configuration.short": "Config", + "labels.connected": "Verbonden", "labels.connecting": "Verbinden", "labels.connection": "Verbinding", "labels.copied": "gekopieerd", @@ -1016,6 +1019,7 @@ "labels.environment": "Omgeving", "labels.explorer": "Verkenner", "labels.file_explorer": "Bestandsverkenner", + "labels.file_use_notifications": "Meldingen Bestandsgebruik", "labels.files": "Bestanden", "labels.folder": "map", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Openbaar} read_only {Alleen lezen} other {Opslaan}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "Stop{short, select, true {} other { Project}}…", "labels.project.settings.stop-project.ok": "Ja, stop project", "labels.projects": "Projecten", + "labels.public_paths": "Openbare Paden", "labels.published_files": "Gepubliceerde bestanden", "labels.purchases": "Aankopen", "labels.ready": "Klaar", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "Kies een titel. Je kunt het later eenvoudig wijzigen!", "projects.create-project.requireLicense": "Er is een licentie vereist om extra projecten te maken.", "projects.filename-search.placeholder": "Zoek naar bestandsnamen die je hebt bewerkt...", - "projects.list.no_starred_found": "Geen gemarkeerde projecten gevonden. Gebruik het sterpictogram naast projecttitels om je favoriete projecten te markeren.", "projects.load-all.label": "Toon alle projecten...", "projects.operations.clear-filter": "Filter wissen", "projects.operations.delete.button": "{deleted, select, true {Alles Herstellen} other {Alles Verwijderen}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "Filteren op hashtags...", "projects.table-controls.hidden.label": "Verborgen", "projects.table-controls.search.placeholder": "Zoek projecten...", + "projects.table.keyboard-row-hint": "Project {title}. Gebruik de pijltjestoetsen omhoog en omlaag om te bewegen; druk op Enter of Spatiebalk om te openen.", "projects.table.last-edited": "Laatst bewerkt", + "projects.table.untitled": "Naamloos", "purchases.automatic-payments-warning.description": "Automatische betalingen zijn veel handiger, zullen je tijd besparen, en ervoor zorgen dat abonnementen niet per ongeluk worden opgezegd.", "purchases.automatic-payments-warning.title": "Automatische betalingen zijn NIET vereist om een abonnement te hebben", "purchases.automatic-payments.are-enabled": "Automatische Betalingen zijn Ingeschakeld", diff --git a/src/packages/frontend/i18n/trans/pl_PL.json b/src/packages/frontend/i18n/trans/pl_PL.json index 3ce0be42a5a..bce3c5556b3 100644 --- a/src/packages/frontend/i18n/trans/pl_PL.json +++ b/src/packages/frontend/i18n/trans/pl_PL.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Informacje o tłumaczeniu", "account.account-button.confirm.ok": "Tak, wyloguj się", "account.account-button.confirm.title": "Wylogować się z konta?", + "account.appearance.accessibility.enabled": "Włącz tryb dostępności: optymalizuj interfejs użytkownika pod kątem funkcji dostępności", + "account.appearance.accessibility.title": "Dostępność", "account.appearance.user_interface.title": "Interfejs użytkownika", "account.delete-account.alert.description": "Natychmiast utracisz dostęp do wszystkich swoich projektów, wszelkie subskrypcje zostaną anulowane, a cały niewykorzystany kredyt zostanie utracony. {br} {hr} Aby USUNĄĆ SWOJE KONTO, najpierw wpisz poniżej \"{required_text}\":", "account.delete-account.alert.message": "Czy na pewno chcesz USUNĄĆ SWOJE KONTO?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "usuń za każdym razem, gdy plik jest zapisywany", "account.editor-settings-autosave-interval.label": "Interwał automatycznego zapisu", "account.editor-settings.basic.title": "Podstawowe ustawienia", - "account.editor-settings.color-schemes.label": "Schemat kolorów edytora", "account.editor-settings.color-schemes.panel_title": "Schemat kolorów edytora", "account.editor-settings.font-size.label": "Domyślny globalny rozmiar czcionki", "account.editor-settings.indent-size.label": "Rozmiar wcięcia", "account.editor-settings.keyboard-bindings.label": "Powiązania klawiatury edytora", "account.editor-settings.keyboard.title": "Klawiatura", - "account.editor-settings.title": "Edytor", "account.editor-settings.x11-keyboard-variant.label": "Odmiana klawiatury (dla pulpitu X11)", "account.editor-settings.x11-physical-keyboard.label": "Układ klawiatury (dla pulpitu X11)", "account.global-ssh-keys.help": "Aby zalogować się do projektu przez SSH, użyj następującego username@host: [project-id-without-dashes]@ssh.cocalc.com Identyfikator projektu bez myślników można znaleźć w części ustawień projektu dotyczącej kluczy SSH. Aby zalogować się przez SSH między projektami, użyj [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Podziel widok w arkuszu Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Przełącz komentarzowanie zaznaczenia", "account.other-settings._page_size.label": "Liczba plików na stronę", - "account.other-settings.browser_performance.title": "Przeglądarka", + "account.other-settings.auto_focus": "Automatyczne ustawianie fokusu na polach tekstowych: automatycznie ustawiaj fokus na polach tekstowych, gdy się pojawiają (np. eksplorator plików, projekty, ...)", "account.other-settings.button_tooltips": "Ukryj podpowiedzi przycisków: ukrywa niektóre podpowiedzi przycisków (to jest tylko częściowe)", "account.other-settings.confirm_close": "Potwierdź zamknięcie: zawsze pytaj o potwierdzenie przed zamknięciem okna przeglądarki", - "account.other-settings.content_display.title": "Wyświetlanie treści", "account.other-settings.default_file_sort.by_name": "Sortuj według nazwy", "account.other-settings.default_file_sort.by_time": "Sortuj według czasu", "account.other-settings.default_file_sort.label": "Domyślne sortowanie plików", + "account.other-settings.dim_file_extensions": "Przyciemnij rozszerzenia plików: wyszarz rozszerzenia plików, aby ich nazwy się wyróżniały.", "account.other-settings.file_popovers": "Ukryj dymki zakładek plików: nie pokazuj dymków nad zakładkami plików", "account.other-settings.filename_generator.description": "Wybierz, jak automatycznie generowane nazwy plików są tworzone. W szczególności, aby były unikalne lub zawierały bieżący czas.", "account.other-settings.filename_generator.label": "Generator nazw plików", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "Ustawienia AI", "account.other-settings.markdown_codebar": "Wyłącz pasek kodu markdown we wszystkich dokumentach markdown. Zaznaczenie tego ukrywa dodatkowe przyciski uruchamiania, kopiowania i wyjaśniania w blokach kodu.", "account.other-settings.mask_files": "Maskuj pliki: wyszarz pliki w przeglądarce plików, których prawdopodobnie nie chcesz otworzyć", - "account.other-settings.messages.title": "Wiadomości", "account.other-settings.project_popovers": "Ukryj dymki kart projektu: nie pokazuj dymków nad kartami projektu", - "account.other-settings.projects.title": "Projekty", "account.other-settings.standby_timeout": "Limit czasu w trybie gotowości", "account.other-settings.symbol_bar_labels": "Pokaż etykiety paska symboli: pokaż etykiety na pasku symboli edytora ramki", - "account.other-settings.theme": "Motyw", "account.other-settings.theme.antd.animations": "Animacje: krótko animuj niektóre aspekty, np. przyciski", "account.other-settings.theme.antd.color_scheme": "Schemat kolorów: użyj kolorów marki zamiast domyślnych kolorów", "account.other-settings.theme.antd.compact": "Kompaktowy układ: użyj bardziej zwartego układu", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "Zarządzaj licencjami oprogramowania i uprawnieniami dostępu.", "account.settings.overview.other": "Ustawienia i opcje różne.", "account.settings.overview.payg": "Skonfiguruj rozliczanie i użytkowanie pay-as-you-go.", + "account.settings.overview.payment_methods": "Zarządzaj zapisanymi metodami płatności lub dodaj nowe.", "account.settings.overview.payments": "Zarządzaj metodami płatności i historią transakcji.", "account.settings.overview.profile": "Zarządzaj swoimi danymi osobowymi, awatarem i szczegółami konta.", "account.settings.overview.purchases": "Wyświetl historię zakupów i paragony.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "Wyświetlaj i zarządzaj swoimi aktywnymi subskrypcjami.", "account.settings.overview.support": "Uzyskaj dostęp do zgłoszeń pomocy i zasobów pomocniczych.", "account.settings.overview.title": "Przegląd ustawień", + "account.settings.overview.upgrades": "Zarządzaj swoimi starszymi ulepszeniami limitu.", "account.settings.sso.account_is_linked": "Twoje konto jest połączone z (kliknij, aby odłączyć)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {Zarejestruj się, używając swojego konta w} other {Kliknij, aby połączyć swoje konto}}", "account.settings.unlisted.label": "Niewymieniony: można cię znaleźć tylko poprzez dokładne dopasowanie adresu e-mail", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "Wygeneruj dokument {docName} za pomocą AI", "ai-generator.select_llm": "Wybierz model językowy", "app.fullscreen-button.tooltip": "Tryb pełnoekranowy, skupiony na bieżącym dokumencie lub stronie.", + "app.hotkey.dialog.help_text": "Kliknij ramki powyżej • Klawisz 0 przełącza czat • Klawisze 1–9 skupiają ramki • Wpisz, aby wyszukać • ↑↓ nawigacja • Return, aby otworzyć • ESC, aby zamknąć", + "app.hotkey.dialog.search_placeholder": "Szukaj plików i stron...", + "app.hotkey.dialog.title": "Szybka nawigacja", "app.verify-email-banner.edit": "Jeśli adres e-mail jest niepoprawny, proszę edytować go w ustawieniach konta.", "app.verify-email-banner.help.text": "Ważne jest posiadanie działającego adresu e-mail. Używamy go do resetowania haseł, wysyłania wiadomości, powiadomień o rozliczeniach i wsparcia. Upewnij się, że Twój e-mail jest poprawny, aby być na bieżąco.", "app.verify-email-banner.text": "{sent, select, true {Email wysłany! Sprawdź swoją skrzynkę odbiorczą (a może spam) i kliknij link potwierdzający.} other {Proszę sprawdzić i zweryfikować swój adres e-mail:}}", @@ -988,6 +990,7 @@ "labels.config": "Konfiguracja", "labels.configuration": "Konfiguracja", "labels.configuration.short": "Konfiguracja", + "labels.connected": "Połączono", "labels.connecting": "Łączenie", "labels.connection": "Połączenie", "labels.copied": "skopiowano", @@ -1016,6 +1019,7 @@ "labels.environment": "Środowisko", "labels.explorer": "Eksplorator", "labels.file_explorer": "Eksplorator plików", + "labels.file_use_notifications": "Powiadomienia o użyciu plików", "labels.files": "Pliki", "labels.folder": "folder", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Publiczny} read_only {Tylko do odczytu} other {Zapisz}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "Zatrzymaj{short, select, true {} other { Projekt}}…", "labels.project.settings.stop-project.ok": "Tak, zatrzymaj projekt", "labels.projects": "Projekty", + "labels.public_paths": "Ścieżki publiczne", "labels.published_files": "Opublikowane Pliki", "labels.purchases": "Zakupy", "labels.ready": "Gotowe", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "Wybierz tytuł. Możesz łatwo zmienić go później!", "projects.create-project.requireLicense": "Do utworzenia dodatkowych projektów wymagana jest licencja.", "projects.filename-search.placeholder": "Szukaj nazw plików, które edytowałeś...", - "projects.list.no_starred_found": "Nie znaleziono oznaczonych projektów. Użyj ikony gwiazdki obok tytułów projektów, aby dodać do zakładek swoje ulubione projekty.", "projects.load-all.label": "Pokaż wszystkie projekty...", "projects.operations.clear-filter": "Wyczyść filtr", "projects.operations.delete.button": "{deleted, select, true {Przywróć wszystko} other {Usuń wszystko}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "Filtruj według hashtagów...", "projects.table-controls.hidden.label": "Ukryty", "projects.table-controls.search.placeholder": "Szukaj projektów...", + "projects.table.keyboard-row-hint": "Projekt {title}. Użyj strzałek w górę i w dół, aby się poruszać; naciśnij Enter lub Spację, aby otworzyć.", "projects.table.last-edited": "Ostatnia edycja", + "projects.table.untitled": "Bez tytułu", "purchases.automatic-payments-warning.description": "Automatyczne płatności są znacznie wygodniejsze, zaoszczędzą Twój czas i zapewnią, że subskrypcje nie zostaną anulowane przez przypadek.", "purchases.automatic-payments-warning.title": "Automatyczne płatności NIE są wymagane do posiadania subskrypcji", "purchases.automatic-payments.are-enabled": "Automatyczne Płatności są Włączone", diff --git a/src/packages/frontend/i18n/trans/pt_BR.json b/src/packages/frontend/i18n/trans/pt_BR.json index ae7b547ba09..7db1bf11a76 100644 --- a/src/packages/frontend/i18n/trans/pt_BR.json +++ b/src/packages/frontend/i18n/trans/pt_BR.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Informações de Tradução", "account.account-button.confirm.ok": "Sim, sair", "account.account-button.confirm.title": "Sair da sua conta?", + "account.appearance.accessibility.enabled": "Ativar o Modo de Acessibilidade: otimizar a interface do usuário para recursos de acessibilidade", + "account.appearance.accessibility.title": "Acessibilidade", "account.appearance.user_interface.title": "Interface do Usuário", "account.delete-account.alert.description": "Você imediatamente perderá acesso a todos os seus projetos, quaisquer assinaturas serão canceladas e todo crédito não utilizado será perdido. {br} {hr} Para DELETAR SUA CONTA, primeiro digite \"{required_text}\" abaixo:", "account.delete-account.alert.message": "Tem certeza de que deseja EXCLUIR SUA CONTA?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "remover sempre que o arquivo for salvo", "account.editor-settings-autosave-interval.label": "Intervalo de salvamento automático", "account.editor-settings.basic.title": "Configurações Básicas", - "account.editor-settings.color-schemes.label": "Esquema de cores do editor", "account.editor-settings.color-schemes.panel_title": "Esquema de Cores do Editor", "account.editor-settings.font-size.label": "Tamanho padrão da fonte global", "account.editor-settings.indent-size.label": "Tamanho da indentação", "account.editor-settings.keyboard-bindings.label": "Atalhos de teclado do editor", "account.editor-settings.keyboard.title": "Teclado", - "account.editor-settings.title": "Configurações do Editor", "account.editor-settings.x11-keyboard-variant.label": "Variante do teclado (para X11 Desktop)", "account.editor-settings.x11-physical-keyboard.label": "Layout de teclado (para X11 Desktop)", "account.global-ssh-keys.help": "Para acessar um projeto via SSH, use o seguinte username@host: [project-id-without-dashes]@ssh.cocalc.com O ID do projeto sem traços pode ser encontrado na parte das configurações do projeto sobre chaves SSH. Para acessar entre projetos via SSH, use [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Divisão de visualização na planilha Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Alternar comentário da seleção", "account.other-settings._page_size.label": "Número de arquivos por página", - "account.other-settings.browser_performance.title": "Navegador", + "account.other-settings.auto_focus": "Focar Automaticamente no Campo de Texto: focar automaticamente nos campos de entrada de texto quando eles aparecem (por exemplo, explorador de arquivos, projetos, ...)", "account.other-settings.button_tooltips": "Ocultar Dicas de Botões: oculta algumas dicas de botões (isso é apenas parcial)", "account.other-settings.confirm_close": "Confirmar Fechamento: sempre pedir confirmação antes de fechar a janela do navegador", - "account.other-settings.content_display.title": "Exibição de Conteúdo", "account.other-settings.default_file_sort.by_name": "Ordenar por nome", "account.other-settings.default_file_sort.by_time": "Ordenar por tempo", "account.other-settings.default_file_sort.label": "Ordenação padrão de arquivos", + "account.other-settings.dim_file_extensions": "Extensões de arquivo em cinza: desbotar extensões de arquivo para que seus nomes se destaquem.", "account.other-settings.file_popovers": "Ocultar Popovers de Abas de Arquivos: não mostrar os popovers sobre as abas de arquivos", "account.other-settings.filename_generator.description": "Selecione como os nomes de arquivos gerados automaticamente são criados. Em particular, para torná-los únicos ou incluir a hora atual.", "account.other-settings.filename_generator.label": "Gerador de nomes de arquivo", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "Configurações de IA", "account.other-settings.markdown_codebar": "Desativar a barra de código markdown em todos os documentos markdown. Marcar isso oculta os botões extras de executar, copiar e explicar em blocos de código delimitados.", "account.other-settings.mask_files": "Mascarar Arquivos: escurecer arquivos no visualizador de arquivos que você provavelmente não deseja abrir", - "account.other-settings.messages.title": "Mensagens", "account.other-settings.project_popovers": "Ocultar Pop-ups das Abas do Projeto: não mostrar os pop-ups sobre as abas do projeto", - "account.other-settings.projects.title": "Projetos", "account.other-settings.standby_timeout": "Tempo de espera em espera", "account.other-settings.symbol_bar_labels": "Mostrar Rótulos da Barra de Símbolos: mostrar rótulos na barra de símbolos do editor de quadros", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animações: animar brevemente alguns aspectos, por exemplo, botões", "account.other-settings.theme.antd.color_scheme": "Esquema de Cores: use cores da marca em vez de cores padrão", "account.other-settings.theme.antd.compact": "Design Compacto: use um design mais compacto", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "Gerenciar licenças de software e permissões de acesso.", "account.settings.overview.other": "Configurações e opções diversas.", "account.settings.overview.payg": "Configure o uso e cobrança conforme o consumo.", + "account.settings.overview.payment_methods": "Gerencie seus métodos de pagamento salvos ou adicione novos.", "account.settings.overview.payments": "Gerenciar métodos de pagamento e histórico de transações.", "account.settings.overview.profile": "Gerencie suas informações pessoais, avatar e detalhes da conta.", "account.settings.overview.purchases": "Ver histórico de compras e recibos.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "Visualize e gerencie suas assinaturas ativas.", "account.settings.overview.support": "Acesse os tickets de suporte e recursos de ajuda.", "account.settings.overview.title": "Visão Geral das Configurações", + "account.settings.overview.upgrades": "Gerencie suas atualizações de cota legadas.", "account.settings.sso.account_is_linked": "Sua conta está vinculada a (clique para desvincular)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {Cadastre-se usando sua conta em} other {Clique para vincular sua conta}}", "account.settings.unlisted.label": "Não listado: só pode ser encontrado por uma correspondência exata de endereço de email", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "Gerar um Documento {docName} usando IA", "ai-generator.select_llm": "Selecionar modelo de linguagem", "app.fullscreen-button.tooltip": "Modo de tela cheia, focado no documento ou página atual.", + "app.hotkey.dialog.help_text": "Clique nos quadros acima • Tecla 0 alterna chat • Teclas 1–9 focam quadros • Digite para pesquisar • ↑↓ navegar • Enter para abrir • ESC para fechar", + "app.hotkey.dialog.search_placeholder": "Pesquisar arquivos e páginas...", + "app.hotkey.dialog.title": "Navegação Rápida", "app.verify-email-banner.edit": "Se o endereço de e-mail estiver errado, por favor edite nas configurações da conta.", "app.verify-email-banner.help.text": "É importante ter um endereço de e-mail funcional. Nós o usamos para redefinir senhas, enviar mensagens, notificações de cobrança e suporte. Por favor, certifique-se de que seu e-mail está correto para se manter informado.", "app.verify-email-banner.text": "{sent, select, true {Email enviado! Por favor, verifique a caixa de entrada do seu email (e talvez o spam) e clique no link de confirmação.} other {Por favor, verifique e confirme seu endereço de email:}}", @@ -988,6 +990,7 @@ "labels.config": "Configuração", "labels.configuration": "Configuração", "labels.configuration.short": "Configuração", + "labels.connected": "Conectado", "labels.connecting": "Conectando", "labels.connection": "Conexão", "labels.copied": "copiado", @@ -1016,6 +1019,7 @@ "labels.environment": "Ambiente", "labels.explorer": "Explorador", "labels.file_explorer": "Explorador de Arquivos", + "labels.file_use_notifications": "Notificações de Uso de Arquivo", "labels.files": "Arquivos", "labels.folder": "pasta", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Público} read_only {Somente leitura} other {Salvar}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "Parar{short, select, true {} other { Projeto}}…", "labels.project.settings.stop-project.ok": "Sim, parar projeto", "labels.projects": "Projetos", + "labels.public_paths": "Caminhos Públicos", "labels.published_files": "Publicado", "labels.purchases": "Compras", "labels.ready": "Pronto", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "Escolha um título. Você pode alterá-lo facilmente depois!", "projects.create-project.requireLicense": "É necessária uma licença para criar projetos adicionais.", "projects.filename-search.placeholder": "Pesquisar por nomes de arquivos que você editou...", - "projects.list.no_starred_found": "Nenhum projeto marcado com estrela encontrado. Use o ícone de estrela ao lado dos títulos dos projetos para marcar seus projetos favoritos.", "projects.load-all.label": "Mostrar todos os projetos...", "projects.operations.clear-filter": "Limpar Filtro", "projects.operations.delete.button": "{deleted, select, true {Restaurar Tudo} other {Excluir Tudo}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "Filtrar por hashtags...", "projects.table-controls.hidden.label": "Oculto", "projects.table-controls.search.placeholder": "Buscar projetos...", + "projects.table.keyboard-row-hint": "Projeto {title}. Use as setas para cima e para baixo para mover; pressione Enter ou Espaço para abrir.", "projects.table.last-edited": "Última Edição", + "projects.table.untitled": "Sem título", "purchases.automatic-payments-warning.description": "Pagamentos automáticos são muito mais convenientes, vão economizar seu tempo e garantir que assinaturas não sejam canceladas por acidente.", "purchases.automatic-payments-warning.title": "Pagamentos automáticos NÃO são necessários para ter uma assinatura", "purchases.automatic-payments.are-enabled": "Pagamentos Automáticos estão Ativados", diff --git a/src/packages/frontend/i18n/trans/pt_PT.json b/src/packages/frontend/i18n/trans/pt_PT.json index e5c65f6b920..714b91d09e4 100644 --- a/src/packages/frontend/i18n/trans/pt_PT.json +++ b/src/packages/frontend/i18n/trans/pt_PT.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Informação sobre a Tradução", "account.account-button.confirm.ok": "Sim, terminar sessão", "account.account-button.confirm.title": "Sair da sua conta?", + "account.appearance.accessibility.enabled": "Ativar Modo de Acessibilidade: otimizar a interface do utilizador para funcionalidades de acessibilidade", + "account.appearance.accessibility.title": "Acessibilidade", "account.appearance.user_interface.title": "Interface do Utilizador", "account.delete-account.alert.description": "Perderá imediatamente o acesso a todos os seus projetos, quaisquer subscrições serão canceladas e todo o crédito não utilizado será perdido. {br} {hr} Para ELIMINAR A SUA CONTA, primeiro introduza \"{required_text}\" abaixo:", "account.delete-account.alert.message": "Tem a certeza de que quer ELIMINAR A SUA CONTA?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "remover sempre que o ficheiro for guardado", "account.editor-settings-autosave-interval.label": "Intervalo de Autossalvamento", "account.editor-settings.basic.title": "Configurações Básicas", - "account.editor-settings.color-schemes.label": "Esquema de cores do editor", "account.editor-settings.color-schemes.panel_title": "Esquema de Cores do Editor", "account.editor-settings.font-size.label": "Tamanho de fonte global padrão", "account.editor-settings.indent-size.label": "Tamanho da indentação", "account.editor-settings.keyboard-bindings.label": "Ligações de teclado do editor", "account.editor-settings.keyboard.title": "Teclado", - "account.editor-settings.title": "Editor", "account.editor-settings.x11-keyboard-variant.label": "Variante de teclado (para Desktop X11)", "account.editor-settings.x11-physical-keyboard.label": "Distribuição do teclado (para X11 Desktop)", "account.global-ssh-keys.help": "Para SSH em um projeto, use o seguinte username@host: [project-id-without-dashes]@ssh.cocalc.com O ID do projeto sem traços pode ser encontrado na parte das configurações do projeto sobre chaves SSH. Para SSH entre projetos, use [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Vista dividida na folha de cálculo Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Alternar comentário da seleção", "account.other-settings._page_size.label": "Número de ficheiros por página", - "account.other-settings.browser_performance.title": "Navegador", + "account.other-settings.auto_focus": "Focar Automaticamente no Campo de Texto: focar automaticamente nos campos de texto quando eles aparecem (por exemplo, explorador de ficheiros, projetos, ...)", "account.other-settings.button_tooltips": "Ocultar Dicas de Botão: oculta algumas dicas de botão (isto é apenas parcial)", "account.other-settings.confirm_close": "Confirmar Fecho: pedir sempre confirmação antes de fechar a janela do navegador", - "account.other-settings.content_display.title": "Exibição de Conteúdo", "account.other-settings.default_file_sort.by_name": "Ordenar por nome", "account.other-settings.default_file_sort.by_time": "Ordenar por tempo", "account.other-settings.default_file_sort.label": "Ordenação padrão de ficheiros", + "account.other-settings.dim_file_extensions": "Extensões de ficheiros em cinzento: atenuar as extensões de ficheiros para que os seus nomes se destaquem.", "account.other-settings.file_popovers": "Ocultar Popovers das Abas de Ficheiro: não mostrar os popovers sobre as abas de ficheiro", "account.other-settings.filename_generator.description": "Selecione como os nomes de ficheiros gerados automaticamente são criados. Em particular, para torná-los únicos ou incluir a hora atual.", "account.other-settings.filename_generator.label": "Gerador de nomes de ficheiros", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "Configurações de IA", "account.other-settings.markdown_codebar": "Desativar a barra de código markdown em todos os documentos markdown. Marcar esta opção oculta os botões extra de execução, cópia e explicação em blocos de código delimitados.", "account.other-settings.mask_files": "Mascarar ficheiros: esbater ficheiros no visualizador de ficheiros que provavelmente não quer abrir", - "account.other-settings.messages.title": "Mensagens", "account.other-settings.project_popovers": "Ocultar Pop-ups das Abas do Projeto: não mostrar os pop-ups sobre as abas do projeto", - "account.other-settings.projects.title": "Projetos", "account.other-settings.standby_timeout": "Tempo limite de espera", "account.other-settings.symbol_bar_labels": "Mostrar Etiquetas da Barra de Símbolos: mostrar etiquetas na barra de símbolos do editor de frames", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animações: animar brevemente alguns aspetos, por exemplo, botões", "account.other-settings.theme.antd.color_scheme": "Esquema de Cores: usar cores da marca em vez de cores padrão", "account.other-settings.theme.antd.compact": "Design Compacto: usar um design mais compacto", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "Gerir licenças de software e permissões de acesso.", "account.settings.overview.other": "Configurações e opções diversas.", "account.settings.overview.payg": "Configurar utilização e faturação pay-as-you-go.", + "account.settings.overview.payment_methods": "Gerir os seus métodos de pagamento guardados ou adicionar novos.", "account.settings.overview.payments": "Gerir métodos de pagamento e histórico de transações.", "account.settings.overview.profile": "Gerir as suas informações pessoais, avatar e detalhes da conta.", "account.settings.overview.purchases": "Ver histórico de compras e recibos.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "Veja e gere as suas assinaturas ativas.", "account.settings.overview.support": "Aceda aos tickets de suporte e recursos de ajuda.", "account.settings.overview.title": "Visão Geral das Definições", + "account.settings.overview.upgrades": "Gerir as suas atualizações de quota legadas.", "account.settings.sso.account_is_linked": "A sua conta está ligada a (clique para desvincular)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {Registe-se usando a sua conta em} other {Clique para ligar a sua conta}}", "account.settings.unlisted.label": "Não listado: só pode ser encontrado por correspondência exata do endereço de email", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "Gerar um Documento {docName} usando IA", "ai-generator.select_llm": "Selecionar modelo de linguagem", "app.fullscreen-button.tooltip": "Modo de ecrã completo, focado no documento ou página atual", + "app.hotkey.dialog.help_text": "Clique nas molduras acima • A tecla 0 alterna o chat • As teclas 1–9 focam nas molduras • Digite para pesquisar • ↑↓ navegue • Enter para abrir • ESC para fechar", + "app.hotkey.dialog.search_placeholder": "Procurar ficheiros e páginas...", + "app.hotkey.dialog.title": "Navegação Rápida", "app.verify-email-banner.edit": "Se o endereço de email estiver errado, por favor edite nas configurações da conta.", "app.verify-email-banner.help.text": "É importante ter um endereço de email funcional. Usamo-lo para redefinições de senha, envio de mensagens, notificações de faturação e suporte. Por favor, certifique-se de que o seu email está correto para se manter informado.", "app.verify-email-banner.text": "{sent, select, true {Email Enviado! Por favor, verifique a sua caixa de entrada de email (e talvez o spam) e clique no link de confirmação.} other {Por favor, verifique e confirme o seu endereço de email:}}", @@ -988,6 +990,7 @@ "labels.config": "Configuração", "labels.configuration": "Configuração", "labels.configuration.short": "Configurar", + "labels.connected": "Ligado", "labels.connecting": "A ligar", "labels.connection": "Ligação", "labels.copied": "copiado", @@ -1016,6 +1019,7 @@ "labels.environment": "Ambiente", "labels.explorer": "Explorador", "labels.file_explorer": "Explorador de Ficheiros", + "labels.file_use_notifications": "Notificações de Uso de Ficheiro", "labels.files": "Ficheiros", "labels.folder": "pasta", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Público} read_only {Só de leitura} other {Guardar}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "Parar{short, select, true {} other { Projeto}}…", "labels.project.settings.stop-project.ok": "Sim, parar projeto", "labels.projects": "Projetos", + "labels.public_paths": "Caminhos Públicos", "labels.published_files": "Ficheiros Publicados", "labels.purchases": "Compras", "labels.ready": "Pronto", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "Escolha um título. Pode alterá-lo facilmente mais tarde!", "projects.create-project.requireLicense": "É necessária uma licença para criar projetos adicionais.", "projects.filename-search.placeholder": "Procurar por nomes de ficheiros que editou...", - "projects.list.no_starred_found": "Nenhum projeto com estrela encontrado. Use o ícone de estrela ao lado dos títulos dos projetos para marcar os seus projetos favoritos.", "projects.load-all.label": "Mostrar todos os projetos...", "projects.operations.clear-filter": "Limpar Filtro", "projects.operations.delete.button": "{deleted, select, true {Recuperar Tudo} other {Eliminar Tudo}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "Filtrar por hashtags...", "projects.table-controls.hidden.label": "Oculto", "projects.table-controls.search.placeholder": "Procurar projetos...", + "projects.table.keyboard-row-hint": "Projeto {title}. Use as setas para cima e para baixo para mover; pressione Enter ou Espaço para abrir.", "projects.table.last-edited": "Última Edição", + "projects.table.untitled": "Sem título", "purchases.automatic-payments-warning.description": "Os pagamentos automáticos são muito mais convenientes, irão poupar-lhe tempo e garantir que as subscrições não sejam canceladas por acidente.", "purchases.automatic-payments-warning.title": "Não são necessários pagamentos automáticos para ter uma subscrição", "purchases.automatic-payments.are-enabled": "Pagamentos Automáticos estão Ativados", diff --git a/src/packages/frontend/i18n/trans/ru_RU.json b/src/packages/frontend/i18n/trans/ru_RU.json index 74e072a11cd..57217b0ad71 100644 --- a/src/packages/frontend/i18n/trans/ru_RU.json +++ b/src/packages/frontend/i18n/trans/ru_RU.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Информация о переводе", "account.account-button.confirm.ok": "Да, выйти", "account.account-button.confirm.title": "Выйти из вашего аккаунта?", + "account.appearance.accessibility.enabled": "Включить режим доступности: оптимизировать пользовательский интерфейс для функций доступности", + "account.appearance.accessibility.title": "Доступность", "account.appearance.user_interface.title": "Пользовательский интерфейс", "account.delete-account.alert.description": "Вы немедленно потеряете доступ ко всем вашим проектам, все подписки будут отменены, а все неиспользованные средства будут утрачены. {br} {hr} Чтобы УДАЛИТЬ ВАШ АККАУНТ, сначала введите \"{required_text}\" ниже:", "account.delete-account.alert.message": "Вы уверены, что хотите УДАЛИТЬ ВАШ АККАУНТ?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "удалить при каждом сохранении файла", "account.editor-settings-autosave-interval.label": "Интервал автосохранения", "account.editor-settings.basic.title": "Основные настройки", - "account.editor-settings.color-schemes.label": "Цветовая схема редактора", "account.editor-settings.color-schemes.panel_title": "Цветовая схема редактора", "account.editor-settings.font-size.label": "Размер шрифта по умолчанию", "account.editor-settings.indent-size.label": "Размер отступа", "account.editor-settings.keyboard-bindings.label": "Клавиатурные привязки редактора", "account.editor-settings.keyboard.title": "Клавиатура", - "account.editor-settings.title": "Редактор", "account.editor-settings.x11-keyboard-variant.label": "Вариант клавиатуры (для X11 Desktop)", "account.editor-settings.x11-physical-keyboard.label": "Раскладка клавиатуры (для X11 Desktop)", "account.global-ssh-keys.help": "Для SSH в проект используйте следующий username@host: [project-id-without-dashes]@ssh.cocalc.com Идентификатор проекта без тире можно найти в разделе настроек проекта о SSH-ключах. Для SSH между проектами используйте [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Разделить вид в рабочем листе Sage", "account.keyboard-shortcuts.shortcut.toggle-comment": "Переключить комментирование выделения", "account.other-settings._page_size.label": "Количество файлов на странице", - "account.other-settings.browser_performance.title": "Браузер", + "account.other-settings.auto_focus": "Автоматическая фокусировка текстового ввода: автоматически фокусировать поля ввода текста, когда они появляются (например, проводник файлов, проекты, ...)", "account.other-settings.button_tooltips": "Скрыть подсказки кнопок: скрывает некоторые подсказки кнопок (это только частично)", "account.other-settings.confirm_close": "Подтвердите закрытие: всегда запрашивать подтверждение перед закрытием окна браузера", - "account.other-settings.content_display.title": "Отображение контента", "account.other-settings.default_file_sort.by_name": "Сортировать по имени", "account.other-settings.default_file_sort.by_time": "Сортировать по времени", "account.other-settings.default_file_sort.label": "Сортировка файлов по умолчанию", + "account.other-settings.dim_file_extensions": "Затемнить расширения файлов: сделать расширения файлов серыми, чтобы их названия выделялись.", "account.other-settings.file_popovers": "Скрыть всплывающие подсказки вкладок файлов: не показывать всплывающие подсказки над вкладками файлов", "account.other-settings.filename_generator.description": "Выберите способ автоматической генерации имен файлов. В частности, чтобы сделать их уникальными или включить текущее время.", "account.other-settings.filename_generator.label": "Генератор имен файлов", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "Настройки AI", "account.other-settings.markdown_codebar": "Отключить панель кода markdown во всех markdown документах. Если установить этот флажок, будут скрыты дополнительные кнопки запуска, копирования и объяснения в блоках кода.", "account.other-settings.mask_files": "Маскировать файлы: затенить файлы в просмотрщике файлов, которые вы, вероятно, не захотите открывать", - "account.other-settings.messages.title": "Сообщения", "account.other-settings.project_popovers": "Скрыть всплывающие подсказки вкладок проекта: не показывать всплывающие подсказки над вкладками проекта", - "account.other-settings.projects.title": "Проекты", "account.other-settings.standby_timeout": "Таймаут ожидания", "account.other-settings.symbol_bar_labels": "Показать метки панели символов: показывать метки на панели символов в редакторе фреймов", - "account.other-settings.theme": "Тема", "account.other-settings.theme.antd.animations": "Анимации: кратко анимировать некоторые аспекты, например, кнопки", "account.other-settings.theme.antd.color_scheme": "Цветовая схема: использовать фирменные цвета вместо стандартных цветов", "account.other-settings.theme.antd.compact": "Компактный дизайн: использовать более компактный дизайн", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "Управление лицензиями на программное обеспечение и разрешениями на доступ.", "account.settings.overview.other": "Разные настройки и параметры.", "account.settings.overview.payg": "Настроить использование и оплату по мере использования.", + "account.settings.overview.payment_methods": "Управляйте сохраненными способами оплаты или добавляйте новые.", "account.settings.overview.payments": "Управление способами оплаты и историей транзакций.", "account.settings.overview.profile": "Управляйте вашей личной информацией, аватаром и данными учетной записи.", "account.settings.overview.purchases": "Просмотреть историю покупок и квитанции.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "Просмотр и управление вашими активными подписками.", "account.settings.overview.support": "Доступ к заявкам в поддержку и ресурсам помощи.", "account.settings.overview.title": "Обзор настроек", + "account.settings.overview.upgrades": "Управляйте своими устаревшими квотами обновлений.", "account.settings.sso.account_is_linked": "Ваша учетная запись связана с (нажмите, чтобы отвязать)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {Зарегистрируйтесь, используя ваш аккаунт в} other {Нажмите, чтобы привязать ваш аккаунт}}", "account.settings.unlisted.label": "Скрыто: вас можно найти только по точному совпадению адреса электронной почты", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "Сгенерировать документ {docName} с помощью ИИ", "ai-generator.select_llm": "Выберите языковую модель", "app.fullscreen-button.tooltip": "Полноэкранный режим, сосредоточенный на текущем документе или странице", + "app.hotkey.dialog.help_text": "Щелкните по кадрам сверху • Клавиша 0 переключает чат • Клавиши 1–9 фокусируются на кадрах • Введите для поиска • ↑↓ навигация • Return для открытия • ESC для закрытия", + "app.hotkey.dialog.search_placeholder": "Искать файлы и страницы...", + "app.hotkey.dialog.title": "Быстрая навигация", "app.verify-email-banner.edit": "Если адрес электронной почты неверен, пожалуйста, отредактируйте его в настройках аккаунта.", "app.verify-email-banner.help.text": "Важно иметь работающий адрес электронной почты. Мы используем его для сброса пароля, отправки сообщений, уведомлений о выставлении счетов и поддержки. Пожалуйста, убедитесь, что ваш адрес электронной почты правильный, чтобы оставаться в курсе событий.", "app.verify-email-banner.text": "{sent, select, true {Электронное письмо отправлено! Пожалуйста, проверьте ваш почтовый ящик (и, возможно, спам) и нажмите на ссылку для подтверждения.} other {Пожалуйста, проверьте и подтвердите ваш адрес электронной почты:}}", @@ -988,6 +990,7 @@ "labels.config": "Конфигурация", "labels.configuration": "Конфигурация", "labels.configuration.short": "Конфиг", + "labels.connected": "Подключено", "labels.connecting": "Соединение", "labels.connection": "Соединение", "labels.copied": "скопировано", @@ -1016,6 +1019,7 @@ "labels.environment": "Окружение", "labels.explorer": "Проводник", "labels.file_explorer": "Проводник файлов", + "labels.file_use_notifications": "Уведомления об использовании файлов", "labels.files": "Файлы", "labels.folder": "папка", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Публичный} read_only {Только чтение} other {Сохранить}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "Остановить{short, select, true {} other { Проект}}…", "labels.project.settings.stop-project.ok": "Да, остановить проект", "labels.projects": "Проекты", + "labels.public_paths": "Публичные пути", "labels.published_files": "Опубликованные файлы", "labels.purchases": "Покупки", "labels.ready": "Готово", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "Выберите название. Вы всегда можете изменить его позже!", "projects.create-project.requireLicense": "Для создания дополнительных проектов требуется лицензия.", "projects.filename-search.placeholder": "Искать измененные вами файлы...", - "projects.list.no_starred_found": "Не найдено избранных проектов. Используйте значок звезды рядом с названиями проектов, чтобы добавить в закладки ваши любимые проекты.", "projects.load-all.label": "Показать все проекты...", "projects.operations.clear-filter": "Очистить фильтр", "projects.operations.delete.button": "{deleted, select, true {Восстановить все} other {Удалить все}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "Фильтр по хэштегам...", "projects.table-controls.hidden.label": "Скрыто", "projects.table-controls.search.placeholder": "Поиск проектов...", + "projects.table.keyboard-row-hint": "Проект {title}. Используйте стрелки вверх и вниз для перемещения; нажмите Enter или пробел для открытия.", "projects.table.last-edited": "Последнее изменение", + "projects.table.untitled": "Без названия", "purchases.automatic-payments-warning.description": "Автоматические платежи гораздо удобнее, сэкономят вам время и гарантируют, что подписки не будут случайно отменены.", "purchases.automatic-payments-warning.title": "Автоматические платежи НЕ требуются для подписки", "purchases.automatic-payments.are-enabled": "Автоматические платежи включены", diff --git a/src/packages/frontend/i18n/trans/tr_TR.json b/src/packages/frontend/i18n/trans/tr_TR.json index 60add4cbb48..b006a48dfd2 100644 --- a/src/packages/frontend/i18n/trans/tr_TR.json +++ b/src/packages/frontend/i18n/trans/tr_TR.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "Çeviri Bilgisi", "account.account-button.confirm.ok": "Evet, çıkış yap", "account.account-button.confirm.title": "Hesabınızdan çıkış yapmak mı?", + "account.appearance.accessibility.enabled": "Erişilebilirlik Modunu Etkinleştir: kullanıcı arayüzünü erişilebilirlik özelliklerine göre optimize et", + "account.appearance.accessibility.title": "Erişilebilirlik", "account.appearance.user_interface.title": "Kullanıcı Arayüzü", "account.delete-account.alert.description": "Derhal tüm projelerinize erişimi kaybedeceksiniz, abonelikler iptal edilecek ve kullanılmamış tüm krediler kaybolacak. {br} {hr} HESABINIZI SİLMEK için önce aşağıya \"{required_text}\" yazın:", "account.delete-account.alert.message": "HESABINIZI SİLMEK istediğinizden emin misiniz?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "dosya her kaydedildiğinde kaldır", "account.editor-settings-autosave-interval.label": "Otomatik kaydetme aralığı", "account.editor-settings.basic.title": "Temel Ayarlar", - "account.editor-settings.color-schemes.label": "Editör renk şeması", "account.editor-settings.color-schemes.panel_title": "Editör Renk Şeması", "account.editor-settings.font-size.label": "Varsayılan genel yazı tipi boyutu", "account.editor-settings.indent-size.label": "Girinti boyutu", "account.editor-settings.keyboard-bindings.label": "Editör klavye bağlamaları", "account.editor-settings.keyboard.title": "Klavye", - "account.editor-settings.title": "Editör", "account.editor-settings.x11-keyboard-variant.label": "Klavye çeşidi (X11 Masaüstü için)", "account.editor-settings.x11-physical-keyboard.label": "Klavye düzeni (X11 Masaüstü için)", "account.global-ssh-keys.help": "Bir projeye SSH ile bağlanmak için şu adresi kullanın username@host: [project-id-without-dashes]@ssh.cocalc.com Proje id'si tireler olmadan, SSH anahtarlarıyla ilgili proje ayarları bölümünde bulunabilir. Projeler arasında SSH yapmak için [project-id-without-dashes]@ssh kullanın", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "Sage çalışma sayfasında bölünmüş görünüm", "account.keyboard-shortcuts.shortcut.toggle-comment": "Yorum satırını değiştir", "account.other-settings._page_size.label": "Sayfa başına dosya sayısı", - "account.other-settings.browser_performance.title": "Tarayıcı", + "account.other-settings.auto_focus": "Otomatik Odak Metin Girişi: metin giriş alanlarını göründüklerinde otomatik olarak odakla (ör. dosya gezgini, projeler, ...)", "account.other-settings.button_tooltips": "Düğme İpuçlarını Gizle: bazı düğme ipuçlarını gizler (bu sadece kısmi)", "account.other-settings.confirm_close": "Kapatmayı Onayla: tarayıcı penceresini kapatmadan önce her zaman onay iste", - "account.other-settings.content_display.title": "İçerik Görüntüleme", "account.other-settings.default_file_sort.by_name": "Ada göre sırala", "account.other-settings.default_file_sort.by_time": "Zamana göre sırala", "account.other-settings.default_file_sort.label": "Varsayılan dosya sıralaması", + "account.other-settings.dim_file_extensions": "Dosya uzantılarını soluklaştır: dosya uzantılarını gri yaparak adlarının öne çıkmasını sağla.", "account.other-settings.file_popovers": "Dosya Sekmesi Açılır Pencerelerini Gizle: dosya sekmelerinin üzerindeki açılır pencereleri gösterme", "account.other-settings.filename_generator.description": "Otomatik olarak oluşturulan dosya adlarının nasıl oluşturulacağını seçin. Özellikle, onları benzersiz yapmak veya mevcut zamanı eklemek için.", "account.other-settings.filename_generator.label": "Dosya adı oluşturucu", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "AI Ayarları", "account.other-settings.markdown_codebar": "Tüm markdown belgelerinde markdown kod çubuğunu devre dışı bırak. Bunu işaretlemek, çitlenmiş kod bloklarındaki ekstra çalıştır, kopyala ve açıkla düğmelerini gizler.", "account.other-settings.mask_files": "Dosya maskesi: Dosya görüntüleyicide muhtemelen açmak istemediğiniz dosyaları gri renkte göster", - "account.other-settings.messages.title": "Mesajlar", "account.other-settings.project_popovers": "Proje Sekmesi Açılır Pencerelerini Gizle: proje sekmelerinin üzerindeki açılır pencereleri gösterme", - "account.other-settings.projects.title": "Projeler", "account.other-settings.standby_timeout": "Bekleme zaman aşımı", "account.other-settings.symbol_bar_labels": "Sembol Çubuğu Etiketlerini Göster: çerçeve düzenleyici sembol çubuğunda etiketleri göster", - "account.other-settings.theme": "Tema", "account.other-settings.theme.antd.animations": "Animasyonlar: bazı unsurları, örneğin düğmeleri kısaca canlandır", "account.other-settings.theme.antd.color_scheme": "Renk Şeması: varsayılan renkler yerine marka renklerini kullan", "account.other-settings.theme.antd.compact": "Kompakt Tasarım: daha kompakt bir tasarım kullan", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "Yazılım lisanslarını ve erişim izinlerini yönetin.", "account.settings.overview.other": "Çeşitli ayarlar ve seçenekler.", "account.settings.overview.payg": "Kullandıkça öde kullanımını ve faturalandırmayı yapılandır.", + "account.settings.overview.payment_methods": "Kayıtlı ödeme yöntemlerinizi yönetin veya yenilerini ekleyin.", "account.settings.overview.payments": "Ödeme yöntemlerini ve işlem geçmişini yönet.", "account.settings.overview.profile": "Kişisel bilgilerinizi, avatarınızı ve hesap ayrıntılarınızı yönetin.", "account.settings.overview.purchases": "Satın alma geçmişini ve makbuzları görüntüle.", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "Aktif aboneliklerinizi görüntüleyin ve yönetin.", "account.settings.overview.support": "Destek taleplerine ve yardım kaynaklarına erişin.", "account.settings.overview.title": "Ayarlar Genel Bakış", + "account.settings.overview.upgrades": "Eski kota yükseltmelerinizi yönetin.", "account.settings.sso.account_is_linked": "Hesabınız bağlantılı (bağlantıyı kaldırmak için tıklayın)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {Hesabınızı kullanarak kaydolun} other {Hesabınızı bağlamak için tıklayın}}", "account.settings.unlisted.label": "Liste Dışı: yalnızca tam bir e-posta adresi eşleşmesiyle bulunabilirsiniz", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "Yapay Zeka kullanarak bir {docName} Belgesi oluştur", "ai-generator.select_llm": "Dil modelini seç", "app.fullscreen-button.tooltip": "Tam ekran modu, mevcut belge veya sayfaya odaklanılmış.", + "app.hotkey.dialog.help_text": "Yukarıdaki çerçevelere tıklayın • 0 tuşu sohbeti açar/kapatır • 1–9 tuşları çerçevelere odaklanır • Aramak için yazın • ↑↓ gezin • Açmak için Enter • Kapatmak için ESC", + "app.hotkey.dialog.search_placeholder": "Dosyaları ve sayfaları ara...", + "app.hotkey.dialog.title": "Hızlı Gezinme", "app.verify-email-banner.edit": "E-posta adresi yanlışsa, lütfen hesap ayarlarında düzenleyin.", "app.verify-email-banner.help.text": "Çalışan bir e-posta adresine sahip olmak önemlidir. Şifre sıfırlama, mesaj gönderme, fatura bildirimleri ve destek için kullanıyoruz. Bilgilendirilmek için e-postanızın doğru olduğundan emin olun.", "app.verify-email-banner.text": "{sent, select, true {E-posta Gönderildi! Lütfen e-posta gelen kutunuzu (ve belki spam klasörünü) kontrol edin ve onay bağlantısına tıklayın.} other {Lütfen e-posta adresinizi kontrol edin ve doğrulayın:}}", @@ -988,6 +990,7 @@ "labels.config": "Yapılandırma", "labels.configuration": "Yapılandırma", "labels.configuration.short": "Yapılandırma", + "labels.connected": "Bağlandı", "labels.connecting": "Bağlanıyor", "labels.connection": "Bağlantı", "labels.copied": "kopyalandı", @@ -1016,6 +1019,7 @@ "labels.environment": "Ortam", "labels.explorer": "Gezer", "labels.file_explorer": "Dosya Gezgini", + "labels.file_use_notifications": "Dosya Kullanım Bildirimleri", "labels.files": "Dosyalar", "labels.folder": "klasör", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {Herkese Açık} read_only {Salt Okunur} other {Kaydet}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "Durdur{short, select, true {} other { Proje}}…", "labels.project.settings.stop-project.ok": "Evet, projeyi durdur", "labels.projects": "Projeler", + "labels.public_paths": "Genel Yollar", "labels.published_files": "Yayınlanan Dosyalar", "labels.purchases": "Satın Alımlar", "labels.ready": "Hazır", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "Bir başlık seçin. Daha sonra kolayca değiştirebilirsiniz!", "projects.create-project.requireLicense": "Ek projeler oluşturmak için bir lisans gereklidir.", "projects.filename-search.placeholder": "Düzenlediğiniz dosya adlarını arayın...", - "projects.list.no_starred_found": "Yıldızlı proje bulunamadı. Favori projelerinizi işaretlemek için proje başlıklarının yanındaki yıldız simgesini kullanın.", "projects.load-all.label": "Tüm projeleri göster...", "projects.operations.clear-filter": "Filtreyi Temizle", "projects.operations.delete.button": "{deleted, select, true {Tümünü Geri Al} other {Tümünü Sil}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "Etiketlere göre filtrele...", "projects.table-controls.hidden.label": "Gizli", "projects.table-controls.search.placeholder": "Projeleri ara...", + "projects.table.keyboard-row-hint": "Proje {title}. Hareket etmek için Yukarı ve Aşağı oklarını kullanın; açmak için Enter veya Boşluk tuşuna basın.", "projects.table.last-edited": "Son Düzenleme", + "projects.table.untitled": "Adsız", "purchases.automatic-payments-warning.description": "Otomatik ödemeler çok daha uygun, zaman kazandırır ve aboneliklerin kazara iptal edilmesini önler.", "purchases.automatic-payments-warning.title": "Otomatik ödemeler bir abonelik için GEREKLİ DEĞİLDİR", "purchases.automatic-payments.are-enabled": "Otomatik Ödemeler Etkinleştirildi", diff --git a/src/packages/frontend/i18n/trans/zh_CN.json b/src/packages/frontend/i18n/trans/zh_CN.json index 66264c8a02d..e0498d5ad55 100644 --- a/src/packages/frontend/i18n/trans/zh_CN.json +++ b/src/packages/frontend/i18n/trans/zh_CN.json @@ -5,6 +5,8 @@ "account.account_page.translation.info.title": "翻译信息", "account.account-button.confirm.ok": "是,退出", "account.account-button.confirm.title": "要退出您的账户吗", + "account.appearance.accessibility.enabled": "启用辅助功能模式:优化用户界面以支持辅助功能", + "account.appearance.accessibility.title": "无障碍", "account.appearance.user_interface.title": "用户界面", "account.delete-account.alert.description": "您将立即失去对所有项目的访问权限,所有订阅将被取消,所有未用完的积分将丢失。{br} {hr} 要删除您的账户,请首先在下方输入“{required_text}”:", "account.delete-account.alert.message": "您确定要删除您的账户吗?", @@ -31,13 +33,11 @@ "account.editor-setting.checkbox.strip_trailing_whitespace": "每次保存文件时删除", "account.editor-settings-autosave-interval.label": "自动保存间隔", "account.editor-settings.basic.title": "基本设置", - "account.editor-settings.color-schemes.label": "编辑器配色方案", "account.editor-settings.color-schemes.panel_title": "编辑器配色方案", "account.editor-settings.font-size.label": "默认全局字体大小", "account.editor-settings.indent-size.label": "缩进大小", "account.editor-settings.keyboard-bindings.label": "编辑器键盘绑定", "account.editor-settings.keyboard.title": "键盘", - "account.editor-settings.title": "编辑器", "account.editor-settings.x11-keyboard-variant.label": "键盘变体(用于X11桌面)", "account.editor-settings.x11-physical-keyboard.label": "键盘布局(用于X11桌面)", "account.global-ssh-keys.help": "要 SSH 进入一个项目,请使用以下 username@host: [project-id-without-dashes]@ssh.cocalc.com 无破折号的项目 ID 可以在项目设置中关于 SSH 密钥的部分找到。要在项目之间 SSH,请使用 [project-id-without-dashes]@ssh", @@ -66,13 +66,13 @@ "account.keyboard-shortcuts.shortcut.split-view-sagews": "在 Sage 工作表中分割视图", "account.keyboard-shortcuts.shortcut.toggle-comment": "切换注释选择", "account.other-settings._page_size.label": "每页文件数量", - "account.other-settings.browser_performance.title": "浏览器", + "account.other-settings.auto_focus": "自动聚焦文本输入:文本输入字段出现时自动聚焦(例如,文件浏览器,项目,...)", "account.other-settings.button_tooltips": "隐藏按钮提示:隐藏部分按钮提示(这只是部分隐藏)", "account.other-settings.confirm_close": "确认关闭:在关闭浏览器窗口前始终要求确认", - "account.other-settings.content_display.title": "内容显示", "account.other-settings.default_file_sort.by_name": "按名称排序", "account.other-settings.default_file_sort.by_time": "按时间排序", "account.other-settings.default_file_sort.label": "默认文件排序", + "account.other-settings.dim_file_extensions": "文件扩展名变暗:将文件扩展名变灰,以突出其名称。", "account.other-settings.file_popovers": "隐藏文件标签弹出窗口:不在文件标签上显示弹出窗口", "account.other-settings.filename_generator.description": "选择如何生成自动生成的文件名。特别是,使它们唯一或包含当前时间。", "account.other-settings.filename_generator.label": "文件名生成器", @@ -85,12 +85,9 @@ "account.other-settings.llm.title": "AI设置", "account.other-settings.markdown_codebar": "禁用markdown代码栏 在所有markdown文档中。选中此项会隐藏围栏代码块中的额外运行、复制和解释按钮。", "account.other-settings.mask_files": "屏蔽文件:在文件查看器中将您可能不想打开的文件置灰", - "account.other-settings.messages.title": "消息", "account.other-settings.project_popovers": "隐藏项目标签弹出框:不在项目标签上显示弹出框", - "account.other-settings.projects.title": "项目", "account.other-settings.standby_timeout": "待机超时", "account.other-settings.symbol_bar_labels": "显示符号栏标签:在框架编辑器符号栏中显示标签", - "account.other-settings.theme": "主题", "account.other-settings.theme.antd.animations": "动画:简要动画某些方面,例如按钮", "account.other-settings.theme.antd.color_scheme": "配色方案: 使用品牌颜色而非默认颜色", "account.other-settings.theme.antd.compact": "紧凑设计:使用更紧凑的设计", @@ -128,6 +125,7 @@ "account.settings.overview.licenses": "管理软件许可证和访问权限。", "account.settings.overview.other": "杂项设置和选项。", "account.settings.overview.payg": "配置按需付费使用和账单。", + "account.settings.overview.payment_methods": "管理您已保存的支付方式或添加新的支付方式。", "account.settings.overview.payments": "管理支付方式和交易历史。", "account.settings.overview.profile": "管理您的个人信息、头像和账户详情。", "account.settings.overview.purchases": "查看购买历史和收据。", @@ -135,6 +133,7 @@ "account.settings.overview.subscriptions": "查看和管理您的有效订阅。", "account.settings.overview.support": "访问支持票据和帮助资源。", "account.settings.overview.title": "设置概览", + "account.settings.overview.upgrades": "管理您的旧版配额升级。", "account.settings.sso.account_is_linked": "您的账户已关联(点击以取消关联)", "account.settings.sso.link_your_account": "{is_anonymous, select, true {使用您的账户注册} other {点击链接您的账户}}", "account.settings.unlisted.label": "未列出:只有通过精确的电子邮件地址匹配才能找到您", @@ -155,6 +154,9 @@ "ai-generate-document.modal.title": "使用AI生成{docName}文档", "ai-generator.select_llm": "选择语言模型", "app.fullscreen-button.tooltip": "全屏模式,专注于当前文档或页面", + "app.hotkey.dialog.help_text": "单击上方的框架 • 按键 0 切换聊天 • 按键 1–9 聚焦框架 • 输入以搜索 • ↑↓ 导航 • 回车以打开 • ESC 关闭", + "app.hotkey.dialog.search_placeholder": "搜索文件和页面...", + "app.hotkey.dialog.title": "快速导航", "app.verify-email-banner.edit": "如果电子邮件地址错误,请在账户设置中编辑。", "app.verify-email-banner.help.text": "拥有一个有效的电子邮件地址非常重要。我们用它来重置密码、发送消息、账单通知和支持。请确保您的电子邮件正确以保持信息畅通。", "app.verify-email-banner.text": "{sent, select, true {邮件已发送!请检查您的电子邮箱收件箱(可能还有垃圾邮件)并点击确认链接。} other {请检查并验证您的电子邮箱地址:}}", @@ -988,6 +990,7 @@ "labels.config": "配置", "labels.configuration": "配置", "labels.configuration.short": "配置", + "labels.connected": "已连接", "labels.connecting": "连接中", "labels.connection": "连接", "labels.copied": "已复制", @@ -1016,6 +1019,7 @@ "labels.environment": "环境", "labels.explorer": "文件", "labels.file_explorer": "文件资源管理器", + "labels.file_use_notifications": "文件使用通知", "labels.files": "文件", "labels.folder": "文件夹", "labels.frame-editors.title-bar.save_label": "{type, select, is_public {公开} read_only {只读} other {保存}}", @@ -1095,6 +1099,7 @@ "labels.project.settings.stop-project.label": "停止{short, select, true {} other {项目}}…", "labels.project.settings.stop-project.ok": "是,停止项目", "labels.projects": "项目", + "labels.public_paths": "公共路径", "labels.published_files": "已发布文件", "labels.purchases": "购买", "labels.ready": "准备好", @@ -1423,7 +1428,6 @@ "projects.create-project.helpTxt": "选择一个标题。您可以稍后轻松更改!", "projects.create-project.requireLicense": "创建额外的项目需要许可证。", "projects.filename-search.placeholder": "搜索你编辑过的文件名", - "projects.list.no_starred_found": "未找到加星标的项目。使用项目标题旁边的星标图标来收藏您喜欢的项目。", "projects.load-all.label": "显示所有项目...", "projects.operations.clear-filter": "清除筛选器", "projects.operations.delete.button": "{deleted, select, true {全部取消删除} other {全部删除}}", @@ -1455,7 +1459,9 @@ "projects.table-controls.hashtags.placeholder": "按标签过滤...", "projects.table-controls.hidden.label": "隐藏", "projects.table-controls.search.placeholder": "搜索项目...", + "projects.table.keyboard-row-hint": "项目 {title}。使用向上和向下箭头移动;按 Enter 或空格键打开。", "projects.table.last-edited": "上次编辑", + "projects.table.untitled": "无标题", "purchases.automatic-payments-warning.description": "自动支付更加方便,可以节省时间,并且确保订阅不会意外取消。", "purchases.automatic-payments-warning.title": "自动付款不需要订阅", "purchases.automatic-payments.are-enabled": "自动付款已启用", diff --git a/src/packages/frontend/index.sass b/src/packages/frontend/index.sass index 2392abe4442..4338d7f430c 100644 --- a/src/packages/frontend/index.sass +++ b/src/packages/frontend/index.sass @@ -26,6 +26,7 @@ @use 'frame-editors/_style' as frame-editors-style @use 'notifications/_style' as notifications-style @use 'account-button' +@use 'account/_account' as account-account @use 'admin/_style' as admin-style @use 'antd_fix' From a428547dd92cd38d4bc01d15449d79fccb880a0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 21:18:24 +0000 Subject: [PATCH 50/58] build(deps): bump validator from 13.15.20 to 13.15.22 in /src/packages Bumps [validator](https://github.com/validatorjs/validator.js) from 13.15.20 to 13.15.22. - [Release notes](https://github.com/validatorjs/validator.js/releases) - [Changelog](https://github.com/validatorjs/validator.js/blob/master/CHANGELOG.md) - [Commits](https://github.com/validatorjs/validator.js/compare/13.15.20...13.15.22) --- updated-dependencies: - dependency-name: validator dependency-version: 13.15.22 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- src/packages/database/package.json | 2 +- src/packages/hub/package.json | 2 +- src/packages/pnpm-lock.yaml | 110 +++++++++++++++++------------ 3 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/packages/database/package.json b/src/packages/database/package.json index f4fbf634973..ca3537746d7 100644 --- a/src/packages/database/package.json +++ b/src/packages/database/package.json @@ -32,7 +32,7 @@ "random-key": "^0.3.2", "read": "^1.0.7", "sql-string-escape": "^1.1.6", - "validator": "^13.15.20" + "validator": "^13.15.22" }, "devDependencies": { "@types/lodash": "^4.14.202", diff --git a/src/packages/hub/package.json b/src/packages/hub/package.json index 52ff57d4259..f7171bf6184 100644 --- a/src/packages/hub/package.json +++ b/src/packages/hub/package.json @@ -41,7 +41,7 @@ "uglify-js": "^3.14.1", "underscore": "^1.12.1", "uuid": "^8.3.2", - "validator": "^13.15.20", + "validator": "^13.15.22", "webpack-dev-middleware": "^7.4.2", "webpack-hot-middleware": "^2.26.1" }, diff --git a/src/packages/pnpm-lock.yaml b/src/packages/pnpm-lock.yaml index 91710cdd4e3..cc2c522da83 100644 --- a/src/packages/pnpm-lock.yaml +++ b/src/packages/pnpm-lock.yaml @@ -278,8 +278,8 @@ importers: specifier: ^1.1.6 version: 1.1.6 validator: - specifier: ^13.15.20 - version: 13.15.20 + specifier: ^13.15.22 + version: 13.15.22 devDependencies: '@types/lodash': specifier: ^4.14.202 @@ -815,8 +815,8 @@ importers: specifier: ^8.3.2 version: 8.3.2 validator: - specifier: ^13.15.20 - version: 13.15.20 + specifier: ^13.15.22 + version: 13.15.22 webpack-dev-middleware: specifier: ^7.4.2 version: 7.4.2(webpack@5.100.1(uglify-js@3.19.3)) @@ -4615,8 +4615,8 @@ packages: '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - '@types/yargs@17.0.34': - resolution: {integrity: sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==} + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} '@typescript-eslint/eslint-plugin@6.21.0': resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} @@ -5278,8 +5278,8 @@ packages: resolution: {integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==} engines: {node: '>=6.0.0'} - baseline-browser-mapping@2.8.20: - resolution: {integrity: sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==} + baseline-browser-mapping@2.8.32: + resolution: {integrity: sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==} hasBin: true basic-auth@2.0.1: @@ -5358,6 +5358,7 @@ packages: bootstrap@3.4.1: resolution: {integrity: sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==} engines: {node: '>=6'} + deprecated: This version of Bootstrap is no longer supported. Please upgrade to the latest version. brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -5374,8 +5375,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - browserslist@4.27.0: - resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -5452,8 +5453,8 @@ packages: caniuse-lite@1.0.30001734: resolution: {integrity: sha512-uhE1Ye5vgqju6OI71HTQqcBCZrvHugk0MjLak7Q+HfoBgoq5Bi+5YnwjP4fjDgrtYr/l8MVRBvzz9dPD4KyK0A==} - caniuse-lite@1.0.30001751: - resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} + caniuse-lite@1.0.30001757: + resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} canvas-fit@1.5.0: resolution: {integrity: sha512-onIcjRpz69/Hx5bB5HGbYKUF2uC6QT6Gp+pfpGm3A7mPfcluSLV5v4Zu+oflDUwLdUw0rLIBhUbi0v8hM4FJQQ==} @@ -5557,6 +5558,9 @@ packages: cjs-module-lexer@2.1.0: resolution: {integrity: sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==} + cjs-module-lexer@2.1.1: + resolution: {integrity: sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==} + cjsx-loader@3.0.0: resolution: {integrity: sha512-bek+Ojam3A0QmqrT8GXvRUdo3ynHyPAjBHNZz2A2lSDccMU9GGZ1eNsTc7+cQ/n2H+UhDL8XW/H1wvG8cNLdZA==} @@ -6556,8 +6560,8 @@ packages: electron-to-chromium@1.5.200: resolution: {integrity: sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==} - electron-to-chromium@1.5.240: - resolution: {integrity: sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==} + electron-to-chromium@1.5.263: + resolution: {integrity: sha512-DrqJ11Knd+lo+dv+lltvfMDLU27g14LMdH2b0O3Pio4uk0x+z7OR+JrmyacTPN2M8w3BrZ7/RTwG3R9B7irPlg==} element-size@1.1.1: resolution: {integrity: sha512-eaN+GMOq/Q+BIWy0ybsgpcYImjGIdNLyjLFJU4XsLHXYQao5jCNb36GyN6C2qwmDDYSfIBmKpPpr4VnBdLCsPQ==} @@ -7208,6 +7212,10 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + hasBin: true + glob@11.0.3: resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} engines: {node: 20 || >=22} @@ -8536,6 +8544,7 @@ packages: keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} engines: {node: '>= 0.6'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -9182,8 +9191,8 @@ packages: node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - node-releases@2.0.26: - resolution: {integrity: sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} node-uuid@1.4.8: resolution: {integrity: sha512-TkCET/3rr9mUuRp+CpO7qfgT++aAxfDRaalQhwPFzI9BY/2rCDn6OfpZOVggi1AXfTPpfkTrg5f5WQx5G1uLxA==} @@ -11152,8 +11161,8 @@ packages: engines: {node: '>=10'} hasBin: true - terser@5.44.0: - resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==} + terser@5.44.1: + resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==} engines: {node: '>=10'} hasBin: true @@ -11697,8 +11706,8 @@ packages: validate.io-number@1.0.3: resolution: {integrity: sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==} - validator@13.15.20: - resolution: {integrity: sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==} + validator@13.15.22: + resolution: {integrity: sha512-uT/YQjiyLJP7HSrv/dPZqK9L28xf8hsNca01HSz1dfmI0DgMfjopp1rO/z13NeGF1tVystF0Ejx3y4rUKPw+bQ==} engines: {node: '>= 0.10'} vary@1.1.2: @@ -13488,7 +13497,7 @@ snapshots: chalk: 4.1.2 collect-v8-coverage: 1.0.3 exit-x: 0.2.2 - glob: 10.4.5 + glob: 10.5.0 graceful-fs: 4.2.11 istanbul-lib-coverage: 3.2.2 istanbul-lib-instrument: 6.0.3 @@ -13621,7 +13630,7 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 '@types/node': 18.19.130 - '@types/yargs': 17.0.34 + '@types/yargs': 17.0.35 chalk: 4.1.2 '@jridgewell/gen-mapping@0.3.12': @@ -15482,7 +15491,7 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@types/yargs@17.0.34': + '@types/yargs@17.0.35': dependencies: '@types/yargs-parser': 21.0.3 @@ -16246,7 +16255,7 @@ snapshots: base64url@3.0.1: {} - baseline-browser-mapping@2.8.20: {} + baseline-browser-mapping@2.8.32: {} basic-auth@2.0.1: dependencies: @@ -16348,13 +16357,13 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.2) - browserslist@4.27.0: + browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.8.20 - caniuse-lite: 1.0.30001751 - electron-to-chromium: 1.5.240 - node-releases: 2.0.26 - update-browserslist-db: 1.1.4(browserslist@4.27.0) + baseline-browser-mapping: 2.8.32 + caniuse-lite: 1.0.30001757 + electron-to-chromium: 1.5.263 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.28.0) bs-logger@0.2.6: dependencies: @@ -16437,7 +16446,7 @@ snapshots: caniuse-lite@1.0.30001734: {} - caniuse-lite@1.0.30001751: {} + caniuse-lite@1.0.30001757: {} canvas-fit@1.5.0: dependencies: @@ -16566,6 +16575,8 @@ snapshots: cjs-module-lexer@2.1.0: {} + cjs-module-lexer@2.1.1: {} + cjsx-loader@3.0.0: dependencies: coffee-react-transform: 4.0.0 @@ -17636,7 +17647,7 @@ snapshots: electron-to-chromium@1.5.200: {} - electron-to-chromium@1.5.240: {} + electron-to-chromium@1.5.263: {} element-size@1.1.1: {} @@ -18496,6 +18507,15 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + glob@11.0.3: dependencies: foreground-child: 3.3.1 @@ -19623,7 +19643,7 @@ snapshots: chalk: 4.1.2 ci-info: 4.3.1 deepmerge: 4.3.1 - glob: 10.4.5 + glob: 10.5.0 graceful-fs: 4.2.11 jest-circus: 30.2.0 jest-docblock: 30.2.0 @@ -19655,7 +19675,7 @@ snapshots: chalk: 4.1.2 ci-info: 4.3.1 deepmerge: 4.3.1 - glob: 10.4.5 + glob: 10.5.0 graceful-fs: 4.2.11 jest-circus: 30.2.0 jest-docblock: 30.2.0 @@ -20011,9 +20031,9 @@ snapshots: '@jest/types': 30.2.0 '@types/node': 18.19.130 chalk: 4.1.2 - cjs-module-lexer: 2.1.0 + cjs-module-lexer: 2.1.1 collect-v8-coverage: 1.0.3 - glob: 10.4.5 + glob: 10.5.0 graceful-fs: 4.2.11 jest-haste-map: 30.2.0 jest-message-util: 30.2.0 @@ -21135,7 +21155,7 @@ snapshots: node-releases@2.0.19: {} - node-releases@2.0.26: {} + node-releases@2.0.27: {} node-uuid@1.4.8: {} @@ -23540,7 +23560,7 @@ snapshots: jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - terser: 5.44.0 + terser: 5.44.1 webpack: 5.100.1(uglify-js@3.19.3) optionalDependencies: uglify-js: 3.19.3 @@ -23551,7 +23571,7 @@ snapshots: jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - terser: 5.44.0 + terser: 5.44.1 webpack: 5.100.1 terser@4.8.1: @@ -23568,7 +23588,7 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 - terser@5.44.0: + terser@5.44.1: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.15.0 @@ -23966,9 +23986,9 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - update-browserslist-db@1.1.4(browserslist@4.27.0): + update-browserslist-db@1.1.4(browserslist@4.28.0): dependencies: - browserslist: 4.27.0 + browserslist: 4.28.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -24091,7 +24111,7 @@ snapshots: validate.io-number@1.0.3: {} - validator@13.15.20: {} + validator@13.15.22: {} vary@1.1.2: {} @@ -24296,7 +24316,7 @@ snapshots: '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.15.0 acorn-import-phases: 1.0.4(acorn@8.15.0) - browserslist: 4.27.0 + browserslist: 4.28.0 chrome-trace-event: 1.0.4 enhanced-resolve: 5.18.3 es-module-lexer: 1.7.0 @@ -24328,7 +24348,7 @@ snapshots: '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.15.0 acorn-import-phases: 1.0.4(acorn@8.15.0) - browserslist: 4.27.0 + browserslist: 4.28.0 chrome-trace-event: 1.0.4 enhanced-resolve: 5.18.3 es-module-lexer: 1.7.0 From 4be65f6e8ce6feae1e8d357c0ab346d0fa17a605 Mon Sep 17 00:00:00 2001 From: Andrey Novoseltsev Date: Wed, 3 Dec 2025 21:28:23 -0700 Subject: [PATCH 51/58] accessibility: restructure the policy/statement page, link fresh VPAT --- .gitignore | 2 ++ .../next/pages/policies/accessibility.tsx | 32 +++++++++++++++--- ...T2.5Rev_WCAG_February2025_December2025.pdf | Bin 0 -> 184005 bytes 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 src/packages/next/public/documents/SageMathInc_VPAT2.5Rev_WCAG_February2025_December2025.pdf diff --git a/.gitignore b/.gitignore index b03ea399673..2bde77a8ef5 100644 --- a/.gitignore +++ b/.gitignore @@ -169,3 +169,5 @@ junit.xml # autogenerated docs **/cocalc-api/site/** + +!src/packages/next/public/documents/*.pdf diff --git a/src/packages/next/pages/policies/accessibility.tsx b/src/packages/next/pages/policies/accessibility.tsx index 11b93c5d38f..57b435aed4c 100644 --- a/src/packages/next/pages/policies/accessibility.tsx +++ b/src/packages/next/pages/policies/accessibility.tsx @@ -4,7 +4,6 @@ import Head from "components/landing/head"; import { Layout } from "antd"; import withCustomize from "lib/with-customize"; import { Customize } from "lib/customize"; -import Accessibility from "components/landing/accessibility"; import { MAX_WIDTH } from "lib/config"; export default function AccessibilityPage({ customize }) { @@ -28,12 +27,37 @@ export default function AccessibilityPage({ customize }) { >

- CoCalc Voluntary Product Accessibility Template (VPAT) + CoCalc - Accessibility Statement

-

Last Updated: July 3, 2019

- +

+ Given the scope of what is possible in CoCalc, such as using + arbitrary Jupyter notebooks with custom styling and a broad + collection of software including user installed packages, it is + infeasible to expect that everything will be fully accessible + and aligned with any standards, such as WCAG. However, we are + committed to do our best to resolve any concrete issues that our + customers face. We have a long history of successfully + facilitating courses for thousands of students (i.e. for users + who cannot easily switch to an alternative platform) as evidence + of success of this approach. +

+

+ If your use case is primarily to interact with Jupyter + notebooks, keep in mind that CoCalc makes it easy to launch + industry standard Jupyter Classic (and Jupyter Lab). These + projects have put substantial deliberate efforts into making + their products accessible, although they still do not claim to + have AA compliance with WCAG. +

+

+ For more specific details, please consult our{" "} + + Voluntary Product Accessibility Template, VPAT® + {" "} + (Last Update: December 2025) +