diff --git a/docs/api/openapi-reference.md b/docs/api/openapi-reference.md
new file mode 100644
index 0000000..a4005a6
--- /dev/null
+++ b/docs/api/openapi-reference.md
@@ -0,0 +1 @@
+[OAD(./openapi.json)]
diff --git a/docs/api/openapi.json b/docs/api/openapi.json
new file mode 100644
index 0000000..dc85f63
--- /dev/null
+++ b/docs/api/openapi.json
@@ -0,0 +1,4781 @@
+{
+ "openapi": "3.1.0",
+ "info": {
+ "title": "Prusa Connect API (Inferred)",
+ "version": "20260225-5dfbf71e",
+ "description": "This is NOT official Prusa Connect API documentation. This is the result of manually reverse engineering the platform when developing the `prusa-connect-sdk-client` Python library. See for full documentation.\n\n## Authentication\n\nMost endpoints require a Bearer JWT access token obtained via OAuth2 PKCE flow.\n\n**OAuth2 Endpoints (on `account.prusa3d.com`):**\n\n- Authorization: `https://account.prusa3d.com/o/authorize/`\n- Token: `https://account.prusa3d.com/o/token/`\n- Client ID: `MRHTlZhZqkNrrQ6FUPtjyusAz8nc59ErHXP8XkS4`\n- Redirect URI: `https://connect.prusa3d.com/login/auth-callback`\n- PKCE Method: S256\n\nOnce obtained, pass the access token as `Authorization: Bearer ` on all authenticated endpoints.\n\n## Notes on Field Names\n\nMany response fields use API-side names that differ from SDK model attributes. Aliases are noted in schema descriptions where observed."
+ },
+ "servers": [
+ {
+ "url": "https://connect.prusa3d.com",
+ "description": "Prusa Connect production server"
+ }
+ ],
+ "security": [
+ {
+ "BearerAuth": []
+ }
+ ],
+ "tags": [
+ {
+ "name": "Auth",
+ "description": "Endpoints for application authentication."
+ },
+ {
+ "name": "Platform",
+ "description": "Public endpoints for API version and application configuration. No authentication required."
+ },
+ {
+ "name": "Printers",
+ "description": "List and inspect printers, retrieve supported commands, and access printer-local files and storage."
+ },
+ {
+ "name": "Jobs",
+ "description": "Job history, job details, failure reasons, and the planned print queue."
+ },
+ {
+ "name": "Teams",
+ "description": "Team (workspace) management and membership."
+ },
+ {
+ "name": "Files",
+ "description": "Team cloud storage: list, download, and upload G-code / firmware files."
+ },
+ {
+ "name": "Cameras",
+ "description": "List cameras and retrieve or trigger snapshots."
+ },
+ {
+ "name": "Notifications",
+ "description": "User notification feed."
+ },
+ {
+ "name": "Statistics",
+ "description": "Per-printer usage and job success statistics over a date range."
+ }
+ ],
+ "components": {
+ "securitySchemes": {
+ "BearerAuth": {
+ "type": "http",
+ "scheme": "bearer",
+ "bearerFormat": "JWT",
+ "description": "Prusa Connect JWT access token passed as `Authorization: Bearer `. Obtain via the `PrusaOAuth2` authorization code + PKCE flow. Token claims include: `jti` (token ID), `sub` (user ID), `exp` (expiry), `sid` (session ID), `app` (app slug), `type` (token type), `connect_id`."
+ },
+ "PrusaOAuth2": {
+ "type": "oauth2",
+ "description": "OAuth2 authorization code flow with PKCE (S256) against the Prusa Account identity provider. The `client_id` is `MRHTlZhZqkNrrQ6FUPtjyusAz8nc59ErHXP8XkS4` and the registered redirect URI is `https://connect.prusa3d.com/login/auth-callback`. Include `code_challenge` (SHA-256 of the verifier, base64url-encoded) and `code_challenge_method=S256` in the authorization request. Exchange the returned code for tokens at the token URL, supplying the original `code_verifier`. The resulting access token is then passed as a Bearer JWT to all Prusa Connect API endpoints.",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "https://account.prusa3d.com/o/authorize/",
+ "tokenUrl": "https://account.prusa3d.com/o/token/",
+ "refreshUrl": "https://account.prusa3d.com/o/token/",
+ "scopes": {
+ "openid": "OpenID Connect identity token.",
+ "basic_info": "Read access to basic account profile information.",
+ "email_lists": "Access to email list subscription preferences.",
+ "connect": "Access to Prusa Connect resources (printers, jobs, files, etc.).",
+ "user_operations": "Perform user-level operations such as team management."
+ },
+ "x-usePkce": {
+ "disableManualConfiguration": true,
+ "hideClientSecretInput": false
+ }
+ }
+ }
+ }
+ },
+ "parameters": {
+ "LimitParam": {
+ "name": "limit",
+ "in": "query",
+ "description": "Maximum number of items to return.",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 100
+ }
+ },
+ "OffsetParam": {
+ "name": "offset",
+ "in": "query",
+ "description": "Number of items to skip.",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 0
+ }
+ },
+ "PrinterUuidParam": {
+ "name": "uuid",
+ "in": "path",
+ "required": true,
+ "description": "The UUID of the printer.",
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ },
+ "TeamIdParam": {
+ "name": "team_id",
+ "in": "path",
+ "required": true,
+ "description": "The numeric ID of the team.",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ },
+ "schemas": {
+ "Pager": {
+ "type": "object",
+ "description": "Pagination metadata returned alongside list responses.",
+ "properties": {
+ "limit": {
+ "type": "integer"
+ },
+ "offset": {
+ "type": "integer"
+ },
+ "total": {
+ "type": "integer"
+ },
+ "from": {
+ "type": "integer",
+ "description": "Index of first item in this page."
+ },
+ "to": {
+ "type": "integer",
+ "description": "Index of last item in this page."
+ }
+ }
+ },
+ "NetworkInfo": {
+ "type": "object",
+ "description": "Network configuration details for a printer.",
+ "properties": {
+ "hostname": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "ipv4": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "ipv6": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "mac": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "wifi_ssid": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "lan_ipv4": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "lan_mac": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ },
+ "SourceInfo": {
+ "type": "object",
+ "description": "Information about the source of an action or resource (e.g., a user).",
+ "properties": {
+ "id": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "first_name": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "last_name": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "public_name": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "avatar": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Avatar URL. Relative paths are automatically prefixed with `https://media.printables.com/media/`."
+ }
+ }
+ },
+ "Owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/SourceInfo"
+ }
+ ],
+ "description": "Represents the owner of a resource. Same fields as SourceInfo."
+ },
+ "SyncInfo": {
+ "type": "object",
+ "description": "Synchronization details for a file or resource.",
+ "properties": {
+ "synced_by": {
+ "type": [
+ "object",
+ "null"
+ ]
+ },
+ "synced": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "source": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ },
+ "Temperatures": {
+ "type": "object",
+ "description": "Current and target temperatures for a printer. Returned nested under the `temp` key in printer responses.",
+ "properties": {
+ "temp_nozzle": {
+ "type": [
+ "number",
+ "null"
+ ],
+ "description": "Current nozzle temperature in °C."
+ },
+ "temp_bed": {
+ "type": [
+ "number",
+ "null"
+ ],
+ "description": "Current bed temperature in °C."
+ },
+ "target_nozzle": {
+ "type": [
+ "number",
+ "null"
+ ],
+ "description": "Target nozzle temperature in °C."
+ },
+ "target_bed": {
+ "type": [
+ "number",
+ "null"
+ ],
+ "description": "Target bed temperature in °C."
+ }
+ }
+ },
+ "FirmwareSupport": {
+ "type": "object",
+ "description": "Firmware version and support information for a printer.",
+ "properties": {
+ "latest": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "current": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "release_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uri"
+ },
+ "stable": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "prerelease": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "release": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "state": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ },
+ "Tool": {
+ "type": "object",
+ "description": "Represents a print head / extruder tool.",
+ "properties": {
+ "material": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "temp": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "nozzle_diameter": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "fan_hotend": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "fan_print": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "mmu": {
+ "type": [
+ "object",
+ "null"
+ ]
+ },
+ "hardened": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "high_flow": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "active": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ }
+ }
+ },
+ "SlotInfo": {
+ "type": "object",
+ "description": "MMU (Multi-Material Unit) slot information.",
+ "properties": {
+ "active": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Index of the currently active slot."
+ },
+ "slots": {
+ "type": [
+ "object",
+ "null"
+ ],
+ "additionalProperties": {
+ "$ref": "#/components/schemas/Tool"
+ }
+ },
+ "state": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "command": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ },
+ "JobInfo": {
+ "type": "object",
+ "description": "Snapshot of the job currently running on a printer. Returned nested under `job_info` in printer responses.",
+ "properties": {
+ "id": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "origin_id": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "path": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "state": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "progress": {
+ "type": [
+ "number",
+ "null"
+ ],
+ "description": "Print progress 0.0–100.0."
+ },
+ "time_printing": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Elapsed print time in seconds."
+ },
+ "time_remaining": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Estimated remaining time in seconds."
+ },
+ "display_name": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "start": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "end": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "hash": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "preview_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uri"
+ },
+ "model_weight": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "weight_remaining": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "print_height": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "total_height": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "lifetime_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ },
+ "CameraResolution": {
+ "type": "object",
+ "required": [
+ "width",
+ "height"
+ ],
+ "properties": {
+ "width": {
+ "type": "integer"
+ },
+ "height": {
+ "type": "integer"
+ }
+ }
+ },
+ "CameraNetworkInfo": {
+ "type": "object",
+ "properties": {
+ "wifi_mac": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "wifi_ipv4": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "wifi_ssid": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ },
+ "CameraConfig": {
+ "type": "object",
+ "description": "Internal configuration snapshot for a camera.",
+ "properties": {
+ "name": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "path": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "driver": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "firmware": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "rotation": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "camera_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "resolution": {
+ "$ref": "#/components/schemas/CameraResolution"
+ },
+ "manufacturer": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "network_info": {
+ "$ref": "#/components/schemas/CameraNetworkInfo"
+ },
+ "trigger_scheme": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ },
+ "CameraOptions": {
+ "type": "object",
+ "properties": {
+ "available_resolutions": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "$ref": "#/components/schemas/CameraResolution"
+ }
+ }
+ }
+ },
+ "Camera": {
+ "type": "object",
+ "description": "Camera device associated with a printer or team.",
+ "properties": {
+ "id": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Numeric ID used for snapshot retrieval."
+ },
+ "token": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Alphanumeric token used for snapshot triggers."
+ },
+ "name": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "origin": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "resolution": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "snapshot_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uri"
+ },
+ "config": {
+ "$ref": "#/components/schemas/CameraConfig"
+ },
+ "options": {
+ "$ref": "#/components/schemas/CameraOptions"
+ },
+ "capabilities": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "type": "string"
+ }
+ },
+ "features": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "type": "string"
+ }
+ },
+ "sort_order": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "registered": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "team_id": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "printer_uuid": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uuid"
+ },
+ "snapshots": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "Printer": {
+ "type": "object",
+ "description": "Full printer object returned by `/app/printers` and `/app/printers/{uuid}`.",
+ "properties": {
+ "uuid": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uuid"
+ },
+ "name": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "printer_state": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Also returned as `state` in some responses. Falls back to `UNKNOWN` for unrecognized values.",
+ "enum": [
+ "READY",
+ "IDLE",
+ "BUSY",
+ "MANIPULATING",
+ "PRINTING",
+ "PAUSED",
+ "FINISHED",
+ "STOPPED",
+ "ATTENTION",
+ "ERROR",
+ "OFFLINE",
+ "UNKNOWN"
+ ]
+ },
+ "state_reason": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "connect_state": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "disabled": {
+ "type": [
+ "object",
+ "null"
+ ],
+ "additionalProperties": {
+ "type": "boolean"
+ }
+ },
+ "printer_model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "printer_type": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "printer_type_name": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "firmware": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Current firmware version string. SDK alias: `firmware_version`."
+ },
+ "support": {
+ "$ref": "#/components/schemas/FirmwareSupport"
+ },
+ "network_info": {
+ "$ref": "#/components/schemas/NetworkInfo"
+ },
+ "tools": {
+ "type": [
+ "object",
+ "null"
+ ],
+ "additionalProperties": {
+ "$ref": "#/components/schemas/Tool"
+ }
+ },
+ "slot": {
+ "$ref": "#/components/schemas/SlotInfo"
+ },
+ "temp": {
+ "$ref": "#/components/schemas/Temperatures",
+ "description": "Current temperatures. SDK alias: `telemetry`."
+ },
+ "job_info": {
+ "$ref": "#/components/schemas/JobInfo",
+ "description": "Current job snapshot. SDK alias: `job`."
+ },
+ "cameras": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "$ref": "#/components/schemas/Camera"
+ }
+ },
+ "nozzle_diameter": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "speed": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Current print speed percentage."
+ },
+ "flow": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Current flow rate percentage."
+ },
+ "axis_x": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "axis_y": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "axis_z": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "team_id": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "team_name": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "organization_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uuid"
+ },
+ "api_key": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "prusalink_api_key": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "prusaconnect_api_key": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "last_online": {
+ "type": [
+ "number",
+ "null"
+ ],
+ "description": "Unix timestamp of last online time."
+ },
+ "created": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "sn": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Printer serial number."
+ },
+ "location": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "is_beta": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "supported_printer_models": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "type": "string"
+ }
+ },
+ "printable_extension": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "type": "string"
+ }
+ },
+ "allowed_functionalities": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "type": "string"
+ }
+ },
+ "max_filename": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "slots": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Number of MMU slots."
+ },
+ "rights_r": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "rights_w": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "rights_u": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "owner": {
+ "$ref": "#/components/schemas/Owner"
+ },
+ "flags": {
+ "type": [
+ "object",
+ "null"
+ ]
+ },
+ "filament": {
+ "type": [
+ "object",
+ "null"
+ ]
+ },
+ "enclosure": {},
+ "appendix": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "time_delta": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "inaccurate_estimates": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "groups": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {}
+ }
+ }
+ },
+ "CommandArgument": {
+ "type": "object",
+ "description": "Describes a single argument for a printer command.",
+ "required": [
+ "name",
+ "type"
+ ],
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "string",
+ "integer",
+ "boolean",
+ "object",
+ "number"
+ ]
+ },
+ "required": {
+ "type": "boolean",
+ "default": false
+ },
+ "default": {},
+ "description": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "reference": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "input": {
+ "type": "boolean",
+ "default": true
+ },
+ "output": {
+ "type": "boolean",
+ "default": false
+ },
+ "unit": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ },
+ "CommandDefinition": {
+ "type": "object",
+ "description": "Defines a command that can be sent to a printer.",
+ "required": [
+ "command"
+ ],
+ "properties": {
+ "command": {
+ "type": "string",
+ "description": "Command identifier. Known values include: `SET_PRINTER_READY`, `CANCEL_PRINTER_READY`, `PAUSE_PRINT`, `RESUME_PRINT`, `STOP_PRINT`, `RESET_PRINTER`, `UNLOAD_FILAMENT`, `SEND_INFO`, `STOP_TRANSFER`, `SEND_STATE_INFO`, `RESET`, `DISABLE_STEPPERS`, `BEEP`."
+ },
+ "args": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CommandArgument"
+ },
+ "default": []
+ },
+ "description": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "template": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "G-code template, if applicable."
+ },
+ "executable_from_state": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "List of printer states from which this command can be executed."
+ },
+ "duplicates_allowed": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ },
+ "JobStatus": {
+ "type": "string",
+ "description": "Status of a job. `FIN_UNKNOWN` is returned for unrecognized values.",
+ "enum": [
+ "PRINTING",
+ "FINISHED",
+ "FIN_OK",
+ "FIN_STOPPED",
+ "FIN_ERROR",
+ "FIN_UNKNOWN"
+ ]
+ },
+ "JobFailureTag": {
+ "type": "string",
+ "description": "Reason tag for a job failure or cancellation.",
+ "enum": [
+ "IGNORED",
+ "CLOGGED_NOZZLE",
+ "NON_ADHERENT_BED",
+ "UNDER_EXTRUSION",
+ "OVER_EXTRUSION",
+ "STRINGING_OR_OOZING",
+ "GAPS_IN_THIN_WALLS",
+ "OVERHEATING",
+ "LAYER_SHIFTING",
+ "SPAGHETTI_MONSTER",
+ "LAYER_SEPARATION",
+ "WARPING",
+ "POOR_BRIDGING",
+ "OTHER"
+ ]
+ },
+ "JobFailureReason": {
+ "type": "object",
+ "properties": {
+ "tag": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/JobFailureTag"
+ }
+ },
+ "other": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Free-text note when tag is OTHER."
+ }
+ }
+ },
+ "CancelableObject": {
+ "type": "object",
+ "required": [
+ "id",
+ "name"
+ ],
+ "description": "An object within a print that can be individually cancelled.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "name": {
+ "type": "string"
+ },
+ "polygon": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "number"
+ }
+ }
+ },
+ "canceled": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ },
+ "Job": {
+ "type": "object",
+ "description": "A print job in the history or queue for a printer.",
+ "required": [
+ "id",
+ "state"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "lifetime_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "printer_uuid": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uuid"
+ },
+ "team_id": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "origin_id": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "source": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "source_info": {
+ "$ref": "#/components/schemas/SourceInfo"
+ },
+ "state": {
+ "$ref": "#/components/schemas/JobStatus"
+ },
+ "cameras": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "$ref": "#/components/schemas/Camera"
+ }
+ },
+ "hash": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "time_printing": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Total print time in seconds."
+ },
+ "start": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Job start time as Unix timestamp."
+ },
+ "end": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Job end time as Unix timestamp."
+ },
+ "progress": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "planned": {
+ "type": [
+ "object",
+ "null"
+ ]
+ },
+ "print_height": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "file": {
+ "$ref": "#/components/schemas/File"
+ },
+ "path": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "reason": {
+ "$ref": "#/components/schemas/JobFailureReason"
+ },
+ "cancelable_objects": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "$ref": "#/components/schemas/CancelableObject"
+ },
+ "description": "Also accessible as `cancelable.objects` in some response formats."
+ },
+ "cancelable_time": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ }
+ }
+ },
+ "Storage": {
+ "type": "object",
+ "description": "A storage device available on the printer (e.g., USB, internal flash).",
+ "required": [
+ "type",
+ "path",
+ "name"
+ ],
+ "properties": {
+ "type": {
+ "type": "string"
+ },
+ "path": {
+ "type": "string"
+ },
+ "mountpoint": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "name": {
+ "type": "string"
+ },
+ "read_only": {
+ "type": "boolean",
+ "default": false
+ },
+ "is_sfn": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "file_count": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "free_space": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Free space in bytes."
+ },
+ "total_space": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Total space in bytes."
+ }
+ }
+ },
+ "PrintFileMeta": {
+ "type": "object",
+ "description": "Metadata extracted from G-code / BG-code slicer comments.",
+ "properties": {
+ "extruder_colour": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "filament_type": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "filament_cost": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "filament_abrasive": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "temperature": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Nozzle temperature in °C."
+ },
+ "bed_temperature": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Bed temperature in °C."
+ },
+ "brim_width": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "ironing": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "nozzle_high_flow": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "support_material": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "total_height": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "max_layer_z": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "layer_height": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "fill_density": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "nozzle_diameter": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "filament_used_m": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "filament_used_mm": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "filament_used_g": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "filament_used_cm3": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "filament_used_mm3": {
+ "type": [
+ "number",
+ "null"
+ ]
+ },
+ "estimated_print_time": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Estimated print time in seconds."
+ },
+ "estimated_printing_time_normal_mode": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "printer_model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "producer": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "slots": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "type": "object"
+ }
+ },
+ "objects_info": {
+ "type": [
+ "object",
+ "null"
+ ]
+ }
+ }
+ },
+ "FirmwareFileMeta": {
+ "type": "object",
+ "description": "Metadata associated with a firmware (.bbf) file.",
+ "properties": {
+ "device_type_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "version": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "sem_ver": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "build_no": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "bbf_version": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "printer_model": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ },
+ "BaseFile": {
+ "type": "object",
+ "description": "Common fields shared by all file types.",
+ "required": [
+ "type",
+ "name"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "description": "Discriminator field. One of `FILE`, `PRINT_FILE`, `FIRMWARE`."
+ },
+ "name": {
+ "type": "string"
+ },
+ "display_name": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "size": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "File size in bytes."
+ },
+ "hash": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "SHA256 hash of the file content."
+ },
+ "team_id": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "upload_id": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "uploaded": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "path": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "display_path": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "read_only": {
+ "type": "boolean",
+ "default": false
+ },
+ "m_timestamp": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Modification timestamp (Unix)."
+ },
+ "sync": {
+ "$ref": "#/components/schemas/SyncInfo"
+ },
+ "owner": {
+ "$ref": "#/components/schemas/Owner"
+ }
+ }
+ },
+ "RegularFile": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseFile"
+ }
+ ],
+ "description": "A generic non-print, non-firmware file.",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "FILE"
+ ]
+ }
+ }
+ },
+ "PrintFile": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseFile"
+ }
+ ],
+ "description": "A G-code or BG-code print file.",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "PRINT_FILE"
+ ]
+ },
+ "preview_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uri"
+ },
+ "preview_mimetype": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "meta": {
+ "$ref": "#/components/schemas/PrintFileMeta"
+ }
+ }
+ },
+ "FirmwareFile": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseFile"
+ }
+ ],
+ "description": "A firmware (.bbf) file.",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "FIRMWARE"
+ ]
+ },
+ "printer_type": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "release_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uri"
+ },
+ "meta": {
+ "$ref": "#/components/schemas/FirmwareFileMeta"
+ }
+ }
+ },
+ "File": {
+ "description": "A file in Prusa Connect storage. Discriminated by the `type` field.",
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/PrintFile"
+ },
+ {
+ "$ref": "#/components/schemas/FirmwareFile"
+ },
+ {
+ "$ref": "#/components/schemas/RegularFile"
+ }
+ ],
+ "discriminator": {
+ "propertyName": "type",
+ "mapping": {
+ "PRINT_FILE": "#/components/schemas/PrintFile",
+ "FIRMWARE": "#/components/schemas/FirmwareFile",
+ "FILE": "#/components/schemas/RegularFile"
+ }
+ }
+ },
+ "UploadStatus": {
+ "type": "object",
+ "description": "Status of an in-progress or completed file upload.",
+ "required": [
+ "id",
+ "team_id",
+ "name",
+ "size",
+ "state"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "team_id": {
+ "type": "integer"
+ },
+ "name": {
+ "type": "string"
+ },
+ "size": {
+ "type": "integer",
+ "description": "File size in bytes."
+ },
+ "hash": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "state": {
+ "type": "string"
+ },
+ "source": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ },
+ "TeamUser": {
+ "type": "object",
+ "description": "A user that is a member of a team.",
+ "required": [
+ "id"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "first_name": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "last_name": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "public_name": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "avatar": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "rights_ro": {
+ "type": [
+ "boolean",
+ "null"
+ ],
+ "description": "Read-only rights."
+ },
+ "rights_rw": {
+ "type": [
+ "boolean",
+ "null"
+ ],
+ "description": "Read-write rights."
+ },
+ "rights_use": {
+ "type": [
+ "boolean",
+ "null"
+ ],
+ "description": "Usage rights (start/stop print)."
+ }
+ }
+ },
+ "Team": {
+ "type": "object",
+ "description": "A Prusa Connect team (workspace).",
+ "required": [
+ "id",
+ "name"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "name": {
+ "type": "string"
+ },
+ "role": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "description": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "capacity": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Maximum number of printers."
+ },
+ "organization_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uuid"
+ },
+ "prusaconnect_api_key": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "user_count": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "users": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "$ref": "#/components/schemas/TeamUser"
+ }
+ },
+ "invitees": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {}
+ }
+ }
+ },
+ "AuthConfig": {
+ "type": "object",
+ "required": [
+ "backends",
+ "server_url",
+ "client_id",
+ "redirect_url"
+ ],
+ "properties": {
+ "backends": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "PRUSA_AUTH"
+ ]
+ },
+ "description": "List of supported authentication providers."
+ },
+ "server_url": {
+ "type": "string",
+ "format": "uri",
+ "description": "Base URL for the authentication server."
+ },
+ "client_id": {
+ "type": "string",
+ "description": "OAuth2 Client ID."
+ },
+ "redirect_url": {
+ "type": "string",
+ "format": "uri",
+ "description": "OAuth2 Redirect URL."
+ },
+ "avatar_server_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uri",
+ "description": "Base URL for retrieving user avatars."
+ },
+ "max_upload_size": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Maximum allowed file upload size in bytes."
+ },
+ "max_snapshot_size": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Maximum allowed camera snapshot size in bytes."
+ },
+ "max_preview_size": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Maximum allowed G-code preview size in bytes."
+ },
+ "afs_enabled": {
+ "type": [
+ "boolean",
+ "null"
+ ],
+ "description": "Whether the Automated Filament System (AFS) is enabled."
+ },
+ "afs_group_id": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "Group ID for AFS."
+ }
+ }
+ },
+ "AppConfig": {
+ "type": "object",
+ "required": [
+ "auth"
+ ],
+ "properties": {
+ "auth": {
+ "$ref": "#/components/schemas/AuthConfig"
+ }
+ }
+ },
+ "PrinterTypeParameters": {
+ "type": "object",
+ "description": "Hardware parameter ranges for a printer type.",
+ "properties": {
+ "extrusion": {
+ "$ref": "#/components/schemas/RangeWithDefault"
+ },
+ "print_flow": {
+ "$ref": "#/components/schemas/RangeWithDefault"
+ },
+ "print_speed": {
+ "$ref": "#/components/schemas/RangeWithDefault"
+ },
+ "min_temp_nozzle_e": {
+ "type": "integer"
+ },
+ "temp_nozzle": {
+ "$ref": "#/components/schemas/Range"
+ },
+ "temp_bed": {
+ "$ref": "#/components/schemas/Range"
+ },
+ "feedrate_e": {
+ "$ref": "#/components/schemas/RangeWithDefault"
+ },
+ "feedrate_x": {
+ "$ref": "#/components/schemas/RangeWithDefault"
+ },
+ "feedrate_y": {
+ "$ref": "#/components/schemas/RangeWithDefault"
+ },
+ "feedrate_z": {
+ "$ref": "#/components/schemas/RangeWithDefault"
+ },
+ "position_x": {
+ "$ref": "#/components/schemas/Range"
+ },
+ "position_y": {
+ "$ref": "#/components/schemas/Range"
+ },
+ "position_z": {
+ "$ref": "#/components/schemas/Range"
+ },
+ "mbl_positions": {
+ "type": "array",
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "description": "Mesh Bed Leveling position grid."
+ }
+ }
+ },
+ "Range": {
+ "type": "object",
+ "properties": {
+ "max": {
+ "type": "number"
+ },
+ "min": {
+ "type": "number"
+ }
+ }
+ },
+ "RangeWithDefault": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Range"
+ }
+ ],
+ "properties": {
+ "default": {
+ "type": "number"
+ }
+ }
+ },
+ "PrinterType": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "type": {
+ "type": "integer"
+ },
+ "version": {
+ "type": "integer"
+ },
+ "subversion": {
+ "type": "integer"
+ },
+ "name": {
+ "type": "string"
+ },
+ "parameters": {
+ "$ref": "#/components/schemas/PrinterTypeParameters"
+ },
+ "decision_maker": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "max_filename": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "fw_device_type_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "support": {
+ "type": "object",
+ "properties": {
+ "stable": {
+ "type": "string"
+ },
+ "unsupported": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "PrintingNotPrintingEntry": {
+ "type": "object",
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "value": {
+ "type": "integer",
+ "description": "Duration in seconds. SDK alias: `duration`."
+ }
+ }
+ },
+ "PrintingNotPrinting": {
+ "type": "object",
+ "description": "Printer usage statistics: time spent printing vs idle.",
+ "required": [
+ "from",
+ "to",
+ "name",
+ "uuid",
+ "data"
+ ],
+ "properties": {
+ "from": {
+ "type": "integer",
+ "description": "Range start as Unix timestamp."
+ },
+ "to": {
+ "type": "integer",
+ "description": "Range end as Unix timestamp."
+ },
+ "name": {
+ "type": "string",
+ "description": "Printer name. SDK alias: `printer_name`."
+ },
+ "uuid": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Printer UUID. SDK alias: `printer_uuid`."
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PrintingNotPrintingEntry"
+ }
+ }
+ }
+ },
+ "MaterialQuantity": {
+ "type": "object",
+ "description": "Printer usage statistics: material quantity consumed.",
+ "required": [
+ "from",
+ "to",
+ "name",
+ "uuid",
+ "data"
+ ],
+ "properties": {
+ "from": {
+ "type": "integer",
+ "description": "Range start as Unix timestamp."
+ },
+ "to": {
+ "type": "integer",
+ "description": "Range end as Unix timestamp."
+ },
+ "name": {
+ "type": "string",
+ "description": "Printer name. SDK alias: `printer_name`."
+ },
+ "uuid": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Printer UUID. SDK alias: `printer_uuid`."
+ },
+ "data": {
+ "type": "array",
+ "items": {}
+ }
+ }
+ },
+ "PlannedTasksSeries": {
+ "type": "object",
+ "required": [
+ "uuid",
+ "name",
+ "data"
+ ],
+ "properties": {
+ "uuid": {
+ "type": "string",
+ "format": "uuid",
+ "description": "SDK alias: `printer_uuid`."
+ },
+ "name": {
+ "type": "string",
+ "description": "SDK alias: `printer_name`."
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "minItems": 2,
+ "maxItems": 2
+ },
+ "description": "Array of [timestamp, count] pairs."
+ }
+ }
+ },
+ "PlannedTasks": {
+ "type": "object",
+ "description": "Printer usage statistics: planned print tasks over time.",
+ "required": [
+ "from",
+ "to",
+ "xAxis",
+ "series"
+ ],
+ "properties": {
+ "from": {
+ "type": "integer",
+ "description": "Range start as Unix timestamp."
+ },
+ "to": {
+ "type": "integer",
+ "description": "Range end as Unix timestamp."
+ },
+ "xAxis": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "description": "Time axis as Unix timestamps. SDK alias: `time_axis`."
+ },
+ "series": {
+ "$ref": "#/components/schemas/PlannedTasksSeries"
+ }
+ }
+ },
+ "JobsSuccessSeries": {
+ "type": "object",
+ "required": [
+ "name",
+ "data"
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Job status value (e.g., `FIN_OK`). SDK alias: `status`.",
+ "enum": [
+ "PRINTING",
+ "FINISHED",
+ "FIN_OK",
+ "FIN_STOPPED",
+ "FIN_ERROR",
+ "FIN_UNKNOWN"
+ ]
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "description": "Count per date bucket."
+ }
+ }
+ },
+ "JobsSuccess": {
+ "type": "object",
+ "description": "Printer usage statistics: job success/failure counts over time.",
+ "required": [
+ "from",
+ "to",
+ "xAxis",
+ "name",
+ "uuid",
+ "series",
+ "time_shift"
+ ],
+ "properties": {
+ "from": {
+ "type": "integer",
+ "description": "Range start as Unix timestamp."
+ },
+ "to": {
+ "type": "integer",
+ "description": "Range end as Unix timestamp."
+ },
+ "xAxis": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Date labels for each bucket. SDK alias: `date_axis`."
+ },
+ "name": {
+ "type": "string",
+ "description": "Printer name. SDK alias: `printer_name`."
+ },
+ "uuid": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Printer UUID. SDK alias: `printer_uuid`."
+ },
+ "series": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/JobsSuccessSeries"
+ }
+ },
+ "time_shift": {
+ "type": "string"
+ }
+ }
+ },
+ "ErrorResponse": {
+ "type": "object",
+ "description": "Standard error response body returned for 4xx/5xx responses. `code` and `message` are always present. `reason` provides additional context for input/validation errors (e.g. invalid path parameters on a 400) but is absent for auth errors.",
+ "required": [
+ "code",
+ "message"
+ ],
+ "properties": {
+ "code": {
+ "type": "string",
+ "description": "Machine-readable error code. Known values: `BAD_REQUEST` (400), `UNAUTHORIZED` (401), `NOT_FOUND_CAMERA` (404), `NOT_FOUND_PRINTER` (404), `NOT_FOUND_TEAM` (404). Full enumeration is not documented.",
+ "example": "UNAUTHORIZED"
+ },
+ "message": {
+ "type": "string",
+ "description": "Human-readable description of the error.",
+ "example": "Missing or invalid security token"
+ },
+ "reason": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Additional context about what caused the error. Present for validation/input errors (e.g. `Invalid path parameter: printer_uuid`); absent for auth errors.",
+ "example": "Invalid path parameter: printer_uuid"
+ }
+ }
+ }
+ }
+ },
+ "paths": {
+ "/o/authorize": {
+ "servers": [
+ {
+ "url": "https://account.prusa3d.com",
+ "description": "Prusa Account Identity Provider"
+ }
+ ],
+ "get": {
+ "operationId": "initiateAuthorization",
+ "summary": "Initiate OAuth2 Authorization",
+ "description": "Step 1 of the OAuth2 PKCE flow. Renders the Prusa Account login page as an HTML document and sets a `csrftoken` cookie. The server embeds the full percent-encoded authorization continuation URL in a hidden `next` field within the login form. The `csrftoken` cookie value must be forwarded as `csrfmiddlewaretoken` and the `next` value must be forwarded verbatim to `POST /login`.",
+ "security": [],
+ "tags": ["Auth"],
+ "parameters": [
+ {
+ "name": "response_type",
+ "in": "query",
+ "required": true,
+ "description": "OAuth2 response type. Must be `code` for the authorization code flow.",
+ "schema": {
+ "type": "string",
+ "enum": ["code"]
+ }
+ },
+ {
+ "name": "client_id",
+ "in": "query",
+ "required": true,
+ "description": "The OAuth2 client ID for Prusa Connect.",
+ "schema": {
+ "type": "string"
+ },
+ "example": "MRHTlZhZqkNrrQ6FUPtjyusAz8nc59ErHXP8XkS4"
+ },
+ {
+ "name": "redirect_uri",
+ "in": "query",
+ "required": true,
+ "description": "The registered callback URI. Must exactly match the registered value.",
+ "schema": {
+ "type": "string",
+ "enum": ["https://connect.prusa3d.com/login/auth-callback"]
+ }
+ },
+ {
+ "name": "code_challenge_method",
+ "in": "query",
+ "required": true,
+ "description": "PKCE challenge method. Only `S256` (SHA-256) is accepted.",
+ "schema": {
+ "type": "string",
+ "enum": ["S256"]
+ }
+ },
+ {
+ "name": "code_challenge",
+ "in": "query",
+ "required": true,
+ "description": "Base64url-encoded SHA-256 hash of the PKCE `code_verifier`. Must be generated fresh for each authorization request.",
+ "schema": {
+ "type": "string"
+ },
+ "example": "-KfhcQVIeAp3Hy1yXxq0jRQxKgYzpzSOTGzBSK8j81"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "HTML login form. Sets a `csrftoken` cookie required as `csrfmiddlewaretoken` in the credential submission. The form (`id=\"recaptcha_form\"`) contains hidden fields `csrfmiddlewaretoken` and `next` carrying the percent-encoded `/o/authorize/` continuation URL through the login chain.",
+ "headers": {
+ "Set-Cookie": {
+ "description": "Sets the `csrftoken` cookie used for CSRF protection on subsequent form submissions to `/login` and `/login/totp`.",
+ "schema": {
+ "type": "string",
+ "example": "csrftoken=RsGaG6vlFBSlmaM9KNvNylBDUxA7I8zF; Path=/; SameSite=Lax"
+ }
+ }
+ },
+ "content": {
+ "text/html": {
+ "schema": {
+ "type": "string",
+ "description": "HTML document containing a login form with hidden fields `csrfmiddlewaretoken` and `next` (the percent-encoded `/o/authorize/` URL to resume after authentication)."
+ }
+ }
+ },
+ "links": {
+ "SubmitCredentials": {
+ "operationId": "submitCredentials",
+ "description": "Pass the `csrftoken` cookie value as `csrfmiddlewaretoken` and the embedded `next` form-field value to `POST /login` to continue the flow."
+ }
+ }
+ }
+ }
+ }
+ },
+ "/login": {
+ "post": {
+ "operationId": "submitCredentials",
+ "summary": "Submit User Credentials",
+ "description": "Step 2 of the OAuth2 PKCE flow. Submits the user's email and password as a form POST together with the CSRF token from `GET /o/authorize` and the `next` continuation URL. On success without MFA the server issues a `302` redirect to `redirect_uri?code=...`. If the account has TOTP enabled the redirect targets `/login/totp/` instead.",
+ "security": [],
+ "tags": ["Auth"],
+ "servers": [
+ {
+ "url": "https://account.prusa3d.com",
+ "description": "Prusa Account Identity Provider"
+ }
+ ],
+ "parameters": [
+ {
+ "name": "next",
+ "in": "query",
+ "required": true,
+ "description": "Percent-encoded `/o/authorize/` URL including all PKCE parameters. Preserved verbatim through every redirect in the login chain.",
+ "schema": {
+ "type": "string"
+ },
+ "example": "/o/authorize/?response_type=code&client_id=MRHTlZhZqkNrrQ6FUPtjyusAz8nc59ErHXP8XkS4&redirect_uri=https%3A%2F%2Fconnect.prusa3d.com%2Flogin%2Fauth-callback&code_challenge_method=S256&code_challenge=LupMrl9-u2zbT3ltQAZv6dGFEsokCe2L7dIh2O0R6qg"
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "description": "Form-encoded credentials and CSRF token. `csrfmiddlewaretoken` is the value of the `csrftoken` cookie set by `GET /o/authorize`. `next` mirrors the query parameter and is used by the server to reconstruct the redirect chain.",
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "type": "object",
+ "required": ["csrfmiddlewaretoken", "next", "email", "password"],
+ "properties": {
+ "csrfmiddlewaretoken": {
+ "type": "string",
+ "description": "CSRF token value from the `csrftoken` cookie set by `GET /o/authorize`."
+ },
+ "next": {
+ "type": "string",
+ "description": "The percent-encoded `/o/authorize/` URL forwarded from the login form's hidden `next` field."
+ },
+ "email": {
+ "type": "string",
+ "format": "email",
+ "description": "The user's Prusa Account email address."
+ },
+ "password": {
+ "type": "string",
+ "format": "password",
+ "description": "The user's Prusa Account password."
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "302": {
+ "description": "Redirect on successful credential verification. Without MFA the `Location` header is `{redirect_uri}?code={authorization_code}`. With TOTP MFA enabled the `Location` header is `https://account.prusa3d.com/login/totp/?next={encoded_authorize_url}`.",
+ "headers": {
+ "Location": {
+ "description": "Redirect destination. Either the `redirect_uri` with an authorization `code` parameter (no MFA), or the TOTP endpoint with the `next` parameter (MFA required).",
+ "schema": {
+ "type": "string"
+ },
+ "examples": {
+ "no-mfa": {
+ "summary": "Authorization code issued directly (no MFA)",
+ "value": "https://connect.prusa3d.com/login/auth-callback?code=YWAV9pnaSuxc6CxA27PdSEWa7Ld2P"
+ },
+ "mfa-required": {
+ "summary": "TOTP step required",
+ "value": "https://account.prusa3d.com/login/totp/?next=/o/authorize/%3Fresponse_type%3Dcode%26client_id%3DMRHTlZhZqkNrrQ6FUPtjyusAz8nc59ErHXP8XkS4%26..."
+ }
+ }
+ }
+ },
+ "links": {
+ "SubmitTotp": {
+ "operationId": "submitTotp",
+ "description": "When MFA is required, follow the redirect to `POST /login/totp` supplying the same CSRF token, the forwarded `next` value, and the 6-digit OTP code."
+ },
+ "ExchangeAuthorizationCode": {
+ "operationId": "exchangeToken",
+ "description": "When MFA is not required, extract the `code` query parameter from the `Location` redirect URL and exchange it for tokens at `POST /o/token/`.",
+ "parameters": {
+ "code": "$response.header.Location"
+ }
+ }
+ }
+ },
+ "200": {
+ "description": "Login page re-rendered with a validation error (invalid credentials or reCAPTCHA failure).",
+ "content": {
+ "text/html": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/login/totp": {
+ "post": {
+ "operationId": "submitTotp",
+ "summary": "Submit TOTP MFA Code",
+ "description": "Step 3 of the OAuth2 PKCE flow — conditional, only required when the account has TOTP MFA enabled. Submits the 6-digit one-time password together with the CSRF token and forwarded `next` URL. On success the server redirects to `redirect_uri?code=...` containing the authorization code.",
+ "security": [],
+ "tags": ["Auth"],
+ "servers": [
+ {
+ "url": "https://account.prusa3d.com",
+ "description": "Prusa Account Identity Provider"
+ }
+ ],
+ "parameters": [
+ {
+ "name": "next",
+ "in": "query",
+ "required": true,
+ "description": "Percent-encoded `/o/authorize/` URL forwarded from the `/login` redirect `Location` header. Must be preserved exactly.",
+ "schema": {
+ "type": "string"
+ },
+ "example": "/o/authorize/?response_type=code&client_id=MRHTlZhZqkNrrQ6FUPtjyusAz8nc59ErHXP8XkS4&redirect_uri=https%3A%2F%2Fconnect.prusa3d.com%2Flogin%2Fauth-callback&code_challenge_method=S256&code_challenge=LupMrl9-u2zbT3ltQAZv6dGFEsokCe2L7dIh2O0R6qg"
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "description": "Form-encoded TOTP submission. `csrfmiddlewaretoken` must match the `csrftoken` cookie from the originating `GET /o/authorize` response.",
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "type": "object",
+ "required": ["csrfmiddlewaretoken", "next", "otp_token"],
+ "properties": {
+ "csrfmiddlewaretoken": {
+ "type": "string",
+ "description": "CSRF token value matching the `csrftoken` cookie from `GET /o/authorize`."
+ },
+ "next": {
+ "type": "string",
+ "description": "The percent-encoded `/o/authorize/` URL forwarded from the `/login` redirect."
+ },
+ "otp_token": {
+ "type": "string",
+ "pattern": "^[0-9]{6}$",
+ "description": "6-digit TOTP code from the user's authenticator app.",
+ "example": "012467"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "302": {
+ "description": "Redirect to `{redirect_uri}?code={authorization_code}` on successful OTP verification.",
+ "headers": {
+ "Location": {
+ "description": "The registered `redirect_uri` with the short-lived authorization `code` appended as a query parameter. Extract this `code` and pass it to `POST /o/token/`.",
+ "schema": {
+ "type": "string"
+ },
+ "example": "https://connect.prusa3d.com/login/auth-callback?code=YWAV9pnaSuxc6CxA27PdSEWa7Ld2P"
+ }
+ },
+ "links": {
+ "ExchangeAuthorizationCode": {
+ "operationId": "exchangeToken",
+ "description": "Extract the `code` query parameter from the `Location` redirect URL and exchange it for tokens at `POST /o/token/`.",
+ "parameters": {
+ "code": "$response.header.Location"
+ }
+ }
+ }
+ },
+ "200": {
+ "description": "TOTP form re-rendered with a validation error (invalid or expired OTP code).",
+ "content": {
+ "text/html": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+
+ "/o/token/": {
+ "post": {
+ "operationId": "exchangeToken",
+ "summary": "Exchange Authorization Code for Tokens",
+ "description": "Step 4 of the OAuth2 PKCE flow. Exchanges the short-lived authorization `code` (extracted from the `redirect_uri` callback query parameter) for a JWT access token, JWT refresh token, and OIDC ID token. The `code_verifier` must correspond to the `code_challenge` submitted in `GET /o/authorize` (RFC 7636). Access tokens are valid for 7200 seconds; refresh tokens are valid for approximately 30 days.",
+ "security": [],
+ "tags": ["Auth"],
+ "servers": [
+ {
+ "url": "https://account.prusa3d.com",
+ "description": "Prusa Account Identity Provider"
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "description": "Standard OAuth2 authorization code token request (RFC 6749 §4.1.3) with PKCE `code_verifier` extension (RFC 7636 §4.5).",
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "type": "object",
+ "required": ["grant_type", "client_id", "code", "code_verifier", "redirect_uri"],
+ "properties": {
+ "grant_type": {
+ "type": "string",
+ "enum": ["authorization_code"],
+ "description": "Must be `authorization_code`."
+ },
+ "client_id": {
+ "type": "string",
+ "description": "The OAuth2 client ID for Prusa Connect.",
+ "example": "MRHTlZhZqkNrrQ6FUPtjyusAz8nc59ErHXP8XkS4"
+ },
+ "code": {
+ "type": "string",
+ "description": "The authorization code extracted from the `code` query parameter of the `redirect_uri` callback.",
+ "example": "ou6xxSTbr99XO0GOWfnvtFMDz1WEgY"
+ },
+ "code_verifier": {
+ "type": "string",
+ "description": "The original PKCE code verifier (high-entropy random string) whose SHA-256 hash was sent as `code_challenge` in `GET /o/authorize`.",
+ "example": "MLeh9nWAw-c_juZoNOyFvmArmZv9GnmSrRRVFx7bU2A"
+ },
+ "redirect_uri": {
+ "type": "string",
+ "description": "Must exactly match the `redirect_uri` used in `GET /o/authorize`.",
+ "enum": ["https://connect.prusa3d.com/login/auth-callback"]
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Token response containing a Bearer access token, refresh token, and OIDC ID token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": ["access_token", "expires_in", "token_type", "scope", "refresh_token", "shared_session_key", "id_token"],
+ "properties": {
+ "access_token": {
+ "type": "string",
+ "description": "RS256-signed JWT Bearer token. Pass as `Authorization: Bearer ` on all authenticated Prusa Connect API calls. Decoded claims: `jti` (token ID), `sub` (account numeric ID string), `exp` (expiry epoch float), `sid` (session UUID), `app` (always `\"connect\"`), `type` (always `\"access\"`), `connect_id` (Connect-specific user ID string)."
+ },
+ "expires_in": {
+ "type": "integer",
+ "description": "Access token lifetime in seconds.",
+ "example": 7200
+ },
+ "token_type": {
+ "type": "string",
+ "enum": ["Bearer"],
+ "description": "Token type. Always `Bearer`."
+ },
+ "scope": {
+ "type": "string",
+ "description": "Space-separated list of granted OAuth2 scopes.",
+ "example": "basic_info connect user_operations email_lists openid"
+ },
+ "refresh_token": {
+ "type": "string",
+ "description": "RS256-signed JWT refresh token used to obtain a new access token without re-authentication. Valid for approximately 30 days. Decoded claims: `jti`, `sub`, `exp`, `sid`, `app`, `type` (always `\"refresh\"`)."
+ },
+ "shared_session_key": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session UUID shared across all tokens issued in this authorization session. Corresponds to the `sid` claim in both the access and refresh tokens.",
+ "example": "8144bf06-4ef4-42e0-9c99-cada1948415b"
+ },
+ "id_token": {
+ "type": "string",
+ "description": "RS256-signed OIDC ID token (JWT). Standard claims: `jti`, `sub` (account ID), `exp`, `aud` (client_id), `iss` (`https://account.prusa3d.com`). Extended `user` claim contains full profile: `id`, `email`, `language`, `first_name`, `last_name`, `public_username`, `avatar` (relative path), `avatar_small` / `avatar_large` (absolute media URLs), `external_service_ids` (`id_verse`, `id_academy`, `id_connect`, `id_shopsy`, `id_knowledgebase`), `otp_enabled`, `is_employee`, `is_staff`, `providers` (e.g. `[\"GOOGLE\"]`), `department`, `custom_data`."
+ }
+ }
+ }
+ }
+ },
+ "links": {
+ "UseAccessToken": {
+ "operationRef": "#/paths/~1app~1printers/get",
+ "description": "Pass the `access_token` value as `Authorization: Bearer ` on all authenticated Prusa Connect API endpoints (see `BearerAuth` security scheme). The token is valid for `expires_in` seconds; use `refresh_token` to obtain a new access token when it expires."
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid token request — malformed parameters, expired or already-used authorization code, or `code_verifier`/`code_challenge` mismatch.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "error": {
+ "type": "string",
+ "description": "OAuth2 error code (e.g. `invalid_grant`, `invalid_client`, `invalid_request`).",
+ "example": "invalid_grant"
+ },
+ "error_description": {
+ "type": "string",
+ "description": "Human-readable description of the error.",
+ "example": "Code has expired."
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/version.json": {
+ "get": {
+ "summary": "Get API version",
+ "description": "Returns the current platform version. Does not require authentication.",
+ "operationId": "getVersion",
+ "security": [],
+ "parameters": [],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "commit": {
+ "type": "string",
+ "description": "8-character commitish string."
+ },
+ "version": {
+ "type": "string",
+ "description": "Version string (currently same as commit)."
+ },
+ "date": {
+ "type": "string",
+ "description": "ISO format UTC timestamp when this version was committed."
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Platform"
+ ]
+ }
+ },
+ "/app/config": {
+ "get": {
+ "summary": "Get application configuration",
+ "description": "Returns public application configuration including OAuth2 parameters, upload limits, and feature flags. Does not require authentication.",
+ "operationId": "getAppConfig",
+ "security": [],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/AppConfig"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Platform"
+ ]
+ }
+ },
+ "/app/printer-types": {
+ "get": {
+ "summary": "Get printer types",
+ "description": "Returns all known printer type definitions with hardware parameter ranges. Does not require authentication.",
+ "operationId": "getPrinterTypes",
+ "security": [],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "printer_types": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PrinterType"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Platform"
+ ]
+ }
+ },
+ "/app/printers": {
+ "get": {
+ "summary": "List printers",
+ "description": "Returns all printers accessible to the authenticated user. Pass `team_id` to filter to a specific team.",
+ "operationId": "listPrinters",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/LimitParam"
+ },
+ {
+ "$ref": "#/components/parameters/OffsetParam"
+ },
+ {
+ "name": "team_id",
+ "in": "query",
+ "required": false,
+ "description": "Filter printers by team ID.",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK. May return either a wrapped object `{\"printers\": [...]}` or a bare array.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "type": "object",
+ "properties": {
+ "printers": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Printer"
+ }
+ }
+ }
+ },
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Printer"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Printers"
+ ]
+ }
+ },
+ "/app/printers/groups": {
+ "get": {
+ "summary": "Get printer groups",
+ "description": "Returns printer groupings. Schema of individual group items is not yet fully documented.",
+ "operationId": "getPrinterGroups",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/LimitParam"
+ },
+ {
+ "$ref": "#/components/parameters/OffsetParam"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "groups": {
+ "type": "array",
+ "items": {}
+ },
+ "pager": {
+ "$ref": "#/components/schemas/Pager"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Printers"
+ ]
+ }
+ },
+ "/app/printers/{uuid}": {
+ "get": {
+ "summary": "Get printer details",
+ "description": "Returns full details for a single printer by UUID.",
+ "operationId": "getPrinter",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/PrinterUuidParam"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Printer"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Printer not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Printers"
+ ]
+ }
+ },
+ "/app/printers/{uuid}/commands": {
+ "get": {
+ "summary": "Get supported commands",
+ "description": "Returns the list of commands supported by the printer. Used to determine printer capabilities before sending commands. The SDK requires `STOP_PRINT` to be present and raises `PrusaCompatibilityError` if missing.",
+ "operationId": "getPrinterCommands",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/PrinterUuidParam"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK. May return a wrapped object or a bare array.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "type": "object",
+ "properties": {
+ "commands": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CommandDefinition"
+ }
+ }
+ }
+ },
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CommandDefinition"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Printer not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Printers"
+ ]
+ }
+ },
+ "/app/printers/{uuid}/commands/sync": {
+ "post": {
+ "summary": "Execute printer command",
+ "description": "Sends a synchronous command to the printer. The command must be listed in the printer's supported commands. Known commands (no additional args): `SET_PRINTER_READY`, `CANCEL_PRINTER_READY`, `PAUSE_PRINT`, `RESUME_PRINT`, `STOP_PRINT`, `RESET_PRINTER`, `UNLOAD_FILAMENT`, `SEND_INFO`, `STOP_TRANSFER`, `SEND_STATE_INFO`, `RESET`, `DISABLE_STEPPERS`, `BEEP`.",
+ "operationId": "executePrinterCommand",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/PrinterUuidParam"
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "command"
+ ],
+ "properties": {
+ "command": {
+ "type": "string",
+ "description": "The command name to execute (e.g., `PAUSE_PRINT`)."
+ },
+ "kwargs": {
+ "type": [
+ "object",
+ "null"
+ ],
+ "description": "Optional named arguments for commands that accept parameters."
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Command accepted."
+ },
+ "204": {
+ "description": "Command accepted, no content returned."
+ },
+ "400": {
+ "description": "Bad request — invalid command or arguments.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Printer not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Printers"
+ ]
+ }
+ },
+ "/app/printers/{uuid}/files": {
+ "get": {
+ "summary": "List printer files",
+ "description": "Returns files stored on the printer's local storage.",
+ "operationId": "listPrinterFiles",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/PrinterUuidParam"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "type": "object",
+ "properties": {
+ "files": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/File"
+ }
+ }
+ }
+ },
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/File"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Printer not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Printers"
+ ]
+ }
+ },
+ "/app/printers/{uuid}/storages": {
+ "get": {
+ "summary": "Get printer storage devices",
+ "description": "Returns storage devices (USB, internal flash, etc.) attached to the printer.",
+ "operationId": "getPrinterStorages",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/PrinterUuidParam"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "type": "object",
+ "properties": {
+ "storages": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Storage"
+ }
+ }
+ }
+ },
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Storage"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Printer not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Printers"
+ ]
+ }
+ },
+ "/app/printers/{uuid}/jobs": {
+ "get": {
+ "summary": "List printer job history",
+ "description": "Returns the job history for a specific printer.",
+ "operationId": "listPrinterJobs",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/PrinterUuidParam"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "jobs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Job"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Printer not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Jobs"
+ ]
+ }
+ },
+ "/app/printers/{uuid}/jobs/{job_id}": {
+ "get": {
+ "summary": "Get job details",
+ "description": "Returns details for a specific job on a printer.",
+ "operationId": "getPrinterJob",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/PrinterUuidParam"
+ },
+ {
+ "name": "job_id",
+ "in": "path",
+ "required": true,
+ "description": "The numeric ID of the job.",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Job"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Printer or job not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Jobs"
+ ]
+ },
+ "patch": {
+ "summary": "Update job (set failure reason)",
+ "description": "Updates a job record. Currently used to attach a failure reason to a finished/failed job.",
+ "operationId": "patchPrinterJob",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/PrinterUuidParam"
+ },
+ {
+ "name": "job_id",
+ "in": "path",
+ "required": true,
+ "description": "The numeric ID of the job.",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "reason": {
+ "$ref": "#/components/schemas/JobFailureReason"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Updated."
+ },
+ "204": {
+ "description": "Updated, no content."
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Printer or job not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Jobs"
+ ]
+ }
+ },
+ "/app/printers/{uuid}/queue": {
+ "get": {
+ "summary": "Get print queue",
+ "description": "Returns the planned/queued jobs for a printer. Response shape varies — may use `planned_jobs`, `jobs`, or `queue` as the key.",
+ "operationId": "getPrinterQueue",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/PrinterUuidParam"
+ },
+ {
+ "$ref": "#/components/parameters/LimitParam"
+ },
+ {
+ "$ref": "#/components/parameters/OffsetParam"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK. Key name for the array varies across firmware versions.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "type": "object",
+ "properties": {
+ "planned_jobs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Job"
+ }
+ }
+ }
+ },
+ {
+ "type": "object",
+ "properties": {
+ "jobs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Job"
+ }
+ }
+ }
+ },
+ {
+ "type": "object",
+ "properties": {
+ "queue": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Job"
+ }
+ }
+ }
+ },
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Job"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Printer not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Jobs"
+ ]
+ }
+ },
+ "/app/users/teams": {
+ "get": {
+ "summary": "List teams",
+ "description": "Returns all teams accessible to the authenticated user.",
+ "operationId": "listTeams",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/LimitParam"
+ },
+ {
+ "name": "offset",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 0
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "teams": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Team"
+ }
+ },
+ "pager": {
+ "$ref": "#/components/schemas/Pager"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Teams"
+ ]
+ }
+ },
+ "/app/users/teams/{team_id}": {
+ "get": {
+ "summary": "Get team details",
+ "description": "Returns full details for a team, including its members (`users`) and invitees.",
+ "operationId": "getTeam",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/TeamIdParam"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Team"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Team not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Teams"
+ ]
+ }
+ },
+ "/app/teams/{team_id}/add-user": {
+ "post": {
+ "summary": "Invite user to team",
+ "description": "Invites a user (by email) to join a team with specified permissions.",
+ "operationId": "addTeamUser",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/TeamIdParam"
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "email"
+ ],
+ "properties": {
+ "email": {
+ "type": "string",
+ "format": "email"
+ },
+ "rights_ro": {
+ "type": "boolean",
+ "default": true,
+ "description": "Grant read-only rights."
+ },
+ "rights_use": {
+ "type": "boolean",
+ "default": false,
+ "description": "Grant usage rights (start/stop print)."
+ },
+ "rights_rw": {
+ "type": "boolean",
+ "default": false,
+ "description": "Grant read-write rights."
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "User invited successfully."
+ },
+ "204": {
+ "description": "User invited, no content returned."
+ },
+ "400": {
+ "description": "Bad request.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Team not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Teams"
+ ]
+ }
+ },
+ "/app/teams/{team_id}/files": {
+ "get": {
+ "summary": "List team files",
+ "description": "Returns all files stored in the team's Connect storage.",
+ "operationId": "listTeamFiles",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/TeamIdParam"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "files": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/File"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Team not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Files"
+ ]
+ }
+ },
+ "/app/teams/{team_id}/files/{file_hash}": {
+ "get": {
+ "summary": "Get team file details",
+ "description": "Returns metadata for a specific file in team storage, identified by its hash.",
+ "operationId": "getTeamFile",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/TeamIdParam"
+ },
+ {
+ "name": "file_hash",
+ "in": "path",
+ "required": true,
+ "description": "SHA256 hash (or identifier) of the file.",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/File"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "File not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Files"
+ ]
+ }
+ },
+ "/app/teams/{team_id}/files/{file_hash}/raw": {
+ "get": {
+ "summary": "Download team file",
+ "description": "Downloads the raw binary content of a file from team storage.",
+ "operationId": "downloadTeamFile",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/TeamIdParam"
+ },
+ {
+ "name": "file_hash",
+ "in": "path",
+ "required": true,
+ "description": "SHA256 hash (or identifier) of the file.",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK — binary file content.",
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "File not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Files"
+ ]
+ }
+ },
+ "/app/users/teams/{team_id}/uploads": {
+ "post": {
+ "summary": "Initiate file upload",
+ "description": "Creates an upload session and returns an `upload_id`. Follow up with `PUT /app/teams/{team_id}/files/raw?upload_id={id}` to send the binary data.",
+ "operationId": "initiateTeamUpload",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/TeamIdParam"
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "destination",
+ "filename",
+ "size"
+ ],
+ "properties": {
+ "destination": {
+ "type": "string",
+ "description": "Target folder path in Connect storage (e.g., `connect/My Projects`)."
+ },
+ "filename": {
+ "type": "string",
+ "description": "The name of the file to create."
+ },
+ "size": {
+ "type": "integer",
+ "description": "File size in bytes."
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Upload session created.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UploadStatus"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad request.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Team not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Files"
+ ]
+ }
+ },
+ "/app/teams/{team_id}/files/raw": {
+ "put": {
+ "summary": "Upload file data",
+ "description": "Uploads the binary content of a file for a previously initiated upload session. Must call `POST /app/users/teams/{team_id}/uploads` first to obtain `upload_id`.",
+ "operationId": "uploadTeamFileData",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/TeamIdParam"
+ },
+ {
+ "name": "upload_id",
+ "in": "query",
+ "required": true,
+ "description": "The upload session ID returned by the initiate upload endpoint.",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "parameters_note": "Additionally requires headers: `Content-Type` (e.g., `application/x-bgcode`) and `Upload-Size` (file size in bytes).",
+ "responses": {
+ "200": {
+ "description": "Upload accepted."
+ },
+ "204": {
+ "description": "Upload complete, no content returned."
+ },
+ "400": {
+ "description": "Bad request — missing or invalid upload ID.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Files"
+ ]
+ }
+ },
+ "/app/cameras": {
+ "get": {
+ "summary": "List cameras",
+ "description": "Returns all cameras accessible to the authenticated user.",
+ "operationId": "listCameras",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/LimitParam"
+ },
+ {
+ "$ref": "#/components/parameters/OffsetParam"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "type": "object",
+ "properties": {
+ "cameras": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Camera"
+ }
+ }
+ }
+ },
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Camera"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Cameras"
+ ]
+ }
+ },
+ "/app/cameras/{camera_id}/snapshots/last": {
+ "get": {
+ "summary": "Get latest camera snapshot",
+ "description": "Returns the most recent snapshot image for a camera, identified by its numeric ID.",
+ "operationId": "getLastCameraSnapshot",
+ "parameters": [
+ {
+ "name": "camera_id",
+ "in": "path",
+ "required": true,
+ "description": "Numeric camera ID (the `id` field on the Camera object).",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK — raw image bytes.",
+ "content": {
+ "image/jpeg": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "image/png": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "image/*": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Camera not found or no snapshot available.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Cameras"
+ ]
+ }
+ },
+ "/app/cameras/{camera_token}/snapshots": {
+ "post": {
+ "summary": "Trigger camera snapshot",
+ "description": "Instructs the camera to capture a new snapshot. Uses the alphanumeric `token` field from the Camera object.",
+ "operationId": "triggerCameraSnapshot",
+ "parameters": [
+ {
+ "name": "camera_token",
+ "in": "path",
+ "required": true,
+ "description": "Alphanumeric camera token (the `token` field on the Camera object).",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Snapshot triggered."
+ },
+ "204": {
+ "description": "Snapshot triggered, no content."
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Camera not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Cameras"
+ ]
+ }
+ },
+ "/app/notifications": {
+ "get": {
+ "summary": "Get notifications",
+ "description": "Returns notifications for the authenticated user.",
+ "operationId": "getNotifications",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/LimitParam"
+ },
+ {
+ "$ref": "#/components/parameters/OffsetParam"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "notifications": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "category": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "severity": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "hash": {
+ "type": "string"
+ },
+ "job_id": {
+ "type": "integer"
+ },
+ "job_name": {
+ "type": "string"
+ },
+ "job_state": {
+ "type": "string"
+ },
+ "preview_url": {
+ "type": "string"
+ },
+ "printer_name": {
+ "type": "string"
+ },
+ "printer_uuid": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ },
+ "created": {
+ "type": "integer",
+ "description": "Unix timestamp."
+ },
+ "text": {
+ "type": "string"
+ },
+ "seen": {
+ "type": "integer",
+ "description": "Unix timestamp when seen, or 0 if unseen."
+ }
+ }
+ }
+ },
+ "pager": {
+ "$ref": "#/components/schemas/Pager"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Notifications"
+ ]
+ }
+ },
+ "/app/stats/printers/{uuid}/printing_not_printing": {
+ "get": {
+ "summary": "Get printer usage stats",
+ "description": "Returns time-based statistics on how long the printer spent printing vs idle over a date range.",
+ "operationId": "getPrinterUsageStats",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/PrinterUuidParam"
+ },
+ {
+ "name": "from",
+ "in": "query",
+ "required": false,
+ "description": "Start of date range as Unix timestamp (seconds).",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "to",
+ "in": "query",
+ "required": false,
+ "description": "End of date range as Unix timestamp (seconds).",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PrintingNotPrinting"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Printer not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Statistics"
+ ]
+ }
+ },
+ "/app/stats/printers/{uuid}/material_quantity": {
+ "get": {
+ "summary": "Get material quantity stats",
+ "description": "Returns statistics on the amount of material consumed by the printer over a date range.",
+ "operationId": "getPrinterMaterialStats",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/PrinterUuidParam"
+ },
+ {
+ "name": "from",
+ "in": "query",
+ "required": false,
+ "description": "Start of date range as Unix timestamp (seconds).",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "to",
+ "in": "query",
+ "required": false,
+ "description": "End of date range as Unix timestamp (seconds).",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/MaterialQuantity"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Printer not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Statistics"
+ ]
+ }
+ },
+ "/app/stats/printers/{uuid}/planned_tasks": {
+ "get": {
+ "summary": "Get planned tasks stats",
+ "description": "Returns time-series data of planned print tasks for the printer over a date range.",
+ "operationId": "getPrinterPlannedTasksStats",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/PrinterUuidParam"
+ },
+ {
+ "name": "from",
+ "in": "query",
+ "required": false,
+ "description": "Start of date range as Unix timestamp (seconds).",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "to",
+ "in": "query",
+ "required": false,
+ "description": "End of date range as Unix timestamp (seconds).",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PlannedTasks"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Printer not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Statistics"
+ ]
+ }
+ },
+ "/app/stats/printers/{uuid}/jobs_success": {
+ "get": {
+ "summary": "Get job success stats",
+ "description": "Returns historical job success/failure counts bucketed by date for the printer.",
+ "operationId": "getPrinterJobsSuccessStats",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/PrinterUuidParam"
+ },
+ {
+ "name": "from",
+ "in": "query",
+ "required": false,
+ "description": "Start of date range as Unix timestamp (seconds).",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "to",
+ "in": "query",
+ "required": false,
+ "description": "End of date range as Unix timestamp (seconds).",
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/JobsSuccess"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized — missing or invalid Bearer token.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Printer not found.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Statistics"
+ ]
+ }
+ }
+ }
+}
diff --git a/docs/css/mkdocsoad.css b/docs/css/mkdocsoad.css
new file mode 100644
index 0000000..e9daefe
--- /dev/null
+++ b/docs/css/mkdocsoad.css
@@ -0,0 +1,1814 @@
+/**
+ * All CSS for the neoteroi-mkdocs extensions.
+ *
+ * https://github.com/Neoteroi/mkdocs-plugins
+**/
+:root {
+ --nt-color-0: #CD853F;
+ --nt-color-1: #B22222;
+ --nt-color-2: #000080;
+ --nt-color-3: #4B0082;
+ --nt-color-4: #3CB371;
+ --nt-color-5: #D2B48C;
+ --nt-color-6: #FF00FF;
+ --nt-color-7: #98FB98;
+ --nt-color-8: #FFEBCD;
+ --nt-color-9: #2E8B57;
+ --nt-color-10: #6A5ACD;
+ --nt-color-11: #48D1CC;
+ --nt-color-12: #FFA500;
+ --nt-color-13: #F4A460;
+ --nt-color-14: #A52A2A;
+ --nt-color-15: #FFE4C4;
+ --nt-color-16: #FF4500;
+ --nt-color-17: #AFEEEE;
+ --nt-color-18: #FA8072;
+ --nt-color-19: #2F4F4F;
+ --nt-color-20: #FFDAB9;
+ --nt-color-21: #BC8F8F;
+ --nt-color-22: #FFC0CB;
+ --nt-color-23: #00FA9A;
+ --nt-color-24: #F0FFF0;
+ --nt-color-25: #FFFACD;
+ --nt-color-26: #F5F5F5;
+ --nt-color-27: #FF6347;
+ --nt-color-28: #FFFFF0;
+ --nt-color-29: #7FFFD4;
+ --nt-color-30: #E9967A;
+ --nt-color-31: #7B68EE;
+ --nt-color-32: #FFF8DC;
+ --nt-color-33: #0000CD;
+ --nt-color-34: #D2691E;
+ --nt-color-35: #708090;
+ --nt-color-36: #5F9EA0;
+ --nt-color-37: #008080;
+ --nt-color-38: #008000;
+ --nt-color-39: #FFE4E1;
+ --nt-color-40: #FFFF00;
+ --nt-color-41: #FFFAF0;
+ --nt-color-42: #DCDCDC;
+ --nt-color-43: #ADFF2F;
+ --nt-color-44: #ADD8E6;
+ --nt-color-45: #8B008B;
+ --nt-color-46: #7FFF00;
+ --nt-color-47: #800000;
+ --nt-color-48: #20B2AA;
+ --nt-color-49: #556B2F;
+ --nt-color-50: #778899;
+ --nt-color-51: #E6E6FA;
+ --nt-color-52: #FFFAFA;
+ --nt-color-53: #FF7F50;
+ --nt-color-54: #FF0000;
+ --nt-color-55: #F5DEB3;
+ --nt-color-56: #008B8B;
+ --nt-color-57: #66CDAA;
+ --nt-color-58: #808000;
+ --nt-color-59: #FAF0E6;
+ --nt-color-60: #00BFFF;
+ --nt-color-61: #C71585;
+ --nt-color-62: #00FFFF;
+ --nt-color-63: #8B4513;
+ --nt-color-64: #F0F8FF;
+ --nt-color-65: #FAEBD7;
+ --nt-color-66: #8B0000;
+ --nt-color-67: #4682B4;
+ --nt-color-68: #F0E68C;
+ --nt-color-69: #BDB76B;
+ --nt-color-70: #A0522D;
+ --nt-color-71: #FAFAD2;
+ --nt-color-72: #FFD700;
+ --nt-color-73: #DEB887;
+ --nt-color-74: #E0FFFF;
+ --nt-color-75: #8A2BE2;
+ --nt-color-76: #32CD32;
+ --nt-color-77: #87CEFA;
+ --nt-color-78: #00CED1;
+ --nt-color-79: #696969;
+ --nt-color-80: #DDA0DD;
+ --nt-color-81: #EE82EE;
+ --nt-color-82: #FFB6C1;
+ --nt-color-83: #8FBC8F;
+ --nt-color-84: #D8BFD8;
+ --nt-color-85: #9400D3;
+ --nt-color-86: #A9A9A9;
+ --nt-color-87: #FFFFE0;
+ --nt-color-88: #FFF5EE;
+ --nt-color-89: #FFF0F5;
+ --nt-color-90: #FFDEAD;
+ --nt-color-91: #800080;
+ --nt-color-92: #B0E0E6;
+ --nt-color-93: #9932CC;
+ --nt-color-94: #DAA520;
+ --nt-color-95: #F0FFFF;
+ --nt-color-96: #40E0D0;
+ --nt-color-97: #00FF7F;
+ --nt-color-98: #006400;
+ --nt-color-99: #808080;
+ --nt-color-100: #87CEEB;
+ --nt-color-101: #0000FF;
+ --nt-color-102: #6495ED;
+ --nt-color-103: #FDF5E6;
+ --nt-color-104: #B8860B;
+ --nt-color-105: #BA55D3;
+ --nt-color-106: #C0C0C0;
+ --nt-color-107: #000000;
+ --nt-color-108: #F08080;
+ --nt-color-109: #B0C4DE;
+ --nt-color-110: #00008B;
+ --nt-color-111: #6B8E23;
+ --nt-color-112: #FFE4B5;
+ --nt-color-113: #FFA07A;
+ --nt-color-114: #9ACD32;
+ --nt-color-115: #FFFFFF;
+ --nt-color-116: #F5F5DC;
+ --nt-color-117: #90EE90;
+ --nt-color-118: #1E90FF;
+ --nt-color-119: #7CFC00;
+ --nt-color-120: #FF69B4;
+ --nt-color-121: #F8F8FF;
+ --nt-color-122: #F5FFFA;
+ --nt-color-123: #00FF00;
+ --nt-color-124: #D3D3D3;
+ --nt-color-125: #DB7093;
+ --nt-color-126: #DA70D6;
+ --nt-color-127: #FF1493;
+ --nt-color-128: #228B22;
+ --nt-color-129: #FFEFD5;
+ --nt-color-130: #4169E1;
+ --nt-color-131: #191970;
+ --nt-color-132: #9370DB;
+ --nt-color-133: #483D8B;
+ --nt-color-134: #FF8C00;
+ --nt-color-135: #EEE8AA;
+ --nt-color-136: #CD5C5C;
+ --nt-color-137: #DC143C;
+}
+
+:root {
+ --nt-group-0-main: #000000;
+ --nt-group-0-dark: #FFFFFF;
+ --nt-group-0-light: #000000;
+ --nt-group-0-main-bg: #F44336;
+ --nt-group-0-dark-bg: #BA000D;
+ --nt-group-0-light-bg: #FF7961;
+ --nt-group-1-main: #000000;
+ --nt-group-1-dark: #FFFFFF;
+ --nt-group-1-light: #000000;
+ --nt-group-1-main-bg: #E91E63;
+ --nt-group-1-dark-bg: #B0003A;
+ --nt-group-1-light-bg: #FF6090;
+ --nt-group-2-main: #FFFFFF;
+ --nt-group-2-dark: #FFFFFF;
+ --nt-group-2-light: #000000;
+ --nt-group-2-main-bg: #9C27B0;
+ --nt-group-2-dark-bg: #6A0080;
+ --nt-group-2-light-bg: #D05CE3;
+ --nt-group-3-main: #FFFFFF;
+ --nt-group-3-dark: #FFFFFF;
+ --nt-group-3-light: #000000;
+ --nt-group-3-main-bg: #673AB7;
+ --nt-group-3-dark-bg: #320B86;
+ --nt-group-3-light-bg: #9A67EA;
+ --nt-group-4-main: #FFFFFF;
+ --nt-group-4-dark: #FFFFFF;
+ --nt-group-4-light: #000000;
+ --nt-group-4-main-bg: #3F51B5;
+ --nt-group-4-dark-bg: #002984;
+ --nt-group-4-light-bg: #757DE8;
+ --nt-group-5-main: #000000;
+ --nt-group-5-dark: #FFFFFF;
+ --nt-group-5-light: #000000;
+ --nt-group-5-main-bg: #2196F3;
+ --nt-group-5-dark-bg: #0069C0;
+ --nt-group-5-light-bg: #6EC6FF;
+ --nt-group-6-main: #000000;
+ --nt-group-6-dark: #FFFFFF;
+ --nt-group-6-light: #000000;
+ --nt-group-6-main-bg: #03A9F4;
+ --nt-group-6-dark-bg: #007AC1;
+ --nt-group-6-light-bg: #67DAFF;
+ --nt-group-7-main: #000000;
+ --nt-group-7-dark: #000000;
+ --nt-group-7-light: #000000;
+ --nt-group-7-main-bg: #00BCD4;
+ --nt-group-7-dark-bg: #008BA3;
+ --nt-group-7-light-bg: #62EFFF;
+ --nt-group-8-main: #000000;
+ --nt-group-8-dark: #FFFFFF;
+ --nt-group-8-light: #000000;
+ --nt-group-8-main-bg: #009688;
+ --nt-group-8-dark-bg: #00675B;
+ --nt-group-8-light-bg: #52C7B8;
+ --nt-group-9-main: #000000;
+ --nt-group-9-dark: #FFFFFF;
+ --nt-group-9-light: #000000;
+ --nt-group-9-main-bg: #4CAF50;
+ --nt-group-9-dark-bg: #087F23;
+ --nt-group-9-light-bg: #80E27E;
+ --nt-group-10-main: #000000;
+ --nt-group-10-dark: #000000;
+ --nt-group-10-light: #000000;
+ --nt-group-10-main-bg: #8BC34A;
+ --nt-group-10-dark-bg: #5A9216;
+ --nt-group-10-light-bg: #BEF67A;
+ --nt-group-11-main: #000000;
+ --nt-group-11-dark: #000000;
+ --nt-group-11-light: #000000;
+ --nt-group-11-main-bg: #CDDC39;
+ --nt-group-11-dark-bg: #99AA00;
+ --nt-group-11-light-bg: #FFFF6E;
+ --nt-group-12-main: #000000;
+ --nt-group-12-dark: #000000;
+ --nt-group-12-light: #000000;
+ --nt-group-12-main-bg: #FFEB3B;
+ --nt-group-12-dark-bg: #C8B900;
+ --nt-group-12-light-bg: #FFFF72;
+ --nt-group-13-main: #000000;
+ --nt-group-13-dark: #000000;
+ --nt-group-13-light: #000000;
+ --nt-group-13-main-bg: #FFC107;
+ --nt-group-13-dark-bg: #C79100;
+ --nt-group-13-light-bg: #FFF350;
+ --nt-group-14-main: #000000;
+ --nt-group-14-dark: #000000;
+ --nt-group-14-light: #000000;
+ --nt-group-14-main-bg: #FF9800;
+ --nt-group-14-dark-bg: #C66900;
+ --nt-group-14-light-bg: #FFC947;
+ --nt-group-15-main: #000000;
+ --nt-group-15-dark: #FFFFFF;
+ --nt-group-15-light: #000000;
+ --nt-group-15-main-bg: #FF5722;
+ --nt-group-15-dark-bg: #C41C00;
+ --nt-group-15-light-bg: #FF8A50;
+ --nt-group-16-main: #FFFFFF;
+ --nt-group-16-dark: #FFFFFF;
+ --nt-group-16-light: #000000;
+ --nt-group-16-main-bg: #795548;
+ --nt-group-16-dark-bg: #4B2C20;
+ --nt-group-16-light-bg: #A98274;
+ --nt-group-17-main: #000000;
+ --nt-group-17-dark: #FFFFFF;
+ --nt-group-17-light: #000000;
+ --nt-group-17-main-bg: #9E9E9E;
+ --nt-group-17-dark-bg: #707070;
+ --nt-group-17-light-bg: #CFCFCF;
+ --nt-group-18-main: #000000;
+ --nt-group-18-dark: #FFFFFF;
+ --nt-group-18-light: #000000;
+ --nt-group-18-main-bg: #607D8B;
+ --nt-group-18-dark-bg: #34515E;
+ --nt-group-18-light-bg: #8EACBB;
+}
+
+.nt-pastello {
+ --nt-group-0-main: #000000;
+ --nt-group-0-dark: #000000;
+ --nt-group-0-light: #000000;
+ --nt-group-0-main-bg: #EF9A9A;
+ --nt-group-0-dark-bg: #BA6B6C;
+ --nt-group-0-light-bg: #FFCCCB;
+ --nt-group-1-main: #000000;
+ --nt-group-1-dark: #000000;
+ --nt-group-1-light: #000000;
+ --nt-group-1-main-bg: #F48FB1;
+ --nt-group-1-dark-bg: #BF5F82;
+ --nt-group-1-light-bg: #FFC1E3;
+ --nt-group-2-main: #000000;
+ --nt-group-2-dark: #000000;
+ --nt-group-2-light: #000000;
+ --nt-group-2-main-bg: #CE93D8;
+ --nt-group-2-dark-bg: #9C64A6;
+ --nt-group-2-light-bg: #FFC4FF;
+ --nt-group-3-main: #000000;
+ --nt-group-3-dark: #000000;
+ --nt-group-3-light: #000000;
+ --nt-group-3-main-bg: #B39DDB;
+ --nt-group-3-dark-bg: #836FA9;
+ --nt-group-3-light-bg: #E6CEFF;
+ --nt-group-4-main: #000000;
+ --nt-group-4-dark: #000000;
+ --nt-group-4-light: #000000;
+ --nt-group-4-main-bg: #9FA8DA;
+ --nt-group-4-dark-bg: #6F79A8;
+ --nt-group-4-light-bg: #D1D9FF;
+ --nt-group-5-main: #000000;
+ --nt-group-5-dark: #000000;
+ --nt-group-5-light: #000000;
+ --nt-group-5-main-bg: #90CAF9;
+ --nt-group-5-dark-bg: #5D99C6;
+ --nt-group-5-light-bg: #C3FDFF;
+ --nt-group-6-main: #000000;
+ --nt-group-6-dark: #000000;
+ --nt-group-6-light: #000000;
+ --nt-group-6-main-bg: #81D4FA;
+ --nt-group-6-dark-bg: #4BA3C7;
+ --nt-group-6-light-bg: #B6FFFF;
+ --nt-group-7-main: #000000;
+ --nt-group-7-dark: #000000;
+ --nt-group-7-light: #000000;
+ --nt-group-7-main-bg: #80DEEA;
+ --nt-group-7-dark-bg: #4BACB8;
+ --nt-group-7-light-bg: #B4FFFF;
+ --nt-group-8-main: #000000;
+ --nt-group-8-dark: #000000;
+ --nt-group-8-light: #000000;
+ --nt-group-8-main-bg: #80CBC4;
+ --nt-group-8-dark-bg: #4F9A94;
+ --nt-group-8-light-bg: #B2FEF7;
+ --nt-group-9-main: #000000;
+ --nt-group-9-dark: #000000;
+ --nt-group-9-light: #000000;
+ --nt-group-9-main-bg: #A5D6A7;
+ --nt-group-9-dark-bg: #75A478;
+ --nt-group-9-light-bg: #D7FFD9;
+ --nt-group-10-main: #000000;
+ --nt-group-10-dark: #000000;
+ --nt-group-10-light: #000000;
+ --nt-group-10-main-bg: #C5E1A5;
+ --nt-group-10-dark-bg: #94AF76;
+ --nt-group-10-light-bg: #F8FFD7;
+ --nt-group-11-main: #000000;
+ --nt-group-11-dark: #000000;
+ --nt-group-11-light: #000000;
+ --nt-group-11-main-bg: #E6EE9C;
+ --nt-group-11-dark-bg: #B3BC6D;
+ --nt-group-11-light-bg: #FFFFCE;
+ --nt-group-12-main: #000000;
+ --nt-group-12-dark: #000000;
+ --nt-group-12-light: #000000;
+ --nt-group-12-main-bg: #FFF59D;
+ --nt-group-12-dark-bg: #CBC26D;
+ --nt-group-12-light-bg: #FFFFCF;
+ --nt-group-13-main: #000000;
+ --nt-group-13-dark: #000000;
+ --nt-group-13-light: #000000;
+ --nt-group-13-main-bg: #FFE082;
+ --nt-group-13-dark-bg: #CAAE53;
+ --nt-group-13-light-bg: #FFFFB3;
+ --nt-group-14-main: #000000;
+ --nt-group-14-dark: #000000;
+ --nt-group-14-light: #000000;
+ --nt-group-14-main-bg: #FFCC80;
+ --nt-group-14-dark-bg: #CA9B52;
+ --nt-group-14-light-bg: #FFFFB0;
+ --nt-group-15-main: #000000;
+ --nt-group-15-dark: #000000;
+ --nt-group-15-light: #000000;
+ --nt-group-15-main-bg: #FFAB91;
+ --nt-group-15-dark-bg: #C97B63;
+ --nt-group-15-light-bg: #FFDDC1;
+ --nt-group-16-main: #000000;
+ --nt-group-16-dark: #000000;
+ --nt-group-16-light: #000000;
+ --nt-group-16-main-bg: #BCAAA4;
+ --nt-group-16-dark-bg: #8C7B75;
+ --nt-group-16-light-bg: #EFDCD5;
+ --nt-group-17-main: #000000;
+ --nt-group-17-dark: #000000;
+ --nt-group-17-light: #000000;
+ --nt-group-17-main-bg: #EEEEEE;
+ --nt-group-17-dark-bg: #BCBCBC;
+ --nt-group-17-light-bg: #FFFFFF;
+ --nt-group-18-main: #000000;
+ --nt-group-18-dark: #000000;
+ --nt-group-18-light: #000000;
+ --nt-group-18-main-bg: #B0BEC5;
+ --nt-group-18-dark-bg: #808E95;
+ --nt-group-18-light-bg: #E2F1F8;
+}
+
+.nt-group-0 .nt-plan-group-summary,
+.nt-group-0 .nt-timeline-dot {
+ color: var(--nt-group-0-dark);
+ background-color: var(--nt-group-0-dark-bg);
+}
+.nt-group-0 .period {
+ color: var(--nt-group-0-main);
+ background-color: var(--nt-group-0-main-bg);
+}
+
+.nt-group-1 .nt-plan-group-summary,
+.nt-group-1 .nt-timeline-dot {
+ color: var(--nt-group-1-dark);
+ background-color: var(--nt-group-1-dark-bg);
+}
+.nt-group-1 .period {
+ color: var(--nt-group-1-main);
+ background-color: var(--nt-group-1-main-bg);
+}
+
+.nt-group-2 .nt-plan-group-summary,
+.nt-group-2 .nt-timeline-dot {
+ color: var(--nt-group-2-dark);
+ background-color: var(--nt-group-2-dark-bg);
+}
+.nt-group-2 .period {
+ color: var(--nt-group-2-main);
+ background-color: var(--nt-group-2-main-bg);
+}
+
+.nt-group-3 .nt-plan-group-summary,
+.nt-group-3 .nt-timeline-dot {
+ color: var(--nt-group-3-dark);
+ background-color: var(--nt-group-3-dark-bg);
+}
+.nt-group-3 .period {
+ color: var(--nt-group-3-main);
+ background-color: var(--nt-group-3-main-bg);
+}
+
+.nt-group-4 .nt-plan-group-summary,
+.nt-group-4 .nt-timeline-dot {
+ color: var(--nt-group-4-dark);
+ background-color: var(--nt-group-4-dark-bg);
+}
+.nt-group-4 .period {
+ color: var(--nt-group-4-main);
+ background-color: var(--nt-group-4-main-bg);
+}
+
+.nt-group-5 .nt-plan-group-summary,
+.nt-group-5 .nt-timeline-dot {
+ color: var(--nt-group-5-dark);
+ background-color: var(--nt-group-5-dark-bg);
+}
+.nt-group-5 .period {
+ color: var(--nt-group-5-main);
+ background-color: var(--nt-group-5-main-bg);
+}
+
+.nt-group-6 .nt-plan-group-summary,
+.nt-group-6 .nt-timeline-dot {
+ color: var(--nt-group-6-dark);
+ background-color: var(--nt-group-6-dark-bg);
+}
+.nt-group-6 .period {
+ color: var(--nt-group-6-main);
+ background-color: var(--nt-group-6-main-bg);
+}
+
+.nt-group-7 .nt-plan-group-summary,
+.nt-group-7 .nt-timeline-dot {
+ color: var(--nt-group-7-dark);
+ background-color: var(--nt-group-7-dark-bg);
+}
+.nt-group-7 .period {
+ color: var(--nt-group-7-main);
+ background-color: var(--nt-group-7-main-bg);
+}
+
+.nt-group-8 .nt-plan-group-summary,
+.nt-group-8 .nt-timeline-dot {
+ color: var(--nt-group-8-dark);
+ background-color: var(--nt-group-8-dark-bg);
+}
+.nt-group-8 .period {
+ color: var(--nt-group-8-main);
+ background-color: var(--nt-group-8-main-bg);
+}
+
+.nt-group-9 .nt-plan-group-summary,
+.nt-group-9 .nt-timeline-dot {
+ color: var(--nt-group-9-dark);
+ background-color: var(--nt-group-9-dark-bg);
+}
+.nt-group-9 .period {
+ color: var(--nt-group-9-main);
+ background-color: var(--nt-group-9-main-bg);
+}
+
+.nt-group-10 .nt-plan-group-summary,
+.nt-group-10 .nt-timeline-dot {
+ color: var(--nt-group-10-dark);
+ background-color: var(--nt-group-10-dark-bg);
+}
+.nt-group-10 .period {
+ color: var(--nt-group-10-main);
+ background-color: var(--nt-group-10-main-bg);
+}
+
+.nt-group-11 .nt-plan-group-summary,
+.nt-group-11 .nt-timeline-dot {
+ color: var(--nt-group-11-dark);
+ background-color: var(--nt-group-11-dark-bg);
+}
+.nt-group-11 .period {
+ color: var(--nt-group-11-main);
+ background-color: var(--nt-group-11-main-bg);
+}
+
+.nt-group-12 .nt-plan-group-summary,
+.nt-group-12 .nt-timeline-dot {
+ color: var(--nt-group-12-dark);
+ background-color: var(--nt-group-12-dark-bg);
+}
+.nt-group-12 .period {
+ color: var(--nt-group-12-main);
+ background-color: var(--nt-group-12-main-bg);
+}
+
+.nt-group-13 .nt-plan-group-summary,
+.nt-group-13 .nt-timeline-dot {
+ color: var(--nt-group-13-dark);
+ background-color: var(--nt-group-13-dark-bg);
+}
+.nt-group-13 .period {
+ color: var(--nt-group-13-main);
+ background-color: var(--nt-group-13-main-bg);
+}
+
+.nt-group-14 .nt-plan-group-summary,
+.nt-group-14 .nt-timeline-dot {
+ color: var(--nt-group-14-dark);
+ background-color: var(--nt-group-14-dark-bg);
+}
+.nt-group-14 .period {
+ color: var(--nt-group-14-main);
+ background-color: var(--nt-group-14-main-bg);
+}
+
+.nt-group-15 .nt-plan-group-summary,
+.nt-group-15 .nt-timeline-dot {
+ color: var(--nt-group-15-dark);
+ background-color: var(--nt-group-15-dark-bg);
+}
+.nt-group-15 .period {
+ color: var(--nt-group-15-main);
+ background-color: var(--nt-group-15-main-bg);
+}
+
+.nt-group-16 .nt-plan-group-summary,
+.nt-group-16 .nt-timeline-dot {
+ color: var(--nt-group-16-dark);
+ background-color: var(--nt-group-16-dark-bg);
+}
+.nt-group-16 .period {
+ color: var(--nt-group-16-main);
+ background-color: var(--nt-group-16-main-bg);
+}
+
+.nt-group-17 .nt-plan-group-summary,
+.nt-group-17 .nt-timeline-dot {
+ color: var(--nt-group-17-dark);
+ background-color: var(--nt-group-17-dark-bg);
+}
+.nt-group-17 .period {
+ color: var(--nt-group-17-main);
+ background-color: var(--nt-group-17-main-bg);
+}
+
+.nt-group-18 .nt-plan-group-summary,
+.nt-group-18 .nt-timeline-dot {
+ color: var(--nt-group-18-dark);
+ background-color: var(--nt-group-18-dark-bg);
+}
+.nt-group-18 .period {
+ color: var(--nt-group-18-main);
+ background-color: var(--nt-group-18-main-bg);
+}
+
+/**
+ * Extra CSS file for MkDocs and the neoteroi.timeline extension.
+ *
+ * https://github.com/Neoteroi/mkdocs-plugins
+**/
+.nt-error {
+ border: 2px dashed darkred;
+ padding: 0 1rem;
+ background: #faf9ba;
+ color: darkred;
+}
+
+.nt-timeline {
+ margin-top: 30px;
+}
+.nt-timeline .nt-timeline-title {
+ font-size: 1.1rem;
+ margin-top: 0;
+}
+.nt-timeline .nt-timeline-sub-title {
+ margin-top: 0;
+}
+.nt-timeline .nt-timeline-content {
+ font-size: 0.8rem;
+ border-bottom: 2px dashed #ccc;
+ padding-bottom: 1.2rem;
+}
+.nt-timeline.horizontal .nt-timeline-items {
+ flex-direction: row;
+ overflow-x: scroll;
+}
+.nt-timeline.horizontal .nt-timeline-items > div {
+ min-width: 400px;
+ margin-right: 50px;
+}
+.nt-timeline.horizontal.reverse .nt-timeline-items {
+ flex-direction: row-reverse;
+}
+.nt-timeline.horizontal.center .nt-timeline-before {
+ background-image: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%);
+ background-repeat: no-repeat;
+ background-size: 100% 2px;
+ background-position: 0 center;
+}
+.nt-timeline.horizontal.center .nt-timeline-after {
+ background-image: linear-gradient(180deg, rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%);
+ background-repeat: no-repeat;
+ background-size: 100% 2px;
+ background-position: 0 center;
+}
+.nt-timeline.horizontal.center .nt-timeline-items {
+ background-image: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%);
+ background-repeat: no-repeat;
+ background-size: 100% 2px;
+ background-position: 0 center;
+}
+.nt-timeline.horizontal .nt-timeline-dot {
+ left: 50%;
+}
+.nt-timeline.horizontal .nt-timeline-dot:not(.bigger) {
+ top: calc(50% - 4px);
+}
+.nt-timeline.horizontal .nt-timeline-dot.bigger {
+ top: calc(50% - 15px);
+}
+.nt-timeline.vertical .nt-timeline-items {
+ flex-direction: column;
+}
+.nt-timeline.vertical.reverse .nt-timeline-items {
+ flex-direction: column-reverse;
+}
+.nt-timeline.vertical.center .nt-timeline-before {
+ background: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat center/2px 100%;
+}
+.nt-timeline.vertical.center .nt-timeline-after {
+ background: linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat center/2px 100%;
+}
+.nt-timeline.vertical.center .nt-timeline-items {
+ background: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat center/2px 100%;
+}
+.nt-timeline.vertical.center .nt-timeline-dot {
+ left: calc(50% - 10px);
+}
+.nt-timeline.vertical.center .nt-timeline-dot:not(.bigger) {
+ top: 10px;
+}
+.nt-timeline.vertical.center .nt-timeline-dot.bigger {
+ left: calc(50% - 20px);
+}
+.nt-timeline.vertical.left {
+ padding-left: 100px;
+}
+.nt-timeline.vertical.left .nt-timeline-item {
+ padding-left: 70px;
+}
+.nt-timeline.vertical.left .nt-timeline-sub-title {
+ left: -100px;
+ width: 100px;
+}
+.nt-timeline.vertical.left .nt-timeline-before {
+ background: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat 30px/2px 100%;
+}
+.nt-timeline.vertical.left .nt-timeline-after {
+ background: linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat 30px/2px 100%;
+}
+.nt-timeline.vertical.left .nt-timeline-items {
+ background: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat 30px/2px 100%;
+}
+.nt-timeline.vertical.left .nt-timeline-dot {
+ left: 21px;
+ top: 8px;
+}
+.nt-timeline.vertical.left .nt-timeline-dot.bigger {
+ top: 0px;
+ left: 10px;
+}
+.nt-timeline.vertical.right {
+ padding-right: 100px;
+}
+.nt-timeline.vertical.right .nt-timeline-sub-title {
+ right: -100px;
+ text-align: left;
+ width: 100px;
+}
+.nt-timeline.vertical.right .nt-timeline-item {
+ padding-right: 70px;
+}
+.nt-timeline.vertical.right .nt-timeline-before {
+ background: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat calc(100% - 30px)/2px 100%;
+}
+.nt-timeline.vertical.right .nt-timeline-after {
+ background: linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat calc(100% - 30px)/2px 100%;
+}
+.nt-timeline.vertical.right .nt-timeline-items {
+ background: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat calc(100% - 30px)/2px 100%;
+}
+.nt-timeline.vertical.right .nt-timeline-dot {
+ right: 21px;
+ top: 8px;
+}
+.nt-timeline.vertical.right .nt-timeline-dot.bigger {
+ top: 10px;
+ right: 10px;
+}
+
+.nt-timeline-items {
+ display: flex;
+ position: relative;
+}
+.nt-timeline-items > div {
+ min-height: 100px;
+ padding-top: 2px;
+ padding-bottom: 20px;
+}
+
+.nt-timeline-before {
+ content: "";
+ height: 15px;
+}
+
+.nt-timeline-after {
+ content: "";
+ height: 60px;
+ margin-bottom: 20px;
+}
+
+.nt-timeline-sub-title {
+ position: absolute;
+ width: 50%;
+ top: 4px;
+ font-size: 18px;
+ color: var(--nt-color-50);
+}
+
+[data-md-color-scheme=slate] .nt-timeline-sub-title {
+ color: var(--nt-color-51);
+}
+
+.nt-timeline-item {
+ position: relative;
+}
+
+.nt-timeline.vertical.center:not(.alternate) .nt-timeline-item {
+ padding-left: calc(50% + 40px);
+}
+.nt-timeline.vertical.center:not(.alternate) .nt-timeline-item .nt-timeline-sub-title {
+ left: 0;
+ padding-right: 40px;
+ text-align: right;
+}
+.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd) {
+ padding-left: calc(50% + 40px);
+}
+.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd) .nt-timeline-sub-title {
+ left: 0;
+ padding-right: 40px;
+ text-align: right;
+}
+.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even) {
+ text-align: right;
+ padding-right: calc(50% + 40px);
+}
+.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even) .nt-timeline-sub-title {
+ right: 0;
+ padding-left: 40px;
+ text-align: left;
+}
+
+.nt-timeline-dot {
+ position: relative;
+ width: 20px;
+ height: 20px;
+ border-radius: 100%;
+ background-color: #fc5b5b;
+ position: absolute;
+ top: 0px;
+ z-index: 2;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+ border: 3px solid white;
+}
+.nt-timeline-dot:not(.bigger) .icon {
+ font-size: 10px;
+}
+.nt-timeline-dot.bigger {
+ width: 40px;
+ height: 40px;
+ padding: 3px;
+}
+.nt-timeline-dot .icon {
+ color: white;
+ position: relative;
+ top: 1px;
+}
+
+/* Fix for webkit (Chrome, Safari) */
+@supports not (-moz-appearance: none) {
+ /*
+ This fix is necessary, for some reason, to render the timeline properly
+ inside `details` elements used by pymdownx. Firefox doesn't need this fix,
+ it renders elements properly.
+ */
+ details .nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd) .nt-timeline-sub-title,
+details .nt-timeline.vertical.center:not(.alternate) .nt-timeline-item .nt-timeline-sub-title {
+ left: -40px;
+ }
+ details .nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even) .nt-timeline-sub-title {
+ right: -40px;
+ }
+ details .nt-timeline.vertical.center .nt-timeline-dot {
+ left: calc(50% - 12px);
+ }
+ details .nt-timeline-dot.bigger {
+ font-size: 1rem !important;
+ }
+}
+/* default colors */
+.nt-timeline-item:nth-child(0) .nt-timeline-dot {
+ background-color: var(--nt-color-0);
+}
+
+.nt-timeline-item:nth-child(1) .nt-timeline-dot {
+ background-color: var(--nt-color-1);
+}
+
+.nt-timeline-item:nth-child(2) .nt-timeline-dot {
+ background-color: var(--nt-color-2);
+}
+
+.nt-timeline-item:nth-child(3) .nt-timeline-dot {
+ background-color: var(--nt-color-3);
+}
+
+.nt-timeline-item:nth-child(4) .nt-timeline-dot {
+ background-color: var(--nt-color-4);
+}
+
+.nt-timeline-item:nth-child(5) .nt-timeline-dot {
+ background-color: var(--nt-color-5);
+}
+
+.nt-timeline-item:nth-child(6) .nt-timeline-dot {
+ background-color: var(--nt-color-6);
+}
+
+.nt-timeline-item:nth-child(7) .nt-timeline-dot {
+ background-color: var(--nt-color-7);
+}
+
+.nt-timeline-item:nth-child(8) .nt-timeline-dot {
+ background-color: var(--nt-color-8);
+}
+
+.nt-timeline-item:nth-child(9) .nt-timeline-dot {
+ background-color: var(--nt-color-9);
+}
+
+.nt-timeline-item:nth-child(10) .nt-timeline-dot {
+ background-color: var(--nt-color-10);
+}
+
+.nt-timeline-item:nth-child(11) .nt-timeline-dot {
+ background-color: var(--nt-color-11);
+}
+
+.nt-timeline-item:nth-child(12) .nt-timeline-dot {
+ background-color: var(--nt-color-12);
+}
+
+.nt-timeline-item:nth-child(13) .nt-timeline-dot {
+ background-color: var(--nt-color-13);
+}
+
+.nt-timeline-item:nth-child(14) .nt-timeline-dot {
+ background-color: var(--nt-color-14);
+}
+
+.nt-timeline-item:nth-child(15) .nt-timeline-dot {
+ background-color: var(--nt-color-15);
+}
+
+.nt-timeline-item:nth-child(16) .nt-timeline-dot {
+ background-color: var(--nt-color-16);
+}
+
+.nt-timeline-item:nth-child(17) .nt-timeline-dot {
+ background-color: var(--nt-color-17);
+}
+
+.nt-timeline-item:nth-child(18) .nt-timeline-dot {
+ background-color: var(--nt-color-18);
+}
+
+.nt-timeline-item:nth-child(19) .nt-timeline-dot {
+ background-color: var(--nt-color-19);
+}
+
+.nt-timeline-item:nth-child(20) .nt-timeline-dot {
+ background-color: var(--nt-color-20);
+}
+
+/**
+ * Extra CSS for the neoteroi.projects.gantt extension.
+ *
+ * https://github.com/Neoteroi/mkdocs-plugins
+**/
+:root {
+ --nt-scrollbar-color: #2751b0;
+ --nt-plan-actions-height: 24px;
+ --nt-units-background: #ff9800;
+ --nt-months-background: #2751b0;
+ --nt-plan-vertical-line-color: #a3a3a3ad;
+}
+
+.nt-pastello {
+ --nt-scrollbar-color: #9fb8f4;
+ --nt-units-background: #f5dc82;
+ --nt-months-background: #5b7fd1;
+}
+
+[data-md-color-scheme=slate] {
+ --nt-units-background: #003773;
+}
+[data-md-color-scheme=slate] .nt-pastello {
+ --nt-units-background: #3f4997;
+}
+
+.nt-plan-root {
+ min-height: 200px;
+ scrollbar-width: 20px;
+ scrollbar-color: var(--nt-scrollbar-color);
+ display: flex;
+}
+.nt-plan-root ::-webkit-scrollbar {
+ width: 20px;
+}
+.nt-plan-root ::-webkit-scrollbar-track {
+ box-shadow: inset 0 0 5px grey;
+ border-radius: 10px;
+}
+.nt-plan-root ::-webkit-scrollbar-thumb {
+ background: var(--nt-scrollbar-color);
+ border-radius: 10px;
+}
+.nt-plan-root .nt-plan {
+ flex: 80%;
+}
+.nt-plan-root.no-groups .nt-plan-periods {
+ padding-left: 0;
+}
+.nt-plan-root.no-groups .nt-plan-group-summary {
+ display: none;
+}
+.nt-plan-root .nt-timeline-dot.bigger {
+ top: -10px;
+}
+.nt-plan-root .nt-timeline-dot.bigger[title] {
+ cursor: help;
+}
+
+.nt-plan {
+ white-space: nowrap;
+ overflow-x: auto;
+ display: flex;
+}
+.nt-plan .ug-timeline-dot {
+ left: 368px;
+ top: -8px;
+ cursor: help;
+}
+
+.months {
+ display: flex;
+}
+
+.month {
+ flex: auto;
+ display: inline-block;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 3px 1px -2px, rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px inset;
+ background-color: var(--nt-months-background);
+ color: white;
+ text-transform: uppercase;
+ font-family: Roboto, Helvetica, Arial, sans-serif;
+ padding: 2px 5px;
+ font-size: 12px;
+ border: 1px solid #000;
+ width: 150px;
+ border-radius: 8px;
+}
+
+.nt-plan-group-activities {
+ flex: auto;
+ position: relative;
+}
+
+.nt-vline {
+ border-left: 1px dashed var(--nt-plan-vertical-line-color);
+ height: 100%;
+ left: 0;
+ position: absolute;
+ margin-left: -0.5px;
+ top: 0;
+ -webkit-transition: all 0.5s linear !important;
+ -moz-transition: all 0.5s linear !important;
+ -ms-transition: all 0.5s linear !important;
+ -o-transition: all 0.5s linear !important;
+ transition: all 0.5s linear !important;
+ z-index: -2;
+}
+
+.nt-plan-activity {
+ display: flex;
+ margin: 2px 0;
+ background-color: rgba(187, 187, 187, 0.2509803922);
+}
+
+.actions {
+ height: var(--nt-plan-actions-height);
+}
+
+.actions {
+ position: relative;
+}
+
+.period {
+ display: inline-block;
+ height: var(--nt-plan-actions-height);
+ width: 120px;
+ position: absolute;
+ left: 0px;
+ background: #1da1f2;
+ border-radius: 5px;
+ transition: all 0.5s;
+ cursor: help;
+ -webkit-transition: width 1s ease-in-out;
+ -moz-transition: width 1s ease-in-out;
+ -o-transition: width 1s ease-in-out;
+ transition: width 1s ease-in-out;
+}
+.period .nt-tooltip {
+ display: none;
+ top: 30px;
+ position: relative;
+ padding: 1rem;
+ text-align: center;
+ font-size: 12px;
+}
+.period:hover .nt-tooltip {
+ display: inline-block;
+}
+
+.period-0 {
+ left: 340px;
+ visibility: visible;
+ background-color: rgb(69, 97, 101);
+}
+
+.period-1 {
+ left: 40px;
+ visibility: visible;
+ background-color: green;
+}
+
+.period-2 {
+ left: 120px;
+ visibility: visible;
+ background-color: pink;
+ width: 80px;
+}
+
+.period-3 {
+ left: 190px;
+ visibility: visible;
+ background-color: darkred;
+ width: 150px;
+}
+
+.weeks > span,
+.days > span {
+ height: 25px;
+}
+
+.weeks > span {
+ display: inline-block;
+ margin: 0;
+ padding: 0;
+ font-weight: bold;
+}
+.weeks > span .week-text {
+ font-size: 10px;
+ position: absolute;
+ display: inline-block;
+ padding: 3px 4px;
+}
+
+.days {
+ z-index: -2;
+ position: relative;
+}
+
+.day-text {
+ font-size: 10px;
+ position: absolute;
+ display: inline-block;
+ padding: 3px 4px;
+}
+
+.period span {
+ font-size: 12px;
+ vertical-align: top;
+ margin-left: 4px;
+ color: black;
+ background: rgba(255, 255, 255, 0.6588235294);
+ border-radius: 6px;
+ padding: 0 4px;
+}
+
+.weeks,
+.days {
+ height: 20px;
+ display: flex;
+ box-sizing: content-box;
+}
+
+.months {
+ display: flex;
+}
+
+.week,
+.day {
+ height: 20px;
+ position: relative;
+ border: 1;
+ flex: auto;
+ border: 2px solid white;
+ border-radius: 4px;
+ background-color: var(--nt-units-background);
+ cursor: help;
+}
+
+.years {
+ display: flex;
+}
+
+.year {
+ text-align: center;
+ border-right: 1px solid var(--nt-plan-vertical-line-color);
+ font-weight: bold;
+}
+.year:first-child {
+ border-left: 1px solid var(--nt-plan-vertical-line-color);
+}
+.year:first-child:last-child {
+ width: 100%;
+}
+
+.quarters {
+ display: flex;
+}
+
+.quarter {
+ width: 12.5%;
+ text-align: center;
+ border-right: 1px solid var(--nt-plan-vertical-line-color);
+ font-weight: bold;
+}
+.quarter:first-child {
+ border-left: 1px solid var(--nt-plan-vertical-line-color);
+}
+
+.nt-plan-group {
+ margin: 20px 0;
+ position: relative;
+}
+
+.nt-plan-group {
+ display: flex;
+}
+
+.nt-plan-group-summary {
+ background: #2751b0;
+ width: 150px;
+ white-space: normal;
+ padding: 0.1rem 0.5rem;
+ border-radius: 5px;
+ color: #fff;
+ z-index: 3;
+}
+.nt-plan-group-summary p {
+ margin: 0;
+ padding: 0;
+ font-size: 0.6rem;
+ color: #fff;
+}
+
+.nt-plan-group-summary,
+.month,
+.period,
+.week,
+.day,
+.nt-tooltip {
+ border: 3px solid white;
+ box-shadow: 0 2px 3px -1px rgba(0, 0, 0, 0.2), 0 3px 3px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
+}
+
+.nt-plan-periods {
+ padding-left: 150px;
+}
+
+.months {
+ z-index: 2;
+ position: relative;
+}
+
+.weeks {
+ position: relative;
+ top: -2px;
+ z-index: 0;
+}
+
+.month,
+.quarter,
+.year,
+.week,
+.day,
+.nt-tooltip {
+ font-family: Roboto, Helvetica, Arial, sans-serif;
+ box-sizing: border-box;
+}
+
+.nt-cards.nt-grid {
+ display: grid;
+ grid-auto-columns: 1fr;
+ gap: 0.5rem;
+ max-width: 100vw;
+ overflow-x: auto;
+ padding: 1px;
+}
+.nt-cards.nt-grid.cols-1 {
+ grid-template-columns: repeat(1, 1fr);
+}
+.nt-cards.nt-grid.cols-2 {
+ grid-template-columns: repeat(2, 1fr);
+}
+.nt-cards.nt-grid.cols-3 {
+ grid-template-columns: repeat(3, 1fr);
+}
+.nt-cards.nt-grid.cols-4 {
+ grid-template-columns: repeat(4, 1fr);
+}
+.nt-cards.nt-grid.cols-5 {
+ grid-template-columns: repeat(5, 1fr);
+}
+.nt-cards.nt-grid.cols-6 {
+ grid-template-columns: repeat(6, 1fr);
+}
+
+@media only screen and (max-width: 400px) {
+ .nt-cards.nt-grid {
+ grid-template-columns: repeat(1, 1fr) !important;
+ }
+}
+.nt-card {
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
+}
+.nt-card:hover {
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.24), 0 3px 1px -2px rgba(0, 0, 0, 0.3), 0 1px 5px 0 rgba(0, 0, 0, 0.22);
+}
+
+[data-md-color-scheme=slate] .nt-card {
+ box-shadow: 0 2px 2px 0 rgba(4, 40, 33, 0.14), 0 3px 1px -2px rgba(40, 86, 94, 0.47), 0 1px 5px 0 rgba(139, 252, 255, 0.64);
+}
+[data-md-color-scheme=slate] .nt-card:hover {
+ box-shadow: 0 2px 2px 0 rgba(0, 255, 206, 0.14), 0 3px 1px -2px rgba(33, 156, 177, 0.47), 0 1px 5px 0 rgba(96, 251, 255, 0.64);
+}
+
+.nt-card > a {
+ color: var(--md-default-fg-color);
+}
+
+.nt-card > a > div {
+ cursor: pointer;
+}
+
+.nt-card {
+ padding: 5px;
+ margin-bottom: 0.5rem;
+}
+
+.nt-card-title {
+ font-size: 1rem;
+ font-weight: bold;
+ margin: 4px 0 8px 0;
+ line-height: 22px;
+}
+
+.nt-card-content {
+ padding: 0.4rem 0.8rem 0.8rem 0.8rem;
+}
+
+.nt-card-text {
+ font-size: 14px;
+ padding: 0;
+ margin: 0;
+}
+
+.nt-card .nt-card-image {
+ text-align: center;
+ border-radius: 2px;
+ background-position: center center;
+ background-size: cover;
+ background-repeat: no-repeat;
+ min-height: 120px;
+}
+
+.nt-card .nt-card-image.tags img {
+ margin-top: 12px;
+}
+
+.nt-card .nt-card-image img {
+ height: 105px;
+ margin-top: 5px;
+}
+
+.nt-card .nt-card-icon {
+ text-align: center;
+ padding-top: 12px;
+ min-height: 120px;
+}
+
+.nt-card .nt-card-icon .icon {
+ font-size: 95px;
+ line-height: 1;
+}
+
+.nt-card a:hover,
+.nt-card a:focus {
+ color: var(--md-accent-fg-color);
+}
+
+.nt-card h2 {
+ margin: 0;
+}
+
+/**
+ * Extra CSS file recommended for MkDocs and neoteroi.spantable extension.
+ *
+ * https://github.com/Neoteroi/mkdocs-plugins
+**/
+.span-table-wrapper table {
+ border-collapse: collapse;
+ margin-bottom: 2rem;
+ border-radius: 0.1rem;
+}
+
+.span-table td,
+.span-table th {
+ padding: 0.2rem;
+ background-color: var(--md-default-bg-color);
+ font-size: 0.64rem;
+ max-width: 100%;
+ overflow: auto;
+ touch-action: auto;
+ border-top: 0.05rem solid var(--md-typeset-table-color);
+ padding: 0.9375em 1.25em;
+ vertical-align: top;
+}
+
+.span-table tr:first-child td {
+ font-weight: 700;
+ min-width: 5rem;
+ padding: 0.9375em 1.25em;
+ vertical-align: top;
+}
+
+.span-table td:first-child {
+ border-left: 0.05rem solid var(--md-typeset-table-color);
+}
+
+.span-table td:last-child {
+ border-right: 0.05rem solid var(--md-typeset-table-color);
+}
+
+.span-table tr:last-child {
+ border-bottom: 0.05rem solid var(--md-typeset-table-color);
+}
+
+.span-table [colspan],
+.span-table [rowspan] {
+ font-weight: bold;
+ border: 0.05rem solid var(--md-typeset-table-color);
+}
+
+.span-table tr:not(:first-child):hover td:not([colspan]):not([rowspan]),
+.span-table td[colspan]:hover,
+.span-table td[rowspan]:hover {
+ background-color: rgba(0, 0, 0, 0.035);
+ box-shadow: 0 0.05rem 0 var(--md-default-bg-color) inset;
+ transition: background-color 125ms;
+}
+
+.nt-contribs {
+ margin-top: 2rem;
+ font-size: small;
+ border-top: 1px dotted lightgray;
+ padding-top: 0.5rem;
+}
+.nt-contribs .nt-contributors {
+ padding-top: 0.5rem;
+ display: flex;
+ flex-wrap: wrap;
+}
+.nt-contribs .nt-contributor {
+ background: lightgrey;
+ background-size: cover;
+ width: 40px;
+ height: 40px;
+ border-radius: 100%;
+ margin: 0 6px 6px 0;
+ cursor: help;
+ opacity: 0.7;
+}
+.nt-contribs .nt-contributor:hover {
+ opacity: 1;
+}
+.nt-contribs .nt-contributors-title {
+ font-style: italic;
+ margin-bottom: 0;
+}
+.nt-contribs .nt-initials {
+ text-transform: uppercase;
+ font-size: 20px;
+ text-align: center;
+ width: 40px;
+ height: 40px;
+ display: inline-block;
+ vertical-align: middle;
+ position: relative;
+ top: 4px;
+ color: inherit;
+ font-weight: bold;
+}
+.nt-contribs .nt-group-0 {
+ background-color: var(--nt-color-0);
+}
+.nt-contribs .nt-group-1 {
+ background-color: var(--nt-color-1);
+}
+.nt-contribs .nt-group-2 {
+ background-color: var(--nt-color-2);
+}
+.nt-contribs .nt-group-3 {
+ background-color: var(--nt-color-3);
+}
+.nt-contribs .nt-group-4 {
+ background-color: var(--nt-color-4);
+}
+.nt-contribs .nt-group-5 {
+ background-color: var(--nt-color-5);
+}
+.nt-contribs .nt-group-6 {
+ background-color: var(--nt-color-6);
+}
+.nt-contribs .nt-group-7 {
+ color: #000;
+ background-color: var(--nt-color-7);
+}
+.nt-contribs .nt-group-8 {
+ color: #000;
+ background-color: var(--nt-color-8);
+}
+.nt-contribs .nt-group-9 {
+ background-color: var(--nt-color-9);
+}
+.nt-contribs .nt-group-10 {
+ background-color: var(--nt-color-10);
+}
+.nt-contribs .nt-group-11 {
+ background-color: var(--nt-color-11);
+}
+.nt-contribs .nt-group-12 {
+ background-color: var(--nt-color-12);
+}
+.nt-contribs .nt-group-13 {
+ background-color: var(--nt-color-13);
+}
+.nt-contribs .nt-group-14 {
+ background-color: var(--nt-color-14);
+}
+.nt-contribs .nt-group-15 {
+ color: #000;
+ background-color: var(--nt-color-15);
+}
+.nt-contribs .nt-group-16 {
+ background-color: var(--nt-color-16);
+}
+.nt-contribs .nt-group-17 {
+ color: #000;
+ background-color: var(--nt-color-17);
+}
+.nt-contribs .nt-group-18 {
+ background-color: var(--nt-color-18);
+}
+.nt-contribs .nt-group-19 {
+ background-color: var(--nt-color-19);
+}
+.nt-contribs .nt-group-20 {
+ color: #000;
+ background-color: var(--nt-color-20);
+}
+.nt-contribs .nt-group-21 {
+ color: #000;
+ background-color: var(--nt-color-21);
+}
+.nt-contribs .nt-group-22 {
+ color: #000;
+ background-color: var(--nt-color-22);
+}
+.nt-contribs .nt-group-23 {
+ color: #000;
+ background-color: var(--nt-color-23);
+}
+.nt-contribs .nt-group-24 {
+ color: #000;
+ background-color: var(--nt-color-24);
+}
+.nt-contribs .nt-group-25 {
+ color: #000;
+ background-color: var(--nt-color-25);
+}
+.nt-contribs .nt-group-26 {
+ color: #000;
+ background-color: var(--nt-color-26);
+}
+.nt-contribs .nt-group-27 {
+ background-color: var(--nt-color-27);
+}
+.nt-contribs .nt-group-28 {
+ color: #000;
+ background-color: var(--nt-color-28);
+}
+.nt-contribs .nt-group-29 {
+ color: #000;
+ background-color: var(--nt-color-29);
+}
+.nt-contribs .nt-group-30 {
+ background-color: var(--nt-color-30);
+}
+.nt-contribs .nt-group-31 {
+ background-color: var(--nt-color-31);
+}
+.nt-contribs .nt-group-32 {
+ color: #000;
+ background-color: var(--nt-color-32);
+}
+.nt-contribs .nt-group-33 {
+ background-color: var(--nt-color-33);
+}
+.nt-contribs .nt-group-34 {
+ background-color: var(--nt-color-34);
+}
+.nt-contribs .nt-group-35 {
+ background-color: var(--nt-color-35);
+}
+.nt-contribs .nt-group-36 {
+ background-color: var(--nt-color-36);
+}
+.nt-contribs .nt-group-37 {
+ background-color: var(--nt-color-37);
+}
+.nt-contribs .nt-group-38 {
+ background-color: var(--nt-color-38);
+}
+.nt-contribs .nt-group-39 {
+ color: #000;
+ background-color: var(--nt-color-39);
+}
+.nt-contribs .nt-group-40 {
+ color: #000;
+ background-color: var(--nt-color-40);
+}
+.nt-contribs .nt-group-41 {
+ color: #000;
+ background-color: var(--nt-color-41);
+}
+.nt-contribs .nt-group-42 {
+ color: #000;
+ background-color: var(--nt-color-42);
+}
+.nt-contribs .nt-group-43 {
+ color: #000;
+ background-color: var(--nt-color-43);
+}
+.nt-contribs .nt-group-44 {
+ color: #000;
+ background-color: var(--nt-color-44);
+}
+.nt-contribs .nt-group-45 {
+ background-color: var(--nt-color-45);
+}
+.nt-contribs .nt-group-46 {
+ color: #000;
+ background-color: var(--nt-color-46);
+}
+.nt-contribs .nt-group-47 {
+ background-color: var(--nt-color-47);
+}
+.nt-contribs .nt-group-48 {
+ background-color: var(--nt-color-48);
+}
+.nt-contribs .nt-group-49 {
+ background-color: var(--nt-color-49);
+}
+
+/**
+ * CSS for OpenAPI HTML generated with PyMdown Extensions option.
+ *
+ * This CSS file works when using the OAD plugin with pymdownx.
+ * See here how to use it:
+ * https://www.neoteroi.dev/mkdocs-plugins/web/oad/
+ *
+ * https://github.com/Neoteroi/mkdocs-plugins
+**/
+:root {
+ --http-get-color: green;
+ --http-delete-color: #dc0101;
+ --http-head-color: slateblue;
+ --http-options-color: steelblue;
+ --http-patch-color: darkorange;
+ --http-post-color: darkblue;
+ --http-put-color: darkmagenta;
+ --http-trace-color: darkcyan;
+ --http-route-param-color: rgb(51, 128, 210);
+ --oad-operation-separator-border-color: gray;
+ --oad-block-border-color: #00bfa5;
+ --oad-small-note-color: #666;
+ --oad-indent-border-color: #c5c5c5;
+}
+
+@media screen {
+ /* Slate theme, i.e. dark mode */
+ [data-md-color-scheme=slate] {
+ --http-get-color: #2ea82e;
+ --http-post-color: #0093c0;
+ --http-put-color: #c333c3;
+ --oad-small-note-color: #afafaf;
+ }
+}
+.api-tag {
+ font-weight: bold;
+}
+
+span[class^=http-] {
+ font-weight: bold;
+ color: #fff;
+ padding: 4px 1rem;
+ border-radius: 2px;
+ margin-right: 0.5rem;
+}
+
+.http-get {
+ background-color: var(--http-get-color);
+}
+
+.http-delete {
+ background-color: var(--http-delete-color);
+}
+
+.http-post {
+ background-color: var(--http-post-color);
+}
+
+.http-patch {
+ background-color: var(--http-patch-color);
+}
+
+.http-trace {
+ background-color: var(--http-trace-color);
+}
+
+.http-put {
+ background-color: var(--http-put-color);
+}
+
+.http-head {
+ background-color: var(--http-head-color);
+}
+
+.http-options {
+ background-color: var(--http-options-color);
+}
+
+.route-param {
+ color: var(--http-route-param-color);
+}
+
+.operation-separator + h3[id^=get] .route-param {
+ color: var(--http-get-color);
+}
+
+.operation-separator + h3[id^=delete] .route-param {
+ color: var(--http-delete-color);
+}
+
+.operation-separator + h3[id^=post] .route-param {
+ color: var(--http-post-color);
+}
+
+.operation-separator + h3[id^=patch] .route-param {
+ color: var(--http-patch-color);
+}
+
+.operation-separator + h3[id^=trace] .route-param {
+ color: var(--http-trace-color);
+}
+
+.operation-separator + h3[id^=put] .route-param {
+ color: var(--http-put-color);
+}
+
+.operation-separator + h3[id^=head] .route-param {
+ color: var(--http-head-color);
+}
+
+.operation-separator + h3[id^=options] .route-param {
+ color: var(--http-options-color);
+}
+
+.api-version {
+ font-size: 1.2rem;
+}
+
+.operation-separator {
+ margin: 0 !important;
+ border-bottom: 2px dotted var(--oad-operation-separator-border-color) !important;
+ padding-top: 0.5rem;
+}
+
+.operation-separator + h3 {
+ margin-top: 1rem;
+}
+
+.string-type {
+ color: var(--md-code-hl-string-color);
+}
+
+.integer-type, .number-type {
+ color: var(--md-code-hl-number-color);
+}
+
+.boolean-type {
+ color: var(--md-code-hl-keyword-color);
+}
+
+.format {
+ color: var(--md-code-hl-name-color);
+}
+
+.null-type {
+ color: var(--md-code-hl-keyword-color);
+}
+
+a.ref-link {
+ color: var(--md-code-hl-special-color);
+}
+
+.request-block + div {
+ padding-left: 1rem;
+ border-left: 2px dashed var(--oad-block-border-color);
+}
+
+.small-note {
+ font-size: 14px;
+ color: var(--oad-small-note-color);
+}
+
+.request-body-title {
+ margin-bottom: 4px;
+}
+
+.request-body-title + .tabbed-set,
+.response-title + .tabbed-set,
+.message-separator + .tabbed-set,
+.common-response,
+.response-section {
+ margin-top: 2px;
+ padding-left: 1rem;
+ border-left: 2px dotted var(--oad-indent-border-color);
+}
+
+.info-data {
+ font-size: 0.6rem;
+}
+
+.message-separator {
+ visibility: hidden;
+}
+
+.sub-section-title {
+ font-style: italic;
+ font-size: 14px;
+}
diff --git a/docs/development_notes/rest_api_reference.md b/docs/development_notes/rest_api_reference.md
index 2d007cd..24ce896 100644
--- a/docs/development_notes/rest_api_reference.md
+++ b/docs/development_notes/rest_api_reference.md
@@ -1,6 +1,7 @@
# Captured Prusa Connect API Interactions
-NOTE: This is mostly notes that I made as I went through API calls
+NOTE: This is mostly notes that I made as I went through API calls. See
+`/docs/api/openapi.json` for better documentation.
## Overview
diff --git a/mkdocs.yml b/mkdocs.yml
index 1dbc9bd..07dceb6 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -19,6 +19,7 @@ markdown_extensions:
- attr_list
- codehilite
- markdown_gfm_admonition
+ - pymdownx.details
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
@@ -39,6 +40,8 @@ plugins:
exclude:
- index.md
- cyclopts
+ - neoteroi.mkdocsoad:
+ use_pymdownx: true
- mkdocstrings:
handlers:
python:
@@ -61,6 +64,7 @@ exclude_docs: |
development_notes/
extra_css:
- css/extra.css
+ - css/mkdocsoad.css
- css/mkdocstrings.css
extra:
analytics:
@@ -88,6 +92,7 @@ nav:
- Auth: api/auth.md
- Client: api/client.md
- Models: api/models.md
+ - OpenAPI Reference: api/openapi-reference.md
- Development Resources:
- Contributing: CONTRIBUTING.md
- Architecture Notes: internal_architecture.md
diff --git a/pyproject.toml b/pyproject.toml
index d5a5ad3..4f0cbc5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -66,6 +66,10 @@ name = "pypi"
url = "https://pypi.org/simple"
default = true
+[tool.uv.sources]
+# Temporary workaround until PR #63 upstream is merged and released
+essentials-openapi = { git = "https://github.com/dcode/essentials-openapi", branch = "main" }
+
[build-system]
requires = ["hatchling", "hatch-protobuf", "mypy-protobuf"]
build-backend = "hatchling.build"
@@ -74,6 +78,8 @@ build-backend = "hatchling.build"
dev = [
"hatch>=1.16.3",
"hatch-protobuf>=0.5.0",
+ "grpcio==1.78.0", # force downgrade due to "https://github.com/grpc/grpc/issues/41725"
+ "grpcio-tools==1.78.0", # force downgrade due to "https://github.com/grpc/grpc/issues/41725"
"mdformat-gfm>=1.0.0",
"mdformat-ruff>=0.1.3",
"mypy-protobuf>=5.0.0",
@@ -97,6 +103,8 @@ docs = [
"mkdocs-git-revision-date-localized-plugin>=1.5.1",
"mkdocs-material[imaging,recommended]>=9.7.1",
"mkdocstrings[python]>=1.0.3",
+ "neoteroi-mkdocs>=1.2.0",
+ "essentials-openapi @ git+https://github.com/dcode/essentials-openapi@main",
"pymdown-extensions>=10.20.1",
]
diff --git a/src/prusa/connect/client/auth.py b/src/prusa/connect/client/auth.py
index 474c98c..b6329cd 100644
--- a/src/prusa/connect/client/auth.py
+++ b/src/prusa/connect/client/auth.py
@@ -248,7 +248,8 @@ def refresh(self) -> None:
if resp.status_code == 200:
new_data = resp.json()
# We need to update our PrusaJWTTokenSet.
- # The refresh response typically contains a new access_token and optionally a new refresh_token.
+ # The refresh response typically contains a new access_token
+ # and optionally a new refresh_token.
# Use dump_tokens() to get current state as dict of raw strings
current_raw = self.tokens.dump_tokens()
@@ -392,11 +393,15 @@ def interactive_login(email: str, password: str, otp_callback: collections.abc.C
}
logger.info("Initiating login flow...")
- logger.debug("Login params", url=consts.AUTH_URL, params=params)
+ # logger.debug("Initial login request", method="GET", params=params,
+ # url=consts.AUTH_URL)
resp = session.get(consts.AUTH_URL, params=params)
+ # logger.debug("Initial login flow reponse", status_code=resp.status_code,
+ # text=resp.text, headers=resp.headers)
if resp.status_code != 200:
raise exceptions.PrusaAuthError(f"Could not load login page (Status: {resp.status_code})")
+ # csrf could optionally be extracted from set-cookie header
csrf = _extract_csrf(resp.text)
if not csrf:
logger.error("CSRF token missing from login page")
@@ -411,7 +416,11 @@ def interactive_login(email: str, password: str, otp_callback: collections.abc.C
}
headers = {"Referer": resp.url, "Origin": "https://account.prusa3d.com"}
+ # logger.debug("Submit user credentialls", method="POST", data=payload,
+ # headers=headers, url=resp.url)
resp = session.post(resp.url, data=payload, headers=headers)
+ # logger.debug("Creds response", status_code=resp.status_code,
+ # url=resp.url, body=resp.text, headers=resp.headers)
# 4. Check for errors or TOTP
if 'class="invalid-feedback"' in resp.text:
@@ -420,6 +429,8 @@ def interactive_login(email: str, password: str, otp_callback: collections.abc.C
if "/login/totp/" in resp.url:
logger.info("2FA Challenge detected.")
resp = _handle_totp(session, resp, otp_callback)
+ # logger.debug("TOTP Response", status_code=resp.status_code, url=resp.url,
+ # body=resp.text, headers=resp.headers)
# 5. Handle Callback
parsed = urllib.parse.urlparse(resp.url)
@@ -431,24 +442,28 @@ def interactive_login(email: str, password: str, otp_callback: collections.abc.C
raise exceptions.PrusaAuthError("Login failed: No authorization code in callback.")
auth_code = query["code"][0]
+ payload = {
+ "grant_type": "authorization_code",
+ "client_id": consts.CLIENT_ID,
+ "code": auth_code,
+ "code_verifier": verifier,
+ "redirect_uri": consts.REDIRECT_URI,
+ }
# 6. Exchange Code
+ # logger.debug("Submit authorization for user token", method="POST",
+ # data=payload, url=consts.TOKEN_URL)
token_resp = session.post(
consts.TOKEN_URL,
- data={
- "grant_type": "authorization_code",
- "client_id": consts.CLIENT_ID,
- "code": auth_code,
- "code_verifier": verifier,
- "redirect_uri": consts.REDIRECT_URI,
- },
+ data=payload,
)
if token_resp.status_code != 200:
raise exceptions.PrusaAuthError(f"Token exchange failed: {token_resp.text}")
logger.info("Token exchange successful.", user_id=token_resp.json().get("sub"))
- logger.debug("Token response", json=token_resp.json())
+ # logger.debug("Token response", status_code=token_resp.status_code,
+ # json=token_resp.json(), headers=token_resp.headers)
return PrusaJWTTokenSet(**token_resp.json())
@@ -470,6 +485,8 @@ def _handle_totp(
field_name: otp_code,
}
+ # logger.debug("Handling TOTP", method="POST", data=payload,
+ # url=resp.url, referer=resp.url)
return session.post(resp.url, data=payload, headers={"Referer": resp.url})
diff --git a/uv.lock b/uv.lock
index 42ebcad..7cec256 100644
--- a/uv.lock
+++ b/uv.lock
@@ -131,16 +131,16 @@ wheels = [
[[package]]
name = "backrefs"
-version = "6.1"
+version = "6.2"
source = { registry = "https://pypi.org/simple" }
-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" }
+sdist = { url = "https://files.pythonhosted.org/packages/4e/a6/e325ec73b638d3ede4421b5445d4a0b8b219481826cc079d510100af356c/backrefs-6.2.tar.gz", hash = "sha256:f44ff4d48808b243b6c0cdc6231e22195c32f77046018141556c66f8bab72a49", size = 7012303, upload-time = "2026-02-16T19:10:15.828Z" }
wheels = [
- { 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" },
+ { url = "https://files.pythonhosted.org/packages/1b/39/3765df263e08a4df37f4f43cb5aa3c6c17a4bdd42ecfe841e04c26037171/backrefs-6.2-py310-none-any.whl", hash = "sha256:0fdc7b012420b6b144410342caeb8adc54c6866cf12064abc9bb211302e496f8", size = 381075, upload-time = "2026-02-16T19:10:04.322Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl", hash = "sha256:08aa7fae530c6b2361d7bdcbda1a7c454e330cc9dbcd03f5c23205e430e5c3be", size = 392874, upload-time = "2026-02-16T19:10:06.314Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/63/77e8c9745b4d227cce9f5e0a6f68041278c5f9b18588b35905f5f19c1beb/backrefs-6.2-py312-none-any.whl", hash = "sha256:c3f4b9cb2af8cda0d87ab4f57800b57b95428488477be164dd2b47be54db0c90", size = 398787, upload-time = "2026-02-16T19:10:08.274Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl", hash = "sha256:12df81596ab511f783b7d87c043ce26bc5b0288cf3bb03610fe76b8189282b2b", size = 400747, upload-time = "2026-02-16T19:10:09.791Z" },
+ { url = "https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl", hash = "sha256:e5f805ae09819caa1aa0623b4a83790e7028604aa2b8c73ba602c4454e665de7", size = 412602, upload-time = "2026-02-16T19:10:12.317Z" },
+ { url = "https://files.pythonhosted.org/packages/21/f8/d02f650c47d05034dcd6f9c8cf94f39598b7a89c00ecda0ecb2911bc27e9/backrefs-6.2-py39-none-any.whl", hash = "sha256:664e33cd88c6840b7625b826ecf2555f32d491800900f5a541f772c485f7cda7", size = 381077, upload-time = "2026-02-16T19:10:13.74Z" },
]
[[package]]
@@ -238,11 +238,11 @@ wheels = [
[[package]]
name = "certifi"
-version = "2026.1.4"
+version = "2026.2.25"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
]
[[package]]
@@ -468,20 +468,20 @@ sdist = { url = "https://files.pythonhosted.org/packages/f1/2a/8c3ac3d8bc94e6de8
[[package]]
name = "cssselect2"
-version = "0.8.0"
+version = "0.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "tinycss2" },
{ name = "webencodings" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/9f/86/fd7f58fc498b3166f3a7e8e0cddb6e620fe1da35b02248b1bd59e95dbaaa/cssselect2-0.8.0.tar.gz", hash = "sha256:7674ffb954a3b46162392aee2a3a0aedb2e14ecf99fcc28644900f4e6e3e9d3a", size = 35716, upload-time = "2025-03-05T14:46:07.988Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/e0/20/92eaa6b0aec7189fa4b75c890640e076e9e793095721db69c5c81142c2e1/cssselect2-0.9.0.tar.gz", hash = "sha256:759aa22c216326356f65e62e791d66160a0f9c91d1424e8d8adc5e74dddfc6fb", size = 35595, upload-time = "2026-02-12T17:16:39.614Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0f/e7/aa315e6a749d9b96c2504a1ba0ba031ba2d0517e972ce22682e3fccecb09/cssselect2-0.8.0-py3-none-any.whl", hash = "sha256:46fc70ebc41ced7a32cd42d58b1884d72ade23d21e5a4eaaf022401c13f0e76e", size = 15454, upload-time = "2025-03-05T14:46:06.463Z" },
+ { url = "https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl", hash = "sha256:6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563", size = 15453, upload-time = "2026-02-12T17:16:38.317Z" },
]
[[package]]
name = "cyclopts"
-version = "4.5.1"
+version = "4.6.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
@@ -489,9 +489,9 @@ dependencies = [
{ name = "rich" },
{ name = "rich-rst" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/d4/93/6085aa89c3fff78a5180987354538d72e43b0db27e66a959302d0c07821a/cyclopts-4.5.1.tar.gz", hash = "sha256:fadc45304763fd9f5d6033727f176898d17a1778e194436964661a005078a3dd", size = 162075, upload-time = "2026-01-25T15:23:54.07Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/49/5c/88a4068c660a096bbe87efc5b7c190080c9e86919c36ec5f092cb08d852f/cyclopts-4.6.0.tar.gz", hash = "sha256:483c4704b953ea6da742e8de15972f405d2e748d19a848a4d61595e8e5360ee5", size = 162724, upload-time = "2026-02-23T15:44:49.286Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1c/7c/996760c30f1302704af57c66ff2d723f7d656d0d0b93563b5528a51484bb/cyclopts-4.5.1-py3-none-any.whl", hash = "sha256:0642c93601e554ca6b7b9abd81093847ea4448b2616280f2a0952416574e8c7a", size = 199807, upload-time = "2026-01-25T15:23:55.219Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/eb/1e8337755a70dc7d7ff10a73dc8f20e9352c9ad6c2256ed863ac95cd3539/cyclopts-4.6.0-py3-none-any.whl", hash = "sha256:0a891cb55bfd79a3cdce024db8987b33316aba11071e5258c21ac12a640ba9f2", size = 200518, upload-time = "2026-02-23T15:44:47.854Z" },
]
[package.optional-dependencies]
@@ -558,13 +558,32 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/96/fd/a40c621ff207f3ce8e484aa0fc8ba4eb6e3ecf52e15b42ba764b457a9550/editorconfig-0.17.1-py3-none-any.whl", hash = "sha256:1eda9c2c0db8c16dbd50111b710572a5e6de934e39772de1959d41f64fc17c82", size = 16360, upload-time = "2025-06-09T08:21:35.654Z" },
]
+[[package]]
+name = "essentials"
+version = "1.1.9"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b6/ff/587c8789abad9c76ef2a5bf613c6d7922a0d09879e3eda1864d108f11b75/essentials-1.1.9.tar.gz", hash = "sha256:7fbea3a518cbeafe5374fb7e2ea2c15a109e8a7fd1eaab62ae87cbd1b3b1e8d0", size = 25068, upload-time = "2025-11-23T20:34:46.533Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/50/8b/68aa342fa78931429ea09e6dc8d9ffa46fa36cfb0cf423e612d22365a9c2/essentials-1.1.9-py2.py3-none-any.whl", hash = "sha256:71ef161e0e27ef77cd6f5fc05e0b8688a575fcab870c01c95940f832e321dfbb", size = 16151, upload-time = "2025-11-23T20:34:45.025Z" },
+]
+
+[[package]]
+name = "essentials-openapi"
+version = "1.3.0"
+source = { git = "https://github.com/dcode/essentials-openapi?branch=main#6f2556a0ee2970790ff5bea0f9ea2d75b5381668" }
+dependencies = [
+ { name = "essentials" },
+ { name = "markupsafe" },
+ { name = "pyyaml" },
+]
+
[[package]]
name = "filelock"
-version = "3.20.3"
+version = "3.24.3"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935, upload-time = "2026-02-19T00:48:20.543Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331, upload-time = "2026-02-19T00:48:18.465Z" },
]
[[package]]
@@ -603,30 +622,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" },
]
-[[package]]
-name = "griffe"
-version = "2.0.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "griffecli" },
- { name = "griffelib" },
-]
-wheels = [
- { url = "https://files.pythonhosted.org/packages/8b/94/ee21d41e7eb4f823b94603b9d40f86d3c7fde80eacc2c3c71845476dddaa/griffe-2.0.0-py3-none-any.whl", hash = "sha256:5418081135a391c3e6e757a7f3f156f1a1a746cc7b4023868ff7d5e2f9a980aa", size = 5214, upload-time = "2026-02-09T19:09:44.105Z" },
-]
-
-[[package]]
-name = "griffecli"
-version = "2.0.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "colorama" },
- { name = "griffelib" },
-]
-wheels = [
- { url = "https://files.pythonhosted.org/packages/e6/ed/d93f7a447bbf7a935d8868e9617cbe1cadf9ee9ee6bd275d3040fbf93d60/griffecli-2.0.0-py3-none-any.whl", hash = "sha256:9f7cd9ee9b21d55e91689358978d2385ae65c22f307a63fb3269acf3f21e643d", size = 9345, upload-time = "2026-02-09T19:09:42.554Z" },
-]
-
[[package]]
name = "griffelib"
version = "2.0.0"
@@ -730,7 +725,7 @@ wheels = [
[[package]]
name = "hatch"
-version = "1.16.3"
+version = "1.16.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "backports-zstd", marker = "python_full_version < '3.14'" },
@@ -751,9 +746,9 @@ dependencies = [
{ name = "uv" },
{ name = "virtualenv" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/41/c1/976b807478878d31d467dd17b9fe642962f292e16ed13c34b593c0453fde/hatch-1.16.3.tar.gz", hash = "sha256:2a50ecc912adfc8122cd2ccdcc15254cdef829e5d158be9014180cd7f0fb7ea9", size = 5219621, upload-time = "2026-01-21T01:36:19.822Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/3a/78/44051bc8b0061b523065e3f67e42fd9b3857ae1ed8b69febdde4f1dd2a3b/hatch-1.16.4.tar.gz", hash = "sha256:1539141fe014be89c4290418964cf4a79e6bee9b6b7638787aa1d1576ff448b1", size = 5220311, upload-time = "2026-02-23T22:45:51.599Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b8/b4/5c5fa4ca8c59e7ef0a224ff10e6336e73ca61c5e0eff09ee691441c9275f/hatch-1.16.3-py3-none-any.whl", hash = "sha256:f5169025cf1cdfe981366eb96127cab1d1bc59f5f2acb87c4cc308c25d95a4b1", size = 141305, upload-time = "2026-01-21T01:36:18.13Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/29/742010d61a7665b863a36208bfa3df93476e9a86fde45413cd13db76f7d0/hatch-1.16.4-py3-none-any.whl", hash = "sha256:0b434f522a5b1b0303ffe6195ad056b7f815403413c72561308001fde82a3b6f", size = 141355, upload-time = "2026-02-23T22:45:49.862Z" },
]
[[package]]
@@ -784,7 +779,7 @@ wheels = [
[[package]]
name = "hatchling"
-version = "1.28.0"
+version = "1.29.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
@@ -792,9 +787,9 @@ dependencies = [
{ name = "pluggy" },
{ name = "trove-classifiers" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/0b/8e/e480359492affde4119a131da729dd26da742c2c9b604dff74836e47eef9/hatchling-1.28.0.tar.gz", hash = "sha256:4d50b02aece6892b8cd0b3ce6c82cb218594d3ec5836dbde75bf41a21ab004c8", size = 55365, upload-time = "2025-11-27T00:31:13.766Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/cf/9c/b4cfe330cd4f49cff17fd771154730555fa4123beb7f292cf0098b4e6c20/hatchling-1.29.0.tar.gz", hash = "sha256:793c31816d952cee405b83488ce001c719f325d9cda69f1fc4cd750527640ea6", size = 55656, upload-time = "2026-02-23T19:42:06.539Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0d/a5/48cb7efb8b4718b1a4c0c331e3364a3a33f614ff0d6afd2b93ee883d3c47/hatchling-1.28.0-py3-none-any.whl", hash = "sha256:dc48722b68b3f4bbfa3ff618ca07cdea6750e7d03481289ffa8be1521d18a961", size = 76075, upload-time = "2025-11-27T00:31:12.544Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/8a/44032265776062a89171285ede55a0bdaadc8ac00f27f0512a71a9e3e1c8/hatchling-1.29.0-py3-none-any.whl", hash = "sha256:50af9343281f34785fab12da82e445ed987a6efb34fd8c2fc0f6e6630dbcc1b0", size = 76356, upload-time = "2026-02-23T19:42:05.197Z" },
]
[[package]]
@@ -1289,16 +1284,16 @@ wheels = [
[[package]]
name = "mkdocs-autorefs"
-version = "1.4.3"
+version = "1.4.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown" },
{ name = "markupsafe" },
{ name = "mkdocs" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/52/c0/f641843de3f612a6b48253f39244165acff36657a91cc903633d456ae1ac/mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197", size = 56588, upload-time = "2026-02-10T15:23:55.105Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" },
+ { url = "https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089", size = 25530, upload-time = "2026-02-10T15:23:53.817Z" },
]
[[package]]
@@ -1346,7 +1341,7 @@ wheels = [
[[package]]
name = "mkdocs-material"
-version = "9.7.2"
+version = "9.7.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "babel" },
@@ -1361,9 +1356,9 @@ dependencies = [
{ name = "pymdown-extensions" },
{ name = "requests" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/34/57/5d3c8c9e2ff9d66dc8f63aa052eb0bac5041fecff7761d8689fe65c39c13/mkdocs_material-9.7.2.tar.gz", hash = "sha256:6776256552290b9b7a7aa002780e25b1e04bc9c3a8516b6b153e82e16b8384bd", size = 4097818, upload-time = "2026-02-18T15:53:07.763Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/8a/b4/f900fcb8e6f510241e334ca401eddcb61ed880fb6572f7f32e4228472ca1/mkdocs_material-9.7.3.tar.gz", hash = "sha256:e5f0a18319699da7e78c35e4a8df7e93537a888660f61a86bd773a7134798f22", size = 4097748, upload-time = "2026-02-24T12:06:22.646Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/cd/19/d194e75e82282b1d688f0720e21b5ac250ed64ddea333a228aaf83105f2e/mkdocs_material-9.7.2-py3-none-any.whl", hash = "sha256:9bf6f53452d4a4d527eac3cef3f92b7b6fc4931c55d57766a7d87890d47e1b92", size = 9305052, upload-time = "2026-02-18T15:53:05.221Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/1b/16ad0193079bb8a15aa1d2620813a9cd15b18de150a4ea1b2c607fb4c74d/mkdocs_material-9.7.3-py3-none-any.whl", hash = "sha256:37ebf7b4788c992203faf2e71900be3c197c70a4be9b0d72aed537b08a91dd9d", size = 9305078, upload-time = "2026-02-24T12:06:19.155Z" },
]
[package.optional-dependencies]
@@ -1453,16 +1448,16 @@ python = [
[[package]]
name = "mkdocstrings-python"
-version = "2.0.2"
+version = "2.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "griffe" },
+ { name = "griffelib" },
{ name = "mkdocs-autorefs" },
{ name = "mkdocstrings" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/25/84/78243847ad9d5c21d30a2842720425b17e880d99dfe824dee11d6b2149b4/mkdocstrings_python-2.0.2.tar.gz", hash = "sha256:4a32ccfc4b8d29639864698e81cfeb04137bce76bb9f3c251040f55d4b6e1ad8", size = 199124, upload-time = "2026-02-09T15:12:01.543Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/29/33/c225eaf898634bdda489a6766fc35d1683c640bffe0e0acd10646b13536d/mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8", size = 199083, upload-time = "2026-02-20T10:38:36.368Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f3/31/7ee938abbde2322e553a2cb5f604cdd1e4728e08bba39c7ee6fae9af840b/mkdocstrings_python-2.0.2-py3-none-any.whl", hash = "sha256:31241c0f43d85a69306d704d5725786015510ea3f3c4bdfdb5a5731d83cdc2b0", size = 104900, upload-time = "2026-02-09T15:12:00.166Z" },
+ { url = "https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12", size = 104779, upload-time = "2026-02-20T10:38:34.517Z" },
]
[[package]]
@@ -1531,6 +1526,23 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b4/b1/ab1e7a49930a8c1d1f7a570bbd4ec7d552ef035acc7aa4b97906e17a34a9/mypy_protobuf-5.0.0-py3-none-any.whl", hash = "sha256:3a7dd753ef3e3b8783a824eb51f07983f62812f9ec066e4fbb1b22d6c5dc36d0", size = 26008, upload-time = "2026-01-13T17:10:11.053Z" },
]
+[[package]]
+name = "neoteroi-mkdocs"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+ { name = "essentials-openapi" },
+ { name = "httpx" },
+ { name = "jinja2" },
+ { name = "mkdocs" },
+ { name = "rich" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d2/36/7cc711922112a1293568b8bc2f94bfaae0104bf5c1e5b3cdcfd6068023c7/neoteroi_mkdocs-1.2.0.tar.gz", hash = "sha256:58e25cb1b9db093ffa8d12bdb33264bf567cac30fb964b56e0a493efa749ad6e", size = 25354, upload-time = "2025-11-23T10:49:23.44Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a3/e9/d365600c11aa84142b878e6655d23c0add7a056d3d6e28ff8f83d7dd2381/neoteroi_mkdocs-1.2.0-py3-none-any.whl", hash = "sha256:91b6aa95a4e46c9bb93e00e021d2044cb0c7d80c0b4600434ff8f440d613a0a8", size = 38485, upload-time = "2025-11-23T10:49:22.149Z" },
+]
+
[[package]]
name = "nodeenv"
version = "1.10.0"
@@ -1659,11 +1671,11 @@ wheels = [
[[package]]
name = "platformdirs"
-version = "4.5.1"
+version = "4.9.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" },
+ { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" },
]
[[package]]
@@ -1734,6 +1746,8 @@ cli = [
[package.dev-dependencies]
dev = [
+ { name = "grpcio" },
+ { name = "grpcio-tools" },
{ name = "hatch" },
{ name = "hatch-protobuf" },
{ name = "mdformat-gfm" },
@@ -1749,6 +1763,7 @@ dev = [
]
docs = [
{ name = "cyclopts", extra = ["mkdocs"] },
+ { name = "essentials-openapi" },
{ name = "hatch-mkdocs" },
{ name = "markdown-callouts" },
{ name = "markdown-gfm-admonition" },
@@ -1759,6 +1774,7 @@ docs = [
{ name = "mkdocs-git-revision-date-localized-plugin" },
{ name = "mkdocs-material", extra = ["imaging", "recommended"] },
{ name = "mkdocstrings", extra = ["python"] },
+ { name = "neoteroi-mkdocs" },
{ name = "pymdown-extensions" },
]
@@ -1783,6 +1799,8 @@ provides-extras = ["all", "cli"]
[package.metadata.requires-dev]
dev = [
+ { name = "grpcio", specifier = "==1.78.0" },
+ { name = "grpcio-tools", specifier = "==1.78.0" },
{ name = "hatch", specifier = ">=1.16.3" },
{ name = "hatch-protobuf", specifier = ">=0.5.0" },
{ name = "mdformat-gfm", specifier = ">=1.0.0" },
@@ -1798,6 +1816,7 @@ dev = [
]
docs = [
{ name = "cyclopts", extras = ["mkdocs"], specifier = ">=4.5.1" },
+ { name = "essentials-openapi", git = "https://github.com/dcode/essentials-openapi?branch=main" },
{ name = "hatch-mkdocs", specifier = ">=0.1.0" },
{ name = "markdown-callouts", specifier = ">=0.4.0" },
{ name = "markdown-gfm-admonition", specifier = ">=0.3.0" },
@@ -1808,6 +1827,7 @@ docs = [
{ name = "mkdocs-git-revision-date-localized-plugin", specifier = ">=1.5.1" },
{ name = "mkdocs-material", extras = ["imaging", "recommended"], specifier = ">=9.7.1" },
{ name = "mkdocstrings", extras = ["python"], specifier = ">=1.0.3" },
+ { name = "neoteroi-mkdocs", specifier = ">=1.2.0" },
{ name = "pymdown-extensions", specifier = ">=10.20.1" },
]
@@ -1929,16 +1949,16 @@ wheels = [
[[package]]
name = "pydantic-settings"
-version = "2.12.0"
+version = "2.13.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "typing-inspection" },
]
-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" }
+sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" }
wheels = [
- { 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" },
+ { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" },
]
[[package]]
@@ -2055,15 +2075,15 @@ wheels = [
[[package]]
name = "pymdown-extensions"
-version = "10.20.1"
+version = "10.21"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown" },
{ name = "pyyaml" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/1e/6c/9e370934bfa30e889d12e61d0dae009991294f40055c238980066a7fbd83/pymdown_extensions-10.20.1.tar.gz", hash = "sha256:e7e39c865727338d434b55f1dd8da51febcffcaebd6e1a0b9c836243f660740a", size = 852860, upload-time = "2026-01-24T05:56:56.758Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/ba/63/06673d1eb6d8f83c0ea1f677d770e12565fb516928b4109c9e2055656a9e/pymdown_extensions-10.21.tar.gz", hash = "sha256:39f4a020f40773f6b2ff31d2cd2546c2c04d0a6498c31d9c688d2be07e1767d5", size = 853363, upload-time = "2026-02-15T20:44:06.748Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/40/6d/b6ee155462a0156b94312bdd82d2b92ea56e909740045a87ccb98bf52405/pymdown_extensions-10.20.1-py3-none-any.whl", hash = "sha256:24af7feacbca56504b313b7b418c4f5e1317bb5fea60f03d57be7fcc40912aa0", size = 268768, upload-time = "2026-01-24T05:56:54.537Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl", hash = "sha256:91b879f9f864d49794c2d9534372b10150e6141096c3908a455e45ca72ad9d3f", size = 268877, upload-time = "2026-02-15T20:44:05.464Z" },
]
[[package]]
@@ -2077,18 +2097,18 @@ wheels = [
[[package]]
name = "pyrefly"
-version = "0.50.1"
+version = "0.54.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/99/74/f59a827418a74d5163d600db0e99b29a81cc7265ce62694dbfa0407bd95c/pyrefly-0.50.1.tar.gz", hash = "sha256:1859f36fb1dc4a903ba2298442c224dfadcda7fce5691aebd6bbc21c5f703299", size = 4901970, upload-time = "2026-01-29T00:10:06.42Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/81/44/c10b16a302fda90d0af1328f880b232761b510eab546616a7be2fdf35a57/pyrefly-0.54.0.tar.gz", hash = "sha256:c6663be64d492f0d2f2a411ada9f28a6792163d34133639378b7f3dd9a8dca94", size = 5098893, upload-time = "2026-02-23T15:44:35.111Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d1/6c/7135b5b2a4d8827b37d5bce0255cf993e4f418566810bbcd69b1c69b7acd/pyrefly-0.50.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:16ea4052b3df75206f5677a87ca7ee6c4c3a92b086081250206e98c60d85e7a8", size = 11832985, upload-time = "2026-01-29T00:09:43.739Z" },
- { url = "https://files.pythonhosted.org/packages/0d/a0/34a40589a89d859db0d8926d60c2e30748a0fbc52d139269344b525d0c6d/pyrefly-0.50.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:aac15d38f3ad548b2cd50a7fc054241743e32753a32872fe5d4bb88982d29b35", size = 11412746, upload-time = "2026-01-29T00:09:46.843Z" },
- { url = "https://files.pythonhosted.org/packages/ee/2f/6b25c59e4ee7b55cc7bbb536c625711a9c3d9de3bb781429ae0239998747/pyrefly-0.50.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:603c4f7893eba4f8472523cfceb41f200a30ae89c0af90f4ba03de4eebf9732b", size = 32329171, upload-time = "2026-01-29T00:09:49.561Z" },
- { url = "https://files.pythonhosted.org/packages/10/bb/1c343197688d63052bf12fac41ed820d1e73302b810b2f77e20d30c0c6e3/pyrefly-0.50.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d2408be4b1f618ef80855042d6a3599128546a067654878002e34fdd56bfc54", size = 34591076, upload-time = "2026-01-29T00:09:52.559Z" },
- { url = "https://files.pythonhosted.org/packages/e0/4a/fad923b203cba9efd60f2d5d81040b87270af426c7cff9197652f3501e25/pyrefly-0.50.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9483e700ec0e156b249c2805896833c1393eda787f0b83ab430084c2723fec7e", size = 35705744, upload-time = "2026-01-29T00:09:55.693Z" },
- { url = "https://files.pythonhosted.org/packages/db/36/bc1a814d4ad19d7aed5638a0ef40787d3a0ae8ef6f1d08e7fdd909999039/pyrefly-0.50.1-py3-none-win32.whl", hash = "sha256:68ff5eac338bfc74469f67ff79cc3500e826b51a753bba349ac2cfcb5705b32b", size = 10832356, upload-time = "2026-01-29T00:09:58.494Z" },
- { url = "https://files.pythonhosted.org/packages/c6/4c/26e21c50c6bc305c86890b7c3865c3f9476356478c757c98717e83eda5ac/pyrefly-0.50.1-py3-none-win_amd64.whl", hash = "sha256:ef9b5644cfef560fefe3e5093a78128f17d8d055eb01c5306e88644063c37a38", size = 11590948, upload-time = "2026-01-29T00:10:01.193Z" },
- { url = "https://files.pythonhosted.org/packages/3d/6a/2ed28e2e96421e8b214d8b996cce5f7a1f8eecc9924a56d1ff4af8d0f784/pyrefly-0.50.1-py3-none-win_arm64.whl", hash = "sha256:b813120b2de86122933c424ebdb8e905e13191e72fca6403ea75ca94154eb898", size = 11126993, upload-time = "2026-01-29T00:10:03.802Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/99/8fdcdb4e55f0227fdd9f6abce36b619bab1ecb0662b83b66adc8cba3c788/pyrefly-0.54.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:58a3f092b6dc25ef79b2dc6c69a40f36784ca157c312bfc0baea463926a9db6d", size = 12223973, upload-time = "2026-02-23T15:44:14.278Z" },
+ { url = "https://files.pythonhosted.org/packages/90/35/c2aaf87a76003ad27b286594d2e5178f811eaa15bfe3d98dba2b47d56dd1/pyrefly-0.54.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:615081414106dd95873bc39c3a4bed68754c6cc24a8177ac51d22f88f88d3eb3", size = 11785585, upload-time = "2026-02-23T15:44:17.468Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/4a/ced02691ed67e5a897714979196f08ad279ec7ec7f63c45e00a75a7f3c0e/pyrefly-0.54.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbcaf20f5fe585079079a95205c1f3cd4542d17228cdf1df560288880623b70", size = 33381977, upload-time = "2026-02-23T15:44:19.736Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/ce/72a117ed437c8f6950862181014b41e36f3c3997580e29b772b71e78d587/pyrefly-0.54.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d5da116c0d34acfbd66663addd3ca8aa78a636f6692a66e078126d3620a883", size = 35962821, upload-time = "2026-02-23T15:44:22.357Z" },
+ { url = "https://files.pythonhosted.org/packages/85/de/89013f5ae0a35d2b6b01274a92a35ee91431ea001050edf0a16748d39875/pyrefly-0.54.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef3ac27f1a4baaf67aead64287d3163350844794aca6315ad1a9650b16ec26a", size = 38496689, upload-time = "2026-02-23T15:44:25.236Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/9a/33b097c7bf498b924742dca32dd5d9c6a3fa6c2b52b63a58eb9e1980ca89/pyrefly-0.54.0-py3-none-win32.whl", hash = "sha256:7d607d72200a8afbd2db10bfefb40160a7a5d709d207161c21649cedd5cfc09a", size = 11295268, upload-time = "2026-02-23T15:44:27.551Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/21/9263fd1144d2a3d7342b474f183f7785b3358a1565c864089b780110b933/pyrefly-0.54.0-py3-none-win_amd64.whl", hash = "sha256:fd416f04f89309385696f685bd5c9141011f18c8072f84d31ca20c748546e791", size = 12081810, upload-time = "2026-02-23T15:44:29.461Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/5b/fad062a196c064cbc8564de5b2f4d3cb6315f852e3b31e8a1ce74c69a1ea/pyrefly-0.54.0-py3-none-win_arm64.whl", hash = "sha256:f06ab371356c7b1925e0bffe193b738797e71e5dbbff7fb5a13f90ee7521211d", size = 11564930, upload-time = "2026-02-23T15:44:33.053Z" },
]
[[package]]
@@ -2133,6 +2153,19 @@ 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-discovery"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "filelock" },
+ { name = "platformdirs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z" },
+]
+
[[package]]
name = "python-dotenv"
version = "1.2.1"
@@ -2251,29 +2284,29 @@ wheels = [
[[package]]
name = "responses"
-version = "0.25.8"
+version = "0.26.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyyaml" },
{ name = "requests" },
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/0e/95/89c054ad70bfef6da605338b009b2e283485835351a9935c7bfbfaca7ffc/responses-0.25.8.tar.gz", hash = "sha256:9374d047a575c8f781b94454db5cab590b6029505f488d12899ddb10a4af1cf4", size = 79320, upload-time = "2025-08-08T19:01:46.709Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/9f/b4/b7e040379838cc71bf5aabdb26998dfbe5ee73904c92c1c161faf5de8866/responses-0.26.0.tar.gz", hash = "sha256:c7f6923e6343ef3682816ba421c006626777893cb0d5e1434f674b649bac9eb4", size = 81303, upload-time = "2026-02-19T14:38:05.574Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1c/4c/cc276ce57e572c102d9542d383b2cfd551276581dc60004cb94fe8774c11/responses-0.25.8-py3-none-any.whl", hash = "sha256:0c710af92def29c8352ceadff0c3fe340ace27cf5af1bbe46fb71275bcd2831c", size = 34769, upload-time = "2025-08-08T19:01:45.018Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/04/7f73d05b556da048923e31a0cc878f03be7c5425ed1f268082255c75d872/responses-0.26.0-py3-none-any.whl", hash = "sha256:03ec4409088cd5c66b71ecbbbd27fe2c58ddfad801c66203457b3e6a04868c37", size = 35099, upload-time = "2026-02-19T14:38:03.847Z" },
]
[[package]]
name = "rich"
-version = "14.3.1"
+version = "14.3.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a1/84/4831f881aa6ff3c976f6d6809b58cdfa350593ffc0dc3c58f5f6586780fb/rich-14.3.1.tar.gz", hash = "sha256:b8c5f568a3a749f9290ec6bddedf835cec33696bfc1e48bcfecb276c7386e4b8", size = 230125, upload-time = "2026-01-24T21:40:44.847Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/87/2a/a1810c8627b9ec8c57ec5ec325d306701ae7be50235e8fd81266e002a3cc/rich-14.3.1-py3-none-any.whl", hash = "sha256:da750b1aebbff0b372557426fb3f35ba56de8ef954b3190315eb64076d6fb54e", size = 309952, upload-time = "2026-01-24T21:40:42.969Z" },
+ { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" },
]
[[package]]
@@ -2300,28 +2333,27 @@ wheels = [
[[package]]
name = "ruff"
-version = "0.14.14"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" },
- { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" },
- { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" },
- { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" },
- { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" },
- { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" },
- { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" },
- { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" },
- { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" },
- { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" },
- { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" },
- { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" },
- { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" },
- { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" },
- { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" },
- { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" },
- { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" },
- { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" },
+version = "0.15.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3c/3b/20d9a0bc954d51b63f20cf710cf506bfe675d1e6138139342dd5ccc90326/ruff-0.15.3.tar.gz", hash = "sha256:78757853320d8ddb9da24e614ef69a37bcbcfd477e5a6435681188d4bce4eaa1", size = 4569031, upload-time = "2026-02-26T15:39:38.015Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ed/00/c544ab1d70f86dc50a2f2a8e1262e5af5025897ccd820415f559f9f2f63f/ruff-0.15.3-py3-none-linux_armv6l.whl", hash = "sha256:f7df0fd6f889a8d8de2ddb48a9eb55150954400f2157ea15b21a2f49ecaaf988", size = 10444066, upload-time = "2026-02-26T15:39:47.708Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/15/9dee3f4e891261adbd690f8c6f075418a7cd76e845601b00a0da2ae2ad6e/ruff-0.15.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0198b5445197d443c3bbf2cc358f4bd477fb3951e3c7f2babc13e9bb490614a8", size = 10853125, upload-time = "2026-02-26T15:40:18.943Z" },
+ { url = "https://files.pythonhosted.org/packages/88/ba/fc5aeda852c89faf821d36c951df866117342e88439e1b1e1e762a07b7fd/ruff-0.15.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:adf95b5be57b25fbbbc07cd68d37414bee8729e807ad0217219558027186967e", size = 10180833, upload-time = "2026-02-26T15:40:13.282Z" },
+ { url = "https://files.pythonhosted.org/packages/06/87/e2f80a39164476fac4d45752a0d4721d6645f40b7f851e48add12af9947e/ruff-0.15.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b56dbd9cd86489ccbad96bb58fa4c958342b5510fdeb60ea13d9d3566bd845c", size = 10536806, upload-time = "2026-02-26T15:40:24.129Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/89/2e5bf0ed30ea3778460ea4d8cc6cb4d88ba96d9732d2c0cc33349cd65196/ruff-0.15.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6f263ce511871955d8c5401b62c7e863988ea4d0527aa0a3b1b7ecff4d4abc4", size = 10276093, upload-time = "2026-02-26T15:39:44.654Z" },
+ { url = "https://files.pythonhosted.org/packages/82/cb/318206d778c7f42917ca7b0f9436cf27652d1731fe434d3c9990c4a611fa/ruff-0.15.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e90fa1bed82ffede5768232b9bd23212c547ab7cd74c752007ecade1d895ee1a", size = 11051593, upload-time = "2026-02-26T15:39:35.157Z" },
+ { url = "https://files.pythonhosted.org/packages/58/8f/65ee4c1b88e49dd4c0a3fc43e81832536c7942f0c702b6f3d25db0f95d6c/ruff-0.15.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e9d53760b7061ddbe5ea9e25381332c607fc14c40bde78f8a25392a93a68d74", size = 11885820, upload-time = "2026-02-26T15:39:59.504Z" },
+ { url = "https://files.pythonhosted.org/packages/db/04/d4261f6729ad9a356bc6e3223ba297acf3b66118cef4795b4a8953b255ff/ruff-0.15.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec90e3b78c56c4acca4264d371dd48e29215ecb673cc2fa3c4b799b72050e491", size = 11340583, upload-time = "2026-02-26T15:39:50.781Z" },
+ { url = "https://files.pythonhosted.org/packages/24/84/490f38b2bc104e0fdc9496c2a66a48fb2d24a01de46ba0c60c4f6c4d4590/ruff-0.15.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7ce448fd395f822e34c8f6f7dfcd84b6726340082950858f92c4daa6baf8915", size = 11160701, upload-time = "2026-02-26T15:40:02.447Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/25/eae9cb7b6c28b425ed8cbe797da89c78146071102181ba74c4cdfd06bbeb/ruff-0.15.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14f7d763962d385f75b9b3b57fcc5661c56c20d8b1ddc9f5c881b5fa0ba499fa", size = 11111482, upload-time = "2026-02-26T15:39:56.462Z" },
+ { url = "https://files.pythonhosted.org/packages/95/18/16d0b5ef143cb9e52724f18cbccb4b3c5cd4d4e2debbd95e2be3aeb64c9e/ruff-0.15.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b57084e3a3d65418d376c7023711c37cce023cd2fb038a76ba15ee21f3c2c2ee", size = 10497151, upload-time = "2026-02-26T15:40:10.64Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/b4/1829314241ddba07c54a742ab387da343fe56a0267a6b6498f3e2ae99821/ruff-0.15.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d567523ff7dcf3112b0f71231d18c3506dd06943359476ee64dea0f9c8f63976", size = 10281955, upload-time = "2026-02-26T15:40:16.033Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/93/80a4ec4bd3cf58ca9b49dccf2bd232b520db14184912fb7e0eb6f3ecc484/ruff-0.15.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4223088d255bf31a50b6640445b39f668164d64c23e5fa403edfb1e0b11122e5", size = 10766613, upload-time = "2026-02-26T15:40:21.55Z" },
+ { url = "https://files.pythonhosted.org/packages/da/92/fe016b862295dc57499997e7f2edc58119469b210f4f03ccb763fa65f130/ruff-0.15.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:32399ddae088970b2db6efd8d3f49981375cb828075359b6c088ed1fe63d64e1", size = 11262113, upload-time = "2026-02-26T15:39:41.5Z" },
+ { url = "https://files.pythonhosted.org/packages/42/b1/77dcd05940388d9ba3de03ac4b8b598826d57935728071e1be9f2ef5b714/ruff-0.15.3-py3-none-win32.whl", hash = "sha256:1f1eb95ff614351e3a89a862b6d94e6c42c170e61916e1f20facd6c38477f5f3", size = 10509423, upload-time = "2026-02-26T15:40:05.217Z" },
+ { url = "https://files.pythonhosted.org/packages/29/d5/76aab0fabbd54e8c77d02fcff2494906ba85b539d22aa9b7124f7100f008/ruff-0.15.3-py3-none-win_amd64.whl", hash = "sha256:2b22dffe5f5e1e537097aa5208684f069e495f980379c4491b1cfb198a444d0c", size = 11637739, upload-time = "2026-02-26T15:39:53.951Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/61/9b4e3682dfd26054321e1b2fdb67a51361dd6ec2fb63f2b50d711f8832ae/ruff-0.15.3-py3-none-win_arm64.whl", hash = "sha256:82443c14d694d4cbd9e598ede27ef5d6f08389ccad91c933be775ea2f4e66f76", size = 10957794, upload-time = "2026-02-26T15:40:08.045Z" },
]
[[package]]
@@ -2530,11 +2562,11 @@ wheels = [
[[package]]
name = "types-protobuf"
-version = "6.32.1.20251210"
+version = "6.32.1.20260221"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/c2/59/c743a842911887cd96d56aa8936522b0cd5f7a7f228c96e81b59fced45be/types_protobuf-6.32.1.20251210.tar.gz", hash = "sha256:c698bb3f020274b1a2798ae09dc773728ce3f75209a35187bd11916ebfde6763", size = 63900, upload-time = "2025-12-10T03:14:25.451Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/5f/e2/9aa4a3b2469508bd7b4e2ae11cbedaf419222a09a1b94daffcd5efca4023/types_protobuf-6.32.1.20260221.tar.gz", hash = "sha256:6d5fb060a616bfb076cbb61b4b3c3969f5fc8bec5810f9a2f7e648ee5cbcbf6e", size = 64408, upload-time = "2026-02-21T03:55:13.916Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/aa/43/58e75bac4219cbafee83179505ff44cae3153ec279be0e30583a73b8f108/types_protobuf-6.32.1.20251210-py3-none-any.whl", hash = "sha256:2641f78f3696822a048cfb8d0ff42ccd85c25f12f871fbebe86da63793692140", size = 77921, upload-time = "2025-12-10T03:14:24.477Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/e8/1fd38926f9cf031188fbc5a96694203ea6f24b0e34bd64a225ec6f6291ba/types_protobuf-6.32.1.20260221-py3-none-any.whl", hash = "sha256:da7cdd947975964a93c30bfbcc2c6841ee646b318d3816b033adc2c4eb6448e4", size = 77956, upload-time = "2026-02-21T03:55:12.894Z" },
]
[[package]]
@@ -2590,42 +2622,42 @@ wheels = [
[[package]]
name = "uv"
-version = "0.9.28"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/c2/7d/005ab1cab03ca928cef75b424284d14d62c5f18775cf8114a63f210a0c9c/uv-0.9.28.tar.gz", hash = "sha256:253c04b26fb40f74c56ead12ce83db3c018bdefde1fcd1a542bcb88fdca4189c", size = 3834456, upload-time = "2026-01-29T20:15:49.794Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/77/dc/e70698756f1bb74c88bf1eaea63a114a580a38f296ea1567a01db9007490/uv-0.9.28-py3-none-linux_armv6l.whl", hash = "sha256:aede961243bb2c0ca09d0e04ea0bf580d7128dd3b14661b79d133be9a5b69894", size = 22040477, upload-time = "2026-01-29T20:16:11.24Z" },
- { url = "https://files.pythonhosted.org/packages/f0/ed/77294752bf722e1d6b666bd6592b6ac975dabcf1fde49e98a75cac23d45c/uv-0.9.28-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3fe9aa2822d24f6ecec035a06dfdd1fbed570ed40b83a864e71714bad37ddfd3", size = 21025194, upload-time = "2026-01-29T20:15:36.504Z" },
- { url = "https://files.pythonhosted.org/packages/b1/a9/78f2da6217c1bbae3371d68515fe747e1160bab049d6898a03e517802573/uv-0.9.28-py3-none-macosx_11_0_arm64.whl", hash = "sha256:58a36bf623c6d36b3d60d3c76eeb7275199d607938786e927d40ce213980059d", size = 19783994, upload-time = "2026-01-29T20:16:19.451Z" },
- { url = "https://files.pythonhosted.org/packages/14/79/55639c444e91b96c81c326d39a0a06551d2e611be0cc917b89010ba9ba88/uv-0.9.28-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:4d479a1d387b1464ad2c1f960b0b26a9ac1dfba67ea2c6789e9643fe6d1e7b9a", size = 21568230, upload-time = "2026-01-29T20:15:39.35Z" },
- { url = "https://files.pythonhosted.org/packages/14/2e/95d7992c0a39981cfbcf56ff8f069c09e0567feb0e70cb8b52bc8a2947a0/uv-0.9.28-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:29eefd4642f55954a2b9a40619cde3d02856300f59b8cf63ed1a161ca0ca9b77", size = 21633679, upload-time = "2026-01-29T20:15:52.363Z" },
- { url = "https://files.pythonhosted.org/packages/da/ee/b6778e03714b1f9da095c8bf0f8e5007f4867d9196c1ae8053504ddf2877/uv-0.9.28-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4155496f624deb753f5ddd80fbe3797587c8480d1250e83c9fd816b4b02e3a41", size = 21632238, upload-time = "2026-01-29T20:15:55.003Z" },
- { url = "https://files.pythonhosted.org/packages/3b/f8/0db6ea9fd8f2752a8723a637e3ed881eb212516665ccb2e8066bbea62a52/uv-0.9.28-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dc98e2d6db0dc9a2f65ce4cda6a34283fa80f3fbfff129befdf40ad7a3d1615", size = 22779474, upload-time = "2026-01-29T20:15:33.513Z" },
- { url = "https://files.pythonhosted.org/packages/54/88/ef70e04113393f4e19e67281cae9f83c82030d14eb4eb811bda83fcd8f44/uv-0.9.28-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d267280b3878aa6ef8e00bff1f11bf61580d0a8bbb69fa95b5d3526d00f77485", size = 24124596, upload-time = "2026-01-29T20:16:05.062Z" },
- { url = "https://files.pythonhosted.org/packages/81/07/9fda9149bc57e79bde5f00cabcef323a68817c1cca9d44e2aa08d18c6b52/uv-0.9.28-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba2a320ff77996468789f4b2c573fd766f9330717c440335af8790043b2b3703", size = 23655701, upload-time = "2026-01-29T20:16:07.735Z" },
- { url = "https://files.pythonhosted.org/packages/18/b5/1f1e910ca1a0aca0d0ede3ba0eaca867fd3c575f44b2fe103a5c9511f071/uv-0.9.28-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c8fd93c5bee89ed88908215f81a3baa0d2a98e35caf995b97e9c226c1c29340", size = 22856456, upload-time = "2026-01-29T20:16:16.582Z" },
- { url = "https://files.pythonhosted.org/packages/9a/fd/82561751105ed232f1781747bc336b20e8d57ee07b4d2ed3fa6cf2718d71/uv-0.9.28-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b8460a2b624d8ab27cb293a2c9f2393f9efc4e36e0fb886a6c2360e23fb48be", size = 22685296, upload-time = "2026-01-29T20:16:13.857Z" },
- { url = "https://files.pythonhosted.org/packages/a9/e4/b905daff0bfde347c49b9c9ba31d09d504c4b84f2749a07db77a9da16dba/uv-0.9.28-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:3798c486ec627bbd7ca41fa219e997ad403b1f803371edf5c8e75893e46161ba", size = 21669854, upload-time = "2026-01-29T20:15:30.277Z" },
- { url = "https://files.pythonhosted.org/packages/9a/01/9a90574fe7290c775332e54f163cba58c767445b655e97646708f9c66050/uv-0.9.28-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e479cc5cbfd72ebdbea3c909d0ab997162e0dfa1ee622b50e2f9dc8d07d4eee3", size = 22388944, upload-time = "2026-01-29T20:15:47.697Z" },
- { url = "https://files.pythonhosted.org/packages/ac/31/cc35014bab3c17b4fe8f6bae84e640ce64d9bb4c8a24694a935e0c0af538/uv-0.9.28-py3-none-musllinux_1_1_i686.whl", hash = "sha256:97d61cdf2436e83a0f188d55d1974e46679d9a787c3a54cb0a40de717c6bf435", size = 22073327, upload-time = "2026-01-29T20:15:58.119Z" },
- { url = "https://files.pythonhosted.org/packages/26/cd/e848570be5c5be4e139b90237cc64f68d5d51e8e92c40a5ac7cf0c34ad4a/uv-0.9.28-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:cbfa56c833caa37b1f14166327fcaf8aa87290451406921eb07296ffef17fef1", size = 22915580, upload-time = "2026-01-29T20:15:42.468Z" },
- { url = "https://files.pythonhosted.org/packages/a1/2a/6c3d839ea289bf8509da32f703a47accd63ab409b33627728aebcd2a1b65/uv-0.9.28-py3-none-win32.whl", hash = "sha256:d5cb780d5b821f837f63e7fd14e2bf75f01824b4575a1e89639888771bfd9efd", size = 20856809, upload-time = "2026-01-29T20:15:45.141Z" },
- { url = "https://files.pythonhosted.org/packages/06/a8/d72229dd90d1e5a3c8368d51a70219018d579380945e67c8dcffbe8e53c0/uv-0.9.28-py3-none-win_amd64.whl", hash = "sha256:203ab59710c0c1b3c5ecc684f9cfc9264340a69c8706aaa8aea75415779f0d74", size = 23447461, upload-time = "2026-01-29T20:16:22.563Z" },
- { url = "https://files.pythonhosted.org/packages/23/df/5852eb0c59e5224f4cb0323906efae348f782f8a7f1069197e7cf6ec9b74/uv-0.9.28-py3-none-win_arm64.whl", hash = "sha256:c29406e1dc6b1b312c478c76b42b9f94b684855a4c001901b5488bab6ccf4ec7", size = 21860859, upload-time = "2026-01-29T20:16:00.764Z" },
+version = "0.10.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d5/53/7a4274dad70b1d17efb99e36d45fc1b5e4e1e531b43247e518604394c761/uv-0.10.6.tar.gz", hash = "sha256:de86e5e1eb264e74a20fccf56889eea2463edb5296f560958e566647c537b52e", size = 3921763, upload-time = "2026-02-25T00:26:27.066Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4f/f9/faf599c6928dc00d941629260bef157dadb67e8ffb7f4b127b8601f41177/uv-0.10.6-py3-none-linux_armv6l.whl", hash = "sha256:2b46ad78c86d68de6ec13ffaa3a8923467f757574eeaf318e0fce0f63ff77d7a", size = 22412946, upload-time = "2026-02-25T00:26:10.826Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/8f/82dd6aa8acd2e1b1ba12fd49210bd19843383538e0e63e8d7a23a7d39d93/uv-0.10.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a1d9873eb26cbef9138f8c52525bc3fd63be2d0695344cdcf84f0dc2838a6844", size = 21524262, upload-time = "2026-02-25T00:27:09.318Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/48/5767af19db6f21176e43dfde46ea04e33c49ba245ac2634e83db15d23c8f/uv-0.10.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5a62cdf5ba356dcc792b960e744d67056b0e6d778ce7381e1d78182357bd82e8", size = 20184248, upload-time = "2026-02-25T00:26:20.281Z" },
+ { url = "https://files.pythonhosted.org/packages/27/1b/13c2fcdb776ae78b5c22eb2d34931bb3ef9bd71b9578b8fa7af8dd7c11c4/uv-0.10.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:b70a04d51e2239b3aee0e4d4ed9af18c910360155953017cecded5c529588e65", size = 22049300, upload-time = "2026-02-25T00:26:07.039Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/43/348e2c378b3733eba15f6144b35a8c84af5c884232d6bbed29e256f74b6f/uv-0.10.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:2b622059a1ae287f8b995dcb6f5548de83b89b745ff112801abbf09e25fd8fa9", size = 22030505, upload-time = "2026-02-25T00:26:46.171Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/3f/dcec580099bc52f73036bfb09acb42616660733de1cc3f6c92287d2c7f3e/uv-0.10.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f43db1aa80776386646453c07d5590e1ae621f031a2afe6efba90f89c34c628c", size = 22041360, upload-time = "2026-02-25T00:26:53.725Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/96/f70abe813557d317998806517bb53b3caa5114591766db56ae9cc142ff39/uv-0.10.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ca8a26694ba7d0ae902f11054734805741f2b080fe8397401b80c99264edab6", size = 23309916, upload-time = "2026-02-25T00:27:12.99Z" },
+ { url = "https://files.pythonhosted.org/packages/db/1d/d8b955937dd0153b48fdcfd5ff70210d26e4b407188e976df620572534fd/uv-0.10.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f2cddae800d14159a9ccb4ff161648b0b0d1b31690d9c17076ec00f538c52ac", size = 24191174, upload-time = "2026-02-25T00:26:30.051Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/3d/3d0669d65bf4a270420d70ca0670917ce5c25c976c8b0acd52465852509b/uv-0.10.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:153fcf5375c988b2161bf3a6a7d9cc907d6bbe38f3cb16276da01b2dae4df72c", size = 23320328, upload-time = "2026-02-25T00:26:23.82Z" },
+ { url = "https://files.pythonhosted.org/packages/85/f2/f2ccc2196fd6cf1321c2e8751a96afabcbc9509b184c671ece3e804effda/uv-0.10.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27f2d135d4533f88537ecd254c72dfd25311d912da8649d15804284d70adb93", size = 23229798, upload-time = "2026-02-25T00:26:50.12Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/b9/1008266a041e8a55430a92aef8ecc58aaaa7eb7107a26cf4f7c127d14363/uv-0.10.6-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:dd993ec2bf5303a170946342955509559763cf8dcfe334ec7bb9f115a0f86021", size = 22143661, upload-time = "2026-02-25T00:26:42.507Z" },
+ { url = "https://files.pythonhosted.org/packages/93/e4/1f8de7da5f844b4c9eafa616e262749cd4e3d9c685190b7967c4681869da/uv-0.10.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8529e4d4aac40b4e7588177321cb332cc3309d36d7cc482470a1f6cfe7a7e14a", size = 22888045, upload-time = "2026-02-25T00:26:15.935Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/2b/03b840dd0101dc69ef6e83ceb2e2970e4b4f118291266cf3332a4b64092c/uv-0.10.6-py3-none-musllinux_1_1_i686.whl", hash = "sha256:ed9e16453a5f73ee058c566392885f445d00534dc9e754e10ab9f50f05eb27a5", size = 22549404, upload-time = "2026-02-25T00:27:05.333Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/4e/1ee4d4301874136a4b3bbd9eeba88da39f4bafa6f633b62aef77d8195c56/uv-0.10.6-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:33e5362039bfa91599df0b7487854440ffef1386ac681ec392d9748177fb1d43", size = 23426872, upload-time = "2026-02-25T00:26:35.01Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/e3/e000030118ff1a82ecfc6bd5af70949821edac739975a027994f5b17258f/uv-0.10.6-py3-none-win32.whl", hash = "sha256:fa7c504a1e16713b845d457421b07dd9c40f40d911ffca6897f97388de49df5a", size = 21501863, upload-time = "2026-02-25T00:26:57.182Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/cc/dd88c9f20c054ef0aea84ad1dd9f8b547463824857e4376463a948983bed/uv-0.10.6-py3-none-win_amd64.whl", hash = "sha256:ecded4d21834b21002bc6e9a2628d21f5c8417fd77a5db14250f1101bcb69dac", size = 23981891, upload-time = "2026-02-25T00:26:38.773Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/06/ca117002cd64f6701359253d8566ec7a0edcf61715b4969f07ee41d06f61/uv-0.10.6-py3-none-win_arm64.whl", hash = "sha256:4b5688625fc48565418c56a5cd6c8c32020dbb7c6fb7d10864c2d2c93c508302", size = 22339889, upload-time = "2026-02-25T00:27:00.818Z" },
]
[[package]]
name = "virtualenv"
-version = "20.36.1"
+version = "21.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "distlib" },
{ name = "filelock" },
{ name = "platformdirs" },
+ { name = "python-discovery" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/ce/4f/d6a5ff3b020c801c808b14e2d2330cdc8ebefe1cdfbc457ecc368e971fec/virtualenv-21.0.0.tar.gz", hash = "sha256:e8efe4271b4a5efe7a4dce9d60a05fd11859406c0d6aa8464f4cf451bc132889", size = 5836591, upload-time = "2026-02-25T20:21:07.691Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" },
+ { url = "https://files.pythonhosted.org/packages/29/d1/3f62e4f9577b28c352c11623a03fb916096d5c131303d4861b4914481b6b/virtualenv-21.0.0-py3-none-any.whl", hash = "sha256:d44e70637402c7f4b10f48491c02a6397a3a187152a70cba0b6bc7642d69fb05", size = 5817167, upload-time = "2026-02-25T20:21:05.476Z" },
]
[[package]]